diff --git a/examples/v1.ipynb b/examples/v1.ipynb index 48b9ba27..94714fbd 100644 --- a/examples/v1.ipynb +++ b/examples/v1.ipynb @@ -291,10 +291,11 @@ "\n", "The entrypoint exposes a few methods to directly interact with the network, such as:\n", "\n", - "- `recall_account_nonce(self, address: Address) -> int;`\n", - "- `send_transaction(self, transaction: Transaction) -> bytes;`\n", - "- `send_transactions(self, transactions: list[Transaction]) -> tuple[int, list[bytes]];`\n", - "- `await_completed_transaction(self, tx_hash: str | bytes) -> TransactionOnNetwork;`\n", + "- `recall_account_nonce(address: Address) -> int;`\n", + "- `send_transaction(transaction: Transaction) -> bytes;`\n", + "- `send_transactions(transactions: list[Transaction]) -> tuple[int, list[bytes]];`\n", + "- `get_transaction(tx_hash: str | bytes) -> TransactionOnNetwork;`\n", + "- `await_completed_transaction(tx_hash: str | bytes) -> TransactionOnNetwork;`\n", "\n", "Some other methods are exposed through a so called network provider. There are two types of network providers: ApiNetworkProvider and ProxyNetworkProvider. The ProxyNetworkProvider interacts directly with the proxy of an observing squad. The ApiNetworkProvider, as the name suggests, interacts with the api, that is a layer over the proxy. It fetches data from the network but also from Elastic Search." ] @@ -592,6 +593,57 @@ "transaction_hash = api.send_transaction(transaction)" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Sending multiple transactions" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from multiversx_sdk import Address, DevnetEntrypoint, Transaction\n", + "\n", + "entrypoint = DevnetEntrypoint()\n", + "api = entrypoint.create_network_provider()\n", + "\n", + "alice = Address.new_from_bech32(\"erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th\")\n", + "bob = Address.new_from_bech32(\"erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx\")\n", + "\n", + "# this transaction is not signed\n", + "first_transaction = Transaction(\n", + " sender=alice,\n", + " receiver=bob,\n", + " gas_limit=50000,\n", + " chain_id=\"D\",\n", + " nonce=2\n", + ")\n", + "\n", + "second_transaction = Transaction(\n", + " sender=bob,\n", + " receiver=alice,\n", + " gas_limit=50000,\n", + " chain_id=\"D\",\n", + " nonce=1\n", + ")\n", + "\n", + "third_transaction = Transaction(\n", + " sender=alice,\n", + " receiver=alice,\n", + " gas_limit=60000,\n", + " chain_id=\"D\",\n", + " nonce=3,\n", + " data=b\"hello\"\n", + ")\n", + "\n", + "# the API will return an error because the transactions are not signed\n", + "num_of_txs, hashes = api.send_transactions([first_transaction, second_transaction, third_transaction])" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -711,8 +763,7 @@ "\n", "\n", "tx_hash = \"exampletransactionhash\"\n", - "transaction_on_network = api.await_transaction_on_condition(\n", - " transaction_hash=tx_hash, condition=condition_to_be_satisfied)" + "transaction_on_network = api.await_transaction_on_condition(transaction_hash=tx_hash, condition=condition_to_be_satisfied)" ] }, { @@ -909,7 +960,7 @@ "source": [ "## Creating transactions\n", "\n", - "In this section, we'll learn how to create different types of transactions. For creating transactions, we can use `controllers` or `factories`. The `controllers` can be used for scripts or quick network interactions, while the `factories` provide a more granular approach. Usually, the `controllers` use the same parameters as the `factories` but also take an `Account` and the `nonce` of the sender as arguments. The `controllers` also hold some extra functionality, like waiting for transaction completion and parsing transactions. The same functionality can be obtained for transactions built using the `factories` as well, we'll see how in the sections bellow." + "In this section, we'll learn how to create different types of transactions. For creating transactions, we can use `controllers` or `factories`. The `controllers` can be used for scripts or quick network interactions, while the `factories` provide a more granular approach, usually needed for DApps. Usually, the `controllers` use the same parameters as the `factories` but also take an `Account` and the `nonce` of the sender as arguments. The `controllers` also hold some extra functionality, like waiting for transaction completion and parsing transactions. The same functionality can be obtained for transactions built using the `factories` as well, we'll see how in the sections bellow. In the following section we'll learn how to create transactions using both." ] }, { @@ -957,7 +1008,7 @@ "source": [ "#### Native token transfers using the controller\n", "\n", - "Because we'll use an `Account`, the transaction we'll be signed." + "Because we'll use an `Account`, the transaction will be signed." ] }, { @@ -983,14 +1034,20 @@ "transaction = transfers_controller.create_transaction_for_transfer(\n", " sender=account,\n", " nonce=account.get_nonce_then_increment(),\n", - " receiver=Address.new_from_bech32(\n", - " \"erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx\"),\n", + " receiver=Address.new_from_bech32(\"erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx\"),\n", " native_transfer_amount=1000000000000000000, # 1 EGLD\n", ")\n", "\n", "tx_hash = entrypoint.send_transaction(transaction)" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If you know youl'll only send native tokens, the same transaction can be created using the `create_transaction_for_native_token_transfer` method." + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -1035,6 +1092,13 @@ "tx_hash = entrypoint.send_transaction(transaction)" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If you know youl'll only send native tokens, the same transaction can be created using the `create_transaction_for_native_token_transfer` method." + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -1065,18 +1129,27 @@ "first_transfer = TokenTransfer(token=esdt, amount=1000000000)\n", "\n", "nft = Token(identifier=\"NFT-987654\", nonce=10)\n", - "second_transfer = TokenTransfer(token=nft, amount=1)\n", + "second_transfer = TokenTransfer(token=nft, amount=1) # when sending NFTs we set the amount to `1`\n", + "\n", + "sft = Token(identifier=\"SFT-123987\", nonce=10)\n", + "third_transfer = TokenTransfer(token=nft, amount=7)\n", "\n", "transfers_controller = entrypoint.create_transfers_controller()\n", "transaction = transfers_controller.create_transaction_for_transfer(\n", " sender=alice,\n", " nonce=alice.get_nonce_then_increment(),\n", - " receiver=Address.new_from_bech32(\n", - " \"erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx\"),\n", - " token_transfers=[first_transfer, second_transfer]\n", + " receiver=Address.new_from_bech32(\"erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx\"),\n", + " token_transfers=[first_transfer, second_transfer, third_transfer]\n", ")" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If you know youl'll only send ESDT tokens, the same transaction can be created using `create_transaction_for_esdt_token_transfer`." + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -1106,16 +1179,19 @@ "\n", "bob = Address.new_from_bech32(\"erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx\")\n", "\n", - "esdt = Token(identifier=\"TEST-123456\")\n", - "first_transfer = TokenTransfer(token=esdt, amount=1000000000)\n", + "esdt = Token(identifier=\"TEST-123456\") # fungible tokens don't have nonce\n", + "first_transfer = TokenTransfer(token=esdt, amount=1000000000) # we set the desired amount we want to send\n", "\n", "nft = Token(identifier=\"NFT-987654\", nonce=10)\n", - "second_transfer = TokenTransfer(token=nft, amount=1)\n", + "second_transfer = TokenTransfer(token=nft, amount=1) # when sending NFTs we set the amount to `1`\n", + "\n", + "sft = Token(identifier=\"SFT-123987\", nonce=10)\n", + "third_transfer = TokenTransfer(token=nft, amount=7) # for SFTs we set the desired amount we want to send\n", "\n", "transaction = factory.create_transaction_for_transfer(\n", " sender=alice.address,\n", " receiver=bob,\n", - " token_transfers=[first_transfer, second_transfer]\n", + " token_transfers=[first_transfer, second_transfer, third_transfer]\n", ")\n", "\n", "# set the sender's nonce\n", @@ -1131,7 +1207,13 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Also, sending both native and custom tokens is now supported. We can send both types of tokens using either the `controller` or the `factory`, but we'll use the controller for the sake of simplicity." + "If you know youl'll only send ESDT tokens, the same transaction can be created using `create_transaction_for_esdt_token_transfer`.\n", + "\n", + "#### Sending native and custom tokens\n", + "\n", + "Also, sending both native and custom tokens is now supported. If a `native_amount` is provided together with `token_transfers`, the native token will also be included in the `MultiESDTNFTTrasfer` built-in function call.\n", + "\n", + "We can send both types of tokens using either the `controller` or the `factory`, but we'll use the controller for the sake of simplicity." ] }, { @@ -1163,8 +1245,7 @@ "transaction = transfers_controller.create_transaction_for_transfer(\n", " sender=account,\n", " nonce=account.get_nonce_then_increment(),\n", - " receiver=Address.new_from_bech32(\n", - " \"erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx\"),\n", + " receiver=Address.new_from_bech32(\"erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx\"),\n", " native_transfer_amount=1000000000000000000, # 1 EGLD\n", " token_transfers=[first_transfer, second_transfer]\n", ")\n", @@ -1172,6 +1253,34 @@ "tx_hash = entrypoint.send_transaction(transaction)" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Decoding transaction data\n", + "\n", + "For example, when sending multiple ESDT and NFT tokens, the receiver field of the transaction is the same as the sender field and also the value is set to `0` because all the information is encoded in the `data` field of the transaction.\n", + "\n", + "For decoding the data field we have a so called `TransactionDecoder`. We fetch the transaction from the network and then use the decoder." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from multiversx_sdk import DevnetEntrypoint, TransactionDecoder\n", + "\n", + "entrypoint = DevnetEntrypoint()\n", + "transaction = entrypoint.get_transaction(\"3e7b39f33f37716186b6ffa8761d066f2139bff65a1075864f612ca05c05c05d\")\n", + "\n", + "decoder = TransactionDecoder()\n", + "decoded_transaction = decoder.get_transaction_metadata(transaction)\n", + "\n", + "print(decoded_transaction.to_dict())" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -1191,6 +1300,7 @@ "metadata": {}, "outputs": [], "source": [ + "from pathlib import Path\n", "from multiversx_sdk.abi import Abi\n", "\n", "abi = Abi.load(Path(\"./contracts/adder.abi.json\"))" @@ -1239,7 +1349,7 @@ "\n", "When creating transactions that interact with smart contracts, we should provide the ABI file to the `controller` or `factory` if possible, so we can pass the arguments as native values. If the abi is not provided and we know what types the contract expects, we can pass the arguments as `typed values` (ex: BigUIntValue, ListValue, StructValue, etc.) or `bytes`.\n", "\n", - "We'll first create a transaction for deploying a smart contract using the `controller`." + "#### Deploying a smart contract using the controller" ] }, { @@ -1381,7 +1491,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Now, let's create the same transaction to deploy a contract using the `factory`. Keep in mind that, after the transaction is created the `nonce` needs to be properly set and the transaction should be signed before broadcasting it." + "#### Deploying a smart contract using the factory\n", + "\n", + "After the transaction is created the `nonce` needs to be properly set and the transaction should be signed before broadcasting it." ] }, { @@ -1411,8 +1523,7 @@ "# Or use simple, plain Python values and objects if you have provided an ABI to the factory:\n", "args = [42]\n", "\n", - "alice_address = Address.new_from_bech32(\n", - " \"erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th\")\n", + "alice_address = Address.new_from_bech32(\"erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th\")\n", "\n", "deploy_transaction = factory.create_transaction_for_deploy(\n", " sender=alice_address,\n", @@ -1463,7 +1574,7 @@ "\n", "In this section we'll see how we can call an endpoint of our previoulsy deployed smart contract using both approaches with the `controller` and the `factory`.\n", "\n", - "Let's create a smart contract call transaction using the `controller`." + "#### Calling a smart contract using the controller" ] }, { @@ -1493,8 +1604,7 @@ "entrypoint = DevnetEntrypoint()\n", "controller = entrypoint.create_smart_contract_controller(abi=abi)\n", "\n", - "contract_address = Address.new_from_bech32(\n", - " \"erd1qqqqqqqqqqqqqpgq7cmfueefdqkjsnnjnwydw902v8pwjqy3d8ssd4meug\")\n", + "contract_address = Address.new_from_bech32(\"erd1qqqqqqqqqqqqqpgq7cmfueefdqkjsnnjnwydw902v8pwjqy3d8ssd4meug\")\n", "\n", "# For deploy arguments, use typed value objects if you haven't provided an ABI\n", "args = [BigUIntValue(42)]\n", @@ -1519,6 +1629,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ + "#### Parsing smart contract call transactions\n", + "\n", "In our case, calling the `add` endpoint does not return anything, but similar to the example above, we could parse this transaction to get the output values of a smart contract call." ] }, @@ -1538,6 +1650,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ + "#### Calling a smart contract and sending tokens (transfer & execute)\n", + "\n", "Aditionally, if our endpoint requires a payment when called, we can also send tokens to the contract when creating a smart contract call transaction. We can send EGLD, ESDT tokens or both. This is supported both on the `controller` and the `factory`." ] }, @@ -1568,8 +1682,7 @@ "entrypoint = DevnetEntrypoint()\n", "controller = entrypoint.create_smart_contract_controller(abi=abi)\n", "\n", - "contract_address = Address.new_from_bech32(\n", - " \"erd1qqqqqqqqqqqqqpgq7cmfueefdqkjsnnjnwydw902v8pwjqy3d8ssd4meug\")\n", + "contract_address = Address.new_from_bech32(\"erd1qqqqqqqqqqqqqpgq7cmfueefdqkjsnnjnwydw902v8pwjqy3d8ssd4meug\")\n", "\n", "# For deploy arguments, use typed value objects if you haven't provided an ABI\n", "args = [BigUIntValue(42)]\n", @@ -1603,7 +1716,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Now, let's create the same smart contract call transaction, but using the `factory`." + "#### Calling a smart contract using the factory\n", + "\n", + "Let's create the same smart contract call transaction, but using the `factory`." ] }, { @@ -1633,8 +1748,7 @@ "entrypoint = DevnetEntrypoint()\n", "factory = entrypoint.create_smart_contract_transactions_factory(abi=abi)\n", "\n", - "contract_address = Address.new_from_bech32(\n", - " \"erd1qqqqqqqqqqqqqpgq7cmfueefdqkjsnnjnwydw902v8pwjqy3d8ssd4meug\")\n", + "contract_address = Address.new_from_bech32(\"erd1qqqqqqqqqqqqqpgq7cmfueefdqkjsnnjnwydw902v8pwjqy3d8ssd4meug\")\n", "\n", "# For deploy arguments, use typed value objects if you haven't provided an ABI to the factory:\n", "args = [BigUIntValue(42)]\n", @@ -1672,7 +1786,7 @@ "source": [ "#### Parsing transaction outcome\n", "\n", - "As said before, the `add` endpoint we called does not return anything, but we could parse the outcome of the transaction, as follows:" + "As said before, the `add` endpoint we called does not return anything, but we could parse the outcome of smart contract call transactions, as follows:" ] }, { @@ -1691,9 +1805,7 @@ "parser = SmartContractTransactionsOutcomeParser(abi=abi)\n", "\n", "# fetch the transaction of the network\n", - "network_provider = DevnetEntrypoint().network_provider # expose get_transaction in entrypoint\n", - "transaction_on_network = network_provider.get_transaction(\n", - " tx_hash) # the tx_hash from the transaction sent above\n", + "transaction_on_network = entrypoint.get_transaction(tx_hash) # the tx_hash from the transaction sent above\n", "\n", "outcome = parser.parse_execute(transaction=transaction_on_network, function=\"add\")" ] @@ -1724,7 +1836,7 @@ "abi = Abi.load(Path(\"contracts/multisig-full.abi.json\"))\n", "\n", "# fetch the transaction of the network\n", - "network_provider = DevnetEntrypoint().network_provider # expose get_transaction in entrypoint\n", + "network_provider = DevnetEntrypoint().create_network_provider()\n", "transaction_on_network = network_provider.get_transaction(tx_hash)\n", "\n", "# extract the event from the transaction\n", @@ -1761,8 +1873,7 @@ "abi = Abi.load(Path(\"contracts/adder.abi.json\"))\n", "\n", "# the contract address we'll query\n", - "contract_address = Address.new_from_bech32(\n", - " \"erd1qqqqqqqqqqqqqpgq7cmfueefdqkjsnnjnwydw902v8pwjqy3d8ssd4meug\")\n", + "contract_address = Address.new_from_bech32(\"erd1qqqqqqqqqqqqqpgq7cmfueefdqkjsnnjnwydw902v8pwjqy3d8ssd4meug\")\n", "\n", "# create the controller\n", "sc_controller = DevnetEntrypoint().create_smart_contract_controller(abi=abi)\n", @@ -1795,8 +1906,7 @@ "abi = Abi.load(Path(\"contracts/adder.abi.json\"))\n", "\n", "# the contract address we'll query\n", - "contract_address = Address.new_from_bech32(\n", - " \"erd1qqqqqqqqqqqqqpgq7cmfueefdqkjsnnjnwydw902v8pwjqy3d8ssd4meug\")\n", + "contract_address = Address.new_from_bech32(\"erd1qqqqqqqqqqqqqpgq7cmfueefdqkjsnnjnwydw902v8pwjqy3d8ssd4meug\")\n", "\n", "# create the controller\n", "sc_controller = DevnetEntrypoint().create_smart_contract_controller(abi=abi)\n", @@ -1823,7 +1933,7 @@ "\n", "Contract upgrade transactions are similar to deployment transactions (see above), in the sense that they also require a contract bytecode. In this context though, the contract address is already known. Similar to deploying a smart contract, we can upgrade a smart contract using either the `controller` or the `factory`.\n", "\n", - "We'll first upgrade our smart contract using the `controller`." + "#### Uprgrading a smart contract using the controller" ] }, { @@ -1886,7 +1996,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Now, let's create the same upgrade transaction using the `factory`." + "#### Upgrading a smart contract using the factory\n", + "\n", + "Let's create the same upgrade transaction using the `factory`." ] }, { @@ -1950,7 +2062,8 @@ "deploy_transaction.signature = alice.sign_transaction(deploy_transaction)\n", "\n", "# broadcasting the transaction\n", - "tx_hash = entrypoint.send_transaction(deploy_transaction)" + "tx_hash = entrypoint.send_transaction(deploy_transaction)\n", + "print(tx_hash.hex())" ] }, { @@ -1963,7 +2076,7 @@ "\n", "Of course, the methods used here are available through the `TokenManagementController` or through the `TokenManagementTransactionsFactory`. The controller also contains methods for awaiting transaction completion and for parsing the transaction outcome. The same can be achieved for the transactions factory by using the `TokenManagementTransactionsOutcomeParser`. For scripts or quick network interactions we advise you use the controller, but for a more granular approach (e.g. DApps) we suggest using the factory.\n", "\n", - "#### Issuing fungible tokens" + "#### Issuing fungible tokens using the controller" ] }, { @@ -1988,9 +2101,9 @@ "transaction = controller.create_transaction_for_issuing_fungible(\n", " sender=alice,\n", " nonce=alice.get_nonce_then_increment(),\n", - " token_name=\"NEWTOKEN\",\n", - " token_ticker=\"TKN\",\n", - " initial_supply=1000000000000, # 1 million tokens, with 6 decimals\n", + " token_name=\"NEWFNG\",\n", + " token_ticker=\"FNG\",\n", + " initial_supply=1_000_000_000000, # 1 million tokens, with 6 decimals\n", " num_decimals=6,\n", " can_freeze=False,\n", " can_wipe=True,\n", @@ -2013,7 +2126,64 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "#### Setting special roles for fungible tokens" + "#### Issuing fungible tokens using the factory" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "from multiversx_sdk import Account, DevnetEntrypoint, TokenManagementTransactionsOutcomeParser\n", + "\n", + "# create the entrypoint and the token management transactions factory\n", + "entrypoint = DevnetEntrypoint()\n", + "factory = entrypoint.create_token_management_transactions_factory()\n", + "\n", + "# create the issuer of the token\n", + "alice = Account.new_from_pem(Path(\"../multiversx_sdk/testutils/testwallets/alice.pem\"))\n", + "\n", + "transaction = factory.create_transaction_for_issuing_fungible(\n", + " sender=alice.address,\n", + " token_name=\"NEWFNG\",\n", + " token_ticker=\"FNG\",\n", + " initial_supply=1_000_000_000000, # 1 million tokens, with 6 decimals\n", + " num_decimals=6,\n", + " can_freeze=False,\n", + " can_wipe=True,\n", + " can_pause=False,\n", + " can_change_owner=True,\n", + " can_upgrade=True,\n", + " can_add_special_roles=True\n", + ")\n", + "\n", + "# fetch the nonce of the network\n", + "alice.nonce = entrypoint.recall_account_nonce(alice.address)\n", + "transaction.nonce = alice.get_nonce_then_increment()\n", + "\n", + "# sign the transaction\n", + "transaction.signature = alice.sign_transaction(transaction)\n", + "\n", + "# sending the transaction\n", + "tx_hash = entrypoint.send_transaction(transaction)\n", + "\n", + "# if we know that the transaction is completed, we can simply call `entrypoint.get_transaction(tx_hash)`\n", + "transaction_on_network = entrypoint.await_completed_transaction(tx_hash)\n", + "\n", + "# extract the token identifier\n", + "parser = TokenManagementTransactionsOutcomeParser()\n", + "outcome = parser.parse_issue_fungible(transaction_on_network)\n", + "\n", + "token_identifier = outcome[0].token_identifier" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Setting special roles for fungible tokens using the controller" ] }, { @@ -2061,7 +2231,60 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "#### Issuing semi-fungible tokens" + "#### Setting special roles for fungible tokens using the factory" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "from multiversx_sdk import Account, DevnetEntrypoint, TokenManagementTransactionsOutcomeParser\n", + "\n", + "# create the entrypoint and the token management transactions factory\n", + "entrypoint = DevnetEntrypoint()\n", + "factory = entrypoint.create_token_management_transactions_factory()\n", + "\n", + "# create the issuer of the token\n", + "alice = Account.new_from_pem(Path(\"../multiversx_sdk/testutils/testwallets/alice.pem\"))\n", + "\n", + "transaction = factory.create_transaction_for_setting_special_role_on_fungible_token(\n", + " sender=alice.address,\n", + " user=bob,\n", + " token_identifier=\"TEST-123456\",\n", + " add_role_local_mint=True,\n", + " add_role_local_burn=True,\n", + " add_role_esdt_transfer_role=True\n", + ")\n", + "\n", + "# fetch the nonce of the network\n", + "alice.nonce = entrypoint.recall_account_nonce(alice.address)\n", + "transaction.nonce = alice.get_nonce_then_increment()\n", + "\n", + "# sign the transaction\n", + "transaction.signature = alice.sign_transaction(transaction)\n", + "\n", + "# sending the transaction\n", + "tx_hash = entrypoint.send_transaction(transaction)\n", + "\n", + "# waits until the transaction is processed and fetches it from the network\n", + "transaction_on_network = entrypoint.await_completed_transaction(tx_hash)\n", + "\n", + "# extract the roles\n", + "parser = TokenManagementTransactionsOutcomeParser()\n", + "outcome = parser.parse_set_special_role(transaction_on_network)\n", + "\n", + "roles = outcome[0].roles\n", + "uaser = outcome[0].user_address" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Issuing semi-fungible tokens using the controller" ] }, { @@ -2086,8 +2309,8 @@ "transaction = controller.create_transaction_for_issuing_semi_fungible(\n", " sender=alice,\n", " nonce=alice.get_nonce_then_increment(),\n", - " token_name=\"NEWTOKEN\",\n", - " token_ticker=\"TKN\",\n", + " token_name=\"NEWSEMI\",\n", + " token_ticker=\"SEMI\",\n", " can_freeze=False,\n", " can_wipe=True,\n", " can_pause=False,\n", @@ -2110,7 +2333,63 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "#### Issuing NFT collection & creating NFTs" + "#### Issuing semi-fungible tokens using the factory" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "from multiversx_sdk import Account, DevnetEntrypoint, TokenManagementTransactionsOutcomeParser\n", + "\n", + "# create the entrypoint and the token management transactions factory\n", + "entrypoint = DevnetEntrypoint()\n", + "factory = entrypoint.create_token_management_transactions_factory()\n", + "\n", + "# create the issuer of the token\n", + "alice = Account.new_from_pem(Path(\"../multiversx_sdk/testutils/testwallets/alice.pem\"))\n", + "\n", + "transaction = factory.create_transaction_for_issuing_semi_fungible(\n", + " sender=alice.address,\n", + " token_name=\"NEWSEMI\",\n", + " token_ticker=\"SEMI\",\n", + " can_freeze=False,\n", + " can_wipe=True,\n", + " can_pause=False,\n", + " can_transfer_nft_create_role=True,\n", + " can_change_owner=True,\n", + " can_upgrade=True,\n", + " can_add_special_roles=True\n", + ")\n", + "\n", + "# fetch the nonce of the network\n", + "alice.nonce = entrypoint.recall_account_nonce(alice.address)\n", + "transaction.nonce = alice.get_nonce_then_increment()\n", + "\n", + "# sign the transaction\n", + "transaction.signature = alice.sign_transaction(transaction)\n", + "\n", + "# sending the transaction\n", + "tx_hash = entrypoint.send_transaction(transaction)\n", + "\n", + "# waits until the transaction is processed and fetches it from the network\n", + "transaction_on_network = entrypoint.await_completed_transaction(tx_hash)\n", + "\n", + "# extract the token identifier\n", + "parser = TokenManagementTransactionsOutcomeParser()\n", + "outcome = parser.parse_issue_semi_fungible(transaction_on_network)\n", + "\n", + "token_identifier = outcome[0].token_identifier" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Issuing NFT collection & creating NFTs using the controller" ] }, { @@ -2132,11 +2411,12 @@ "# fetch the nonce of the network\n", "alice.nonce = entrypoint.recall_account_nonce(alice.address)\n", "\n", + "# issue NFT collection\n", "transaction = controller.create_transaction_for_issuing_non_fungible(\n", " sender=alice,\n", " nonce=alice.get_nonce_then_increment(),\n", - " token_name=\"NEWTOKEN\",\n", - " token_ticker=\"TKN\",\n", + " token_name=\"NEWNFT\",\n", + " token_ticker=\"NFT\",\n", " can_freeze=False,\n", " can_wipe=True,\n", " can_pause=False,\n", @@ -2182,21 +2462,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "These are just a few examples of what we can do using the token management controller or factory. For a full list of what methods are supported for both, check out the autogenerated documentation:\n", - "- [TokenManagementController](#)\n", - "- [TokenManagementTransactionsFactory](#)\n", - "\n", - "### Account management\n", - "\n", - "The account management controller and factory allow us to create transactions for managing accounts, like guarding and unguarding accounts and saving key-value pairs.\n", - "\n", - "#### Guarding an account\n", - "\n", - "To read more about Guardians, check out the [documentation](https://docs.multiversx.com/developers/built-in-functions/#setguardian).\n", - "\n", - "A guardian can also be set using the WebWallet. The wallet uses our hosted `Trusted Co-Signer Service`. Check out the steps to guard an account using the wallet [here](https://docs.multiversx.com/wallet/web-wallet/#guardian).\n", - "\n", - "Keep in mind that, all the methods presented bellow are available in the `AccountManagementTransactionsFactory`, as well." + "#### Issuing NFT collection & creating NFTs using the factory" ] }, { @@ -2206,36 +2472,97 @@ "outputs": [], "source": [ "from pathlib import Path\n", - "from multiversx_sdk import Account, Address, DevnetEntrypoint\n", + "from multiversx_sdk import Account, DevnetEntrypoint, TokenManagementTransactionsOutcomeParser\n", "\n", - "# create the entrypoint and the token management controller\n", + "# create the entrypoint and the token management transactions factory\n", "entrypoint = DevnetEntrypoint()\n", - "controller = entrypoint.create_account_controller()\n", + "factory = entrypoint.create_token_management_transactions_factory()\n", "\n", - "# create the account to guard\n", + "# create the issuer of the token\n", "alice = Account.new_from_pem(Path(\"../multiversx_sdk/testutils/testwallets/alice.pem\"))\n", "\n", + "# issue NFT collection\n", + "transaction = factory.create_transaction_for_issuing_non_fungible(\n", + " sender=alice.address,\n", + " token_name=\"NEWTOKEN\",\n", + " token_ticker=\"TKN\",\n", + " can_freeze=False,\n", + " can_wipe=True,\n", + " can_pause=False,\n", + " can_transfer_nft_create_role=True,\n", + " can_change_owner=True,\n", + " can_upgrade=True,\n", + " can_add_special_roles=True\n", + ")\n", + "\n", "# fetch the nonce of the network\n", "alice.nonce = entrypoint.recall_account_nonce(alice.address)\n", + "transaction.nonce = alice.get_nonce_then_increment()\n", "\n", - "# we can use a trusted service that provides a guardian, or simply set another address we own or trust\n", - "guardian = Address.new_from_bech32(\"erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx\")\n", + "# sign the transaction\n", + "transaction.signature = alice.sign_transaction(transaction)\n", "\n", - "transaction = controller.create_transaction_for_setting_guardian(\n", - " sender=alice,\n", - " nonce=alice.get_nonce_then_increment(),\n", - " guardian_address=guardian,\n", - " service_id=\"SelfOwnedAddress\" # this is just an example\n", + "# sending the transaction\n", + "tx_hash = entrypoint.send_transaction(transaction)\n", + "\n", + "# if we know that the transaction is completed, we can simply call `get_transaction(tx_hash)`\n", + "transaction_on_network = entrypoint.await_completed_transaction(tx_hash)\n", + "\n", + "# extract the collection identifier\n", + "parser = TokenManagementTransactionsOutcomeParser()\n", + "outcome = parser.parse_issue_non_fungible(transaction_on_network)\n", + "\n", + "collection_identifier = outcome[0].token_identifier\n", + "\n", + "# create a NFT\n", + "transaction = factory.create_transaction_for_creating_nft(\n", + " sender=alice.address,\n", + " token_identifier=collection_identifier,\n", + " initial_quantity=1,\n", + " name=\"TEST\",\n", + " royalties=2500, # 25%\n", + " hash=\"\",\n", + " attributes=b\"\",\n", + " uris=[]\n", ")\n", + "# set the nonce\n", + "transaction.nonce = alice.get_nonce_then_increment()\n", "\n", - "tx_hash = entrypoint.send_transaction(transaction)" + "# sign the transaction\n", + "transaction.signature = alice.sign_transaction(transaction)\n", + "\n", + "# sending the transaction\n", + "tx_hash = entrypoint.send_transaction(transaction)\n", + "\n", + "# waits until the transaction is processed and fetches it from the network\n", + "transaction_on_network = entrypoint.await_completed_transaction(tx_hash)\n", + "\n", + "# extract the nft identifier\n", + "parser = TokenManagementTransactionsOutcomeParser()\n", + "outcome = parser.parse_nft_create(transaction_on_network)\n", + "\n", + "identifier = outcome[0].token_identifier\n", + "nonce = outcome[0].nonce\n", + "initial_quantity = outcome[0].initial_quantity" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "After we've set a guardian, we have to wait 20 epochs until we can activate the guardian. After the guardian is set, all the transactions we send should be signed by the guardian, as well. To activate the guardian we can do as follows:" + "These are just a few examples of what we can do using the token management controller or factory. For a full list of what methods are supported for both, check out the autogenerated documentation:\n", + "- [TokenManagementController](#)\n", + "- [TokenManagementTransactionsFactory](#)\n", + "\n", + "### Account management\n", + "\n", + "The account management controller and factory allow us to create transactions for managing accounts, like guarding and unguarding accounts and saving key-value pairs.\n", + "\n", + "To read more about Guardians, check out the [documentation](https://docs.multiversx.com/developers/built-in-functions/#setguardian).\n", + "\n", + "A guardian can also be set using the WebWallet. The wallet uses our hosted `Trusted Co-Signer Service`. Check out the steps to guard an account using the wallet [here](https://docs.multiversx.com/wallet/web-wallet/#guardian).\n", + "\n", + "#### Guarding an account using the controller" ] }, { @@ -2245,9 +2572,9 @@ "outputs": [], "source": [ "from pathlib import Path\n", - "from multiversx_sdk import Account, DevnetEntrypoint\n", + "from multiversx_sdk import Account, Address, DevnetEntrypoint\n", "\n", - "# create the entrypoint and the token management controller\n", + "# create the entrypoint and the account controller\n", "entrypoint = DevnetEntrypoint()\n", "controller = entrypoint.create_account_controller()\n", "\n", @@ -2257,9 +2584,14 @@ "# fetch the nonce of the network\n", "alice.nonce = entrypoint.recall_account_nonce(alice.address)\n", "\n", - "transaction = controller.create_transaction_for_guarding_account(\n", + "# we can use a trusted service that provides a guardian, or simply set another address we own or trust\n", + "guardian = Address.new_from_bech32(\"erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx\")\n", + "\n", + "transaction = controller.create_transaction_for_setting_guardian(\n", " sender=alice,\n", - " nonce=alice.get_nonce_then_increment()\n", + " nonce=alice.get_nonce_then_increment(),\n", + " guardian_address=guardian,\n", + " service_id=\"SelfOwnedAddress\" # this is just an example\n", ")\n", "\n", "tx_hash = entrypoint.send_transaction(transaction)" @@ -2269,7 +2601,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "If at some point, we want to disable our guardian, we can send the following transaction:" + "#### Guarding an account using the factory" ] }, { @@ -2281,25 +2613,31 @@ "from pathlib import Path\n", "from multiversx_sdk import Account, Address, DevnetEntrypoint\n", "\n", - "# create the entrypoint and the token management controller\n", + "# create the entrypoint and the account transactions factory\n", "entrypoint = DevnetEntrypoint()\n", - "controller = entrypoint.create_account_controller()\n", + "factory = entrypoint.create_account_transactions_factory()\n", "\n", "# create the account to guard\n", "alice = Account.new_from_pem(Path(\"../multiversx_sdk/testutils/testwallets/alice.pem\"))\n", "\n", + "# we can use a trusted service that provides a guardian, or simply set another address we own or trust\n", + "guardian = Address.new_from_bech32(\"erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx\")\n", + "\n", + "transaction = factory.create_transaction_for_setting_guardian(\n", + " sender=alice.address,\n", + " guardian_address=guardian,\n", + " service_id=\"SelfOwnedAddress\" # this is just an example\n", + ")\n", + "\n", "# fetch the nonce of the network\n", "alice.nonce = entrypoint.recall_account_nonce(alice.address)\n", "\n", - "guardian = Address.new_from_bech32(\"erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx\")\n", + "# set the nonce\n", + "transaction.nonce = alice.get_nonce_then_increment()\n", "\n", - "transaction = controller.create_transaction_for_unguarding_account(\n", - " sender=alice,\n", - " nonce=alice.get_nonce_then_increment(),\n", - " guardian=guardian\n", - ")\n", + "# sign the transaction\n", + "transaction.signature = alice.sign_transaction(transaction)\n", "\n", - "# the transaction should also be signed by the guardian before being sent\n", "tx_hash = entrypoint.send_transaction(transaction)" ] }, @@ -2307,9 +2645,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "#### Saving a key-value pair to an account\n", + "After we've set a guardian, we have to wait 20 epochs until we can activate the guardian. After the guardian is set, all the transactions we send should be signed by the guardian, as well.\n", "\n", - "We can store key-value pairs for an acoount on the network. To do so, we create the following transaction:" + "#### Activating the guardian using the controller" ] }, { @@ -2321,7 +2659,7 @@ "from pathlib import Path\n", "from multiversx_sdk import Account, DevnetEntrypoint\n", "\n", - "# create the entrypoint and the token management controller\n", + "# create the entrypoint and the account controller\n", "entrypoint = DevnetEntrypoint()\n", "controller = entrypoint.create_account_controller()\n", "\n", @@ -2331,16 +2669,9 @@ "# fetch the nonce of the network\n", "alice.nonce = entrypoint.recall_account_nonce(alice.address)\n", "\n", - "# creating the key-value pairs we want to save\n", - "values = {\n", - " \"testKey\".encode(): \"testValue\".encode(),\n", - " b\"anotherKey\": b\"anotherValue\"\n", - "}\n", - "\n", - "transaction = controller.create_transaction_for_saving_key_value(\n", + "transaction = controller.create_transaction_for_guarding_account(\n", " sender=alice,\n", - " nonce=alice.get_nonce_then_increment(),\n", - " key_value_pairs=values\n", + " nonce=alice.get_nonce_then_increment()\n", ")\n", "\n", "tx_hash = entrypoint.send_transaction(transaction)" @@ -2350,15 +2681,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Delegation management\n", - "\n", - "To read more about staking providers and delegation, please check out the [docs](https://docs.multiversx.com/validators/delegation-manager/#introducing-staking-providers). \n", - "\n", - "In this section, we are going to create a new delegation contract, get the address of the contract, delegate funds to the contract, redelegate rewards, claim rewards, undelegate and withdraw funds from the contract. The operations can be performed using both the `controller` and the `factory`. For a full list of all the methods supported check out the auto-generated documentation:\n", - "- [DelegationController](#)\n", - "- [DelegationTransactionsFactory](#)\n", - "\n", - "#### Creating a new delegation contract" + "#### Activating the guardian using the factory" ] }, { @@ -2368,41 +2691,39 @@ "outputs": [], "source": [ "from pathlib import Path\n", - "from multiversx_sdk import Account, DevnetEntrypoint\n", + "from multiversx_sdk import Account, Address, DevnetEntrypoint\n", "\n", - "# create the entrypoint and the token management controller\n", + "# create the entrypoint and the account transactions factory\n", "entrypoint = DevnetEntrypoint()\n", - "controller = entrypoint.create_delegation_controller()\n", + "factory = entrypoint.create_account_transactions_factory()\n", "\n", "# create the account to guard\n", "alice = Account.new_from_pem(Path(\"../multiversx_sdk/testutils/testwallets/alice.pem\"))\n", "\n", - "# fetch the nonce of the network\n", - "alice.nonce = entrypoint.recall_account_nonce(alice.address)\n", + "# we can use a trusted service that provides a guardian, or simply set another address we own or trust\n", + "guardian = Address.new_from_bech32(\"erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx\")\n", "\n", - "transaction = controller.create_transaction_for_new_delegation_contract(\n", - " sender=alice,\n", - " nonce=alice.get_nonce_then_increment(),\n", - " total_delegation_cap=0, # uncapped,\n", - " service_fee=0,\n", - " amount=1250000000000000000000 # 1250 EGLD\n", + "transaction = factory.create_transaction_for_guarding_account(\n", + " sender=alice.address,\n", ")\n", "\n", - "tx_hash = entrypoint.send_transaction(transaction)\n", + "# fetch the nonce of the network\n", + "alice.nonce = entrypoint.recall_account_nonce(alice.address)\n", "\n", - "# wait for transaction completion, extract delegation contract's address\n", - "outcome = controller.await_completed_create_new_delegation_contract(tx_hash)\n", + "# set the nonce\n", + "transaction.nonce = alice.get_nonce_then_increment()\n", "\n", - "contract_address = outcome[0].contract_address" + "# sign the transaction\n", + "transaction.signature = alice.sign_transaction(transaction)\n", + "\n", + "tx_hash = entrypoint.send_transaction(transaction)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "#### Delegating funds to the contract\n", - "\n", - "We can send funds to a delegation contract to earn rewards." + "#### Unguarding the account using the controller" ] }, { @@ -2414,9 +2735,9 @@ "from pathlib import Path\n", "from multiversx_sdk import Account, Address, DevnetEntrypoint\n", "\n", - "# create the entrypoint and the token management controller\n", + "# create the entrypoint and the account controller\n", "entrypoint = DevnetEntrypoint()\n", - "controller = entrypoint.create_delegation_controller()\n", + "controller = entrypoint.create_account_controller()\n", "\n", "# create the account to guard\n", "alice = Account.new_from_pem(Path(\"../multiversx_sdk/testutils/testwallets/alice.pem\"))\n", @@ -2424,16 +2745,15 @@ "# fetch the nonce of the network\n", "alice.nonce = entrypoint.recall_account_nonce(alice.address)\n", "\n", - "# delegation contract\n", - "contract = Address.new_from_bech32(\"erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqf8llllswuedva\")\n", + "guardian = Address.new_from_bech32(\"erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx\")\n", "\n", - "transaction = controller.create_transaction_for_delegating(\n", + "transaction = controller.create_transaction_for_unguarding_account(\n", " sender=alice,\n", " nonce=alice.get_nonce_then_increment(),\n", - " delegation_contract=contract,\n", - " amount=5000000000000000000000 # 5000 EGLD\n", + " guardian=guardian\n", ")\n", "\n", + "# the transaction should also be signed by the guardian before being sent otherwise it won't be executed\n", "tx_hash = entrypoint.send_transaction(transaction)" ] }, @@ -2441,9 +2761,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "#### Redelegating rewards\n", - "\n", - "After a period of time, we might have enough rewards that we want to redelegate to the contract to earn even more rewards. " + "#### Unguarding the account using the factory" ] }, { @@ -2455,25 +2773,31 @@ "from pathlib import Path\n", "from multiversx_sdk import Account, Address, DevnetEntrypoint\n", "\n", - "# create the entrypoint and the token management controller\n", + "# create the entrypoint and the account transactions factory\n", "entrypoint = DevnetEntrypoint()\n", - "controller = entrypoint.create_delegation_controller()\n", + "factory = entrypoint.create_account_transactions_factory()\n", "\n", "# create the account to guard\n", "alice = Account.new_from_pem(Path(\"../multiversx_sdk/testutils/testwallets/alice.pem\"))\n", "\n", + "# we can use a trusted service that provides a guardian, or simply set another address we own or trust\n", + "guardian = Address.new_from_bech32(\"erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx\")\n", + "\n", + "transaction = factory.create_transaction_for_unguarding_account(\n", + " sender=alice.address,\n", + " guardian=guardian\n", + ")\n", + "\n", "# fetch the nonce of the network\n", "alice.nonce = entrypoint.recall_account_nonce(alice.address)\n", "\n", - "# delegation contract\n", - "contract = Address.new_from_bech32(\"erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqf8llllswuedva\")\n", + "# set the nonce\n", + "transaction.nonce = alice.get_nonce_then_increment()\n", "\n", - "transaction = controller.create_transaction_for_redelegating_rewards(\n", - " sender=alice,\n", - " nonce=alice.get_nonce_then_increment(),\n", - " delegation_contract=contract\n", - ")\n", + "# sign the transaction\n", + "transaction.signature = alice.sign_transaction(transaction)\n", "\n", + "# the transaction should also be signed by the guardian before being sent otherwise it won't be executed\n", "tx_hash = entrypoint.send_transaction(transaction)" ] }, @@ -2481,9 +2805,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "#### Claiming rewards\n", + "#### Saving a key-value pair to an account using the controller\n", "\n", - "We can also claim our rewards." + "We can store key-value pairs for an acoount on the network. To do so, we create the following transaction:" ] }, { @@ -2493,11 +2817,11 @@ "outputs": [], "source": [ "from pathlib import Path\n", - "from multiversx_sdk import Account, Address, DevnetEntrypoint\n", + "from multiversx_sdk import Account, DevnetEntrypoint\n", "\n", - "# create the entrypoint and the token management controller\n", + "# create the entrypoint and the account controller\n", "entrypoint = DevnetEntrypoint()\n", - "controller = entrypoint.create_delegation_controller()\n", + "controller = entrypoint.create_account_controller()\n", "\n", "# create the account to guard\n", "alice = Account.new_from_pem(Path(\"../multiversx_sdk/testutils/testwallets/alice.pem\"))\n", @@ -2505,15 +2829,19 @@ "# fetch the nonce of the network\n", "alice.nonce = entrypoint.recall_account_nonce(alice.address)\n", "\n", - "# delegation contract\n", - "contract = Address.new_from_bech32(\"erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqf8llllswuedva\")\n", + "# creating the key-value pairs we want to save\n", + "values = {\n", + " \"testKey\".encode(): \"testValue\".encode(),\n", + " b\"anotherKey\": b\"anotherValue\"\n", + "}\n", "\n", - "transaction = controller.create_transaction_for_claiming_rewards(\n", + "transaction = controller.create_transaction_for_saving_key_value(\n", " sender=alice,\n", " nonce=alice.get_nonce_then_increment(),\n", - " delegation_contract=contract\n", + " key_value_pairs=values\n", ")\n", "\n", + "# broadcast the transaction\n", "tx_hash = entrypoint.send_transaction(transaction)" ] }, @@ -2521,9 +2849,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "#### Undelegating funds\n", - "\n", - "By undelegating we let the contract know we want to get back our staked funds. This operation has a 10 epochs unbonding period." + "#### Saving a key-value pair to an account using the factory" ] }, { @@ -2533,28 +2859,36 @@ "outputs": [], "source": [ "from pathlib import Path\n", - "from multiversx_sdk import Account, Address, DevnetEntrypoint\n", + "from multiversx_sdk import Account, DevnetEntrypoint\n", "\n", - "# create the entrypoint and the token management controller\n", + "# create the entrypoint and the account transactions factory\n", "entrypoint = DevnetEntrypoint()\n", - "controller = entrypoint.create_delegation_controller()\n", + "factory = entrypoint.create_account_transactions_factory()\n", "\n", "# create the account to guard\n", "alice = Account.new_from_pem(Path(\"../multiversx_sdk/testutils/testwallets/alice.pem\"))\n", "\n", + "# creating the key-value pairs we want to save\n", + "values = {\n", + " \"testKey\".encode(): \"testValue\".encode(),\n", + " b\"anotherKey\": b\"anotherValue\"\n", + "}\n", + "\n", + "transaction = factory.create_transaction_for_saving_key_value(\n", + " sender=alice.address,\n", + " key_value_pairs=values\n", + ")\n", + "\n", "# fetch the nonce of the network\n", "alice.nonce = entrypoint.recall_account_nonce(alice.address)\n", "\n", - "# delegation contract\n", - "contract = Address.new_from_bech32(\"erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqf8llllswuedva\")\n", + "# set the nonce\n", + "transaction.nonce = alice.get_nonce_then_increment()\n", "\n", - "transaction = controller.create_transaction_for_undelegating(\n", - " sender=alice,\n", - " nonce=alice.get_nonce_then_increment(),\n", - " delegation_contract=contract,\n", - " amount=1000000000000000000000 # 1000 EGLD\n", - ")\n", + "# sign the transaction\n", + "transaction.signature = alice.sign_transaction(transaction)\n", "\n", + "# broadcast the transaction\n", "tx_hash = entrypoint.send_transaction(transaction)" ] }, @@ -2562,9 +2896,15 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "#### Withdrawing funds\n", + "### Delegation management\n", + "\n", + "To read more about staking providers and delegation, please check out the [docs](https://docs.multiversx.com/validators/delegation-manager/#introducing-staking-providers). \n", + "\n", + "In this section, we are going to create a new delegation contract, get the address of the contract, delegate funds to the contract, redelegate rewards, claim rewards, undelegate and withdraw funds from the contract. The operations can be performed using both the `controller` and the `factory`. For a full list of all the methods supported check out the auto-generated documentation:\n", + "- [DelegationController](#)\n", + "- [DelegationTransactionsFactory](#)\n", "\n", - "After the unbonding period has passed, we can withdraw our funds from the contract" + "#### Creating a new delegation contract using the controller" ] }, { @@ -2574,9 +2914,9 @@ "outputs": [], "source": [ "from pathlib import Path\n", - "from multiversx_sdk import Account, Address, DevnetEntrypoint\n", + "from multiversx_sdk import Account, DevnetEntrypoint\n", "\n", - "# create the entrypoint and the token management controller\n", + "# create the entrypoint and the account controller\n", "entrypoint = DevnetEntrypoint()\n", "controller = entrypoint.create_delegation_controller()\n", "\n", @@ -2586,25 +2926,27 @@ "# fetch the nonce of the network\n", "alice.nonce = entrypoint.recall_account_nonce(alice.address)\n", "\n", - "# delegation contract\n", - "contract = Address.new_from_bech32(\"erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqf8llllswuedva\")\n", - "\n", - "transaction = controller.create_transaction_for_withdrawing(\n", + "transaction = controller.create_transaction_for_new_delegation_contract(\n", " sender=alice,\n", " nonce=alice.get_nonce_then_increment(),\n", - " delegation_contract=contract\n", + " total_delegation_cap=0, # uncapped,\n", + " service_fee=0,\n", + " amount=1250000000000000000000 # 1250 EGLD\n", ")\n", "\n", - "tx_hash = entrypoint.send_transaction(transaction)" - ] - }, + "tx_hash = entrypoint.send_transaction(transaction)\n", + "\n", + "# wait for transaction completion, extract delegation contract's address\n", + "outcome = controller.await_completed_create_new_delegation_contract(tx_hash)\n", + "\n", + "contract_address = outcome[0].contract_address" + ] + }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Relayed transactions\n", - "\n", - "We are currently on the third iteration of relayed transactions. V1 and V2 are soon to be deactivated so we'll focus on V3. For V3, two new fields have been added on transactions: `releyer` and `relayerSignature`. Before the sender signs the transaction, the relayer needs to be set. After the sender has signed the transaction, the relayer can also sign the transaction and broadcast it. Keep in mind that, for relayed V3 transactions we need an extra `50_000` gas. Let's see how we can create a relayed transaction:" + "#### Creating a new delegation contract using the factory" ] }, { @@ -2614,38 +2956,82 @@ "outputs": [], "source": [ "from pathlib import Path\n", - "from multiversx_sdk import Account, Address, DevnetEntrypoint, Transaction\n", + "from multiversx_sdk import Account, DevnetEntrypoint, DelegationTransactionsOutcomeParser\n", + "\n", + "# create the entrypoint and the account transactions factory\n", + "entrypoint = DevnetEntrypoint()\n", + "factory = entrypoint.create_delegation_transactions_factory()\n", "\n", "# create the account to guard\n", "alice = Account.new_from_pem(Path(\"../multiversx_sdk/testutils/testwallets/alice.pem\"))\n", "\n", - "bob = Address.new_from_bech32(\"erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx\")\n", - "\n", - "# carol will be our relayer, that means she is paying the gas for the transaction\n", - "carol = Account.new_from_pem(Path(\"../multiversx_sdk/testutils/testwallets/carol.pem\"))\n", + "transaction = factory.create_transaction_for_new_delegation_contract(\n", + " sender=alice.address,\n", + " total_delegation_cap=0, # uncapped,\n", + " service_fee=0,\n", + " amount=1250000000000000000000 # 1250 EGLD\n", + ")\n", "\n", - "# fetch the sender's nonce of the network\n", + "# fetch the nonce of the network\n", "alice.nonce = entrypoint.recall_account_nonce(alice.address)\n", "\n", - "# create the transaction\n", - "transaction = Transaction(\n", - " sender=alice.address,\n", - " receiver=bob,\n", - " gas_limit=110_000,\n", - " chain_id=\"D\",\n", - " nonce=alice.get_nonce_then_increment(),\n", - " relayer=carol.address,\n", - " data=\"hello\".encode()\n", - ")\n", + "# set the nonce\n", + "transaction.nonce = alice.get_nonce_then_increment()\n", "\n", - "# sender signs the transaction\n", + "# sign the transaction\n", "transaction.signature = alice.sign_transaction(transaction)\n", "\n", - "# relayer signs the transaction\n", - "transaction.relayer_signature = carol.sign_transaction(transaction)\n", + "# send the transaction\n", + "tx_hash = entrypoint.send_transaction(transaction)\n", "\n", - "# broadcast the transcation\n", + "# waits until the transaction is processed and fetches it from the network\n", + "transaction_on_network = entrypoint.await_completed_transaction(tx_hash)\n", + "\n", + "# extract the contract's address\n", + "parser = DelegationTransactionsOutcomeParser()\n", + "outcome = parser.parse_create_new_delegation_contract(transaction_on_network)\n", + "\n", + "contract_address = outcome[0].contract_address" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Delegating funds to the contract using the controller\n", + "\n", + "We can send funds to a delegation contract to earn rewards." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "from multiversx_sdk import Account, Address, DevnetEntrypoint\n", + "\n", + "# create the entrypoint and the account controller\n", "entrypoint = DevnetEntrypoint()\n", + "controller = entrypoint.create_delegation_controller()\n", + "\n", + "# create the account to guard\n", + "alice = Account.new_from_pem(Path(\"../multiversx_sdk/testutils/testwallets/alice.pem\"))\n", + "\n", + "# fetch the nonce of the network\n", + "alice.nonce = entrypoint.recall_account_nonce(alice.address)\n", + "\n", + "# delegation contract\n", + "contract = Address.new_from_bech32(\"erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqf8llllswuedva\")\n", + "\n", + "transaction = controller.create_transaction_for_delegating(\n", + " sender=alice,\n", + " nonce=alice.get_nonce_then_increment(),\n", + " delegation_contract=contract,\n", + " amount=5000000000000000000000 # 5000 EGLD\n", + ")\n", + "\n", "tx_hash = entrypoint.send_transaction(transaction)" ] }, @@ -2653,9 +3039,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "#### Creating relayed transactions using controllers\n", - "\n", - "We can create relayed transactions using any of the controllers. Each controller has a `relayer` argument, that can be set if we want to create a relayed transaction. Let's issue a fungible token creating a relayed transaction:" + "#### Delegating funds to the contract using the factory" ] }, { @@ -2667,39 +3051,69 @@ "from pathlib import Path\n", "from multiversx_sdk import Account, DevnetEntrypoint\n", "\n", - "# create the entrypoint and the token management controller\n", + "# create the entrypoint and the account transactions factory\n", "entrypoint = DevnetEntrypoint()\n", - "controller = entrypoint.create_token_management_controller()\n", + "factory = entrypoint.create_delegation_transactions_factory()\n", "\n", - "# create the issuer of the token\n", + "# create the account to guard\n", "alice = Account.new_from_pem(Path(\"../multiversx_sdk/testutils/testwallets/alice.pem\"))\n", "\n", - "# carol will be our relayer, that means she is paying the gas for the transaction\n", - "carol = Account.new_from_pem(Path(\"../multiversx_sdk/testutils/testwallets/carol.pem\"))\n", + "transaction = factory.create_transaction_for_delegating(\n", + " sender=alice.address,\n", + " delegation_contract=contract,\n", + " amount=5000000000000000000000 # 5000 EGLD\n", + ")\n", "\n", "# fetch the nonce of the network\n", "alice.nonce = entrypoint.recall_account_nonce(alice.address)\n", "\n", - "transaction = controller.create_transaction_for_issuing_fungible(\n", + "# set the nonce\n", + "transaction.nonce = alice.get_nonce_then_increment()\n", + "\n", + "# sign the transaction\n", + "transaction.signature = alice.sign_transaction(transaction)\n", + "\n", + "# send the transaction\n", + "tx_hash = entrypoint.send_transaction(transaction)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Redelegating rewards using the controller\n", + "\n", + "After a period of time, we might have enough rewards that we want to redelegate to the contract to earn even more rewards. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "from multiversx_sdk import Account, Address, DevnetEntrypoint\n", + "\n", + "# create the entrypoint and the account controller\n", + "entrypoint = DevnetEntrypoint()\n", + "controller = entrypoint.create_delegation_controller()\n", + "\n", + "# create the account to guard\n", + "alice = Account.new_from_pem(Path(\"../multiversx_sdk/testutils/testwallets/alice.pem\"))\n", + "\n", + "# fetch the nonce of the network\n", + "alice.nonce = entrypoint.recall_account_nonce(alice.address)\n", + "\n", + "# delegation contract\n", + "contract = Address.new_from_bech32(\"erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqf8llllswuedva\")\n", + "\n", + "transaction = controller.create_transaction_for_redelegating_rewards(\n", " sender=alice,\n", " nonce=alice.get_nonce_then_increment(),\n", - " token_name=\"NEWTOKEN\",\n", - " token_ticker=\"TKN\",\n", - " initial_supply=1000000000000, # 1 million tokens, with 6 decimals\n", - " num_decimals=6,\n", - " can_freeze=False,\n", - " can_wipe=True,\n", - " can_pause=False,\n", - " can_change_owner=True,\n", - " can_upgrade=True,\n", - " can_add_special_roles=True,\n", - " relayer=carol.address\n", + " delegation_contract=contract\n", ")\n", "\n", - "# relayer also signs the transaction\n", - "transaction.relayer_signature = carol.sign_transaction(transaction)\n", - "\n", - "# sending the transaction\n", "tx_hash = entrypoint.send_transaction(transaction)" ] }, @@ -2707,9 +3121,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "#### Create relayed transactions using factories\n", - "\n", - "The transactions factories do not have a `relayer` argument, the relayer needs to be set after creating the transaction. This is good because the transaction is not signed by the sender when created. Let's issue a fungible token using the `TokenManagementTransactionsFactory`:" + "#### Redelegating rewards using the factory" ] }, { @@ -2721,46 +3133,28 @@ "from pathlib import Path\n", "from multiversx_sdk import Account, DevnetEntrypoint\n", "\n", - "# create the entrypoint and the token management factory\n", + "# create the entrypoint and the account transactions factory\n", "entrypoint = DevnetEntrypoint()\n", - "factory = entrypoint.create_token_management_transactions_factory()\n", + "factory = entrypoint.create_delegation_transactions_factory()\n", "\n", - "# create the issuer of the token\n", + "# create the account to guard\n", "alice = Account.new_from_pem(Path(\"../multiversx_sdk/testutils/testwallets/alice.pem\"))\n", "\n", - "# carol will be our relayer, that means she is paying the gas for the transaction\n", - "carol = Account.new_from_pem(Path(\"../multiversx_sdk/testutils/testwallets/carol.pem\"))\n", - "\n", - "transaction = factory.create_transaction_for_issuing_fungible(\n", + "transaction = factory.create_transaction_for_redelegating_rewards(\n", " sender=alice.address,\n", - " token_name=\"NEWTOKEN\",\n", - " token_ticker=\"TKN\",\n", - " initial_supply=1000000000000, # 1 million tokens, with 6 decimals\n", - " num_decimals=6,\n", - " can_freeze=False,\n", - " can_wipe=True,\n", - " can_pause=False,\n", - " can_change_owner=True,\n", - " can_upgrade=True,\n", - " can_add_special_roles=True\n", + " delegation_contract=contract\n", ")\n", "\n", "# fetch the nonce of the network\n", "alice.nonce = entrypoint.recall_account_nonce(alice.address)\n", "\n", - "# set the nonce of the sender\n", + "# set the nonce\n", "transaction.nonce = alice.get_nonce_then_increment()\n", "\n", - "# set the relayer\n", - "transaction.relayer = carol.address\n", - "\n", - "# sender signs the transaction\n", + "# sign the transaction\n", "transaction.signature = alice.sign_transaction(transaction)\n", "\n", - "# relayer signs the transaction\n", - "transaction.relayer_signature = carol.sign_transaction(transaction)\n", - "\n", - "# broadcast the transaction\n", + "# send the transaction\n", "tx_hash = entrypoint.send_transaction(transaction)" ] }, @@ -2768,9 +3162,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "#### Creating guarded transactions using controllers\n", + "#### Claiming rewards using the controller\n", "\n", - "Very similar to relayers, we have a field `guardian` and a field `guardianSignature`. Each controller has an argument for the guardian. The transaction can be sent to a service that signs it using the guardian's account or we can use another account as a guardian. Let's issue a token using a guarded account." + "We can also claim our rewards." ] }, { @@ -2780,41 +3174,27 @@ "outputs": [], "source": [ "from pathlib import Path\n", - "from multiversx_sdk import Account, DevnetEntrypoint\n", + "from multiversx_sdk import Account, Address, DevnetEntrypoint\n", "\n", - "# create the entrypoint and the token management controller\n", + "# create the entrypoint and the account controller\n", "entrypoint = DevnetEntrypoint()\n", - "controller = entrypoint.create_token_management_controller()\n", + "controller = entrypoint.create_delegation_controller()\n", "\n", - "# create the issuer of the token\n", + "# create the account to guard\n", "alice = Account.new_from_pem(Path(\"../multiversx_sdk/testutils/testwallets/alice.pem\"))\n", "\n", - "# carol is the guardian\n", - "carol = Account.new_from_pem(Path(\"../multiversx_sdk/testutils/testwallets/carol.pem\"))\n", - "\n", "# fetch the nonce of the network\n", "alice.nonce = entrypoint.recall_account_nonce(alice.address)\n", "\n", - "transaction = controller.create_transaction_for_issuing_fungible(\n", + "# delegation contract\n", + "contract = Address.new_from_bech32(\"erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqf8llllswuedva\")\n", + "\n", + "transaction = controller.create_transaction_for_claiming_rewards(\n", " sender=alice,\n", " nonce=alice.get_nonce_then_increment(),\n", - " token_name=\"NEWTOKEN\",\n", - " token_ticker=\"TKN\",\n", - " initial_supply=1000000000000, # 1 million tokens, with 6 decimals\n", - " num_decimals=6,\n", - " can_freeze=False,\n", - " can_wipe=True,\n", - " can_pause=False,\n", - " can_change_owner=True,\n", - " can_upgrade=True,\n", - " can_add_special_roles=True,\n", - " guardian=carol.address\n", + " delegation_contract=contract\n", ")\n", "\n", - "# guardian also signs the transaction\n", - "transaction.guardian_signature = carol.sign_transaction(transaction)\n", - "\n", - "# sending the transaction\n", "tx_hash = entrypoint.send_transaction(transaction)" ] }, @@ -2822,9 +3202,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "#### Creating guarded transactions using factories\n", - "\n", - "The transactions factories do not have a `guardian` argument, the guardian needs to be set after creating the transaction. This is good because the transaction is not signed by the sender when created. Let's issue a fungible token using the `TokenManagementTransactionsFactory`:" + "#### Claiming rewards using the factory" ] }, { @@ -2836,46 +3214,28 @@ "from pathlib import Path\n", "from multiversx_sdk import Account, DevnetEntrypoint\n", "\n", - "# create the entrypoint and the token management factory\n", + "# create the entrypoint and the account transactions factory\n", "entrypoint = DevnetEntrypoint()\n", - "factory = entrypoint.create_token_management_transactions_factory()\n", + "factory = entrypoint.create_delegation_transactions_factory()\n", "\n", - "# create the issuer of the token\n", + "# create the account to guard\n", "alice = Account.new_from_pem(Path(\"../multiversx_sdk/testutils/testwallets/alice.pem\"))\n", "\n", - "# carol is the guardian\n", - "carol = Account.new_from_pem(Path(\"../multiversx_sdk/testutils/testwallets/carol.pem\"))\n", - "\n", - "transaction = factory.create_transaction_for_issuing_fungible(\n", + "transaction = factory.create_transaction_for_claiming_rewards(\n", " sender=alice.address,\n", - " token_name=\"NEWTOKEN\",\n", - " token_ticker=\"TKN\",\n", - " initial_supply=1000000000000, # 1 million tokens, with 6 decimals\n", - " num_decimals=6,\n", - " can_freeze=False,\n", - " can_wipe=True,\n", - " can_pause=False,\n", - " can_change_owner=True,\n", - " can_upgrade=True,\n", - " can_add_special_roles=True\n", + " delegation_contract=contract\n", ")\n", "\n", "# fetch the nonce of the network\n", "alice.nonce = entrypoint.recall_account_nonce(alice.address)\n", "\n", - "# set the nonce of the sender\n", + "# set the nonce\n", "transaction.nonce = alice.get_nonce_then_increment()\n", "\n", - "# set the guardian\n", - "transaction.guardian = carol.address\n", - "\n", - "# sender signs the transaction\n", + "# sign the transaction\n", "transaction.signature = alice.sign_transaction(transaction)\n", "\n", - "# guardian signs the transaction\n", - "transaction.guardian_signature = carol.sign_transaction(transaction)\n", - "\n", - "# broadcast the transaction\n", + "# send the transaction\n", "tx_hash = entrypoint.send_transaction(transaction)" ] }, @@ -2883,7 +3243,1124 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We can also create guarded relayed transactions the same way we did before. Keep in mind that, only the sender can be guarded, the relayer cannot. The same flow can be used. Using controllers, we set both `guardian` and `relayer` fields and then the transaction should be signed by both. Using a factory, we create the transaction, set both both fields and then sign the transaction using the sender's account, then the the guardian and the relayer sign the transaction." + "#### Undelegating funds using the controller\n", + "\n", + "By undelegating we let the contract know we want to get back our staked funds. This operation has a 10 epochs unbonding period." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "from multiversx_sdk import Account, Address, DevnetEntrypoint\n", + "\n", + "# create the entrypoint and the account controller\n", + "entrypoint = DevnetEntrypoint()\n", + "controller = entrypoint.create_delegation_controller()\n", + "\n", + "# create the account to guard\n", + "alice = Account.new_from_pem(Path(\"../multiversx_sdk/testutils/testwallets/alice.pem\"))\n", + "\n", + "# fetch the nonce of the network\n", + "alice.nonce = entrypoint.recall_account_nonce(alice.address)\n", + "\n", + "# delegation contract\n", + "contract = Address.new_from_bech32(\"erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqf8llllswuedva\")\n", + "\n", + "transaction = controller.create_transaction_for_undelegating(\n", + " sender=alice,\n", + " nonce=alice.get_nonce_then_increment(),\n", + " delegation_contract=contract,\n", + " amount=1000000000000000000000 # 1000 EGLD\n", + ")\n", + "\n", + "tx_hash = entrypoint.send_transaction(transaction)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Undelegating funds using the factory" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "from multiversx_sdk import Account, DevnetEntrypoint\n", + "\n", + "# create the entrypoint and the account transactions factory\n", + "entrypoint = DevnetEntrypoint()\n", + "factory = entrypoint.create_delegation_transactions_factory()\n", + "\n", + "# create the account to guard\n", + "alice = Account.new_from_pem(Path(\"../multiversx_sdk/testutils/testwallets/alice.pem\"))\n", + "\n", + "transaction = factory.create_transaction_for_undelegating(\n", + " sender=alice.address,\n", + " delegation_contract=contract,\n", + " amount=1000000000000000000000 # 1000 EGLD\n", + ")\n", + "\n", + "# fetch the nonce of the network\n", + "alice.nonce = entrypoint.recall_account_nonce(alice.address)\n", + "\n", + "# set the nonce\n", + "transaction.nonce = alice.get_nonce_then_increment()\n", + "\n", + "# sign the transaction\n", + "transaction.signature = alice.sign_transaction(transaction)\n", + "\n", + "# send the transaction\n", + "tx_hash = entrypoint.send_transaction(transaction)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Withdrawing funds using the controller\n", + "\n", + "After the unbonding period has passed, we can withdraw our funds from the contract." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "from multiversx_sdk import Account, Address, DevnetEntrypoint\n", + "\n", + "# create the entrypoint and the account controller\n", + "entrypoint = DevnetEntrypoint()\n", + "controller = entrypoint.create_delegation_controller()\n", + "\n", + "# create the account to guard\n", + "alice = Account.new_from_pem(Path(\"../multiversx_sdk/testutils/testwallets/alice.pem\"))\n", + "\n", + "# fetch the nonce of the network\n", + "alice.nonce = entrypoint.recall_account_nonce(alice.address)\n", + "\n", + "# delegation contract\n", + "contract = Address.new_from_bech32(\"erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqf8llllswuedva\")\n", + "\n", + "transaction = controller.create_transaction_for_withdrawing(\n", + " sender=alice,\n", + " nonce=alice.get_nonce_then_increment(),\n", + " delegation_contract=contract\n", + ")\n", + "\n", + "tx_hash = entrypoint.send_transaction(transaction)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Withdrawing funds using the factory" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "from multiversx_sdk import Account, DevnetEntrypoint\n", + "\n", + "# create the entrypoint and the account transactions factory\n", + "entrypoint = DevnetEntrypoint()\n", + "factory = entrypoint.create_delegation_transactions_factory()\n", + "\n", + "# create the account to guard\n", + "alice = Account.new_from_pem(Path(\"../multiversx_sdk/testutils/testwallets/alice.pem\"))\n", + "\n", + "transaction = factory.create_transaction_for_withdrawing(\n", + " sender=alice.address,\n", + " delegation_contract=contract\n", + ")\n", + "\n", + "# fetch the nonce of the network\n", + "alice.nonce = entrypoint.recall_account_nonce(alice.address)\n", + "\n", + "# set the nonce\n", + "transaction.nonce = alice.get_nonce_then_increment()\n", + "\n", + "# sign the transaction\n", + "transaction.signature = alice.sign_transaction(transaction)\n", + "\n", + "# send the transaction\n", + "tx_hash = entrypoint.send_transaction(transaction)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Relayed transactions\n", + "\n", + "We are currently on the third iteration of relayed transactions. V1 and V2 are soon to be deactivated so we'll focus on V3. For V3, two new fields have been added on transactions: `releyer` and `relayerSignature`. Before the sender signs the transaction, the relayer needs to be set. After the sender has signed the transaction, the relayer can also sign the transaction and broadcast it. Keep in mind that, for relayed V3 transactions we need an extra `50_000` gas. Let's see how we can create a relayed transaction:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "from multiversx_sdk import Account, Address, DevnetEntrypoint, Transaction\n", + "\n", + "# create the account to guard\n", + "alice = Account.new_from_pem(Path(\"../multiversx_sdk/testutils/testwallets/alice.pem\"))\n", + "\n", + "bob = Address.new_from_bech32(\"erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx\")\n", + "\n", + "# carol will be our relayer, that means she is paying the gas for the transaction\n", + "carol = Account.new_from_pem(Path(\"../multiversx_sdk/testutils/testwallets/carol.pem\"))\n", + "\n", + "# fetch the sender's nonce of the network\n", + "alice.nonce = entrypoint.recall_account_nonce(alice.address)\n", + "\n", + "# create the transaction\n", + "transaction = Transaction(\n", + " sender=alice.address,\n", + " receiver=bob,\n", + " gas_limit=110_000,\n", + " chain_id=\"D\",\n", + " nonce=alice.get_nonce_then_increment(),\n", + " relayer=carol.address,\n", + " data=\"hello\".encode()\n", + ")\n", + "\n", + "# sender signs the transaction\n", + "transaction.signature = alice.sign_transaction(transaction)\n", + "\n", + "# relayer signs the transaction\n", + "transaction.relayer_signature = carol.sign_transaction(transaction)\n", + "\n", + "# broadcast the transcation\n", + "entrypoint = DevnetEntrypoint()\n", + "tx_hash = entrypoint.send_transaction(transaction)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Creating relayed transactions using controllers\n", + "\n", + "We can create relayed transactions using any of the controllers. Each controller has a `relayer` argument, that can be set if we want to create a relayed transaction. Let's issue a fungible token creating a relayed transaction:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "from multiversx_sdk import Account, DevnetEntrypoint\n", + "\n", + "# create the entrypoint and the token management controller\n", + "entrypoint = DevnetEntrypoint()\n", + "controller = entrypoint.create_token_management_controller()\n", + "\n", + "# create the issuer of the token\n", + "alice = Account.new_from_pem(Path(\"../multiversx_sdk/testutils/testwallets/alice.pem\"))\n", + "\n", + "# carol will be our relayer, that means she is paying the gas for the transaction\n", + "carol = Account.new_from_pem(Path(\"../multiversx_sdk/testutils/testwallets/carol.pem\"))\n", + "\n", + "# fetch the nonce of the network\n", + "alice.nonce = entrypoint.recall_account_nonce(alice.address)\n", + "\n", + "transaction = controller.create_transaction_for_issuing_fungible(\n", + " sender=alice,\n", + " nonce=alice.get_nonce_then_increment(),\n", + " token_name=\"NEWTOKEN\",\n", + " token_ticker=\"TKN\",\n", + " initial_supply=1_000_000_000000, # 1 million tokens, with 6 decimals\n", + " num_decimals=6,\n", + " can_freeze=False,\n", + " can_wipe=True,\n", + " can_pause=False,\n", + " can_change_owner=True,\n", + " can_upgrade=True,\n", + " can_add_special_roles=True,\n", + " relayer=carol.address\n", + ")\n", + "\n", + "# relayer also signs the transaction\n", + "transaction.relayer_signature = carol.sign_transaction(transaction)\n", + "\n", + "# sending the transaction\n", + "tx_hash = entrypoint.send_transaction(transaction)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Create relayed transactions using factories\n", + "\n", + "The transactions factories do not have a `relayer` argument, the relayer needs to be set after creating the transaction. This is good because the transaction is not signed by the sender when created. Let's issue a fungible token using the `TokenManagementTransactionsFactory`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "from multiversx_sdk import Account, DevnetEntrypoint\n", + "\n", + "# create the entrypoint and the token management transactions factory\n", + "entrypoint = DevnetEntrypoint()\n", + "factory = entrypoint.create_token_management_transactions_factory()\n", + "\n", + "# create the issuer of the token\n", + "alice = Account.new_from_pem(Path(\"../multiversx_sdk/testutils/testwallets/alice.pem\"))\n", + "\n", + "# carol will be our relayer, that means she is paying the gas for the transaction\n", + "carol = Account.new_from_pem(Path(\"../multiversx_sdk/testutils/testwallets/carol.pem\"))\n", + "\n", + "transaction = factory.create_transaction_for_issuing_fungible(\n", + " sender=alice.address,\n", + " token_name=\"NEWTOKEN\",\n", + " token_ticker=\"TKN\",\n", + " initial_supply=1000000000000, # 1 million tokens, with 6 decimals\n", + " num_decimals=6,\n", + " can_freeze=False,\n", + " can_wipe=True,\n", + " can_pause=False,\n", + " can_change_owner=True,\n", + " can_upgrade=True,\n", + " can_add_special_roles=True\n", + ")\n", + "\n", + "# fetch the nonce of the network\n", + "alice.nonce = entrypoint.recall_account_nonce(alice.address)\n", + "\n", + "# set the nonce of the sender\n", + "transaction.nonce = alice.get_nonce_then_increment()\n", + "\n", + "# set the relayer\n", + "transaction.relayer = carol.address\n", + "\n", + "# sender signs the transaction\n", + "transaction.signature = alice.sign_transaction(transaction)\n", + "\n", + "# relayer signs the transaction\n", + "transaction.relayer_signature = carol.sign_transaction(transaction)\n", + "\n", + "# broadcast the transaction\n", + "tx_hash = entrypoint.send_transaction(transaction)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Creating guarded transactions using controllers\n", + "\n", + "Very similar to relayers, we have a field `guardian` and a field `guardianSignature`. Each controller has an argument for the guardian. The transaction can be sent to a service that signs it using the guardian's account or we can use another account as a guardian. Let's issue a token using a guarded account." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "from multiversx_sdk import Account, DevnetEntrypoint\n", + "\n", + "# create the entrypoint and the token management controller\n", + "entrypoint = DevnetEntrypoint()\n", + "controller = entrypoint.create_token_management_controller()\n", + "\n", + "# create the issuer of the token\n", + "alice = Account.new_from_pem(Path(\"../multiversx_sdk/testutils/testwallets/alice.pem\"))\n", + "\n", + "# carol is the guardian\n", + "carol = Account.new_from_pem(Path(\"../multiversx_sdk/testutils/testwallets/carol.pem\"))\n", + "\n", + "# fetch the nonce of the network\n", + "alice.nonce = entrypoint.recall_account_nonce(alice.address)\n", + "\n", + "transaction = controller.create_transaction_for_issuing_fungible(\n", + " sender=alice,\n", + " nonce=alice.get_nonce_then_increment(),\n", + " token_name=\"NEWTOKEN\",\n", + " token_ticker=\"TKN\",\n", + " initial_supply=1000000000000, # 1 million tokens, with 6 decimals\n", + " num_decimals=6,\n", + " can_freeze=False,\n", + " can_wipe=True,\n", + " can_pause=False,\n", + " can_change_owner=True,\n", + " can_upgrade=True,\n", + " can_add_special_roles=True,\n", + " guardian=carol.address\n", + ")\n", + "\n", + "# guardian also signs the transaction\n", + "transaction.guardian_signature = carol.sign_transaction(transaction)\n", + "\n", + "# sending the transaction\n", + "tx_hash = entrypoint.send_transaction(transaction)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Creating guarded transactions using factories\n", + "\n", + "The transactions factories do not have a `guardian` argument, the guardian needs to be set after creating the transaction. This is good because the transaction is not signed by the sender when created. Let's issue a fungible token using the `TokenManagementTransactionsFactory`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "from multiversx_sdk import Account, DevnetEntrypoint\n", + "\n", + "# create the entrypoint and the token management transactions factory\n", + "entrypoint = DevnetEntrypoint()\n", + "factory = entrypoint.create_token_management_transactions_factory()\n", + "\n", + "# create the issuer of the token\n", + "alice = Account.new_from_pem(Path(\"../multiversx_sdk/testutils/testwallets/alice.pem\"))\n", + "\n", + "# carol is the guardian\n", + "carol = Account.new_from_pem(Path(\"../multiversx_sdk/testutils/testwallets/carol.pem\"))\n", + "\n", + "transaction = factory.create_transaction_for_issuing_fungible(\n", + " sender=alice.address,\n", + " token_name=\"NEWTOKEN\",\n", + " token_ticker=\"TKN\",\n", + " initial_supply=1000000000000, # 1 million tokens, with 6 decimals\n", + " num_decimals=6,\n", + " can_freeze=False,\n", + " can_wipe=True,\n", + " can_pause=False,\n", + " can_change_owner=True,\n", + " can_upgrade=True,\n", + " can_add_special_roles=True\n", + ")\n", + "\n", + "# fetch the nonce of the network\n", + "alice.nonce = entrypoint.recall_account_nonce(alice.address)\n", + "\n", + "# set the nonce of the sender\n", + "transaction.nonce = alice.get_nonce_then_increment()\n", + "\n", + "# set the guardian\n", + "transaction.guardian = carol.address\n", + "\n", + "# sender signs the transaction\n", + "transaction.signature = alice.sign_transaction(transaction)\n", + "\n", + "# guardian signs the transaction\n", + "transaction.guardian_signature = carol.sign_transaction(transaction)\n", + "\n", + "# broadcast the transaction\n", + "tx_hash = entrypoint.send_transaction(transaction)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can also create guarded relayed transactions the same way we did before. Keep in mind that, only the sender can be guarded, the relayer cannot. The same flow can be used. Using controllers, we set both `guardian` and `relayer` fields and then the transaction should be signed by both. Using a factory, we create the transaction, set both both fields and then sign the transaction using the sender's account, then the the guardian and the relayer sign the transaction." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Addresses\n", + "\n", + "Create an `Address` object from a _bech32-encoded_ string:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from multiversx_sdk import Address\n", + "\n", + "address = Address.new_from_bech32(\"erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th\")\n", + "\n", + "print(\"Address (bech32-encoded)\", address.to_bech32())\n", + "print(\"Public key (hex-encoded):\", address.to_hex())\n", + "print(\"Public key (hex-encoded):\", address.get_public_key().hex())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Create an address from a _hex-encoded_ string - note that you have to provide the address prefix, also known as the **HRP** (_human-readable part_ of the address). If not provided, the default one will be used." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from multiversx_sdk import Address\n", + "\n", + "address = Address.new_from_hex(\"0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1\", \"erd\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Create an address from a raw public key:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from multiversx_sdk import Address\n", + "\n", + "pubkey = bytes.fromhex(\"0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1\")\n", + "address = Address(pubkey, \"erd\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Alternatively, you can use an `AddressFactory` (initialized with a specific **HRP**) to create addresses. If the hrp is not provided, the default one will be used." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from multiversx_sdk import AddressFactory\n", + "\n", + "factory = AddressFactory(\"erd\")\n", + "\n", + "address = factory.create_from_bech32(\"erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th\")\n", + "address = factory.create_from_hex(\"0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1\")\n", + "address = factory.create_from_public_key(bytes.fromhex(\"0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1\"))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Getting the shard of an address" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from multiversx_sdk import Address, AddressComputer\n", + "\n", + "address_computer = AddressComputer()\n", + "address = Address.new_from_bech32(\"erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th\")\n", + "\n", + "print(\"Shard:\", address_computer.get_shard_of_address(address))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Checking if the address is a smart contract address" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from multiversx_sdk import Address\n", + "\n", + "address = Address.new_from_bech32(\"erd1qqqqqqqqqqqqqpgquzmh78klkqwt0p4rjys0qtp3la07gz4d396qn50nnm\")\n", + "print(\"Is contract address:\", address.is_smart_contract())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Changing the default hrp\n", + "\n", + "We have a configuration class, called `LibraryConfig`, that only stores (for the moment) the **default hrp** of the addresses. The default value is `erd`. The hrp can be changed when instantiating an address, or it can be changed in the `LibraryConfig` class, and all the addresses created will have the newly set hrp." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from multiversx_sdk import Address\n", + "from multiversx_sdk import LibraryConfig\n", + "\n", + "\n", + "print(LibraryConfig.default_address_hrp)\n", + "address = Address.new_from_hex(\"0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1\")\n", + "print(address.to_bech32())\n", + "\n", + "LibraryConfig.default_address_hrp = \"test\"\n", + "address = Address.new_from_hex(\"0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1\")\n", + "print(address.to_bech32())\n", + "\n", + "# setting back the default value\n", + "LibraryConfig.default_address_hrp = \"erd\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Wallets\n", + "\n", + "#### Generating a mnemonic\n", + "\n", + "Mnemonic generation is based on [`trezor/python-mnemonic`](https://github.com/trezor/python-mnemonic) and can be achieved as follows:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from multiversx_sdk import Mnemonic\n", + "\n", + "mnemonic = Mnemonic.generate()\n", + "words = mnemonic.get_words()\n", + "\n", + "print(words)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Saving the mnemonic to a keystore file\n", + "\n", + "The mnemonic can be saved to a keystore file:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "from multiversx_sdk import Mnemonic, UserWallet\n", + "\n", + "mnemonic = Mnemonic.generate()\n", + "\n", + "# saves the mnemonic to a keystore file with kind=mnemonic\n", + "wallet = UserWallet.from_mnemonic(mnemonic.get_text(), \"password\")\n", + "wallet.save(Path(\"walletWithMnemonic.json\"))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Deriving secret keys from a mnemonic\n", + "\n", + "Given a mnemonic, we can derive keypairs:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from multiversx_sdk import Mnemonic\n", + "\n", + "mnemonic = Mnemonic.generate()\n", + "\n", + "secret_key = mnemonic.derive_key(0)\n", + "public_key = secret_key.generate_public_key()\n", + "\n", + "print(\"Secret key:\", secret_key.hex())\n", + "print(\"Public key:\", public_key.hex())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Saving a secret key to a keystore file\n", + "\n", + "The secret key can also be saved to a keystore file:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "from multiversx_sdk import Mnemonic, UserWallet\n", + "\n", + "mnemonic = Mnemonic.generate()\n", + "\n", + "# by default, derives using the index = 0\n", + "secret_key = mnemonic.derive_key()\n", + "\n", + "# saves the mnemonic to a keystore file with kind=secretKey\n", + "wallet = UserWallet.from_secret_key(secret_key, \"password\")\n", + "wallet.save(Path(\"walletWithSecretKey.json\"))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Saving a secrey key to a PEM file\n", + "\n", + "We can save a secret key to a pem file. This is not recommended as it is not secure, but it's very convenient for testing purposes." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "from multiversx_sdk import Address, UserPEM\n", + "\n", + "mnemonic = Mnemonic.generate()\n", + "\n", + "# by default, derives using the index = 0\n", + "secret_key = mnemonic.derive_key()\n", + "\n", + "label = Address(public_key.buffer, \"erd\").to_bech32()\n", + "pem = UserPEM(label=label, secret_key=secret_key)\n", + "pem.save(Path(\"wallet.pem\"))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Generating a KeyPair\n", + "\n", + "A `KeyPair` is a wrapper over a secret key and a public key. We can create a keypair and use it for signing or verifying. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from multiversx_sdk import KeyPair\n", + "\n", + "keypair = KeyPair.generate()\n", + "\n", + "# get secret key\n", + "secret_key = keypair.get_secret_key()\n", + "\n", + "# get public key\n", + "public_key = keypair.get_public_key()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Loading a wallets from keystore mnemonic file\n", + "\n", + "Load a keystore that holds an _encrypted mnemonic_ (and perform wallet derivation at the same time):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "from multiversx_sdk import UserWallet\n", + "\n", + "# loads the mnemonic and derives the a secret key; default index = 0\n", + "secret_key = UserWallet.load_secret_key(Path(\"walletWithMnemonic.json\"), \"password\")\n", + "address = secret_key.generate_public_key().to_address(\"erd\")\n", + "\n", + "print(\"Secret key:\", secret_key.hex())\n", + "print(\"Address:\", address.to_bech32())\n", + "\n", + "# derive secret key with index = 7\n", + "secret_key = UserWallet.load_secret_key(\n", + " path=Path(\"walletWithMnemonic.json\"),\n", + " password=\"password\",\n", + " address_index=7\n", + ")\n", + "address = secret_key.generate_public_key().to_address()\n", + "\n", + "print(\"Secret key:\", secret_key.hex())\n", + "print(\"Address:\", address.to_bech32())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Loading a wallet from a keystore secret key file" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "from multiversx_sdk import UserWallet\n", + "\n", + "secret_key = UserWallet.load_secret_key(Path(\"walletWithSecretKey.json\"), \"password\")\n", + "address = secret_key.generate_public_key().to_address(\"erd\")\n", + "\n", + "print(\"Secret key:\", secret_key.hex())\n", + "print(\"Address:\", address.to_bech32())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Loading a wallet from a PEM file" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "from multiversx_sdk import UserPEM\n", + "\n", + "pem = UserPEM.from_file(Path(\"wallet.pem\"))\n", + "\n", + "print(\"Secret key:\", pem.secret_key.hex())\n", + "print(\"Public key:\", pem.public_key.hex())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Signing objects\n", + "\n", + "The signing is performed using the **secret key** of an account. We have a few wrappers over the secret key, like [Account](#creating-accounts) that make siging easier. We'll first learn how we can sign using an `Account` and then we'll see how we can sign using the secret key.\n", + "\n", + "#### Signing a Transaction using an Account\n", + "\n", + "We are going to assume we have an account at this point. If you don't fell free to check out the [creating an account section](#creating-accounts)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "from multiversx_sdk import Account, Address, Transaction\n", + "\n", + "account = Account.new_from_pem(Path(\"../multiversx_sdk/testutils/testwallets/alice.pem\"))\n", + "\n", + "transaction = Transaction(\n", + " nonce=90,\n", + " sender=account.address,\n", + " receiver=Address.new_from_bech32(\"erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx\"),\n", + " value=1000000000000000000,\n", + " gas_limit=50000,\n", + " chain_id=\"D\"\n", + ")\n", + "\n", + "# apply the signature on the transaction\n", + "transaction.signature = account.sign_transaction(transaction)\n", + "\n", + "print(transaction.to_dictionary())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Signing a Transaction using a SecretKey" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from multiversx_sdk import Transaction, TransactionComputer, UserSecretKey\n", + "\n", + "secret_key_hex = \"413f42575f7f26fad3317a778771212fdb80245850981e48b58a4f25e344e8f9\"\n", + "secret_key = UserSecretKey(bytes.fromhex(secret_key_hex))\n", + "public_key = secret_key.generate_public_key()\n", + "\n", + "transaction = Transaction(\n", + " nonce=90,\n", + " sender=public_key.to_address(),\n", + " receiver=Address.new_from_bech32(\"erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx\"),\n", + " value=1000000000000000000,\n", + " gas_limit=50000,\n", + " chain_id=\"D\"\n", + ")\n", + "\n", + "# serialize the transaction\n", + "transaction_computer = TransactionComputer()\n", + "serialized_transaction = transaction_computer.compute_bytes_for_signing(transaction)\n", + "\n", + "# apply the signature on the transaction\n", + "transaction.signature = secret_key.sign(serialized_transaction)\n", + "\n", + "print(transaction.to_dictionary())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Signing a Transaction by hash" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "from multiversx_sdk import Account, Address, Transaction, TransactionComputer\n", + "\n", + "account = Account.new_from_pem(Path(\"../multiversx_sdk/testutils/testwallets/alice.pem\"))\n", + "\n", + "transaction = Transaction(\n", + " nonce=90,\n", + " sender=account.address,\n", + " receiver=Address.new_from_bech32(\"erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx\"),\n", + " value=1000000000000000000,\n", + " gas_limit=50000,\n", + " chain_id=\"D\"\n", + ")\n", + "\n", + "transaction_computer = TransactionComputer()\n", + "\n", + "# sets the least significant bit of the options field to `1`\n", + "transaction_computer.apply_options_for_hash_signing(transaction)\n", + "\n", + "# compute the hash for signing\n", + "hash = transaction_computer.compute_hash_for_signing(transaction)\n", + "\n", + "# sign and apply the signature on the transaction\n", + "transaction.signature = account.sign(hash)\n", + "\n", + "print(transaction.to_dictionary())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Signing a Message using an Account" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "from multiversx_sdk import Account, Message, MessageComputer\n", + "\n", + "account = Account.new_from_pem(Path(\"../multiversx_sdk/testutils/testwallets/alice.pem\"))\n", + "\n", + "# creating a message\n", + "message = Message(data=\"this is a test message\".encode(), address=account.address)\n", + "\n", + "# signing the message\n", + "message.signature = account.sign_message(message)\n", + "\n", + "# dictionary representation of the message\n", + "print(MessageComputer().pack_message(message))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Signing a message using a SecretKey" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from multiversx_sdk import UserSecretKey, Message, MessageComputer\n", + "\n", + "secret_key_hex = \"413f42575f7f26fad3317a778771212fdb80245850981e48b58a4f25e344e8f9\"\n", + "secret_key = UserSecretKey(bytes.fromhex(secret_key_hex))\n", + "public_key = secret_key.generate_public_key()\n", + "\n", + "message_computer = MessageComputer()\n", + "\n", + "# creating a message\n", + "message = Message(data=\"this is a test message\".encode(), address=public_key.to_address())\n", + "\n", + "# serialize the message\n", + "serialized_message = message_computer.compute_bytes_for_signing(message)\n", + "\n", + "# signing the message\n", + "message.signature = secret_key.sign(serialized_message)\n", + "\n", + "# dictionary representation of the message\n", + "print(message_computer.pack_message(message))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Verifying signatures\n", + "\n", + "The verification of a signature is done using the **public key** of an account. We have a few wrappers over public keys that make the verification of signatures a little bit easier.\n", + "\n", + "#### Verifying a signature using a UserVerifier" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "from multiversx_sdk import Account, Address, Transaction, TransactionComputer, UserVerifier\n", + "\n", + "account = Account.new_from_pem(Path(\"../multiversx_sdk/testutils/testwallets/alice.pem\"))\n", + "\n", + "transaction = Transaction(\n", + " nonce=90,\n", + " sender=account.address,\n", + " receiver=Address.new_from_bech32(\"erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx\"),\n", + " value=1000000000000000000,\n", + " gas_limit=50000,\n", + " chain_id=\"D\"\n", + ")\n", + "\n", + "# apply the signature on the transaction\n", + "transaction.signature = account.sign_transaction(transaction)\n", + "\n", + "# instantiating a user verifier; basically gets the public key\n", + "alice = Address.new_from_bech32(\"erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th\")\n", + "alice_verifier = UserVerifier.from_address(alice)\n", + "\n", + "# serialize the transaction for verification\n", + "transaction_computer = TransactionComputer()\n", + "serialized_transaction = transaction_computer.compute_bytes_for_verifying(transaction)\n", + "\n", + "# verify the signature\n", + "is_signed_by_alice = alice_verifier.verify(\n", + " data=serialized_transaction,\n", + " signature=transaction.signature\n", + ")\n", + "\n", + "print(\"Transaction is signed by Alice:\", is_signed_by_alice)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Verifying a signature using the public key" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "from multiversx_sdk import Account, Address, Transaction, TransactionComputer, UserPublicKey\n", + "\n", + "account = Account.new_from_pem(Path(\"../multiversx_sdk/testutils/testwallets/alice.pem\"))\n", + "\n", + "transaction = Transaction(\n", + " nonce=90,\n", + " sender=account.address,\n", + " receiver=Address.new_from_bech32(\"erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx\"),\n", + " value=1000000000000000000,\n", + " gas_limit=50000,\n", + " chain_id=\"D\"\n", + ")\n", + "\n", + "# apply the signature on the transaction\n", + "transaction.signature = account.sign_transaction(transaction)\n", + "\n", + "# instantiating a public key\n", + "alice = Address.new_from_bech32(\"erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th\")\n", + "public_key = UserPublicKey(alice.get_public_key())\n", + "\n", + "# serialize the transaction for verification\n", + "transaction_computer = TransactionComputer()\n", + "serialized_transaction = transaction_computer.compute_bytes_for_verifying(transaction)\n", + "\n", + "# verify the signature\n", + "is_signed_by_alice = public_key.verify(\n", + " data=serialized_transaction,\n", + " signature=transaction.signature\n", + ")\n", + "\n", + "print(\"Transaction is signed by Alice:\", is_signed_by_alice)" ] } ], diff --git a/multiversx_sdk/account_management/account_controller.py b/multiversx_sdk/account_management/account_controller.py index fc023544..655eaa05 100644 --- a/multiversx_sdk/account_management/account_controller.py +++ b/multiversx_sdk/account_management/account_controller.py @@ -66,13 +66,13 @@ def create_transaction_for_guarding_account(self, def create_transaction_for_unguarding_account(self, sender: IAccount, nonce: int, - guardian: Optional[Address] = None, + guardian: Address, relayer: Optional[Address] = None) -> Transaction: transaction = self.factory.create_transaction_for_unguarding_account( - sender=sender.address + sender=sender.address, + guardian=guardian ) - transaction.guardian = guardian transaction.relayer = relayer transaction.nonce = nonce transaction.signature = sender.sign(self.tx_computer.compute_bytes_for_signing(transaction)) diff --git a/multiversx_sdk/account_management/account_transactions_factory.py b/multiversx_sdk/account_management/account_transactions_factory.py index 472d60c3..be578edc 100644 --- a/multiversx_sdk/account_management/account_transactions_factory.py +++ b/multiversx_sdk/account_management/account_transactions_factory.py @@ -59,7 +59,7 @@ def create_transaction_for_guarding_account(self, sender: Address) -> Transactio add_data_movement_gas=True ).build() - def create_transaction_for_unguarding_account(self, sender: Address) -> Transaction: + def create_transaction_for_unguarding_account(self, sender: Address, guardian: Address) -> Transaction: data_parts = ["UnGuardAccount"] transaction = TransactionBuilder( @@ -71,6 +71,7 @@ def create_transaction_for_unguarding_account(self, sender: Address) -> Transact add_data_movement_gas=True ).build() transaction.options = 2 + transaction.guardian = guardian return transaction diff --git a/multiversx_sdk/entrypoints/entrypoints.py b/multiversx_sdk/entrypoints/entrypoints.py index 8cc8c0c8..8275c739 100644 --- a/multiversx_sdk/entrypoints/entrypoints.py +++ b/multiversx_sdk/entrypoints/entrypoints.py @@ -102,6 +102,9 @@ def send_transaction(self, transaction: Transaction) -> bytes: def await_completed_transaction(self, tx_hash: Union[str, bytes]) -> TransactionOnNetwork: return self.network_provider.await_transaction_completed(tx_hash) + def get_transaction(self, tx_hash: Union[str, bytes]) -> TransactionOnNetwork: + return self.network_provider.get_transaction(tx_hash) + def create_network_provider(self) -> Union[ApiNetworkProvider, ProxyNetworkProvider]: return self.network_provider