diff --git a/examples/v1.ipynb b/examples/v1.ipynb index da655c78..975d1dd9 100644 --- a/examples/v1.ipynb +++ b/examples/v1.ipynb @@ -694,7 +694,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 `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, 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." ] }, { @@ -992,7 +992,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "metadata": {}, "outputs": [ { @@ -1028,9 +1028,9 @@ "# load the contract bytecode\n", "bytecode = Path(\"contracts/adder.wasm\").read_bytes()\n", "\n", - "# For deploy arguments, use typed value objects if you haven't provided an ABI to the factory:\n", + "# For deploy arguments, use typed value objects if you haven't provided an ABI\n", "args = [BigUIntValue(42)]\n", - "# Or use simple, plain Python values and objects if you have provided an ABI to the factory:\n", + "# Or use simple, plain Python values and objects if you have provided an ABI\n", "args = [42]\n", "\n", "deploy_transaction = controller.create_transaction_for_deploy(\n", @@ -1137,7 +1137,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Now, let's create the same transaction 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." + "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." ] }, { @@ -1250,9 +1250,9 @@ "\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", + "# For deploy arguments, use typed value objects if you haven't provided an ABI\n", "args = [BigUIntValue(42)]\n", - "# Or use simple, plain Python values and objects if you have provided an ABI to the factory:\n", + "# Or use simple, plain Python values and objects if you have provided an ABI\n", "args = [42]\n", "\n", "deploy_transaction = controller.create_transaction_for_execute(\n", @@ -1282,8 +1282,8 @@ "metadata": {}, "outputs": [], "source": [ - "# we use the transaction hash we got when broadcasting the transaction\n", - "contract_call_outcome = controller.await_completed_execute(tx_hash) # waits for transaction completion and parses the result\n", + "# waits for transaction completion and parses the result\n", + "contract_call_outcome = controller.await_completed_execute(tx_hash) # we use the transaction hash we got when broadcasting the transaction\n", "values = contract_call_outcome.values" ] }, @@ -1291,7 +1291,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Aditionally, if our endpoint requires a payment when called, we can also send tokens to the contract when creating a smart contract call transaction. This is supported both on the `controller` and the `factory`." + "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`." ] }, { @@ -1323,9 +1323,9 @@ "\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", + "# For deploy arguments, use typed value objects if you haven't provided an ABI\n", "args = [BigUIntValue(42)]\n", - "# Or use simple, plain Python values and objects if you have provided an ABI to the factory:\n", + "# Or use simple, plain Python values and objects if you have provided an ABI\n", "args = [42]\n", "\n", "# creating the transfer\n", @@ -1335,7 +1335,7 @@ "second_token = Token(\"BAR-c80d29\")\n", "second_transfer = TokenTransfer(second_token, 10000000000000000000)\n", "\n", - "deploy_transaction = controller.create_transaction_for_execute(\n", + "execute_transaction = controller.create_transaction_for_execute(\n", " sender=account,\n", " nonce=account.get_nonce_then_increment(),\n", " contract=contract_address,\n", @@ -1347,9 +1347,351 @@ ")\n", "\n", "# broadcasting the transaction\n", + "tx_hash = entrypoint.send_transaction(execute_transaction)\n", + "print(tx_hash.hex())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, let's create the same smart contract call transaction, but using the `factory`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "\n", + "from multiversx_sdk import Account, DevnetEntrypoint, Token, TokenTransfer\n", + "from multiversx_sdk.abi import Abi, BigUIntValue\n", + "\n", + "# prepare the account\n", + "account = Account.new_from_keystore(\n", + " file_path=Path(\"../multiversx_sdk/testutils/testwallets/withDummyMnemonic.json\"),\n", + " password=\"password\",\n", + " address_index=0\n", + ")\n", + "# the user is responsible for managing the nonce\n", + "account.nonce = entrypoint.recall_account_nonce(account.address)\n", + "\n", + "# load tha abi file\n", + "abi = Abi.load(Path(\"contracts/adder.abi.json\"))\n", + "\n", + "# get the smart contracts controller\n", + "entrypoint = DevnetEntrypoint()\n", + "factory = entrypoint.create_smart_contract_factory(abi=abi)\n", + "\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", + "# Or use simple, plain Python values and objects if you have provided an ABI to the factory:\n", + "args = [42]\n", + "\n", + "# creating the transfer\n", + "first_token = Token(\"TEST-38f249\", 10)\n", + "first_transfer = TokenTransfer(first_token, 1)\n", + "\n", + "second_token = Token(\"BAR-c80d29\")\n", + "second_transfer = TokenTransfer(second_token, 10000000000000000000)\n", + "\n", + "execute_transaction = factory.create_transaction_for_execute(\n", + " sender=account.address,\n", + " contract=contract_address,\n", + " gas_limit=5000000,\n", + " function=\"add\",\n", + " arguments=args,\n", + " native_transfer_amount=1000000000000000000, # 1 EGLD,\n", + " token_transfers=[first_transfer, second_transfer]\n", + ")\n", + "\n", + "execute_transaction.nonce = account.get_nonce_then_increment()\n", + "execute_transaction.signature = account.sign_transaction(execute_transaction)\n", + "\n", + "# broadcasting the transaction\n", + "tx_hash = entrypoint.send_transaction(execute_transaction)\n", + "print(tx_hash.hex())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "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:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from multiversx_sdk import DevnetEntrypoint, SmartContractTransactionsOutcomeParser\n", + "from multiversx_sdk.abi import Abi\n", + "\n", + "# load tha abi file\n", + "abi = Abi.load(Path(\"contracts/adder.abi.json\"))\n", + "\n", + "# create the parser\n", + "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(tx_hash) # the tx_hash from the transaction sent above\n", + "\n", + "outcome = parser.parse_execute(transaction=transaction_on_network, function=\"add\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Decoding transaction events\n", + "\n", + "You might be interested into decoding events emitted by a contract. You can do so by using the `TransactionEventsParser`.\n", + "\n", + "Suppose we'd like to decode a `startPerformAction` event emitted by the [multisig](https://github.com/multiversx/mx-contracts-rs/tree/main/contracts/multisig) contract.\n", + "\n", + "First, we load the abi file, then we fetch the transaction, we extract the event from the transaction and then we parse it. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from multiversx_sdk import DevnetEntrypoint, TransactionEventsParser, find_events_by_first_topic\n", + "from multiversx_sdk.abi import Abi\n", + "\n", + "# load tha abi file\n", + "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", + "transaction_on_network = network_provider.get_transaction(tx_hash)\n", + "\n", + "# extract the event from the transaction\n", + "[event] = find_events_by_first_topic(transaction_on_network, \"startPerformAction\")\n", + "\n", + "# create the parser\n", + "events_parser = TransactionEventsParser(abi=abi)\n", + "\n", + "# parse the event\n", + "parsed_event = events_parser.parse_event(event)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Smart Contract queries\n", + "\n", + "When querying a smart contract, a `view function` is called. That function does not modify the state of the contract, thus we don't need to send a transaction.\n", + "\n", + "To query a smart contract, we need to use the `SmartContractController`. Of course, we can use the contract's abi file to encode the arguments of the query, but also parse the result. In this example, we are going to use the [adder](https://github.com/multiversx/mx-contracts-rs/tree/main/contracts/adder) smart contract and we'll call the `getSum` endpoint." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from multiversx_sdk import Address, DevnetEntrypoint\n", + "from multiversx_sdk.abi import Abi\n", + "\n", + "# load tha abi file\n", + "abi = Abi.load(Path(\"contracts/adder.abi.json\"))\n", + "\n", + "# the contract address we'll query\n", + "contract_address = Address.new_from_bech32(\"erd1qqqqqqqqqqqqqpgq7cmfueefdqkjsnnjnwydw902v8pwjqy3d8ssd4meug\")\n", + "\n", + "# create the controller\n", + "sc_controller = DevnetEntrypoint().create_smart_contract_controller(abi=abi)\n", + "\n", + "# creates the query, runs the query, parses the result\n", + "response = sc_controller.query(\n", + " contract=contract_address,\n", + " function=\"getSum\",\n", + " arguments=[] # our function expects no arguments, so we provide an empty list\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If we need more granular control, we can split the process in three steps: create the query, run the query and parse the query response. This does the exact same as the example above." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from multiversx_sdk import Address, DevnetEntrypoint\n", + "from multiversx_sdk.abi import Abi\n", + "\n", + "# load tha abi file\n", + "abi = Abi.load(Path(\"contracts/adder.abi.json\"))\n", + "\n", + "# the contract address we'll query\n", + "contract_address = Address.new_from_bech32(\"erd1qqqqqqqqqqqqqpgq7cmfueefdqkjsnnjnwydw902v8pwjqy3d8ssd4meug\")\n", + "\n", + "# create the controller\n", + "sc_controller = DevnetEntrypoint().create_smart_contract_controller(abi=abi)\n", + "\n", + "# creates the query\n", + "query = sc_controller.create_query(\n", + " contract=contract_address,\n", + " function=\"getSum\",\n", + " arguments=[] # our function expects no arguments, so we provide an empty list\n", + ")\n", + "\n", + "# run the query\n", + "result = sc_controller.run_query(query)\n", + "\n", + "# parse the result\n", + "parsed_result = sc_controller.parse_query_response(result)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Upgrading a smart contract\n", + "\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`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "\n", + "from multiversx_sdk import Account, DevnetEntrypoint\n", + "from multiversx_sdk.abi import Abi, BigUIntValue\n", + "\n", + "# prepare the account\n", + "account = Account.new_from_keystore(\n", + " file_path=Path(\"../multiversx_sdk/testutils/testwallets/withDummyMnemonic.json\"),\n", + " password=\"password\",\n", + " address_index=0\n", + ")\n", + "# the user is responsible for managing the nonce\n", + "account.nonce = entrypoint.recall_account_nonce(account.address)\n", + "\n", + "# load tha abi file\n", + "abi = Abi.load(Path(\"contracts/adder.abi.json\"))\n", + "\n", + "# get the smart contracts controller\n", + "entrypoint = DevnetEntrypoint()\n", + "controller = entrypoint.create_smart_contract_controller(abi=abi)\n", + "\n", + "# load the contract bytecode; this is the new contract code, the one we want to upgrade to\n", + "bytecode = Path(\"contracts/adder.wasm\").read_bytes()\n", + "\n", + "# For deploy arguments, use typed value objects if you haven't provided an ABI\n", + "args = [BigUIntValue(42)]\n", + "# Or use simple, plain Python values and objects if you have provided an ABI\n", + "args = [42]\n", + "\n", + "deploy_transaction = controller.create_transaction_for_upgrade(\n", + " sender=account,\n", + " nonce=account.get_nonce_then_increment(),\n", + " contract=contract_address,\n", + " bytecode=bytecode,\n", + " gas_limit=5000000,\n", + " arguments=args,\n", + " is_upgradeable=True,\n", + " is_readable=True,\n", + " is_payable=True,\n", + " is_payable_by_sc=True\n", + ")\n", + "\n", + "# broadcasting the transaction\n", "tx_hash = entrypoint.send_transaction(deploy_transaction)\n", "print(tx_hash.hex())" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, let's create the same upgrade transaction using the `factory`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "\n", + "from multiversx_sdk import Address, DevnetEntrypoint, SmartContractTransactionsOutcomeParser\n", + "from multiversx_sdk.abi import Abi, BigUIntValue\n", + "\n", + "\n", + "# load tha abi file\n", + "abi = Abi.load(Path(\"contracts/adder.abi.json\"))\n", + "\n", + "# get the smart contracts controller\n", + "entrypoint = DevnetEntrypoint()\n", + "factory = entrypoint.create_smart_contract_factory(abi=abi)\n", + "\n", + "# load the contract bytecode; this is the new contract code, the one we want to upgrade to\n", + "bytecode = Path(\"contracts/adder.wasm\").read_bytes()\n", + "\n", + "# For deploy arguments, use typed value objects if you haven't provided an ABI to the factory:\n", + "args = [BigUIntValue(42)]\n", + "# 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(\"erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th\")\n", + "\n", + "deploy_transaction = factory.create_transaction_for_upgrade(\n", + " sender=alice_address,\n", + " bytecode=bytecode,\n", + " gas_limit=5000000,\n", + " arguments=args,\n", + " is_upgradeable=True,\n", + " is_readable=True,\n", + " is_payable=True,\n", + " is_payable_by_sc=True\n", + ")\n", + "\n", + "# load the account\n", + "alice = Account.new_from_keystore(\n", + " file_path=Path(\"../multiversx_sdk/testutils/testwallets/withDummyMnemonic.json\"),\n", + " password=\"password\",\n", + " address_index=0\n", + ")\n", + "# the user is responsible for managing the nonce\n", + "alice.nonce = entrypoint.recall_account_nonce(alice.address)\n", + "\n", + "# set the nonce\n", + "deploy_transaction.nonce = alice.nonce\n", + "\n", + "# sign transaction\n", + "deploy_transaction.signature = alice.sign_transaction(deploy_transaction)\n", + "\n", + "# broadcasting the transaction\n", + "tx_hash = entrypoint.send_transaction(deploy_transaction)" + ] } ], "metadata": {