From 2479301b2bdd0e7cbc59ea885a7ff15e4b0e92d0 Mon Sep 17 00:00:00 2001 From: Vini Barbosa <83308157+vinibarbosabr@users.noreply.github.com> Date: Sat, 15 Feb 2025 11:55:31 -0300 Subject: [PATCH 1/6] Add comment related to `Wallet::from_pem_file("/ping-pong/wallet/wallet-owner.pem"` When following the tutorial, I wasn't able to deploy the sc because the program was panicking when not finding the "/wallet-owner.pem". The fix was adding the absolute path, completing with the missing directories above "/ping-pong/wallet/wallet-owner.pem". I added a comment at line 178, suggesting this fix for devs following the tutorial. --- docs/developers/tutorials/your-first-dapp.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/developers/tutorials/your-first-dapp.md b/docs/developers/tutorials/your-first-dapp.md index c22696725..dd13c6840 100644 --- a/docs/developers/tutorials/your-first-dapp.md +++ b/docs/developers/tutorials/your-first-dapp.md @@ -175,6 +175,8 @@ let alice_wallet_address = interactor .register_wallet(Wallet::from_pem_file("/ping-pong/wallet/wallet-owner.pem").unwrap()) .await; ``` +Make sure to add the absolute path at `Wallet::from_pem_file("/ping-pong/wallet/wallet-owner.pem")`, completing the missing directories above "ping-pong". + This next command deploys the Ping-Pong contract with the following settings: - Ping Amount: **1 EGLD**. - Lock Duration: **180 seconds** (3 minutes). From e171db223bb344550ba116728b8d740b8533465a Mon Sep 17 00:00:00 2001 From: Vini Barbosa <83308157+vinibarbosabr@users.noreply.github.com> Date: Tue, 25 Feb 2025 10:55:58 -0300 Subject: [PATCH 2/6] =?UTF-8?q?Add=20paragraph=20before=20=C2=ABThe=20**no?= =?UTF-8?q?des**=20(...)=C2=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added a paragraph after «(...) by automated processes.» and before «The **nodes** (...)». Better to distinguish the entities and easier to read. --- docs/learn/entities.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/learn/entities.md b/docs/learn/entities.md index 70b34baeb..e6fb2bff6 100644 --- a/docs/learn/entities.md +++ b/docs/learn/entities.md @@ -9,6 +9,8 @@ A **user** is anyone holding one or more pairs of keys (one secret, one public). An account is uniquely identified by its _address_. The MultiversX network defines the address of an account to be equal to the public key of its corresponding pair of keys (the secret key remains known only by the user that owns the key pair). The public key is 32 bytes in length, which means that the address of an account is 32 bytes as well. As a standard, the MultiversX network uses the Bech32 human-readable representation for account addresses. -Users normally manage the keys of their accounts using _wallets_, which are applications dedicated to securely contain these keys. While it's completely possible to manage one's keys and accounts without a wallet application, it is an uncommon practice and employed mostly by advanced users or by automated processes. The **nodes** are the devices connected to the MultiversX network, which perform the operations requested by its users. Nodes can be either passive or actively engaged in processing tasks. _Eligible validators_ are active participants in the network. Specifically, they are responsible for performing consensus, adding blocks, maintaining the state, and they are also rewarded for their contribution. Each eligible validator can be uniquely identified by its 96-byte-long BLS public key (not to be confused with account keys, which are generated with the Schnorr algorithm). +Users normally manage the keys of their accounts using _wallets_, which are applications dedicated to securely contain these keys. While it's completely possible to manage one's keys and accounts without a wallet application, it is an uncommon practice and employed mostly by advanced users or by automated processes. + +The **nodes** are the devices connected to the MultiversX network, which perform the operations requested by its users. Nodes can be either passive or actively engaged in processing tasks. _Eligible validators_ are active participants in the network. Specifically, they are responsible for performing consensus, adding blocks, maintaining the state, and they are also rewarded for their contribution. Each eligible validator can be uniquely identified by its 96-byte-long BLS public key (not to be confused with account keys, which are generated with the Schnorr algorithm). A user that manages one or multiple nodes is called a **node operator**. These users must stake a substantial amount of EGLD tokens for each of their node as a collateral, effectively vouching for the correctness and performance of the nodes. The network locks the staked amount, which cannot be accessed by the node operator unless they withdraw both the stake _and the nodes_. Nodes that have been staked for by a user are promoted to **validator** status, and they may participate in consensus and earn rewards for their contribution. Without staking, nodes remain **observers** of the network. While passive, observers are still important in the network. From 073c0b4bb640427528cccb42807e499746ab6f9b Mon Sep 17 00:00:00 2001 From: danielailie Date: Thu, 13 Mar 2025 16:34:10 +0200 Subject: [PATCH 3/6] Add v14 Cookbook for sdk-js --- docs/developers/sc-calls-format.md | 2 +- .../signing-programmatically.md | 2 +- docs/sdk-and-tools/overview.md | 2 +- docs/sdk-and-tools/sdk-js/migration-guides.md | 2 +- .../sdk-js/sdk-js-cookbook-v12.md | 983 ----- .../sdk-js/sdk-js-cookbook-v13.md | 8 +- .../sdk-js/sdk-js-cookbook-v14.md | 3181 +++++++++++++++++ .../sdk-js/sdk-js-cookbook-versions.md | 4 +- .../writing-and-running-sdk-js-snippets.md | 2 +- sidebars.js | 4 +- 10 files changed, 3196 insertions(+), 994 deletions(-) delete mode 100644 docs/sdk-and-tools/sdk-js/sdk-js-cookbook-v12.md create mode 100644 docs/sdk-and-tools/sdk-js/sdk-js-cookbook-v14.md diff --git a/docs/developers/sc-calls-format.md b/docs/developers/sc-calls-format.md index a09ed1a8d..59e17521f 100644 --- a/docs/developers/sc-calls-format.md +++ b/docs/developers/sc-calls-format.md @@ -76,7 +76,7 @@ There are multiple ways of formatting the data field: - manually convert each argument, and then join the function name, alongside the argument via the `@` character. - use a pre-defined arguments serializer, such as [the one found in sdk-js](https://github.com/multiversx/mx-sdk-js-core/blob/main/src/smartcontracts/argSerializer.ts). -- use sdk-js's [contract calls](/sdk-and-tools/sdk-js/sdk-js-cookbook-v13/#contract-interactions). +- use sdk-js's [contract calls](/sdk-and-tools/sdk-js/sdk-js-cookbook-v14/#smart-contracts). - use sdk-cpp's [contract calls](https://github.com/multiversx/mx-sdk-cpp/blob/main/src/smartcontracts/contract_call.cpp). - and so on diff --git a/docs/developers/signing-transactions/signing-programmatically.md b/docs/developers/signing-transactions/signing-programmatically.md index 4f56379fc..5ee35a7d5 100644 --- a/docs/developers/signing-transactions/signing-programmatically.md +++ b/docs/developers/signing-transactions/signing-programmatically.md @@ -5,5 +5,5 @@ title: Signing programmatically In order to sign a transaction (or a message) using one of the SDKs, follow: -- [Signing objects using **sdk-js**](/sdk-and-tools/sdk-js/sdk-js-cookbook-v13#signing-objects) +- [Signing objects using **sdk-js**](/sdk-and-tools/sdk-js/sdk-js-cookbook-v14#signing-objects) - [Signing objects using **sdk-py**](/sdk-and-tools/sdk-py/sdk-py-cookbook#signing-objects) diff --git a/docs/sdk-and-tools/overview.md b/docs/sdk-and-tools/overview.md index e4e5d5e8d..ee2f66570 100644 --- a/docs/sdk-and-tools/overview.md +++ b/docs/sdk-and-tools/overview.md @@ -30,7 +30,7 @@ Note that Rust is also the recommended programming language for writing Smart Co | Name | Description | | -------------------------------------------------------------------------------------------------------- | --------------------------------------------------- | | [sdk-js](/sdk-and-tools/sdk-js) | High level overview about sdk-js. | -| [sdk-js cookbook](/sdk-and-tools/sdk-js/sdk-js-cookbook-v13) | Learn how to handle common tasks by using sdk-js. | +| [sdk-js cookbook](/sdk-and-tools/sdk-js/sdk-js-cookbook-v14) | Learn how to handle common tasks by using sdk-js. | | [Extending sdk-js](/sdk-and-tools/sdk-js/extending-sdk-js) | How to extend and tailor certain modules of sdk-js. | | [Writing and testing sdk-js interactions](/sdk-and-tools/sdk-js/writing-and-testing-sdk-js-interactions) | Write sdk-js interactions for Visual Studio Code | | [sdk-js migration guides](/sdk-and-tools/sdk-js/sdk-js-migration-guides) | Migrate from sdk-js v9.x to v10+ | diff --git a/docs/sdk-and-tools/sdk-js/migration-guides.md b/docs/sdk-and-tools/sdk-js/migration-guides.md index d584540ba..d48028808 100644 --- a/docs/sdk-and-tools/sdk-js/migration-guides.md +++ b/docs/sdk-and-tools/sdk-js/migration-guides.md @@ -8,7 +8,7 @@ title: Migration guides This page links to resources which are helpful in upgrading to newer versions of a **sdk-js** package. :::important -Make sure you have a look over the [cookbook](/sdk-and-tools/sdk-js/sdk-js-cookbook-v13), in advance. +Make sure you have a look over the [cookbook](/sdk-and-tools/sdk-js/sdk-js-cookbook-v14), in advance. ::: ## Migrate to a newer sdk-core diff --git a/docs/sdk-and-tools/sdk-js/sdk-js-cookbook-v12.md b/docs/sdk-and-tools/sdk-js/sdk-js-cookbook-v12.md deleted file mode 100644 index b0b83a867..000000000 --- a/docs/sdk-and-tools/sdk-js/sdk-js-cookbook-v12.md +++ /dev/null @@ -1,983 +0,0 @@ ---- -id: sdk-js-cookbook-v12 -title: Cookbook (v12) -pagination_prev: sdk-and-tools/sdk-js/sdk-js-cookbook-versions -pagination_next: null ---- - -[comment]: # (mx-abstract) - -This page will guide you through the process of handling common tasks using **sdk-js v12 (legacy, previous version)**. - -:::important -A newer variant of the **sdk-js** cookbook is available [here](/sdk-and-tools/sdk-js/sdk-js-cookbook-v13). -::: - -:::important -In order to migrate to the newer `sdk-js v13`, please follow [the migration guide](https://github.com/multiversx/mx-sdk-js-core/issues/392). -::: - -[comment]: # (mx-context-auto) - -## Creating network providers - -Creating an API provider: - -```js -import { ApiNetworkProvider } from "@multiversx/sdk-network-providers"; - -const apiNetworkProvider = new ApiNetworkProvider("https://devnet-api.multiversx.com"); -``` - -Creating a Proxy provider: - -```js -import { ProxyNetworkProvider } from "@multiversx/sdk-network-providers"; - -const proxyNetworkProvider = new ProxyNetworkProvider("https://devnet-gateway.multiversx.com"); -``` - -Use the classes from `@multiversx/sdk-network-providers` **only as a starting point**. -As your dApp matures, make sure you **switch to using your own network provider**, tailored to your requirements -(whether deriving from the default ones or writing a new one, from scratch) that directly interacts with the MultiversX API (or Gateway). - -On this topic, please see [extending sdk-js](https://docs.multiversx.com/sdk-and-tools/sdk-js/extending-sdk-js). - -[comment]: # (mx-context-auto) - -## Fetching network parameters - -```js -const networkConfig = await apiNetworkProvider.getNetworkConfig(); -console.log(networkConfig.MinGasPrice); -console.log(networkConfig.ChainID); -``` - -[comment]: # (mx-context-auto) - -## Working with accounts - -[comment]: # (mx-context-auto) - -### Synchronizing an account object - -The following snippet fetches (from the Network) the **nonce** and the **balance** of an account, and updates the local representation of the account. - -```js -import { Account } from "@multiversx/sdk-core"; - -const alice = new Account(addressOfAlice); -const aliceOnNetwork = await apiNetworkProvider.getAccount(addressOfAlice); -alice.update(aliceOnNetwork); - -console.log("Nonce:", alice.nonce); -console.log("Balance:", alice.balance.toString()); -``` - -[comment]: # (mx-context-auto) - -### Managing the sender nonce locally - -When sending a bunch of transactions, you usually have to first fetch the account nonce from the network (see above), then manage it locally (e.g. increment upon signing & broadcasting a transaction): - -```js -alice.incrementNonce(); -console.log("Nonce:", alice.nonce); -``` - -Alternatively, you can also use `setNonce` on a `Transaction` object: - -```js -notYetSignedTx.setNonce(alice.getNonceThenIncrement()); -``` - -For further reference, please see [nonce management](https://docs.multiversx.com/integrators/creating-transactions/#nonce-management). - -[comment]: # (mx-context-auto) - -## Preparing `TokenTransfer` objects - -A `TokenTransfer` object for **EGLD transfers** (value movements): - -```js -import { TokenTransfer } from "@multiversx/sdk-core"; - -let firstTransfer = TokenTransfer.egldFromAmount("1.5"); -let secondTransfer = TokenTransfer.egldFromBigInteger("1500000000000000000"); - -console.log(firstTransfer.valueOf(), secondTransfer.valueOf()); -console.log(firstTransfer.toPrettyString(), secondTransfer.toPrettyString()); -``` - -A `TokenTransfer` object for transferring **fungible** tokens: - -```js -const identifier = "FOO-123456"; -const numDecimals = 2; -firstTransfer = TokenTransfer.fungibleFromAmount(identifier, "1.5", numDecimals); -secondTransfer = TokenTransfer.fungibleFromBigInteger(identifier, "4000", numDecimals); - -console.log(firstTransfer.toString()); // Will output: 150. -console.log(firstTransfer.toPrettyString()); // Will output: 1.50 FOO-123456. -console.log(secondTransfer.toString()); // Will output: 4000. -console.log(secondTransfer.toPrettyString()); // Will output: 40.00 FOO-123456. -``` - -A `TokenTransfer` object for transferring **semi-fungible** tokens: - -```js -let nonce = 3; -let quantity = 50; -let transfer = TokenTransfer.semiFungible(identifier, nonce, quantity); -``` - -A `TokenTransfer` object for transferring **non-fungible** tokens (the quantity doesn't need to be specified for NFTs, as the token is only one of its kind): - -```js -nonce = 7; -transfer = TokenTransfer.nonFungible(identifier, nonce); -``` - -A `TokenTransfer` object for transferring **meta-esdt** tokens: - -```js -transfer = TokenTransfer.metaEsdtFromAmount(identifier, nonce, "0.1", numDecimals); -``` - -[comment]: # (mx-context-auto) - -## Broadcasting transactions - -[comment]: # (mx-context-auto) - -### Preparing a simple transaction - -```js -import { Transaction, TransactionPayload } from "@multiversx/sdk-core"; - -const tx = new Transaction({ - data: new TransactionPayload("helloWorld"), - gasLimit: 70000, - sender: addressOfAlice, - receiver: addressOfBob, - value: TokenTransfer.egldFromAmount(1), - chainID: "D" -}); - -tx.setNonce(alice.getNonceThenIncrement()); -``` - -[comment]: # (mx-context-auto) - -### Broadcast using a network provider - -```js -let txHash = await proxyNetworkProvider.sendTransaction(tx); -console.log("Hash:", txHash); -``` - -Note that the transaction **must be signed before being broadcasted**. Signing can be achieved using a signing provider. - -:::important -Note that, for all purposes, **we recommend using [sdk-dapp](https://github.com/multiversx/mx-sdk-dapp)** instead of integrating the signing providers on your own. -::: - -[comment]: # (mx-context-auto) - -### Broadcast using `axios` - -```js -import axios from "axios"; - -const data = readyToBroadcastTx.toSendable(); -const url = "https://devnet-api.multiversx.com/transactions"; -const response = await axios.post(url, data, { - headers: { - "Content-Type": "application/json", - }, -}); -let txHash = response.data.txHash; -``` - -[comment]: # (mx-context-auto) - -### Wait for transaction completion - -```js -import { TransactionWatcher } from "@multiversx/sdk-core"; - -const watcher = new TransactionWatcher(apiNetworkProvider); -const transactionOnNetwork = await watcher.awaitCompleted(tx); -``` - -If only the `txHash` is available, then: - -```js -const transactionOnNetwork = await watcher.awaitCompleted({ getHash: () => txHash }); -console.log(transactionOnNetwork); -``` - -In order to wait for multiple transactions: - -```js -await Promise.all([watcher.awaitCompleted(tx1), watcher.awaitCompleted(tx2), watcher.awaitCompleted(tx3)]); -``` - -For a different awaiting strategy, also see [extending sdk-js](https://docs.multiversx.com/sdk-and-tools/sdk-js/extending-sdk-js). - -[comment]: # (mx-context-auto) - -## Token transfers - -First, let's create a `TransferTransactionsFactory`. - -```js -import { GasEstimator, TransferTransactionsFactory } from "@multiversx/sdk-core"; - -const factory = new TransferTransactionsFactory(new GasEstimator()); -``` - -[comment]: # (mx-context-auto) - -### Single ESDT transfer - -```js -import { TokenTransfer } from "@multiversx/sdk-core"; - -const transfer1 = TokenTransfer.fungibleFromAmount("TEST-8b028f", "100.00", 2); - -const tx1 = factory.createESDTTransfer({ - tokenTransfer: transfer1, - nonce: 7, - sender: addressOfAlice, - receiver: addressOfBob, - chainID: "D" -}); -``` - -[comment]: # (mx-context-auto) - -### Single NFT transfer - -```js -const transfer2 = TokenTransfer.nonFungible("TEST-38f249", 1); - -const tx2 = factory.createESDTNFTTransfer({ - tokenTransfer: transfer2, - nonce: 8, - sender: addressOfAlice, - destination: addressOfBob, - chainID: "D" -}); -``` - -[comment]: # (mx-context-auto) - -### Single SFT transfer - -```js -const transfer3 = TokenTransfer.semiFungible("SEMI-9efd0f", 1, 5); - -const tx3 = factory.createESDTNFTTransfer({ - tokenTransfer: transfer3, - nonce: 9, - sender: addressOfAlice, - destination: addressOfBob, - chainID: "D" -}); -``` - -[comment]: # (mx-context-auto) - -### Multi ESDT / NFT transfer - -```js -const transfers = [transfer1, transfer2, transfer3]; - -const tx4 = factory.createMultiESDTNFTTransfer({ - tokenTransfers: transfers, - nonce: 10, - sender: addressOfAlice, - destination: addressOfBob, - chainID: "D" -}); -``` - -[comment]: # (mx-context-auto) - -## Contract deployments - -[comment]: # (mx-context-auto) - -### Load the bytecode from a file - -```js -import { Code } from "@multiversx/sdk-core"; -import { promises } from "fs"; - -let buffer = await promises.readFile("../contracts/adder.wasm"); -let code = Code.fromBuffer(buffer); -``` - -[comment]: # (mx-context-auto) - -### Load the bytecode from an URL - -```js -import axios from "axios"; - -let response = await axios.get("https://github.com/multiversx/mx-sdk-js-core/raw/main/src/testdata/adder.wasm", { - responseType: "arraybuffer", - transformResponse: [], - headers: { - "Accept": "application/wasm" - } -}); - -buffer = Buffer.from(response.data); -code = Code.fromBuffer(buffer); -``` - -[comment]: # (mx-context-auto) - -### Perform a contract deployment - -Create a `SmartContract` object: - -```js -import { SmartContract } from "@multiversx/sdk-core"; - -let contract = new SmartContract(); -``` - -Prepare the deploy transaction: - -```js -import { CodeMetadata } from "@multiversx/sdk-core"; - -const deployerAddress = addressOfAlice; - -const deployTransaction = contract.deploy({ - deployer: deployerAddress, - code: code, - codeMetadata: new CodeMetadata(/* set the parameters accordingly */), - initArguments: [/* set the initial arguments, if any */], - gasLimit: 20000000, - chainID: "D" -}); -``` - -Then, set the transaction nonce. - -Note that the account nonce must be synchronized beforehand. -Also, locally increment the nonce of the deployer (optional). - -```js -import { Account } from "@multiversx/sdk-core"; - -const deployer = new Account(deployerAddress); -const deployerOnNetwork = await networkProvider.getAccount(deployerAddress); -deployer.update(deployerOnNetwork); - -deployTransaction.setNonce(deployer.getNonceThenIncrement()); -``` - -Then **sign the transaction** using a wallet / signing provider of your choice (not shown here). - -Upon signing, you would usually compute the contract address (deterministically computable), as follows: - -```js -let contractAddress = SmartContract.computeAddress(deployTransaction.getSender(), deployTransaction.getNonce()); -console.log("Contract address:", contractAddress.bech32()); -``` - -In order to broadcast the transaction and await its completion, use a network provider and a transaction watcher: - -```js -import { TransactionWatcher } from "@multiversx/sdk-core"; - -await networkProvider.sendTransaction(deployTransaction); -let transactionOnNetwork = await new TransactionWatcher(networkProvider).awaitCompleted(deployTransaction); -``` - -In the end, parse the results: - -```js -import { ResultsParser } from "@multiversx/sdk-core"; - -let { returnCode } = new ResultsParser().parseUntypedOutcome(transactionOnNetwork); -console.log("Return code:", returnCode); -``` - -[comment]: # (mx-context-auto) - -## ABI - -[comment]: # (mx-context-auto) - -### Load the ABI from a file - -```js -import { AbiRegistry, Address, SmartContract } from "@multiversx/sdk-core"; -import { promises } from "fs"; - -let abiJson = await promises.readFile("../contracts/adder.abi.json", { encoding: "utf8" }); -let abiObj = JSON.parse(abiJson); -let abiRegistry = AbiRegistry.create(abiObj); -let existingContractAddress = Address.fromBech32("erd1qqqqqqqqqqqqqpgq5sup58y38q3pwyqklagxmuraetshrqwpd8ssh0ssph"); -let existingContract = new SmartContract({ address: existingContractAddress, abi: abiRegistry }); -``` - -[comment]: # (mx-context-auto) - -### Load the ABI from an URL - -```js -import axios from "axios"; - -const response = await axios.get("https://github.com/multiversx/mx-sdk-js-core/raw/main/src/testdata/adder.abi.json"); -abiRegistry = AbiRegistry.create(response.data); -existingContract = new SmartContract({ address: existingContractAddress, abi: abiRegistry }); -``` - -[comment]: # (mx-context-auto) - -## Contract queries - -[comment]: # (mx-context-auto) - -### When the ABI is not available - -```js -import { AddressValue, BigUIntType, BinaryCodec, ResultsParser, SmartContract } from "@multiversx/sdk-core"; - -let legacyDelegationContract = new SmartContract({ - address: legacyDelegationContractAddress -}); - -let query = legacyDelegationContract.createQuery({ - func: "getClaimableRewards", - args: [new AddressValue(addressOfFirstDevnetDelegator)] -}); - -let queryResponse = await networkProvider.queryContract(query); -let bundle = new ResultsParser().parseUntypedQueryResponse(queryResponse); -let firstValue = bundle.values[0]; -let decodedValue = new BinaryCodec().decodeTopLevel(firstValue, new BigUIntType()); - -console.log(bundle.returnCode); -console.log(bundle.returnMessage); -console.log(bundle.values); -console.log(decodedValue.valueOf().toFixed(0)); -``` - -[comment]: # (mx-context-auto) - -### Using `Interaction`, when the ABI is not available - -```js -import { Interaction } from "@multiversx/sdk-core"; - -let args = [new AddressValue(addressOfFirstDevnetDelegator)]; -query = new Interaction(legacyDelegationContract, "getClaimableRewards", args) - .buildQuery(); - -let queryResponseFromInteraction = await networkProvider.queryContract(query); - -console.assert(JSON.stringify(queryResponseFromInteraction) === JSON.stringify(queryResponse)); -``` - -Then, parse the response as above. - -[comment]: # (mx-context-auto) - -### When the ABI is available - -```js -import { AbiRegistry } from "@multiversx/sdk-core"; - -const legacyDelegationAbi = AbiRegistry.create({ - "endpoints": [ - { - "name": "getClaimableRewards", - "inputs": [{ - "type": "Address" - }], - "outputs": [{ - "type": "BigUint" - }] - } - ] -}); - -const getClaimableRewardsEndpoint = legacyDelegationAbi.getEndpoint("getClaimableRewards"); - -query = legacyDelegationContract.createQuery({ - func: "getClaimableRewards", - args: [new AddressValue(addressOfFirstDevnetDelegator)] -}); - -queryResponse = await networkProvider.queryContract(query); -let { values } = new ResultsParser().parseQueryResponse(queryResponse, getClaimableRewardsEndpoint); -console.log(values[0].valueOf().toFixed(0)); -``` - -[comment]: # (mx-context-auto) - -### Using `Interaction`, when the ABI is available - -Prepare the interaction, check it, then build the query: - -```js -legacyDelegationContract = new SmartContract({ - address: legacyDelegationContractAddress, - abi: legacyDelegationAbi -}); - -let interaction = legacyDelegationContract.methods.getClaimableRewards([addressOfFirstDevnetDelegator]); -query = interaction.check().buildQuery(); -``` - -Then, run the query and parse the results: - -```js -queryResponse = await networkProvider.queryContract(query); -let typedBundle = new ResultsParser().parseQueryResponse(queryResponse, interaction.getEndpoint()); -console.log(typedBundle.values[0].valueOf().toFixed(0)); -``` - -Depending on the context, reinterpret (cast) the results: - -```js -let firstValueAsStruct = firstValue; -``` - -[comment]: # (mx-context-auto) - -## Contract interactions - -[comment]: # (mx-context-auto) - -### When the ABI is not available - -```js -import { Address, AddressValue, SmartContract, U64Value } from "@multiversx/sdk-core"; - -let contractAddress = new Address("erd1qqqqqqqqqqqqqpgq5sup58y38q3pwyqklagxmuraetshrqwpd8ssh0ssph"); -let contract = new SmartContract({ address: contractAddress }); - -let tx1 = contract.call({ - caller: addressOfAlice, - func: "doSomething", - gasLimit: 5000000, - args: [new AddressValue(addressOfCarol), new U64Value(1000)], - chainID: "D" -}); - -tx1.setNonce(42); -``` - -Then, sign, broadcast `tx` and wait for its completion. - -[comment]: # (mx-context-auto) - -### Using `Interaction`, when the ABI is not available - -```js -import { Interaction, TokenTransfer, U32Value } from "@multiversx/sdk-core"; - -let args = [new U32Value(1), new U32Value(2), new U32Value(3)]; -let interaction = new Interaction(contract, "doSomethingWithValue", args); - -let tx2 = interaction - .withSender(addressOfAlice) - .withNonce(43) - .withValue(TokenTransfer.egldFromAmount(1)) - .withGasLimit(20000000) - .withChainID("D") - .buildTransaction(); -``` - -Then, sign, broadcast `tx` and wait for its completion. - -[comment]: # (mx-context-auto) - -### Using `Interaction`, when the ABI is available - -```js -import { AbiRegistry } from "@multiversx/sdk-core"; - -let abiRegistry = AbiRegistry.create({ - "endpoints": [ - { - "name": "foobar", - "inputs": [], - "outputs": [] - }, - { - "name": "doSomethingWithValue", - "inputs": [{ - "type": "u32" - }, - { - "type": "u32" - }, - { - "type": "u32" - }], - "outputs": [] - } - ] -}); - -contract = new SmartContract({ address: contractAddress, abi: abiRegistry }); - -let tx3 = contract.methods.doSomethingWithValue([1, 2, 3]) - .withSender(addressOfAlice) - .withNonce(44) - .withValue(TokenTransfer.egldFromAmount(1)) - .withGasLimit(20000000) - .withChainID("D") - .buildTransaction(); -``` - -Now let's see an example using variadic arguments, as well: - -```js -import { StringValue, VariadicValue } from "@multiversx/sdk-core"; - -abiRegistry = AbiRegistry.create({ - "endpoints": [ - { - "name": "foobar", - "inputs": [], - "outputs": [] - }, - { - "name": "doSomething", - "inputs": [{ - "type": "counted-variadic" - }, - { - "type": "variadic" - }], - "outputs": [] - } - ] -}); - -contract = new SmartContract({ address: contractAddress, abi: abiRegistry }); - -let tx4 = contract.methods.doSomething( - [ - // Counted variadic must be explicitly typed - VariadicValue.fromItemsCounted(StringValue.fromUTF8("foo"), StringValue.fromUTF8("bar")), - // Regular variadic can be implicitly typed - 1, 2, 3 - ]) - .withSender(addressOfAlice) - .withNonce(45) - .withGasLimit(20000000) - .withChainID("D") - .buildTransaction(); -``` - -[comment]: # (mx-context-auto) - -### Transfer & execute - -Given an interaction: - -```js -interaction = contract.methods.foobar([]); -``` - -One can apply token transfers to the smart contract call, as well. - -For single payments, do as follows: - -```js -// Fungible token -interaction.withSingleESDTTransfer(TokenTransfer.fungibleFromAmount("FOO-6ce17b", "1.5", 18)); - -// Non-fungible token -interaction.withSingleESDTNFTTransfer(TokenTransfer.nonFungible("SDKJS-38f249", 1)); -``` - -For multiple payments: - -```js -interaction.withMultiESDTNFTTransfer([ - TokenTransfer.fungibleFromAmount("FOO-6ce17b", "1.5", 18), - TokenTransfer.nonFungible("SDKJS-38f249", 1) -]); -``` - -[comment]: # (mx-context-auto) - -## Parsing contract results - -:::important -When the default `ResultsParser` misbehaves, please open an issue [on GitHub](https://github.com/multiversx/mx-sdk-js-core/issues), and also provide as many details as possible about the unparsable results (e.g. provide a dump of the transaction object if possible - make sure to remove any sensitive information). -::: - -[comment]: # (mx-context-auto) - -### When the ABI is not available - -```js -import { ResultsParser } from "@multiversx/sdk-core"; - -let resultsParser = new ResultsParser(); -let txHash = "d415901a9c88e564adf25b71b724b936b1274a2ad03e30752fdc79235af8ea3e"; -let transactionOnNetwork = await networkProvider.getTransaction(txHash); -let untypedBundle = resultsParser.parseUntypedOutcome(transactionOnNetwork); - -console.log(untypedBundle.returnCode, untypedBundle.values.length); -``` - -[comment]: # (mx-context-auto) - -### When the ABI is available - -```js -let endpointDefinition = AbiRegistry.create({ - "name": "adder", - "endpoints": [{ - "name": "add", - "inputs": [{ "type": "u64" }], - "outputs": [{ "type": "u64" }] - }] -}).getEndpoint("add"); - -transactionOnNetwork = await networkProvider.getTransaction(txHash); -let typedBundle = resultsParser.parseOutcome(transactionOnNetwork, endpointDefinition); - -console.log(typedBundle.returnCode, typedBundle.values.length); -``` - -Above, `endpointDefinition` is manually constructed. -However, in practice, it can be obtained from the `Interaction` object, if available in the context: - -```js -endpointDefinition = interaction.getEndpoint(); -``` - -Alternatively, the `endpointDefinition` can also be obtained from the `SmartContract` object: - -```js -let endpointDefinition = smartContract.getEndpoint("myFunction"); -``` - -For customizing the default parser, also see [extending sdk-js](/sdk-and-tools/sdk-js/extending-sdk-js). - -[comment]: # (mx-context-auto) - -## Contract events - -[comment]: # (mx-context-auto) - -### Decode transaction events - -Example of decoding a transaction event having the identifier `deposit`: - -```js -const abiContent = await promises.readFile("../contracts/example.abi.json", { encoding: "utf8" }); -const abiObj = JSON.parse(abiContent); -const abiRegistry = AbiRegistry.create(abiObj); -const resultsParser = new ResultsParser(); - -const eventIdentifier = "deposit"; -const eventDefinition = abiRegistry.getEvent(eventIdentifier); -const transaction = await networkProvider.getTransaction("532087e5021c9ab8be8a4db5ad843cfe0610761f6334d9693b3765992fd05f67"); -const event = transaction.contractResults.items[0].logs.findFirstOrNoneEvent(eventIdentifier); -const outcome = resultsParser.parseEvent(event, eventDefinition); -console.log(JSON.stringify(outcome, null, 4)); -``` - -[comment]: # (mx-context-auto) - -## Explicit decoding / encoding of values - -[comment]: # (mx-context-auto) - -### Decoding a custom type - -Example of decoding a custom type (a structure) called `DepositEvent` from binary data: - -```js -import { AbiRegistry, BinaryCodec } from "@multiversx/sdk-core"; -import { promises } from "fs"; - -const abiJson = await promises.readFile("../contracts/example.abi.json", { encoding: "utf8" }); -const abiObj = JSON.parse(abiJson); -const abiRegistry = AbiRegistry.create(abiObj); -const depositCustomType = abiRegistry.getCustomType("DepositEvent"); -const codec = new BinaryCodec(); -let data = Buffer.from("00000000000003db000000", "hex"); -let decoded = codec.decodeTopLevel(data, depositCustomType); -let decodedValue = decoded.valueOf(); - -console.log(JSON.stringify(decodedValue, null, 4)); -``` - -Example of decoding a custom type (a structure) called `Reward` from binary data: - -```js -const rewardStructType = abiRegistry.getStruct("Reward"); -data = Buffer.from("010000000445474c440000000201f400000000000003e80000000000000000", "hex"); - -[decoded] = codec.decodeNested(data, rewardStructType); -decodedValue = decoded.valueOf(); -console.log(JSON.stringify(decodedValue, null, 4)); -``` - -[comment]: # (mx-context-auto) - -## Signing objects - -:::note -For **dApps**, use the available **[signing providers](/sdk-and-tools/sdk-js/sdk-js-signing-providers)** instead. -::: - -Creating a `UserSigner` from a JSON wallet: - -```js -import { UserSigner } from "@multiversx/sdk-wallet"; -import { promises } from "fs"; - -const fileContent = await promises.readFile("../testwallets/alice.json", { encoding: "utf8" }); -const walletObject = JSON.parse(fileContent); -let signer = UserSigner.fromWallet(walletObject, "password"); -``` - -Creating a `UserSigner` from a PEM file: - -```js -const pemText = await promises.readFile("../testwallets/alice.pem", { encoding: "utf8" }); -signer = UserSigner.fromPem(pemText); -``` - -Signing a transaction: - -```js -import { Transaction } from "@multiversx/sdk-core"; - -const transaction = new Transaction({ - gasLimit: 50000, - gasPrice: 1000000000, - sender: addressOfAlice, - receiver: addressOfBob, - chainID: "D", - version: 1 -}); - -const serializedTransaction = transaction.serializeForSigning(); -const transactionSignature = await signer.sign(serializedTransaction); -transaction.applySignature(transactionSignature); - -console.log("Transaction signature", transaction.getSignature().toString("hex")); -console.log("Transaction hash", transaction.getHash().toString()); -``` - -Signing an arbitrary message: - -```js -import { SignableMessage } from "@multiversx/sdk-core"; - -let message = new SignableMessage({ - message: Buffer.from("hello") -}); - -let serializedMessage = message.serializeForSigning(); -let messageSignature = await signer.sign(serializedMessage); -message.applySignature(messageSignature); - -console.log("Message signature", message.getSignature().toString("hex")); -``` - -[comment]: # (mx-context-auto) - -## Verifying signatures - -Creating a `UserVerifier`: - -```js -import { UserVerifier } from "@multiversx/sdk-wallet"; - -const aliceVerifier = UserVerifier.fromAddress(addressOfAlice); -const bobVerifier = UserVerifier.fromAddress(addressOfBob); -``` - -Suppose we have the following transaction: - -```js -const tx = Transaction.fromPlainObject({ - nonce: 42, - value: "12345", - sender: addressOfAlice.bech32(), - receiver: addressOfBob.bech32(), - gasPrice: 1000000000, - gasLimit: 50000, - chainID: "D", - version: 1, - signature: "3c5eb2d1c9b3ab2f578541e62dcfa5008976d11f85644a48884a8a6c4d2980fa14954ab2924d6e67c051562488096d2e79cd3c0378edf234a52e648e672d1b0a" -}); - -const serializedTx = tx.serializeForSigning(); -const txSignature = tx.getSignature(); -``` - -And / or the following message and signature: - -```js -message = new SignableMessage({ message: Buffer.from("hello") }); -serializedMessage = message.serializeForSigning(); -messageSignature = Buffer.from("561bc58f1dc6b10de208b2d2c22c9a474ea5e8cabb59c3d3ce06bbda21cc46454aa71a85d5a60442bd7784effa2e062fcb8fb421c521f898abf7f5ec165e5d0f", "hex"); -``` - -We can verify their signatures as follows: - -```js -console.log("Is signature of Alice?", aliceVerifier.verify(serializedTx, txSignature)); -console.log("Is signature of Alice?", aliceVerifier.verify(serializedMessage, messageSignature)); -console.log("Is signature of Bob?", bobVerifier.verify(serializedTx, txSignature)); -console.log("Is signature of Bob?", bobVerifier.verify(serializedMessage, messageSignature)); -``` - -[comment]: # (mx-context-auto) - -## Decoding transaction metadata - -[comment]: # (mx-context-auto) - -### Using the `transaction-decoder` - -In order to decode the metadata (function, arguments, transfers) from a transaction payload, do as follows: - -```js -import { TransactionDecoder, TransactionMetadata } from "@multiversx/sdk-transaction-decoder"; - -let transactionOnNetwork = await networkProvider.getTransaction(txHash); - -let metadata = new TransactionDecoder().getTransactionMetadata({ - sender: transactionOnNetwork.sender.bech32(), - receiver: transactionOnNetwork.receiver.bech32(), - data: transactionOnNetwork.data.toString("base64"), - value: transactionOnNetwork.value.toString(), - type: transactionOnNetwork.type -}); -``` - -[comment]: # (mx-context-auto) - -### Using the `esdtHelpers` and `scArgumentsParser` of `sdk-js 9x` - -The classes `esdtHelpers` and `scArgumentsParser` have been removed in `sdk-js 10`, in favor of the [@multiversx/sdk-transaction-decoder](https://www.npmjs.com/package/@multiversx/sdk-transaction-decoder) (see above). - -However, you can still find the previous implementations at the following location: - -- [esdtHelpers](https://github.com/multiversx/mx-sdk-js-core/blob/release/v9/src/esdtHelpers.ts) -- [esdtHelpers examples](https://github.com/multiversx/mx-sdk-js-core/blob/release/v9/src/esdtHelpers.spec.ts) -- [scArgumentsParser](https://github.com/multiversx/mx-sdk-js-core/blob/release/v9/src/scArgumentsParser.ts) -- [scArgumentsParser examples](https://github.com/multiversx/mx-sdk-js-core/blob/release/v9/src/scArgumentsParser.spec.ts) diff --git a/docs/sdk-and-tools/sdk-js/sdk-js-cookbook-v13.md b/docs/sdk-and-tools/sdk-js/sdk-js-cookbook-v13.md index 2d58b02e0..9c87f5508 100644 --- a/docs/sdk-and-tools/sdk-js/sdk-js-cookbook-v13.md +++ b/docs/sdk-and-tools/sdk-js/sdk-js-cookbook-v13.md @@ -7,10 +7,14 @@ pagination_next: null [comment]: # (mx-abstract) -This page will guide you through the process of handling common tasks using **sdk-js v13 (latest, stable version)**. +This page will guide you through the process of handling common tasks using **sdk-js v13 (legacy, previous version)**. :::important -This cookbook makes use of `sdk-js v13`. In order to migrate from `sdk-js v12.x` to `sdk-js v13`, please also follow [the migration guide](https://github.com/multiversx/mx-sdk-js-core/issues/392). +A newer variant of the **sdk-js** cookbook is available [here](/sdk-and-tools/sdk-js/sdk-js-cookbook-v13). +::: + +:::important +In order to migrate to the newer `sdk-js v14`, please follow [the migration guide](https://github.com/multiversx/mx-sdk-js-core/issues/576). ::: ## Creating network providers diff --git a/docs/sdk-and-tools/sdk-js/sdk-js-cookbook-v14.md b/docs/sdk-and-tools/sdk-js/sdk-js-cookbook-v14.md new file mode 100644 index 000000000..f0a505418 --- /dev/null +++ b/docs/sdk-and-tools/sdk-js/sdk-js-cookbook-v14.md @@ -0,0 +1,3181 @@ +--- +id: sdk-js-cookbook-v14 +title: Cookbook (v14) +pagination_prev: sdk-and-tools/sdk-js/sdk-js-cookbook-versions +pagination_next: null +--- + +[comment]: # (mx-abstract) + +# Overview + +This guide walks you through handling common tasks using the MultiversX Javascript SDK (v14, latest stable version). + +:::important +This cookbook makes use of `sdk-js v14`. In order to migrate from `sdk-js v13.x` to `sdk-js v14`, please also follow [the migration guide](https://github.com/multiversx/mx-sdk-js-core/issues/576). +::: + +# Creating an Entrypoint + +An Entrypoint represents a network client that simplifies access to the most common operations. +There is a dedicated entrypoint for each network: `MainnetEntrypoint`, `DevnetEntrypoint`, `TestnetEntrypoint`, `LocalnetEntrypoint`. + +For example, to create a Devnet entrypoint you have the following command: + +```js +const entrypoint = new DevnetEntrypoint(); +``` + +#### Using a Custom API +If you'd like to connect to a third-party API, you can specify the url parameter: + +```js +const apiEntrypoint = new DevnetEntrypoint({ url: 'https://custom-multiversx-devnet-api.com' }); +``` + +#### Using a Proxy + +By default, the DevnetEntrypoint uses the standard API. However, you can create a custom entrypoint that interacts with a proxy by specifying the kind parameter: + + +```js +const customEntrypoint = new DevnetEntrypoint({ + url: 'https://devnet-gateway.multiversx.com', + kind: 'proxy' +}); +``` + +# Creating Accounts + +You can initialize an account directly from the entrypoint. Keep in mind that the account is network agnostic, meaning it doesn't matter which entrypoint is used. +Accounts are used for signing transactions and messages and managing the account's nonce. They can also be saved to a PEM or keystore file for future use. + +```js +{ + const entrypoint = new DevnetEntrypoint(); + const account = entrypoint.createAccount(); +} +``` + +## Other Ways to Instantiate an Account + +### From a Secret Key +```js +{ + const secretKeyHex = "413f42575f7f26fad3317a778771212fdb80245850981e48b58a4f25e344e8f9"; + const secretKey = new UserSecretKey(Buffer.from(secretKeyHex, 'hex')); + + const accountFromSecretKey = new Account(secretKey); +} +``` + +### From a PEM file +```js +{ + const filePath = path.join("src", "testdata", "testwallets", "alice.pem"); + const accountFromPem = Account.newFromPem(filePath); +} +``` + +### From a Keystore File +```js +{ + const keystorePath = path.join("src", "testdata", "testwallets", "alice.json"); + const accountFromKeystore = Account.newFromKeystore({ + filePath: keystorePath, + password: "password" + }); +} +``` + +### From a Mnemonic +```js + +const mnemonic = Mnemonic.generate(); +const accountFromMnemonic = Account.newFromMnemonic(mnemonic.getText()); +``` + +### From a KeyPair + +```js +const keypair = KeyPair.generate(); +const accountFromKeyPairs = Account.newFromKeypair(keypair); +``` + +## Managing the Account Nonce + +An account has a `nonce` property that the user is responsible for managing. +You can fetch the nonce from the network and increment it after each transaction. +Each transaction must have the correct nonce, otherwise it will fail to execute. + +```js +{ + const secretKeyHex = "413f42575f7f26fad3317a778771212fdb80245850981e48b58a4f25e344e8f9"; + const key = new UserSecretKey(Buffer.from(secretKeyHex, 'hex')); + + const accountWithNonce = new Account(key); + const entrypoint = new DevnetEntrypoint(); + + // Fetch the current nonce from the network + accountWithNonce.nonce = await entrypoint.recallAccountNonce(accountWithNonce.address); + + // Create and send a transaction here... + + // Increment nonce after each transaction + const nonce = accountWithNonce.getNonceThenIncrement(); +} +``` + +For more details, see the [Creating Transactions](#creating-transactions) section. + +### Saving the Account to a File + +Accounts can be saved to either a PEM file or a keystore file. +While PEM wallets are less secure for storing secret keys, they are convenient for testing purposes. +Keystore files offer a higher level of security. + +#### Saving the Account to a PEM File +```js +{ + const secretKeyHex = "413f42575f7f26fad3317a778771212fdb80245850981e48b58a4f25e344e8f9"; + const secretKey = new UserSecretKey(Buffer.from(secretKeyHex, 'hex')); + + const account = new Account(secretKey); + account.saveToPem({ path: path.resolve("wallet.pem") }); +} +``` + +#### Saving the Account to a Keystore File +```js +{ + const secretKeyHex = "413f42575f7f26fad3317a778771212fdb80245850981e48b58a4f25e344e8f9"; + const secretKey = new UserSecretKey(Buffer.from(secretKeyHex, 'hex')); + + const account = new Account(secretKey); + account.saveToKeystore({ + path: path.resolve("keystoreWallet.json"), + password: "password" + }); +} + +``` + +## Using a Ledger Device + +You can manage your account with a Ledger device, allowing you to sign both transactions and messages while keeping your keys secure. + +Note: **The multiversx-sdk package does not include Ledger support by default. To enable it, install the package with Ledger dependencies**: +```bash +npm install @multiversx/sdk-hw-provider +``` + +### Creating a Ledger Account +This can be done using the dedicated library. You can find more information [here](/sdk-and-tools/sdk-js/sdk-js-signing-providers/#the-hardware-wallet-provider). + +When signing transactions or messages, the Ledger device will prompt you to confirm the details before proceeding. + +## Compatibility with IAccount Interface + +The `Account` implements the `IAccount` interface, making it compatible with transaction controllers and any other component that expects this interface. + +# Calling the Faucet + +This functionality is not yet available through the entrypoint, but we recommend using the faucet available within the Web Wallet. For more details about hthe faucet [see this](/wallet/web-wallet/#testnet-and-devnet-faucet). + +- [Testnet Wallet](https://testnet-wallet.multiversx.com/). +- [Devnet Wallet](https://devnet-wallet.multiversx.com/). + +## Interacting with the network + +The entrypoint exposes a few ways to directly interact with the network, such as: + +- `recallAccountNonce(address: Address): Promise;` +- `sendTransactions(transactions: Transaction[]): Promise<[number, string[]]>;` +- `sendTransaction(transaction: Transaction): Promise;` +- `getTransaction(txHash: string): Promise;` +- `awaitCompletedTransaction(txHash: string): Promise;` + +Some other methods are exposed through a so called **network provider**. + +- **ApiNetworkProvider**: Interacts with the API, which is a layer over the proxy. It fetches data from the network and `Elastic Search`. +- **ProxyNetworkProvider**: Interacts directly with the proxy of an observing squad. + +To get the underlying network provider from our entrypoint, we can do as follows: + +```js +{ + const entrypoint = new DevnetEntrypoint(); + const networkProvider = entrypoint.createNetworkProvider(); +} +``` + +## Creating a network provider +When manually instantiating a network provider, you can provide a configuration to specify the client name and set custom request options. + +```js +{ + // Create a configuration object + const config = { + clientName: "hello-multiversx", + requestsOptions: { + timeout: 1000, // Timeout in milliseconds + auth: { + username: "user", + password: "password" + } + } + }; + + // Instantiate the network provider with the config + const api = new ApiNetworkProvider("https://devnet-api.multiversx.com", config); +} +``` + +A full list of available methods for `ApiNetworkProvider` can be found [here](https://multiversx.github.io/mx-sdk-js-core/v14/classes/ApiNetworkProvider.html). + +Both `ApiNetworkProvider` and `ProxyNetworkProvider` implement a common interface, which can be found [here](https://multiversx.github.io/mx-sdk-js-core/v14/interfaces/INetworkProvider.html). This allows them to be used interchangeably. + +The classes returned by the API expose the most commonly used fields directly for convenience. However, each object also contains a `raw` field that stores the original API response, allowing access to additional fields if needed. + + +# Fetching data from the network + +## Fetching the network config + +```js +{ + const entrypoint = new DevnetEntrypoint(); + const networkProvider = entrypoint.createNetworkProvider(); + + const networkConfig = entrypoint.getNetworkConfig(); +} +``` + +## Fetching the network status + +```js +{ + const entrypoint = new DevnetEntrypoint(); + const networkProvider = entrypoint.createNetworkProvider(); + + const metaNetworkStatus = entrypoint.getNetworkStatus(); // fetches status from metachain + const networkStatus = entrypoint.getNetworkStatus(1); // fetches status from shard one +} +``` + +## Fetching a Block from the Network +To fetch a block, we first instantiate the required arguments and use its hash. The API only supports fetching blocks by hash, whereas the **PROXY** allows fetching blocks by either hash or nonce. + +When using the **PROXY**, keep in mind that the shard must also be specified in the arguments. + +### Fetching a block using the **API** +```js +{ + const api = new ApiNetworkProvider("https://devnet-api.multiversx.com"); + const blockHash = "1147e111ce8dd860ae43a0f0d403da193a940bfd30b7d7f600701dd5e02f347a"; + const block = await api.getBlock(blockHash); +} +``` + +Additionally, we can fetch the latest block from the network: + +```js +{ + const api = new ApiNetworkProvider("https://devnet-api.multiversx.com"); + const latestBlock = await api.getLatestBlock(); +} +``` + +### Fetching a block using the **PROXY** + +When using the proxy, we have to provide the shard, as well. +```js +{ + const proxy = new ProxyNetworkProvider("https://devnet-api.multiversx.com"); + const blockHash = "1147e111ce8dd860ae43a0f0d403da193a940bfd30b7d7f600701dd5e02f347a"; + const block = proxy.getBlock({ blockHash, shard: 1 }); +} +``` + +We can also fetch the latest block from the network. +By default, the shard will be the metachain, but we can specify a different shard if needed. + +```js +{ + const proxy = new ProxyNetworkProvider("https://devnet-api.multiversx.com"); + const latestBlock = proxy.getLatestBlock(); +} +``` + +## Fetching an Account +To fetch an account, we need its address. Once we have the address, we create an `Address` object and pass it as an argument to the method. + +```js +{ + const entrypoint = new DevnetEntrypoint(); + const api = entrypoint.createNetworkProvider(); + const alice = Address.newFromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + const account = await api.getAccount(alice); +} +``` + +## Fetching an Account's Storage +We can also fetch an account's storage, allowing us to retrieve all key-value pairs saved for that account. + +```js +{ + const entrypoint = new DevnetEntrypoint(); + const api = entrypoint.createNetworkProvider(); + const alice = Address.newFromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + const account = await api.getAccountStorage(alice); +} +``` + +If we only want to fetch a specific key, we can do so as follows: + +```js +{ + const entrypoint = new DevnetEntrypoint(); + const api = entrypoint.createNetworkProvider(); + const alice = Address.newFromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + const account = await api.getAccountStorageEntry(alice, "testKey"); +} +``` + +## Waiting for an Account to Meet a Condition +There are times when we need to wait for a specific condition to be met before proceeding with an action. +For example, let's say we want to send 7 EGLD from Alice to Bob, but this can only happen once Alice's balance reaches at least 7 EGLD. +This approach is useful in scenarios where you're waiting for external funds to be sent to Alice, enabling her to transfer the required amount to another recipient. + +To implement this, we need to define the condition to check each time the account is fetched from the network. We create a function that takes an `AccountOnNetwork` object as an argument and returns a `bool`. +Keep in mind that this method has a default timeout, which can be adjusted using the `AwaitingOptions` class. + +```js +{ + const entrypoint = new DevnetEntrypoint(); + const api = entrypoint.createNetworkProvider(); + + const condition = (account) => { + return account.balance >= 7000000000000000000; // 7 EGLD + }; + const alice = Address.newFromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + const account = await api.awaitAccountOnCondition(alice, condition); +} +``` + +## Sending and Simulating Transactions +To execute transactions, we use the network providers to broadcast them to the network. Keep in mind that for transactions to be processed, they must be signed. + +### Sending a Transaction + +```js +{ + const entrypoint = new DevnetEntrypoint(); + const api = entrypoint.createNetworkProvider(); + + const alice = Address.newFromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + const bob = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); + + const transaction = new Transaction({ + sender: alice, + receiver: bob, + gasLimit: 50000n, + chainID: "D", + }); + + // set the correct nonce and sign the transaction ... + + const transactionHash = await api.sendTransaction(transaction); +} +``` + +### Sending multiple transactions +```js +{ + const entrypoint = new DevnetEntrypoint(); + const api = entrypoint.createNetworkProvider(); + + const alice = Address.newFromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + const bob = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); + + const firstTransaction = new Transaction({ + sender: alice, + receiver: bob, + gasLimit: 50000n, + chainID: "D", + nonce: 2 + }); + + const secondTransaction = new Transaction({ + sender: bob, + receiver: alice, + gasLimit: 50000n, + chainID: "D", + nonce: 1, + }); + + const thirdTransaction = new Transaction({ + sender: alice, + receiver: alice, + gasLimit: 60000n, + chainID: "D", + nonce: 3, + data: new Uint8Array(Buffer.from("hello")) + }); + + // set the correct nonce and sign the transaction ... + + const [numOfSentTxs, hashes] = await api.sendTransactions([firstTransaction, secondTransaction, thirdTransaction]); +} +``` + +### Simulating transactions +A transaction can be simulated before being sent for processing by the network. This is primarily used for smart contract calls, allowing you to preview the results produced by the smart contract. + +```js +{ + const entrypoint = new DevnetEntrypoint(); + const api = entrypoint.createNetworkProvider(); + + const alice = Address.newFromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + const contract = Address.newFromBech32("erd1qqqqqqqqqqqqqpgqccmyzj9sade2495w78h42erfrw7qmqxpd8sss6gmgn"); + + const transaction = new Transaction({ + sender: alice, + receiver: contract, + gasLimit: 5000000n, + chainID: "D", + data: new Uint8Array(Buffer.from("add@07")) + }); + + const transactionOnNetwork = await api.simulateTransaction(transaction); +} +``` + +### Estimating the gas cost of a transaction +Before sending a transaction to the network for processing, you can retrieve the estimated gas limit required for the transaction to be executed. + +```js +{ + const entrypoint = new DevnetEntrypoint(); + const api = entrypoint.createNetworkProvider(); + + const alice = Address.newFromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + const contract = Address.newFromBech32("erd1qqqqqqqqqqqqqpgqccmyzj9sade2495w78h42erfrw7qmqxpd8sss6gmgn"); + + const nonce = await entrypoint.recallAccountNonce(alice); + + const transaction = new Transaction({ + sender: alice, + receiver: contract, + gasLimit: 5000000, + chainID: "D", + data: new Uint8Array(Buffer.from("add@07")), + nonce: nonce + }); + + const transactionCostResponse = await api.estimateTransactionCost(transaction); +} +``` + +## Waiting for transaction completion +After sending a transaction, you may want to wait until it is processed before proceeding with another action. Keep in mind that this method has a default timeout, which can be adjusted using the `AwaitingOptions` class. + +```js +{ + const entrypoint = new DevnetEntrypoint(); + const api = entrypoint.createNetworkProvider(); + + const txHash = "exampletransactionhash"; + const transactionOnNetwork = await api.awaitTransactionCompleted(txHash); +} +``` + +## Waiting for a Transaction to Satisfy a Condition +Similar to accounts, we can wait until a transaction meets a specific condition. + +```js +{ + const entrypoint = new DevnetEntrypoint(); + const api = entrypoint.createNetworkProvider(); + + const condition = (txOnNetwork) => !txOnNetwork.status.isSuccessful(); + + const txHash = "exampletransactionhash"; + const transactionOnNetwork = await api.awaitTransactionOnCondition(txHash, condition); +} +``` + +## Waiting for transaction completion +After sending a transaction, you may want to wait until it is processed before proceeding with another action. Keep in mind that this method has a default timeout, which can be adjusted using the `AwaitingOptions` class. + +```js +{ + const entrypoint = new DevnetEntrypoint(); + const api = entrypoint.createNetworkProvider(); + + const txHash = "exampletransactionhash"; + const transactionOnNetwork = await api.awaitTransactionCompleted(txHash); +} +``` + +## Fetching Transactions from the Network +After sending a transaction, we can fetch it from the network using the transaction hash, which we receive after broadcasting the transaction. + +```js +{ + const entrypoint = new DevnetEntrypoint(); + const api = entrypoint.createNetworkProvider(); + + const txHash = "exampletransactionhash"; + const transactionOnNetwork = await api.getTransaction(txHash); +} +``` + +## Fetching a token from an account +We can fetch a specific token (ESDT, MetaESDT, SFT, NFT) from an account by providing the account's address and the token identifier. + +```js +{ + const entrypoint = new DevnetEntrypoint(); + const api = entrypoint.createNetworkProvider(); + + const alice = Address.newFromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + let token = new Token({ identifier: "TEST-ff155e" }); // ESDT + let tokenOnNetwork = await api.getTokenOfAccount(alice, token); + + + token = new Token({ identifier: "NFT-987654", nonce: 11n }); // NFT + tokenOnNetwork = await api.getTokenOfAccount(alice, token); +} +``` + +## Fetching all fungible tokens of an account +Fetches all fungible tokens held by an account. Note that this method does not handle pagination, but it can be achieved using `doGetGeneric`. + +```js +{ + const entrypoint = new DevnetEntrypoint(); + const api = entrypoint.createNetworkProvider(); + + const alice = Address.newFromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + const fungibleTokens = await api.getFungibleTokensOfAccount(alice); +} +``` + +## Fetching all non-fungible tokens of an account +Fetches all non-fungible tokens held by an account. Note that this method does not handle pagination, but it can be achieved using `doGetGeneric`. + +```js +{ + const entrypoint = new DevnetEntrypoint(); + const api = entrypoint.createNetworkProvider(); + + const alice = Address.newFromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + const nfts = await api.getNonFungibleTokensOfAccount(alice); +} +``` + +## Fetching token metadata +If we want to fetch the metadata of a token (e.g., owner, decimals, etc.), we can use the following methods: + +```js +{ + const entrypoint = new DevnetEntrypoint(); + const api = entrypoint.createNetworkProvider(); + + // used for ESDT + const fungibleTokenDefinition = await api.getDefinitionOfFungibleToken("TEST-ff155e"); + + // used for METAESDT, SFT, NFT + const nonFungibleTokenDefinition = await api.getDefinitionOfTokenCollection("NFTEST-ec88b8"); +} +``` + +## Querying Smart Contracts +Smart contract queries, or view functions, are endpoints that only read data from the contract. To send a query to the observer nodes, we can proceed as follows: + +```js +{ + const entrypoint = new DevnetEntrypoint(); + const api = entrypoint.createNetworkProvider(); + + const query = new SmartContractQuery({ + contract: Address.newFromBech32("erd1qqqqqqqqqqqqqpgqqy34h7he2ya6qcagqre7ur7cc65vt0mxrc8qnudkr4"), + function: "getSum", + arguments: [], + }); + const response = await api.queryContract(query); +} +``` + +## Custom Api/Proxy calls +The methods exposed by the `ApiNetworkProvider` or `ProxyNetworkProvider` are the most common and widely used. However, there may be times when custom API calls are needed. For these cases, we’ve created generic methods for both GET and POST requests. +Let’s assume we want to retrieve all the transactions sent by Alice in which the `delegate` function was called. + +```js +{ + const entrypoint = new DevnetEntrypoint(); + const api = entrypoint.createNetworkProvider(); + + const alice = Address.newFromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + const url = `transactions/${alice.toBech32()}?function=delegate`; + + const response = await api.doGetGeneric(url); +} +``` + +# Creating transactions + +In this section, we’ll explore how to create different types of transactions. To create transactions, we can use either controllers or factories. +Controllers are ideal for quick scripts or network interactions, while factories provide a more granular and lower-level approach, typically required for DApps. + +Controllers typically use the same parameters as factories, but they also require an Account object and the sender’s nonce. +Controllers also include extra functionality, such as waiting for transaction completion and parsing transactions. +The same functionality can be achieved for transactions built using factories, and we’ll see how in the sections below. In the next section, we’ll learn how to create transactions using both methods. + +## Instantiating Controllers and Factories +There are two ways to create controllers and factories: +1. Get them from the entrypoint. +2. Manually instantiate them. + +```js +{ + const entrypoint = new DevnetEntrypoint(); + + // getting the controller and the factory from the entrypoint + const transfersController = entrypoint.createTransfersController(); + const transfersFactory = entrypoint.createTransfersTransactionsFactory(); + + // manually instantiating the controller and the factory + const controller = new TransfersController({ chainID: 'D' }); + + const config = new TransactionsFactoryConfig({ chainID: 'D' }); + const factory = new TransferTransactionsFactory({ config }); +} +``` + +## Token transfers +We can send both native tokens (EGLD) and ESDT tokens using either the controller or the factory. +### Native Token Transfers Using the Controller +When using the controller, the transaction will be signed because we’ll be working with an Account. + +```js +{ + const entrypoint = new DevnetEntrypoint(); + + const filePath = path.join("src", "testdata", "testwallets", "alice.pem"); + const alice = await Account.newFromPem(filePath); + const bob = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); + + // the developer is responsible for managing the nonce + alice.nonce = await entrypoint.recallAccountNonce(alice.address); + + const transfersController = entrypoint.createTransfersController(); + const transaction = await transfersController.createTransactionForTransfer( + alice, + alice.getNonceThenIncrement(), + { + receiver: bob, + nativeAmount: 1n, + }, + ); + + const txHash = await entrypoint.sendTransaction(transaction); +} +``` + +If you know you’ll only be sending native tokens, you can create the transaction using the `createTransactionForNativeTokenTransfer` method. + +### Native Token Transfers Using the Factory +When using the factory, only the sender's address is required. As a result, the transaction won’t be signed, and the nonce field won’t be set correctly. +You will need to handle these aspects after the transaction is created. + +```js +{ + const entrypoint = new DevnetEntrypoint(); + const factory = entrypoint.createTransfersTransactionsFactory(); + + const filePath = path.join("src", "testdata", "testwallets", "alice.pem"); + const alice = await Account.newFromPem(filePath); + + // the developer is responsible for managing the nonce + alice.nonce = await entrypoint.recallAccountNonce(alice.address); + + const bob = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); + + const transaction = factory.createTransactionForTransfer(alice, { + receiver: bob, + nativeAmount: 1000000000000000000n, + }); + + // set the sender's nonce + transaction.nonce = alice.getNonceThenIncrement(); + + // sign the transaction using the sender's account + transaction.signature = await alice.signTransaction(transaction); + + const txHash = await entrypoint.sendTransaction(transaction); +} +``` + +If you know you’ll only be sending native tokens, you can create the transaction using the `createTransactionForNativeTokenTransfer` method. + +### Custom token transfers using the controller + +```js +{ + const entrypoint = new DevnetEntrypoint(); + + const filePath = path.join("src", "testdata", "testwallets", "alice.pem"); + const alice = await Account.newFromPem(filePath); + const bob = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); + + // the developer is responsible for managing the nonce + alice.nonce = await entrypoint.recallAccountNonce(alice.address); + + const esdt = new Token({ identifier: "TEST-123456" }); + const firstTransfer = new TokenTransfer({ token: esdt, amount: 1000000000n }); + + const nft = new Token({ identifier: "NFT-987654", nonce: 10n }); + const secondTransfer = new TokenTransfer({ token: nft, amount: 1n }); + + const sft = new Token({ identifier: "SFT-987654", nonce: 10n }); + const thirdTransfer = new TokenTransfer({ token: sft, amount: 7n }); + + const transfersController = entrypoint.createTransfersController(); + const transaction = transfersController.createTransactionForTransfer(alice, alice.getNonceThenIncrement(), { + receiver: bob, + tokenTransfers: [firstTransfer, secondTransfer, thirdTransfer], + }); + + const txHash = await entrypoint.sendTransaction(transaction); +} +``` + +If you know you'll only send ESDT tokens, the same transaction can be created using createTransactionForEsdtTokenTransfer. + +### Custom token transfers using the factory +When using the factory, only the sender's address is required. As a result, the transaction won’t be signed, and the nonce field won’t be set correctly. These aspects should be handled after the transaction is created. + +```js +{ + const entrypoint = new DevnetEntrypoint(); + const factory = entrypoint.createTransfersTransactionsFactory(); + + const filePath = path.join("src", "testdata", "testwallets", "alice.pem"); + const alice = await Account.newFromPem(filePath); + const bob = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); + + // the developer is responsible for managing the nonce + alice.nonce = await entrypoint.recallAccountNonce(alice.address); + + const esdt = new Token({ identifier: "TEST-123456" }); // fungible tokens don't have a nonce + const firstTransfer = new TokenTransfer({ token: esdt, amount: 1000000000n }); // we set the desired amount we want to send + + const nft = new Token({ identifier: "NFT-987654", nonce: 10n }); + const secondTransfer = new TokenTransfer({ token: nft, amount: 1n }); // for NFTs we set the amount to `1` + + const sft = new Token({ identifier: "SFT-987654", nonce: 10n }); + const thirdTransfer = new TokenTransfer({ token: sft, amount: 7n }); // for SFTs we set the desired amount we want to send + + const transaction = factory.createTransactionForTransfer(alice, { + receiver: bob, + tokenTransfers: [firstTransfer, secondTransfer, thirdTransfer], + }); + + // set the sender's nonce + transaction.nonce = alice.getNonceThenIncrement(); + + // sign the transaction using the sender's account + transaction.signature = await alice.signTransaction(transaction); + + const txHash = await entrypoint.sendTransaction(transaction); +} +``` + +If you know you'll only send ESDT tokens, the same transaction can be created using createTransactionForEsdtTokenTransfer. + +### Sending native and custom tokens +Both native and custom tokens can now be sent. If a `nativeAmount` is provided along with `tokenTransfers`, the native token will be included in the `MultiESDTNFTTransfer` built-in function call. +We can send both types of tokens using either the `controller` or the `factory`, but for simplicity, we’ll use the controller in this example. + +```js +{ + const entrypoint = new DevnetEntrypoint(); + + const filePath = path.join("src", "testdata", "testwallets", "alice.pem"); + const alice = await Account.newFromPem(filePath); + const bob = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); + + // the developer is responsible for managing the nonce + alice.nonce = await entrypoint.recallAccountNonce(alice.address); + + const esdt = new Token({ identifier: "TEST-123456" }); + const firstTransfer = new TokenTransfer({ token: esdt, amount: 1000000000n }); + + const nft = new Token({ identifier: "NFT-987654", nonce: 10n }); + const secondTransfer = new TokenTransfer({ token: nft, amount: 1n }); + + const transfersController = entrypoint.createTransfersController(); + const transaction = transfersController.createTransactionForTransfer(alice, alice.getNonceThenIncrement(), { + receiver: bob, + nativeAmount: 1000000000000000000n, + tokenTransfers: [firstTransfer, secondTransfer], + }); + + const txHash = await entrypoint.sendTransaction(transaction); +} +``` + +## Smart Contracts + +### Contract ABIs + +A contract's ABI (Application Binary Interface) describes the endpoints, data structures, and events that the contract exposes. +While interactions with the contract are possible without the ABI, they are much easier to implement when the definitions are available. + +#### Loading the ABI from a file +```js +{ + let abiJson = await promises.readFile("../contracts/adder.abi.json", { encoding: "utf8" }); + let abiObj = JSON.parse(abiJson); + let abi = AbiRegistry.create(abiObj); +} +``` + +#### Loading the ABI from an URL + +```js +{ + const response = await axios.get("https://github.com/multiversx/mx-sdk-js-core/raw/main/src/testdata/adder.abi.json"); + abi = AbiRegistry.create(response.data); +} +``` + +#### Manually construct the ABI + +If an ABI file isn’t available, but you know the contract’s endpoints and data types, you can manually construct the ABI. + +```js +{ + abi = AbiRegistry.create({ + "endpoints": [{ + "name": "add", + "inputs": [], + "outputs": [] + }] + }); +} +``` + +```js +{ + abi = AbiRegistry.create({ + "endpoints": [ + { + "name": "foo", + "inputs": [ + { "type": "BigUint" }, + { "type": "u32" }, + { "type": "Address" } + ], + "outputs": [ + { "type": "u32" } + ] + }, + { + "name": "bar", + "inputs": [ + { "type": "counted-variadic" }, + { "type": "variadic" } + ], + "outputs": [] + } + ] + }); +} +``` + +## Smart Contract deployments +For creating smart contract deployment transactions, we have two options: a controller and a factory. Both function similarly to the ones used for token transfers. +When creating transactions that interact with smart contracts, it's recommended to provide the ABI file to the controller or factory if possible. +This allows arguments to be passed as native Javascript values. If the ABI is not available, but we know the expected data types, we can pass arguments as typed values (e.g., `BigUIntValue`, `ListValue`, `StructValue`, etc.) or as raw bytes. + +### Deploying a Smart Contract Using the Controller + +```js +{ + const filePath = path.join("src", "testdata", "testwallets", "alice.pem"); + const sender = await Account.newFromPem(filePath); + const entrypoint = new DevnetEntrypoint(); + + // the developer is responsible for managing the nonce + sender.nonce = await entrypoint.recallAccountNonce(sender.address); + + // load the contract bytecode + const bytecode = await promises.readFile("../contracts/adder.wasm"); + // load the abi file + const abi = await loadAbiRegistry("src/testdata/adder.abi.json"); + + const controller = entrypoint.createSmartContractController(abi); + + // For deploy arguments, use "TypedValue" objects if you haven't provided an ABI to the factory: + let args = [new U32Value(42)]; + // Or use simple, plain JavaScript values and objects if you have provided an ABI to the factory: + args = [42]; + + const deployTransaction = await controller.createTransactionForDeploy( + sender, + sender.getNonceThenIncrement(), + { + bytecode: bytecode, + gasLimit: 6000000n, + arguments: args, + }, + ); + + // broadcasting the transaction + const txHash = await entrypoint.sendTransaction(deployTransaction); +} +``` + +:::tip +When creating transactions using [`SmartContractController`](https://multiversx.github.io/mx-sdk-js-core/v13/classes/SmartContractController.html) or [`SmartContractTransactionsFactory`](https://multiversx.github.io/mx-sdk-js-core/v13/classes/SmartContractTransactionsFactory.html), even if the ABI is available and provided, +you can still use [`TypedValue`](https://multiversx.github.io/mx-sdk-js-core/v13/classes/TypedValue.html) objects as arguments for deployments and interactions. + +Even further, you can use a mix of [`TypedValue`](https://multiversx.github.io/mx-sdk-js-core/v13/classes/TypedValue.html) objects and plain JavaScript values and objects. For example: + +```js +let args = [new U32Value(42), "hello", { foo: "bar" }, new TokenIdentifierValue("TEST-abcdef")]; +``` + +::: + +### Parsing contract deployment transactions + +```js +{ + // We use the transaction hash we got when broadcasting the transaction + const outcome = await controller.awaitCompletedDeploy(txHash); // waits for transaction completion and parses the result + const contractAddress = outcome.contracts[0].address; +} +``` + +If we want to wait for transaction completion and parse the result in two different steps, we can do as follows: + +```js +{ + // We use the transaction hash we got when broadcasting the transaction + // If we want to wait for transaction completion and parse the result in two different steps, we can do as follows: + const transactionOnNetwork = await controller.awaitTransactionCompleted(txHash); + + // parsing the transaction + const outcome = await controller.parseDeploy(transactionOnNetwork); +} +``` + +### Computing the contract address + +Even before broadcasting, at the moment you know the sender's address and the nonce for your deployment transaction, you can (deterministically) compute the (upcoming) address of the smart contract: + +```js +{ + const addressComputer = new AddressComputer(); + const contractAddress = addressComputer.computeContractAddress( + deployTransaction.sender, + deployTransaction.nonce + ); + + console.log("Contract address:", contractAddress.toBech32()); +} +``` + +### Deploying a Smart Contract using the factory +After the transaction is created the nonce needs to be properly set and the transaction should be signed before broadcasting it. + +```js +{ + const entrypoint = new DevnetEntrypoint(); + const factory = entrypoint.createTransfersTransactionsFactory(); + + // load the contract bytecode + const bytecode = await promises.readFile("../contracts/adder.wasm"); + + // For deploy arguments, use "TypedValue" objects if you haven't provided an ABI to the factory: + let args = [new BigUIntValue(42)]; + // Or use simple, plain JavaScript values and objects if you have provided an ABI to the factory: + args = [42]; + + const filePath = path.join("src", "testdata", "testwallets", "alice.pem"); + const alice = await Account.newFromPem(filePath); + + const deployTransaction = await factory.createTransactionForDeploy( + sender, + { + bytecode: bytecode, + gasLimit: 6000000n, + arguments: args, + }, + ); + + // the developer is responsible for managing the nonce + alice.nonce = await entrypoint.recallAccountNonce(sender.address); + + // set the nonce + deployTransaction.nonce = alice.nonce; + + // sign the transaction + deployTransaction.signature = await alice.signTransaction(transaction); + + // broadcasting the transaction + const txHash = await entrypoint.sendTransaction(deployTransaction); + + // waiting for transaction to complete + const transactionOnNetwork = await entrypoint.awaitTransactionCompleted(txHash); + + // parsing transaction + const parser = new SmartContractTransactionsOutcomeParser(); + const parsedOutcome = parser.parseDeploy(transactionOnNetwork); + const contractAddress = parsedOutcome.contracts[0].address; + + console.log(contractAddress.toBech32()); +} +``` + +## Smart Contract calls + +In this section we'll see how we can call an endpoint of our previously deployed smart contract using both approaches with the `controller` and the `factory`. + +### Calling a smart contract using the controller + +```js +{ + const filePath = path.join("src", "testdata", "testwallets", "alice.pem"); + const sender = await Account.newFromPem(filePath); + const entrypoint = new DevnetEntrypoint(); + + // the developer is responsible for managing the nonce + sender.nonce = await entrypoint.recallAccountNonce(sender.address); + + // load the abi file + const abi = await loadAbiRegistry("src/testdata/adder.abi.json");; + const controller = entrypoint.createSmartContractController(abi); + + const contractAddress = Address.newFromBech32("erd1qqqqqqqqqqqqqpgq7cmfueefdqkjsnnjnwydw902v8pwjqy3d8ssd4meug"); + + // For deploy arguments, use "TypedValue" objects if you haven't provided an ABI to the factory: + let args = [new U32Value(42)]; + // Or use simple, plain JavaScript values and objects if you have provided an ABI to the factory: + args = [42]; + + const transaction = await controller.createTransactionForExecute( + sender, + sender.getNonceThenIncrement(), + { + contract: contractAddress, + gasLimit: 5000000n, + function: "add", + arguments: args, + }, + ); + + // broadcasting the transaction + const txHash = await entrypoint.sendTransaction(transaction); + + console.log(txHash); +} +``` + +### Parsing smart contract call transactions +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. + +```js +{ + // waits for transaction completion and parses the result + const parsedOutcome = controller.awaitCompletedExecute(transactionOnNetwork); + const values = parsedOutcome.contracts.values; +} +``` + +### Calling a smart contract and sending tokens (transfer & execute) +Additionally, if an endpoint requires a payment when called, we can send tokens to the contract while creating a smart contract call transaction. +Both EGLD and ESDT tokens or a combination of both can be sent. This functionality is supported by both the controller and the factory. + +```js +{ + const filePath = path.join("src", "testdata", "testwallets", "alice.pem"); + const sender = await Account.newFromPem(filePath); + const entrypoint = new DevnetEntrypoint(); + + // the developer is responsible for managing the nonce + sender.nonce = await entrypoint.recallAccountNonce(sender.address); + + // load the abi file + const abi = await loadAbiRegistry("src/testdata/adder.abi.json"); + + // get the smart contracts controller + const controller = entrypoint.createSmartContractController(abi); + + const contractAddress = Address.newFromBech32("erd1qqqqqqqqqqqqqpgq7cmfueefdqkjsnnjnwydw902v8pwjqy3d8ssd4meug"); + + // For deploy arguments, use "TypedValue" objects if you haven't provided an ABI to the factory: + let args = [new U32Value(42)]; + // Or use simple, plain JavaScript values and objects if you have provided an ABI to the factory: + args = [42]; + + // creating the transfers + const firstToken = new Token({ identifier: "TEST-38f249", nonce: 10 }); + const firstTransfer = new TokenTransfer({ token: firstToken, amount: 1n }); + + const secondToken = new Token({ identifier: "BAR-c80d29" }); + const secondTransfer = new TokenTransfer({ token: secondToken, amount: 10000000000000000000n }); + + const transaction = await controller.createTransactionForExecute( + sender, + sender.getNonceThenIncrement(), + { + contract: contractAddress, + gasLimit: 5000000n, + function: "add", + arguments: args, + nativeTransferAmount: 1000000000000000000n, + tokenTransfers: [firstTransfer, secondTransfer] + }, + ); + + // broadcasting the transaction + const txHash = await entrypoint.sendTransaction(transaction); + + console.log(txHash); +} +``` + +### Calling a smart contract using the factory +Let's create the same smart contract call transaction, but using the `factory`. + +```js +{ + const filePath = path.join("src", "testdata", "testwallets", "alice.pem"); + const alice = await Account.newFromPem(filePath); + const entrypoint = new DevnetEntrypoint(); + + // the developer is responsible for managing the nonce + alice.nonce = await entrypoint.recallAccountNonce(alice.address); + + // load the abi file + const abi = await loadAbiRegistry("src/testdata/adder.abi.json"); + + // get the smart contracts controller + const controller = entrypoint.createSmartContractTransactionsFactory(abi); + + const contractAddress = Address.newFromBech32("erd1qqqqqqqqqqqqqpgq7cmfueefdqkjsnnjnwydw902v8pwjqy3d8ssd4meug"); + + // For deploy arguments, use "TypedValue" objects if you haven't provided an ABI to the factory: + let args = [new U32Value(42)]; + // Or use simple, plain JavaScript values and objects if you have provided an ABI to the factory: + args = [42]; + + // creating the transfers + const firstToken = new Token({ identifier: "TEST-38f249", nonce: 10 }); + const firstTransfer = new TokenTransfer({ token: firstToken, amount: 1n }); + + const secondToken = new Token({ identifier: "BAR-c80d29" }); + const secondTransfer = new TokenTransfer({ token: secondToken, amount: 10000000000000000000n }); + + const transaction = await controller.createTransactionForExecute( + sender, + { + contract: contractAddress, + gasLimit: 5000000n, + function: "add", + arguments: args, + nativeTransferAmount: 1000000000000000000n, + tokenTransfers: [firstTransfer, secondTransfer] + }, + ); + + transaction.nonce = alice.getNonceThenIncrement(); + transaction.signature = await alice.signTransaction(transaction); + + // broadcasting the transaction + const txHash = await entrypoint.sendTransaction(transaction); + + console.log(txHash); +} +``` + +### Parsing transaction outcome +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: + +```js +{ + // load the abi file + const entrypoint = new DevnetEntrypoint(); + const abi = await loadAbiRegistry("src/testdata/adder.abi.json"); + const parser = SmartContractTransactionsOutcomeParser({ abi }); + const transactionOnNetwork = entrypoint.getTransaction(txHash); + const outcome = parser.parseExecute(transactionOnNetwork); +} +``` + +### Decoding transaction events +You might be interested into decoding events emitted by a contract. You can do so by using the `TransactionEventsParser`. + +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. + +First, we load the abi file, then we fetch the transaction, we extract the event from the transaction and then we parse it. + +```js +{ + // load the abi files + const entrypoint = new DevnetEntrypoint(); + const abi = await loadAbiRegistry("src/testdata/adder.abi.json"); + const parser = new TransactionEventsParser({ abi }); + const transactionOnNetwork = entrypoint.getTransaction(txHash); + const events = gatherAllEvents(transactionOnNetwork); + const outcome = parser.parseEvents({ events }); +} +``` + +### Decoding transaction events +Whenever needed, the contract ABI can be used for manually encoding or decoding custom types. + +Let's encode a struct called EsdtTokenPayment (of [multisig](https://github.com/multiversx/mx-contracts-rs/tree/main/contracts/multisig) contract) into binary data. +```js +{ + const abi = await loadAbiRegistry("src/testdata/multisig-full.abi.json"); + const paymentType = abi.getStruct("EsdtTokenPayment"); + + const paymentStruct = new Struct(paymentType, [ + new Field(new TokenIdentifierValue("TEST-8b028f"), "token_identifier"), + new Field(new U64Value(0n), "token_nonce"), + new Field(new BigUIntValue(10000n), "amount") + ]); + + const encoded = codec.encodeNested(paymentStruct); + + console.log(encoded.toString("hex")); +} +``` + +Now let's decode a struct using the ABI. +```js +{ + const abi = await loadAbiRegistry("src/testdata/multisig-full.abi.json"); + const actionStructType = abi.getEnum("Action"); + const data = Buffer.from("0500000000000000000500d006f73c4221216fa679bc559005584c4f1160e569e1000000012a0000000003616464000000010000000107", "hex"); + + const [decoded] = codec.decodeNested(data, actionStructType); + const decodedValue = decoded.valueOf(); + console.log(JSON.stringify(decodedValue, null, 4)); +} +``` + +## Smart Contract queries +When querying a smart contract, a **view function** is called. A view function does not modify the state of the contract, so we do not need to send a transaction. +To perform this query, we use the **SmartContractController**. While we can use the contract's ABI file to encode the query arguments, we can also use it to parse the result. +In this example, we will query the **adder smart contract** by calling its `getSum` endpoint. + +```js +{ + const entrypoint = new DevnetEntrypoint(); + const contractAddress = Address.newFromBech32("erd1qqqqqqqqqqqqqpgq7cmfueefdqkjsnnjnwydw902v8pwjqy3d8ssd4meug"); + const abi = await loadAbiRegistry("src/testdata/adder.abi.json"); + + // create the controller + const controller = entrypoint.createSmartContractController(abi); + + // creates the query, runs the query, parses the result + const response = await controller.query({ contract: contractAddress, function: "getSum", arguments: [] }); +} +``` + +If we need more granular control, we can split the process into three steps: **create the query, run the query, and parse the query response**. +This approach achieves the same result as the previous example. + +```js +{ + const entrypoint = new DevnetEntrypoint(); + + // load the abi + const abi = await loadAbiRegistry("src/testdata/adder.abi.json"); + + // the contract address we'll query + const contractAddress = Address.newFromBech32("erd1qqqqqqqqqqqqqpgq7cmfueefdqkjsnnjnwydw902v8pwjqy3d8ssd4meug"); + + // create the controller + const controller = entrypoint.createSmartContractController(abi); + + // create the query + const query = await controller.createQuery({ contract: contractAddress, function: "getSum", arguments: [] }); + // runs the query + const response = await controller.runQuery(query); + + // parse the result + const parsedResponse = controller.parseQueryResponse(response); +} +``` + +## Upgrading a smart contract +Contract upgrade transactions are similar to deployment transactions (see above) because they also require contract bytecode. +However, in this case, the contract address is already known. Like deploying a smart contract, we can upgrade a smart contract using either the **controller** or the **factory**. + +### Uprgrading a smart contract using the controller +```js +{ + // prepare the account + const entrypoint = new DevnetEntrypoint(); + const keystorePath = path.join("src", "testdata", "testwallets", "alice.json"); + const account = Account.newFromKeystore({ + filePath: keystorePath, + password: "password" + }); + // the developer is responsible for managing the nonce + account.nonce = entrypoint.recall_account_nonce(account.address); + + // load the abi + const abi = await loadAbiRegistry("src/testdata/adder.abi.json"); + + // create the controller + const controller = entrypoint.createSmartContractController(abi); + + // load the contract bytecode; this is the new contract code, the one we want to upgrade to + const bytecode = await promises.readFile("../contracts/adder.wasm"); + + // For deploy arguments, use "TypedValue" objects if you haven't provided an ABI to the factory: + let args = [new U32Value(42)]; + // Or use simple, plain JavaScript values and objects if you have provided an ABI to the factory: + args = [42]; + + const contractAddress = Address.newFromBech32("erd1qqqqqqqqqqqqqpgq7cmfueefdqkjsnnjnwydw902v8pwjqy3d8ssd4meug"); + + const upgradeTransaction = await controller.createTransactionForUpgrade( + sender, + sender.getNonceThenIncrement(), + { + contract: contractAddress, + bytecode: bytecode, + gasLimit: 6000000n, + arguments: args, + }, + ); + + // broadcasting the transaction + const txHash = await entrypoint.sendTransaction(upgradeTransaction); + + console.log({ txHash }); +} +``` + +## Token management + +In this section, we're going to create transactions to issue fungible tokens, issue semi-fungible tokens, create NFTs, set token roles, but also parse these transactions to extract their outcome (e.g. get the token identifier of the newly issued token). + +These methods are available through the `TokenManagementController` and the `TokenManagementTransactionsFactory`. +The controller also includes built-in methods for awaiting the completion of transactions and parsing their outcomes. +For the factory, the same functionality can be achieved using the `TokenManagementTransactionsOutcomeParser`. + +For scripts or quick network interactions, we recommend using the controller. However, for a more granular approach (e.g., DApps), the factory is the better choice. + +### Issuing fungible tokens using the controller +```js +{ + // create the entrypoint and the token management controller + const entrypoint = new DevnetEntrypoint(); + const controller = entrypoint.createTokenManagementController(); + + // create the issuer of the token + const filePath = path.join("src", "testdata", "testwallets", "alice.pem"); + const alice = await Account.newFromPem(filePath); + + // fetch the nonce of the network + alice.nonce = await entrypoint.recallAccountNonce(alice.address); + + const transaction = await controller.createTransactionForIssuingFungible( + alice, + alice.getNonceThenIncrement(), + { + tokenName: "NEWFNG", + tokenTicker: "FNG", + initialSupply: 1_000_000_000000n, + numDecimals: 6n, + canFreeze: false, + canWipe: true, + canPause: false, + canChangeOwner: true, + canUpgrade: true, + canAddSpecialRoles: false, + }, + ); + + // sending the transaction + const txHash = await entrypoint.sendTransaction(transaction); + + // wait for transaction to execute, extract the token identifier + const outcome = await entrypoint.awaitCompletedIssueFungible(txHash); + + const tokenIdentifier = outcome[0].tokenIdentifier; + +} +``` + +### Issuing fungible tokens using the factory +```js +{ + // create the entrypoint and the token management transactions factory + const entrypoint = new DevnetEntrypoint(); + const factory = entrypoint.createTokenManagementTransactionsFactory(); + + // create the issuer of the token + const filePath = path.join("src", "testdata", "testwallets", "alice.pem"); + const alice = await Account.newFromPem(filePath); + + const transaction = await factory.createTransactionForIssuingFungible( + alice, + { + tokenName: "NEWFNG", + tokenTicker: "FNG", + initialSupply: 1_000_000_000000n, + numDecimals: 6n, + canFreeze: false, + canWipe: true, + canPause: false, + canChangeOwner: true, + canUpgrade: true, + canAddSpecialRoles: false, + }, + ); + // fetch the nonce of the network + alice.nonce = await entrypoint.recallAccountNonce(alice.address); + transaction.nonce = alice.getNonceThenIncrement(); + + // sign the transaction + transaction.signature = await alice.signTransaction(transaction); + + // sending the transaction + const txHash = await entrypoint.sendTransaction(transaction); + + // wait for transaction to execute, extract the token identifier + // if we know that the transaction is completed, we can simply call `entrypoint.get_transaction(tx_hash)` + const transactionOnNetwork = await entrypoint.awaitCompletedTransaction(txHash); + + // extract the token identifier + const parser = new TokenManagementTransactionsOutcomeParser(); + const outcome = parser.parseIssueFungible(transactionOnNetwork); + const tokenIdentifier = outcome[0].tokenIdentifier; +} +``` + + +### Setting special roles for fungible tokens using the controller +```js +{ + // create the entrypoint and the token management controller + const entrypoint = new DevnetEntrypoint(); + const controller = entrypoint.createTokenManagementController(); + + // create the issuer of the token + const filePath = path.join("src", "testdata", "testwallets", "alice.pem"); + const alice = await Account.newFromPem(filePath); + + // fetch the nonce of the network + alice.nonce = await entrypoint.recallAccountNonce(alice.address); + + const bob = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); + + const transaction = await controller.createTransactionForSettingSpecialRoleOnFungibleToken( + alice, + alice.getNonceThenIncrement(), + { + user: bob, + tokenIdentifier: "TEST-123456", + addRoleLocalMint: true, + addRoleLocalBurn: true, + addRoleESDTTransferRole: true, + }, + ); + + // sending the transaction + const txHash = await entrypoint.sendTransaction(transaction); + + // wait for transaction to execute, extract the token identifier + const outcome = await entrypoint.awaitCompletedSetSpecialRoleOnFungibleToken(transaction); + + const roles = outcome[0].roles; + const user = outcome[0].userAddress; +} +``` + +### Setting special roles for fungible tokens using the factory +```js +{ + // create the entrypoint and the token management controller + const entrypoint = new DevnetEntrypoint(); + const factory = entrypoint.createTokenManagementTransactionsFactory(); + + // create the issuer of the token + const filePath = path.join("src", "testdata", "testwallets", "alice.pem"); + const alice = await Account.newFromPem(filePath); + const bob = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); + + const transaction = await factory.createTransactionForIssuingFungible( + alice, + { + user: bob, + tokenIdentifier: "TEST-123456", + addRoleLocalMint: true, + addRoleLocalBurn: true, + addRoleESDTTransferRole: true, + }, + ); + // fetch the nonce of the network + alice.nonce = await entrypoint.recallAccountNonce(alice.address); + transaction.nonce = alice.getNonceThenIncrement(); + + // sign the transaction + transaction.signature = await alice.signTransaction(transaction); + + // sending the transaction + const txHash = await entrypoint.sendTransaction(transaction); + + // wait for transaction to execute, extract the token identifier + // if we know that the transaction is completed, we can simply call `entrypoint.get_transaction(tx_hash)` + const transactionOnNetwork = await entrypoint.awaitCompletedTransaction(txHash); + + const parser = new TokenManagementTransactionsOutcomeParser(); + const outcome = parser.parseSetSpecialRole(transactionOnNetwork); + + const roles = outcome[0].roles; + const user = outcome[0].userAddress; +} +``` + +### Issuing semi-fungible tokens using the controller +```js +{ + // create the entrypoint and the token management controller + const entrypoint = new DevnetEntrypoint(); + const controller = entrypoint.createTokenManagementController(); + + // create the issuer of the token + const filePath = path.join("src", "testdata", "testwallets", "alice.pem"); + const alice = await Account.newFromPem(filePath); + + // fetch the nonce of the network + alice.nonce = await entrypoint.recallAccountNonce(alice.address); + + const transaction = await controller.createTransactionForIssuingSemiFungible( + alice, + alice.getNonceThenIncrement(), + { + tokenName: "NEWSEMI", + tokenTicker: "SEMI", + canFreeze: false, + canWipe: true, + canPause: false, + canTransferNFTCreateRole: true, + canChangeOwner: true, + canUpgrade: true, + canAddSpecialRoles: true, + }, + ); + + // sending the transaction + const txHash = await entrypoint.sendTransaction(transaction); + + // wait for transaction to execute, extract the token identifier + const outcome = await entrypoint.awaitCompletedIssueSemiFungible(txHash); + + const tokenIdentifier = outcome[0].tokenIdentifier; +} +``` + +### Issuing semi-fungible tokens using the factory +```js +{ + // create the entrypoint and the token management controller + const entrypoint = new DevnetEntrypoint(); + const factory = entrypoint.createTokenManagementTransactionsFactory(); + + // create the issuer of the token + const filePath = path.join("src", "testdata", "testwallets", "alice.pem"); + const alice = await Account.newFromPem(filePath); + + const transaction = await factory.createTransactionForIssuingSemiFungible( + alice, + { + tokenName: "NEWSEMI", + tokenTicker: "SEMI", + canFreeze: false, + canWipe: true, + canPause: false, + canTransferNFTCreateRole: true, + canChangeOwner: true, + canUpgrade: true, + canAddSpecialRoles: true, + }, + ); + // fetch the nonce of the network + alice.nonce = await entrypoint.recallAccountNonce(alice.address); + transaction.nonce = alice.getNonceThenIncrement(); + + // sign the transaction + transaction.signature = await alice.signTransaction(transaction); + + // sending the transaction + const txHash = await entrypoint.sendTransaction(transaction); + + // wait for transaction to execute, extract the token identifier + const transactionOnNetwork = await entrypoint.awaitCompletedTransaction(txHash); + + // extract the token identifier + const parser = new TokenManagementTransactionsOutcomeParser(); + const outcome = parser.parseIssueSemiFungible(transactionOnNetwork); + + const tokenIdentifier = outcome[0].tokenIdentifier; +} +``` + +### Issuing NFT collection & creating NFTs using the controller + +```js +{ + // create the entrypoint and the token management controller + const entrypoint = new DevnetEntrypoint(); + const controller = entrypoint.creatTokenManagementController(); + + // create the issuer of the token + const filePath = path.join("src", "testdata", "testwallets", "alice.pem"); + const alice = await Account.newFromPem(filePath); + + // fetch the nonce of the network + alice.nonce = await entrypoint.recallAccountNonce(alice.address); + + let transaction = await controller.createTransactionForIssuingNonFungible( + alice, + alice.getNonceThenIncrement(), + { + tokenName: "NEWNFT", + tokenTicker: "NFT", + canFreeze: false, + canWipe: true, + canPause: false, + canTransferNFTCreateRole: true, + canChangeOwner: true, + canUpgrade: true, + canAddSpecialRoles: true, + }, + ); + + // sending the transaction + let txHash = await entrypoint.sendTransaction(transaction); + + // wait for transaction to execute, extract the token identifier + let outcome = await entrypoint.awaitCompletedIssueNonFungible(txHash); + + const collectionIdentifier = outcome[0].tokenIdentifier; + + // create an NFT + transaction = controller.createTransactionForCreatingNft(alice, + alice.getNonceThenIncrement(), + { + tokenIdentifier: "FRANK-aa9e8d", + initialQuantity: 1n, + name: "test", + royalties: 1000, + hash: "abba", + attributes: Buffer.from("test"), + uris: ["a", "b"], + }, + ); + + // sending the transaction + txHash = await entrypoint.sendTransaction(transaction); + + // wait for transaction to execute, extract the token identifier + outcome = await entrypoint.awaitCompletedCreateNft(txHash); + + const identifier = outcome[0].tokenIdentifier; + const nonce = outcome[0].nonce; + const initialQuantity = outcome[0].initialQuantity; + +} +``` + +### Issuing NFT collection & creating NFTs using the factory +```js +{ + // create the entrypoint and the token management transdactions factory + const entrypoint = new DevnetEntrypoint(); + const factory = entrypoint.createTokenManagementTransactionsFactory(); + + // create the issuer of the token + const filePath = path.join("src", "testdata", "testwallets", "alice.pem"); + const alice = await Account.newFromPem(filePath); + + let transaction = await factory.createTransactionForIssuingNonFungible( + alice, + { + tokenName: "NEWNFT", + tokenTicker: "NFT", + canFreeze: false, + canWipe: true, + canPause: false, + canTransferNFTCreateRole: true, + canChangeOwner: true, + canUpgrade: true, + canAddSpecialRoles: true, + }, + ); + // fetch the nonce of the network + alice.nonce = await entrypoint.recallAccountNonce(alice.address); + transaction.nonce = alice.getNonceThenIncrement(); + + // sign the transaction + transaction.signature = await alice.signTransaction(transaction); + + // sending the transaction + let txHash = await entrypoint.sendTransaction(transaction); + + // wait for transaction to execute, extract the token identifier + let transactionOnNetwork = await entrypoint.awaitCompletedTransaction(txHash); + + // extract the token identifier + let parser = new TokenManagementTransactionsOutcomeParser(); + let outcome = parser.parseIssueNonFungible(transactionOnNetwork); + + const collectionIdentifier = outcome[0].tokenIdentifier; + + transaction = await factory.createTransactionForCreatingNFT( + alice, + { + tokenIdentifier: "FRANK-aa9e8d", + initialQuantity: 1n, + name: "test", + royalties: 1000, + hash: "abba", + attributes: Buffer.from("test"), + uris: ["a", "b"], + }, + ); + + transaction.nonce = alice.getNonceThenIncrement(); + + // sign the transaction + transaction.signature = await alice.signTransaction(transaction); + + // sending the transaction + txHash = await entrypoint.sendTransaction(transaction); + + // ## wait for transaction to execute, extract the token identifier + transactionOnNetwork = await entrypoint.awaitCompletedTransaction(txHash); + + outcome = parser.parseIssueNonFungible(transactionOnNetwork); + + const identifier = outcome[0].tokenIdentifier; + const nonce = outcome[0].nonce; + const initialQuantity = outcome[0].initialQuantity; +} +``` + +These are just a few examples of what you can do using the token management controller or factory. For a complete list of supported methods, please refer to the autogenerated documentation: + +- [TokenManagementController](https://multiversx.github.io/mx-sdk-js-core/v14/classes/TokenManagementController.html) +- [TokenManagementTransactionsFactory](https://multiversx.github.io/mx-sdk-js-core/v14/classes/TokenManagementTransactionsFactory.html) + +## Account management + +The account management controller and factory allow us to create transactions for managing accounts, such as: +- Guarding and unguarding accounts +- Saving key-value pairs in the account storage, on the blockchain. + +To learn more about Guardians, please refer to the [official documentation](/developers/built-in-functions/#setguardian). +A guardian can also be set using the WebWallet, which leverages our hosted `Trusted Co-Signer Service`. Follow [this guide](/wallet/web-wallet/#guardian) for step-by-step instructions on guarding an account using the wallet. + +### Guarding an account using the controller +```js +{ + // create the entrypoint and the account controller + const entrypoint = new DevnetEntrypoint(); + const controller = entrypoint.createAccountController(); + + // create the account to guard + const filePath = path.join("src", "testdata", "testwallets", "alice.pem"); + const alice = await Account.newFromPem(filePath); + + // fetch the nonce of the network + alice.nonce = await entrypoint.recallAccountNonce(alice.address); + + // we can use a trusted service that provides a guardian, or simply set another address we own or trust + const guardian = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); + + const transaction = await controller.createTransactionForSettingGuardian( + alice, + alice.getNonceThenIncrement(), + { + guardianAddress: guardian, + serviceID: "SelfOwnedAddress", // this is just an example + }, + ); + + // sending the transaction + const txHash = await entrypoint.sendTransaction(transaction); +} +``` + +### Guarding an account using the factory +```js +{ + // create the entrypoint and the account management factory + const entrypoint = new DevnetEntrypoint(); + const factory = entrypoint.createAccountTransactionsFactory(); + + // create the account to guard + const filePath = path.join("src", "testdata", "testwallets", "alice.pem"); + const alice = await Account.newFromPem(filePath); + + // we can use a trusted service that provides a guardian, or simply set another address we own or trust + const guardian = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); + + const transaction = await factory.createTransactionForSettingGuardian( + alice, + { + guardianAddress: guardian, + serviceID: "SelfOwnedAddress", // this is just an example + }, + ); + // fetch the nonce of the network + alice.nonce = await entrypoint.recallAccountNonce(alice.address); + + // set the nonce + transaction.nonce = alice.getNonceThenIncrement(); + + // sign the transaction + transaction.signature = await alice.signTransaction(transaction); + + // sending the transaction + const txHash = await entrypoint.sendTransaction(transaction); +} +``` + +Once a guardian is set, we must wait **20 epochs** before it can be activated. After activation, all transactions sent from the account must also be signed by the guardian. + +### Activating the guardian using the controller +```js +{ + // create the entrypoint and the account controller + const entrypoint = new DevnetEntrypoint(); + const controller = entrypoint.createAccountController(); + + // create the account to guard + const filePath = path.join("src", "testdata", "testwallets", "alice.pem"); + const alice = await Account.newFromPem(filePath); + + // fetch the nonce of the network + alice.nonce = await entrypoint.recallAccountNonce(alice.address); + + const transaction = await controller.createTransactionForGuardingAccount( + alice, + alice.getNonceThenIncrement(), + ); + + // sending the transaction + const txHash = await entrypoint.sendTransaction(transaction); +} +``` + +### Activating the guardian using the factory +```js +{ + // create the entrypoint and the account factory + const entrypoint = new DevnetEntrypoint(); + const factory = entrypoint.createAccountTransactionsFactory(); + + // create the account to guard + const filePath = path.join("src", "testdata", "testwallets", "alice.pem"); + const alice = await Account.newFromPem(filePath); + + const transaction = await factory.createTransactionForGuardingAccount( + alice.address, + ); + + // fetch the nonce of the network + alice.nonce = await entrypoint.recallAccountNonce(alice.address); + + // set the nonce + transaction.nonce = alice.getNonceThenIncrement(); + + // sign the transaction + transaction.signature = await alice.signTransaction(transaction); + + // sending the transaction + const txHash = await entrypoint.sendTransaction(transaction); +} +``` + +### Unguarding the account using the controller +```js +{ + // create the entrypoint and the account controller + const entrypoint = new DevnetEntrypoint(); + const controller = entrypoint.createAccountController(); + + // create the account to unguard + const filePath = path.join("src", "testdata", "testwallets", "alice.pem"); + const alice = await Account.newFromPem(filePath); + + // fetch the nonce of the network + alice.nonce = await entrypoint.recallAccountNonce(alice.address); + + const guardian = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); + + const transaction = await controller.createTransactionForUnguardingAccount( + alice, + alice.getNonceThenIncrement(), + { + guardian: guardian + } + ); + + // the transaction should also be signed by the guardian before being sent otherwise it won't be executed + const txHash = await entrypoint.sendTransaction(transaction); +} +``` + +### Unguarding the guardian using the factory +```js +{ + // create the entrypoint and the account factory + const entrypoint = new DevnetEntrypoint(); + const factory = entrypoint.createAccountTransactionsFactory(); + + // create the account to guard + const filePath = path.join("src", "testdata", "testwallets", "alice.pem"); + const alice = await Account.newFromPem(filePath); + const guardian = Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); + + const transaction = await factory.createTransactionForUnguardingAccount( + alice.address, + guardian + ); + + // fetch the nonce of the network + alice.nonce = await entrypoint.recallAccountNonce(alice.address); + + // set the nonce + transaction.nonce = alice.getNonceThenIncrement(); + + // sign the transaction + transaction.signature = await alice.signTransaction(transaction); + + // sending the transaction + const txHash = await entrypoint.sendTransaction(transaction); +} +``` + +### Saving a key-value pair to an account using the controller +You can find more information [here](/developers/account-storage) regarding the account storage. + +```js +{ + // create the entrypoint and the account controller + const entrypoint = new DevnetEntrypoint(); + const controller = entrypoint.createAccountController(); + + const filePath = path.join("src", "testdata", "testwallets", "alice.pem"); + const alice = await Account.newFromPem(filePath); + + // creating the key-value pairs we want to save + const keyValuePairs = new Map([[Buffer.from("key0"), Buffer.from("value0")]]); + + // fetch the nonce of the network + alice.nonce = await entrypoint.recallAccountNonce(alice.address); + + + const transaction = await controller.createTransactionForSavingKeyValue( + alice, + alice.getNonceThenIncrement(), + { + keyValuePairs: keyValuePairs + } + ); + + // sending the transaction + const txHash = await entrypoint.sendTransaction(transaction); +} +``` + +### Saving a key-value pair to an account using the factory +```js +{ + // create the entrypoint and the account factory + const entrypoint = new DevnetEntrypoint(); + const factory = entrypoint.createAccountTransactionsFactory(); + + // create the account to guard + const filePath = path.join("src", "testdata", "testwallets", "alice.pem"); + const alice = await Account.newFromPem(filePath); + + // creating the key-value pairs we want to save + const keyValuePairs = new Map([[Buffer.from("key0"), Buffer.from("value0")]]); + + const transaction = await factory.createTransactionForSavingKeyValue( + alice.address, { + keyValuePairs: keyValuePairs, + }); + + // fetch the nonce of the network + alice.nonce = await entrypoint.recallAccountNonce(alice.address); + + // set the nonce + transaction.nonce = alice.getNonceThenIncrement(); + + // sign the transaction + transaction.signature = await alice.signTransaction(transaction); + + // sending the transaction + const txHash = await entrypoint.sendTransaction(transaction); +} +``` + +## Delegation management + +To learn more about staking providers and delegation, please refer to the official [documentation](/validators/delegation-manager/#introducing-staking-providers). +In this section, we'll cover how to: +- Create a new delegation contract +- Retrieve the contract address +- Delegate funds to the contract +- Redelegate rewards +- Claim rewards +- Undelegate and withdraw funds + +These operations can be performed using both the controller and the **factory**. For a complete list of supported methods, please refer to the autogenerated documentation: +- [DelegationController](https://multiversx.github.io/mx-sdk-js-core/v14/classes/DelegationController.html) +- [DelegationTransactionsFactory](https://multiversx.github.io/mx-sdk-js-core/v14/classes/DelegationTransactionsFactory.html) + +### Creating a New Delegation Contract Using the Controller +```js +{ + // create the entrypoint and the delegation controller + const entrypoint = new DevnetEntrypoint(); + const controller = entrypoint.createDelegationController(); + + const filePath = path.join("src", "testdata", "testwallets", "alice.pem"); + const alice = await Account.newFromPem(filePath); + + // fetch the nonce of the network + alice.nonce = await entrypoint.recallAccountNonce(alice.address); + + const transaction = await controller.createTransactionForNewDelegationContract( + alice.address, + alice.getNonceThenIncrement(), + { + totalDelegationCap: 0, + serviceFee: 10n, + amount: 1250000000000000000000n, + }); + + // sending the transaction + const txHash = await entrypoint.sendTransaction(transaction); + + // wait for transaction completion, extract delegation contract's address + const outcome = await controller.awaitCompletedCreateNewDelegationContract(txHash); + + const contractAddress = outcome[0].contractAddress; +} +``` + +### Creating a new delegation contract using the factory +```js +{ + // create the entrypoint and the delegation factory + const entrypoint = new DevnetEntrypoint(); + const factory = entrypoint.createDelegationTransactionsFactory(); + + const filePath = path.join("src", "testdata", "testwallets", "alice.pem"); + const alice = await Account.newFromPem(filePath); + + + const transaction = await factory.createTransactionForNewDelegationContract(alice.address, + { + totalDelegationCap: 0, + serviceFee: 10n, + amount: 1250000000000000000000n, + }); + // fetch the nonce of the network + alice.nonce = await entrypoint.recallAccountNonce(alice.address); + + // set the nonce + transaction.nonce = alice.getNonceThenIncrement(); + + // sign the transaction + transaction.signature = await alice.signTransaction(transaction); + + // sending the transaction + const txHash = await entrypoint.sendTransaction(transaction); + + // waits until the transaction is processed and fetches it from the network + const transactionOnNetwork = await entrypoint.awaitCompletedTransaction(txHash); + + // extract the contract address + const parser = new TokenManagementTransactionsOutcomeParser(); + const outcome = parser.parseIssueFungible(transactionOnNetwork); + const contractAddress = outcome[0].contractAddress; +} +``` + +### Delegating funds to the contract using the Controller +We can send funds to a delegation contract to earn rewards. + +```js +{ + // create the entrypoint and the delegation controller + const entrypoint = new DevnetEntrypoint(); + const controller = entrypoint.createDelegationController(); + + const filePath = path.join("src", "testdata", "testwallets", "alice.pem"); + const alice = await Account.newFromPem(filePath); + + // fetch the nonce of the network + alice.nonce = await entrypoint.recallAccountNonce(alice.address); + + const contract = Address.newFromBech32("erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqf8llllswuedva"); + + const transaction = await controller.createTransactionForDelegating( + alice.address, + alice.getNonceThenIncrement(), + { + delegationContract: contract, + amount: 5000000000000000000000n, + }); + + // sending the transaction + const txHash = await entrypoint.sendTransaction(transaction); +} +``` + +### Delegating funds to the contract using the factory +```js +{ + // create the entrypoint and the delegation factory + const entrypoint = new DevnetEntrypoint(); + const factory = entrypoint.createDelegationTransactionsFactory(); + + const filePath = path.join("src", "testdata", "testwallets", "alice.pem"); + const alice = await Account.newFromPem(filePath); + + const contract = Address.newFromBech32("erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqf8llllswuedva"); + + const transaction = await factory.createTransactionForDelegating(alice.address, + { + delegationContract: contract, + amount: 5000000000000000000000n, + }); + // fetch the nonce of the network + alice.nonce = await entrypoint.recallAccountNonce(alice.address); + + // set the nonce + transaction.nonce = alice.getNonceThenIncrement(); + + // sign the transaction + transaction.signature = await alice.signTransaction(transaction); + + // sending the transaction + const txHash = await entrypoint.sendTransaction(transaction); +} +``` + +### Redelegating rewards using the Controller +Over time, as rewards accumulate, we may choose to redelegate them to the contract to maximize earnings. + +```js +{ + // create the entrypoint and the delegation controller + const entrypoint = new DevnetEntrypoint(); + const controller = entrypoint.createDelegationController(); + + const filePath = path.join("src", "testdata", "testwallets", "alice.pem"); + const alice = await Account.newFromPem(filePath); + + const contract = Address.newFromBech32("erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqf8llllswuedva"); + // fetch the nonce of the network + alice.nonce = await entrypoint.recallAccountNonce(alice.address); + + const transaction = await controller.createTransactionForRedelegatingRewards( + alice.address, + alice.getNonceThenIncrement(), + { + delegationContract: contract, + }); + + // sending the transaction + const txHash = await entrypoint.sendTransaction(transaction); +} +``` + +### Redelegating rewards using the factory +```js +{ + // create the entrypoint and the delegation factory + const entrypoint = new DevnetEntrypoint(); + const factory = entrypoint.createDelegationTransactionsFactory(); + + const filePath = path.join("src", "testdata", "testwallets", "alice.pem"); + const alice = await Account.newFromPem(filePath); + + const contract = Address.newFromBech32("erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqf8llllswuedva"); + + const transaction = await factory.createTransactionForRedelegatingRewards(alice.address, + { + delegationContract: contract, + }); + // fetch the nonce of the network + alice.nonce = await entrypoint.recallAccountNonce(alice.address); + + // set the nonce + transaction.nonce = alice.getNonceThenIncrement(); + + // sign the transaction + transaction.signature = await alice.signTransaction(transaction); + + // sending the transaction + const txHash = await entrypoint.sendTransaction(transaction); +} +``` + +### Claiming rewards using the Controller +We can also claim our rewards when needed. + +```js +{ + // create the entrypoint and the delegation controller + const entrypoint = new DevnetEntrypoint(); + const controller = entrypoint.createDelegationController(); + + const filePath = path.join("src", "testdata", "testwallets", "alice.pem"); + const alice = await Account.newFromPem(filePath); + + const contract = Address.newFromBech32("erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqf8llllswuedva"); + // fetch the nonce of the network + alice.nonce = await entrypoint.recallAccountNonce(alice.address); + + const transaction = await controller.createTransactionForClaimingRewards( + alice.address, + alice.getNonceThenIncrement(), + { + delegationContract: contract, + }); + + // sending the transaction + const txHash = await entrypoint.sendTransaction(transaction); +} +``` + +### Claiming rewards using the factory +```js +{ + // create the entrypoint and the delegation factory + const entrypoint = new DevnetEntrypoint(); + const factory = entrypoint.createDelegationTransactionsFactory(); + + const filePath = path.join("src", "testdata", "testwallets", "alice.pem"); + const alice = await Account.newFromPem(filePath); + + const contract = Address.newFromBech32("erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqf8llllswuedva"); + + const transaction = await factory.createTransactionForClaimingRewards(alice.address, + { + delegationContract: contract, + }); + // fetch the nonce of the network + alice.nonce = await entrypoint.recallAccountNonce(alice.address); + + // set the nonce + transaction.nonce = alice.getNonceThenIncrement(); + + // sign the transaction + transaction.signature = await alice.signTransaction(transaction); + + // sending the transaction + const txHash = await entrypoint.sendTransaction(transaction); +} +``` + +### Undelegating funds using the Controller +By **undelegating**, we signal the contract that we want to retrieve our staked funds. This process requires a **10-epoch unbonding period** before the funds become available. + +```js +{ + // create the entrypoint and the delegation controller + const entrypoint = new DevnetEntrypoint(); + const controller = entrypoint.createDelegationController(); + + const filePath = path.join("src", "testdata", "testwallets", "alice.pem"); + const alice = await Account.newFromPem(filePath); + + const contract = Address.newFromBech32("erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqf8llllswuedva"); + // fetch the nonce of the network + alice.nonce = await entrypoint.recallAccountNonce(alice.address); + + const transaction = await controller.createTransactionForUndelegating( + alice.address, + alice.getNonceThenIncrement(), + { + delegationContract: contract, + amount: 1000000000000000000000n // 1000 EGLD + }); + + // sending the transaction + const txHash = await entrypoint.sendTransaction(transaction); +} +``` + +### Undelegating funds using the factory +```js +{ + // create the entrypoint and the delegation factory + const entrypoint = new DevnetEntrypoint(); + const factory = entrypoint.createDelegationTransactionsFactory(); + + const filePath = path.join("src", "testdata", "testwallets", "alice.pem"); + const alice = await Account.newFromPem(filePath); + + const contract = Address.newFromBech32("erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqf8llllswuedva"); + + const transaction = await factory.createTransactionForUndelegating(alice.address, + { + delegationContract: contract, + amount: 1000000000000000000000n // 1000 EGLD + }); + // fetch the nonce of the network + alice.nonce = await entrypoint.recallAccountNonce(alice.address); + + // set the nonce + transaction.nonce = alice.getNonceThenIncrement(); + + // sign the transaction + transaction.signature = await alice.signTransaction(transaction); + + // sending the transaction + const txHash = await entrypoint.sendTransaction(transaction); +} +``` + +### Withdrawing funds using the Controller +After the `10-epoch unbonding period` is complete, we can proceed with withdrawing our staked funds using the controller. This final step allows us to regain access to the previously delegated funds. + +```js +{ + // create the entrypoint and the delegation controller + const entrypoint = new DevnetEntrypoint(); + const controller = entrypoint.createDelegationController(); + + const filePath = path.join("src", "testdata", "testwallets", "alice.pem"); + const alice = await Account.newFromPem(filePath); + + const contract = Address.newFromBech32("erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqf8llllswuedva"); + + // fetch the nonce of the network + alice.nonce = await entrypoint.recallAccountNonce(alice.address); + + const transaction = await controller.createTransactionForWithdrawing( + alice.address, + alice.getNonceThenIncrement(), + { + delegationContract: contract, + }); + + // sending the transaction + const txHash = await entrypoint.sendTransaction(transaction); +} +``` + +### Withdrawing funds using the factory +```js +{ + // create the entrypoint and the delegation factory + const entrypoint = new DevnetEntrypoint(); + const factory = entrypoint.createDelegationTransactionsFactory(); + + const filePath = path.join("src", "testdata", "testwallets", "alice.pem"); + const alice = await Account.newFromPem(filePath); + + const contract = Address.newFromBech32("erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqf8llllswuedva"); + + const transaction = await factory.createTransactionForWithdrawing(alice.address, + { + delegationContract: contract, + }); + // fetch the nonce of the network + alice.nonce = await entrypoint.recallAccountNonce(alice.address); + + // set the nonce + transaction.nonce = alice.getNonceThenIncrement(); + + // sign the transaction + transaction.signature = await alice.signTransaction(transaction); + + // sending the transaction + const txHash = await entrypoint.sendTransaction(transaction); +} +``` + +## Relayed transactions +We are currently on the `third iteration (V3)` of relayed transactions. V1 and V2 will soon be deactivated, so we will focus on V3. + +For V3, two new fields have been added to transactions: +- relayer +- relayerSignature + +Signing Process: +1. The relayer must be set before the sender signs the transaction. +2. Once the sender has signed, the relayer can also sign the transaction and broadcast it. + +**Important Consideration**: +Relayed V3 transactions require an additional `50,000` gas. +Let’s see how to create a relayed transaction: + +```js +{ + const walletsPath = path.join("src", "testdata", "testwallets"); + const alice = await Account.newFromPem(path.join(walletsPath, "alice.pem")); + const bob = await Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); + const carol = await Account.newFromPem(path.join(walletsPath, "carol.pem")); + + // fetch the nonce of the network + alice.nonce = await entrypoint.recallAccountNonce(alice.address); + + const transaction = new Transaction({ + chainID: "D", + sender: alice.address, + receiver: bob, + relayer: carol.address, + gasLimit: 110_000n, + data: Buffer.from("hello"), + nonce: alice.getNonceThenIncrement() + }); + + // sender signs the transaction + transaction.signature = await alice.signTransaction(transaction); + + // relayer signs the transaction + transaction.RelayerSignature = await carol.signTransaction(transaction); + + // broadcast the transaction + const entrypoint = new DevnetEntrypoint(); + const txHash = await entrypoint.sendTransaction(transaction); +} +``` + +### Creating relayed transactions using controllers +We can create relayed transactions using any of the available controllers. +Each controller includes a relayer argument, which must be set if we want to create a relayed transaction. + +Let’s issue a fungible token using a relayed transaction: + +```js +{ + // create the entrypoint and the token management controller + const entrypoint = new DevnetEntrypoint(); + const controller = entrypoint.creatTokenManagementController(); + + // create the issuer of the token + const walletsPath = path.join("src", "testdata", "testwallets"); + const alice = await Account.newFromPem(path.join(walletsPath, "alice.pem")); + + // Carol will be our relayer, that means she is paying the gas for the transaction + const carol = await Account.newFromPem(path.join(walletsPath, "carol.pem")); + + // fetch the nonce of the network + alice.nonce = await entrypoint.recallAccountNonce(alice.address); + + const transaction = await controller.createTransactionForIssuingFungible( + alice, + alice.getNonceThenIncrement(), + { + tokenName: "NEWFNG", + tokenTicker: "FNG", + initialSupply: 1_000_000_000000n, + numDecimals: 6n, + canFreeze: false, + canWipe: true, + canPause: false, + canChangeOwner: true, + canUpgrade: true, + canAddSpecialRoles: false, + guardian: carol.address, + }, + ); + + // relayer also signs the transaction + transaction.relayerSignature = await carol.signTransaction(transaction); + + // broadcast the transaction + const txHash = await entrypoint.sendTransaction(transaction); +} +``` + +### Creating relayed transactions using factories +Unlike controllers, `transaction factories` do not have a `relayer` argument. Instead, the **relayer must be set after creating the transaction**. +This approach is beneficial because the **transaction is not signed by the sender at the time of creation**, allowing flexibility in setting the relayer before signing. + +Let’s issue a fungible token using the `TokenManagementTransactionsFactory`: + +```js +{ + // create the entrypoint and the token management factory + const entrypoint = new DevnetEntrypoint(); + const factory = entrypoint.creatTokenManagementController(); + + // create the issuer of the token + const walletsPath = path.join("src", "testdata", "testwallets"); + const alice = await Account.newFromPem(path.join(walletsPath, "alice.pem")); + + // carol will be our relayer, that means she is paying the gas for the transaction + const carol = await Account.newFromPem(path.join(walletsPath, "carol.pem")); + + const transaction = await factory.createTransactionForIssuingFungible( + alice.address, + { + tokenName: "NEWFNG", + tokenTicker: "FNG", + initialSupply: 1_000_000_000000n, + numDecimals: 6n, + canFreeze: false, + canWipe: true, + canPause: false, + canChangeOwner: true, + canUpgrade: true, + canAddSpecialRoles: false, + }, + ); + + // fetch the nonce of the network + alice.nonce = await entrypoint.recallAccountNonce(alice.address); + transaction.nonce = alice.getNonceThenIncrement(); + + // set the relayer + transaction.relayer = carol.address; + + // sign the transaction + transaction.signature = await alice.signTransaction(transaction); + + // relayer also signs the transaction + transaction.relayerSignature = await carol.signTransaction(transaction); + + // broadcast the transaction + const txHash = await entrypoint.sendTransaction(transaction); +} +``` + +## Guarded transactions +Similar to relayers, transactions also have two additional fields: + +- guardian +- guardianSignature + +Each controller includes an argument for the guardian. The transaction can either: +1. Be sent to a service that signs it using the guardian’s account, or +2. Be signed by another account acting as a guardian. + +Let’s issue a token using a guarded account: + +### Creating guarded transactions using controllers +We can create guarded transactions using any of the available controllers. + +Each controller method includes a guardian argument, which must be set if we want to create a guarded transaction. +Let’s issue a fungible token using a relayed transaction: + +```js +{ + // create the entrypoint and the token management controller + const entrypoint = new DevnetEntrypoint(); + const controller = entrypoint.creatTokenManagementController(); + + // create the issuer of the token + const walletsPath = path.join("src", "testdata", "testwallets"); + const alice = await Account.newFromPem(path.join(walletsPath, "alice.pem")); + + // carol will be our guardian + const carol = await Account.newFromPem(path.join(walletsPath, "carol.pem")); + + // fetch the nonce of the network + alice.nonce = await entrypoint.recallAccountNonce(alice.address); + + const transaction = await controller.createTransactionForIssuingFungible( + alice, + alice.getNonceThenIncrement(), + { + tokenName: "NEWFNG", + tokenTicker: "FNG", + initialSupply: 1_000_000_000000n, + numDecimals: 6n, + canFreeze: false, + canWipe: true, + canPause: false, + canChangeOwner: true, + canUpgrade: true, + canAddSpecialRoles: false, + guardian: carol.address, + }, + ); + + // guardian also signs the transaction + transaction.guardianSignature = await carol.signTransaction(transaction); + + // broadcast the transaction + const txHash = await entrypoint.sendTransaction(transaction); +} +``` + +### Creating guarded transactions using factories +Unlike controllers, `transaction factories` do not have a `guardian` argument. Instead, the **guardian must be set after creating the transaction**. +This approach is beneficial because the transaction is **not signed by the sender at the time of creation**, allowing flexibility in setting the guardian before signing. + +Let’s issue a fungible token using the `TokenManagementTransactionsFactory`: + +```js +{ + // create the entrypoint and the token management factory + const entrypoint = new DevnetEntrypoint(); + const factory = entrypoint.createTokenManagementController(); + + // create the issuer of the token + const walletsPath = path.join("src", "testdata", "testwallets"); + const alice = await Account.newFromPem(path.join(walletsPath, "alice.pem")); + + // carol will be our guardian + const carol = await Account.newFromPem(path.join(walletsPath, "carol.pem")); + + const transaction = await factory.createTransactionForIssuingFungible( + alice.address, + { + tokenName: "NEWFNG", + tokenTicker: "FNG", + initialSupply: 1_000_000_000000n, + numDecimals: 6n, + canFreeze: false, + canWipe: true, + canPause: false, + canChangeOwner: true, + canUpgrade: true, + canAddSpecialRoles: false, + }, + ); + + // fetch the nonce of the network + alice.nonce = await entrypoint.recallAccountNonce(alice.address); + transaction.nonce = alice.getNonceThenIncrement(); + + // set the guardian + transaction.guardian = carol.address; + + // sign the transaction + transaction.signature = await alice.signTransaction(transaction); + + // guardian also signs the transaction + transaction.guardianSignature = await carol.signTransaction(transaction); + + // broadcast the transaction + const txHash = await entrypoint.sendTransaction(transaction); +} +``` + +We can create guarded relayed transactions just like we did before. However, keep in mind: + +Only the sender can be guarded, the relayer cannot be guarded. + +Flow for Creating Guarded Relayed Transactions: +- Using Controllers: +1. Set both guardian and relayer fields. +2. The transaction must be signed by both the guardian and the relayer. +- Using Factories: + +1. Create the transaction. +2. Set both guardian and relayer fields. +3. First, the sender signs the transaction. +4. Then, the guardian signs. +5. Finally, the relayer signs before broadcasting. + +# Addresses + +Create an `Address` object from a bech32-encoded string: + +``` js +{ + // Create an Address object from a bech32-encoded string + const address = Address.newFromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + + console.log("Address (bech32-encoded):", address.toBech32()); + console.log("Public key (hex-encoded):", address.toHex()); + console.log("Public key (hex-encoded):", Buffer.from(address.getPublicKey()).toString("hex")); +} + +``` + +Here’s how you can create an address from a hex-encoded string using the MultiversX JavaScript SDK: +If the HRP (human-readable part) is not provided, the SDK will use the default one ("erd"). + +``` js +{ + // Create an address from a hex-encoded string with a specified HRP + const address = Address.newFromHex("0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1", "erd"); + + console.log("Address (bech32-encoded):", address.toBech32()); + console.log("Public key (hex-encoded):", address.toHex()); +} +``` + +### Create an address from a raw public key + +``` js +{ + const pubkey = Buffer.from("0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1", "hex"); + const addressFromPubkey = new Address(pubkey, "erd"); +} +``` + +### Using an AddressFactory to create addresses +AddressFactory allows creating addresses with a custom HRP, ensuring consistency across your application. + +``` js +{ + const factory = new AddressFactory("erd"); + + const address1 = factory.newFromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + const address2 = factory.newFromHex("0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1"); + const address3 = factory.fromPublicKey(Buffer.from("0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1", "hex")); +} +``` + +### Getting the shard of an address +``` js + +const addressComputer = new AddressComputer(); +console.log("Shard:", addressComputer.getShardOfAddress(address)); +``` + +Checking if an address is a smart contract +``` js + +const contractAddress = Address.newFromBech32("erd1qqqqqqqqqqqqqpgquzmh78klkqwt0p4rjys0qtp3la07gz4d396qn50nnm"); +console.log("Is contract address:", contractAddress.isSmartContract()); +``` + +## Changing the default hrp +The **LibraryConfig** class manages the default **HRP** (human-readable part) for addresses, which is set to `"erd"` by default. +You can change the HRP when creating an address or modify it globally in **LibraryConfig**, affecting all newly created addresses. +``` js + +console.log(LibraryConfig.defaultAddressHrp); +const defaultAddress = Address.newFromHex("0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1"); +console.log(defaultAddress.toBech32()); + +LibraryConfig.defaultAddressHrp = "test"; +const testAddress = Address.newFromHex("0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1"); +console.log(testAddress.toBech32()); + +// Reset HRP back to "erd" to avoid affecting other parts of the application. +LibraryConfig.defaultAddressHrp = "erd"; +``` + +# Wallets + +### Generating a mnemonic +Mnemonic generation is based on [bip39](https://www.npmjs.com/package/bip39) and can be achieved as follows: + +``` js + +const mnemonic = Mnemonic.generate(); +const words = mnemonic.getWords(); +console.log({ words }); +``` + +### Saving the mnemonic to a keystore file +The mnemonic can be saved to a keystore file: + +``` js +{ + const mnemonic = Mnemonic.generate(); + + // saves the mnemonic to a keystore file with kind=mnemonic + const wallet = UserWallet.fromMnemonic({ mnemonic: mnemonic.getText(), password: "password" }); + + const filePath = path.join("src", "testdata", "testwallets", "walletWithMnemonic.json"); + wallet.save(filePath); +} +``` + +### Deriving secret keys from a mnemonic +Given a mnemonic, we can derive keypairs: + +``` js +{ + const mnemonic = Mnemonic.generate(); + + const secretKey = mnemonic.deriveKey(0); + const publicKey = secretKey.generatePublicKey(); + + console.log("Secret key: ", secretKey.hex()); + console.log("Public key: ", publicKey.hex()); +} +``` + +### Saving a secret key to a keystore file +The secret key can also be saved to a keystore file: + +``` js +{ + const mnemonic = Mnemonic.generate(); + const secretKey = mnemonic.deriveKey(); + + const wallet = UserWallet.fromSecretKey({ secretKey: secretKey, password: "password" }); + + const filePath = path.join("src", "testdata", "testwallets", "walletWithSecretKey.json"); + wallet.save(filePath); +} +``` + +### Saving a secret key to a PEM file +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.* + +``` js +{ + const mnemonic = Mnemonic.generate(); + + // by default, derives using the index = 0 + const secretKey = mnemonic.deriveKey(); + const publicKey = secretKey.generatePublicKey(); + + const label = publicKey.toAddress().toBech32(); + const pem = new UserPem(label, secretKey); + + const filePath = path.join("src", "testdata", "testwallets", "wallet.pem"); + pem.save(filePath); +} +``` + +### Generating a KeyPair +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. + +``` js +{ + const keypair = KeyPair.generate(); + + // by default, derives using the index = 0 + const secretKey = keypair.getSecretKey(); + const publicKey = keypair.getPublicKey(); +} +``` + +### Loading a wallet from keystore mnemonic file +Load a keystore that holds an encrypted mnemonic (and perform wallet derivation at the same time): + +``` js +{ + const filePath = path.join("src", "testdata", "testwallets", "walletWithMnemonic.json"); + + // loads the mnemonic and derives the a secret key; default index = 0 + let secretKey = UserWallet.loadSecretKey(filePath, "password"); + let address = secretKey.generatePublicKey().toAddress('erd'); + + console.log("Secret key: ", secretKey.hex()); + console.log("Address: ", address.toBech32()); + + // derive secret key with index = 7 + secretKey = UserWallet.loadSecretKey(path, "password", 7); + address = secretKey.generatePublicKey().toAddress(); + + console.log("Secret key: ", secretKey.hex()); + console.log("Address: ", address.toBech32()); +} +``` + +### Loading a wallet from a keystore secret key file + +``` js +{ + const filePath = path.join("src", "testdata", "testwallets", "walletWithSecretKey.json"); + + let secretKey = UserWallet.loadSecretKey(filePath, "password"); + let address = secretKey.generatePublicKey().toAddress('erd'); + + console.log("Secret key: ", secretKey.hex()); + console.log("Address: ", address.toBech32()); +} +``` + +### Loading a wallet from a PEM file + +``` js +{ + const filePath = path.join("src", "testdata", "testwallets", "wallet.pem"); + + let pem = UserPem.fromFile(filePath); + + console.log("Secret key: ", pem.secretKey.hex()); + console.log("Public key: ", pem.publicKey.hex()); +} +``` + +# Signing objects + +Signing is done using an account's secret key. To simplify this process, we provide wrappers like [Account](#creating-accounts), which streamline signing operations. +First, we'll explore how to sign using an Account, followed by signing directly with a secret key. + +### Signing a Transaction using an Account +We are going to assume we have an account at this point. If you don't, feel free to check out the [creating an account](#creating-accounts) section. +```js +{ + const filePath = path.join("src", "testdata", "testwallets", "alice.pem"); + const alice = await Account.newFromPem(filePath); + + const transaction = new Transaction({ + chainID: "D", + sender: alice.address, + receiver: Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"), + gasLimit: 50000n, + nonce: 90n + }); + + transaction.signature = await alice.signTransaction(transaction); + console.log(transaction.toPlainObject()); +} +``` + +### Signing a Transaction using a SecretKey +```js +{ + const secretKeyHex = "413f42575f7f26fad3317a778771212fdb80245850981e48b58a4f25e344e8f9"; + const secretKey = UserSecretKey.fromString(secretKeyHex); + const publickKey = secretKey.generatePublicKey(); + + const transaction = new Transaction({ + nonce: 90n, + sender: publickKey.toAddress(), + receiver: Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"), + value: 1000000000000000000n, + gasLimit: 50000n, + chainID: "D" + }); + + // serialize the transaction + const transactionComputer = new TransactionComputer(); + const serializedTransaction = transactionComputer.computeBytesForSigning(transaction); + + // apply the signature on the transaction + transaction.signature = await secretKey.sign(serializedTransaction); + + console.log(transaction.toPlainObject()); +} +``` + +### Signing a Transaction by hash +```js +{ + const filePath = path.join("src", "testdata", "testwallets", "alice.pem"); + const alice = await Account.newFromPem(filePath); + + const transaction = new Transaction({ + nonce: 90n, + sender: publickKey.toAddress(), + receiver: Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"), + value: 1000000000000000000n, + gasLimit: 50000n, + chainID: "D" + }); + + const transactionComputer = new TransactionComputer(); + + // sets the least significant bit of the options field to `1` + transactionComputer.applyOptionsForHashSigning(transaction); + + // compute a keccak256 hash for signing + const hash = transactionComputer.computeHashForSigning(transaction); + + // sign and apply the signature on the transaction + transaction.signature = await alice.signTransaction(hash); + + console.log(transaction.toPlainObject()); +} +``` + +### Signing a Message using an Account: +```js +{ + const filePath = path.join("src", "testdata", "testwallets", "alice.pem"); + const alice = await Account.newFromPem(filePath); + + const message = new Message({ + data: new Uint8Array(Buffer.from("hello")), + address: alice.address + }); + + message.signature = await alice.signMessage(message); +} +``` + +### Signing a Message using an SecretKey: +```js +{ + const secretKeyHex = "413f42575f7f26fad3317a778771212fdb80245850981e48b58a4f25e344e8f9"; + const secretKey = UserSecretKey.fromString(secretKeyHex); + const publicKey = secretKey.generatePublicKey(); + + const messageComputer = new MessageComputer(); + const message = new Message({ + data: new Uint8Array(Buffer.from("hello")), + address: publicKey.toAddress() + }); + // serialized the message + const serialized = messageComputer.computeBytesForSigning(message); + + message.signature = await secretKey.sign(serialized); +} +``` + +# Verifying signatures + +Signature verification is performed using an account’s public key. +To simplify this process, we provide wrappers over public keys that make verification easier and more convenient. + +### Verifying Transaction signature using a UserVerifier + +```js +{ + const filePath = path.join("src", "testdata", "testwallets", "alice.pem"); + const account = await Account.newFromPem(filePath); + + const transaction = new Transaction({ + nonce: 90n, + sender: account.address, + receiver: Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"), + value: 1000000000000000000n, + gasLimit: 50000n, + chainID: "D" + }); + + // sign and apply the signature on the transaction + transaction.signature = await account.sign(transaction); + + // instantiating a user verifier; basically gets the public key + const alice = Address.newFromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + const aliceVerifier = UserVerifier.fromAddress(alice); + + // serialize the transaction for verification + const transactionComputer = new TransactionComputer(); + const serializedTransaction = transactionComputer.computeBytesForVerifying(transaction); + + // verify the signature + const isSignedByAlice = aliceVerifier.verify(serializedTransaction, transaction.signature); + + + console.log("Transaction is signed by Alice: ", isSignedByAlice); +} +``` + +### Verifying Message signature using a UserVerifier + +```js +{ + const filePath = path.join("src", "testdata", "testwallets", "alice.pem"); + const account = await Account.newFromPem(filePath); + + const message = new Message({ + data: new Uint8Array(Buffer.from("hello")), + address: account.address + }); + + // sign and apply the signature on the message + message.signature = await account.sign(message); + + // instantiating a user verifier; basically gets the public key + const alice = Address.newFromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + const aliceVerifier = UserVerifier.fromAddress(alice); + + // serialize the message for verification + const messageComputer = new MessageComputer(); + const serializedMessage = messageComputer.computeBytesForVerifying(message); + + // verify the signature + const isSignedByAlice = aliceVerifier.verify(serializedMessage, message.signature); + + console.log("Message is signed by Alice: ", isSignedByAlice); +} +``` + +### Verifying a signature using a public key +```js +{ + const filePath = path.join("src", "testdata", "testwallets", "alice.pem"); + const account = await Account.newFromPem(filePath); + + const transaction = new Transaction({ + nonce: 90n, + sender: account.address, + receiver: Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"), + value: 1000000000000000000n, + gasLimit: 50000n, + chainID: "D" + }); + + // sign and apply the signature on the transaction + transaction.signature = await account.sign(transaction); + + // instantiating a public key + const alice = Address.newFromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + const publicKey = new UserPublicKey(alice.getPublicKey()); + + // serialize the transaction for verification + const transactionComputer = new TransactionComputer(); + const serializedTransaction = transactionComputer.computeBytesForVerifying(transaction); + + // verify the signature + const isSignedByAlice = publicKey.verify(serializedTransaction, transaction.signature); + console.log("Transaction is signed by Alice: ", isSignedByAlice); +} +``` + +### Sending messages over boundaries +Signed Message objects are typically sent to a remote party (e.g., a service), which can then verify the signature. +To prepare a message for transmission, you can use the `MessageComputer.packMessage()` utility method. + +```js +{ + const filePath = path.join("src", "testdata", "testwallets", "alice.pem"); + const account = await Account.newFromPem(filePath); + + const message = new Message({ + data: new Uint8Array(Buffer.from("hello")), + address: account.address + }); + + // sign and apply the signature on the message + message.signature = await account.sign(message); + + const messageComputer = new MessageComputer(); + const packedMessage = messageComputer.packMessage(message); + + console.log("Packed message", packedMessage); +} +``` + +Then, on the receiving side, you can use [`MessageComputer.unpackMessage()`](https://multiversx.github.io/mx-sdk-js-core/v13/classes/MessageComputer.html#unpackMessage) to reconstruct the message, prior verification: + +```js +{ + const alice = Address.newFromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + + const messageComputer = new MessageComputer(); + + // restore message + const message = messageComputer.unpackMessage(packedMessage); + + // verify the signature + const publicKey = new UserPublicKey(alice.getPublicKey()); + const isSignedByAlice = publicKey.verify(messageComputer.computeBytesForVerifying(message), message.signature); + + console.log("Transaction is signed by Alice: ", isSignedByAlice); +} +``` diff --git a/docs/sdk-and-tools/sdk-js/sdk-js-cookbook-versions.md b/docs/sdk-and-tools/sdk-js/sdk-js-cookbook-versions.md index 202160448..adfb670cc 100644 --- a/docs/sdk-and-tools/sdk-js/sdk-js-cookbook-versions.md +++ b/docs/sdk-and-tools/sdk-js/sdk-js-cookbook-versions.md @@ -13,5 +13,5 @@ The cookbook version corresponds to the version of the [**sdk-js-core**](https:/ Please follow the version you are interested in: -- [Cookbook v13 **(stable, current version)**](/sdk-and-tools/sdk-js/sdk-js-cookbook-v13) -- [Cookbook v12 **(legacy, previous version)**](/sdk-and-tools/sdk-js/sdk-js-cookbook-v12) +- [Cookbook v14 **(stable, current version)**](/sdk-and-tools/sdk-js/sdk-js-cookbook-v14) +- [Cookbook v13 **(legacy, previous version)**](/sdk-and-tools/sdk-js/sdk-js-cookbook-v13) diff --git a/docs/sdk-and-tools/sdk-js/writing-and-running-sdk-js-snippets.md b/docs/sdk-and-tools/sdk-js/writing-and-running-sdk-js-snippets.md index 64a7866b2..94a6a0230 100644 --- a/docs/sdk-and-tools/sdk-js/writing-and-running-sdk-js-snippets.md +++ b/docs/sdk-and-tools/sdk-js/writing-and-running-sdk-js-snippets.md @@ -7,6 +7,6 @@ title: Writing and testing interactions Generally speaking, we recommended to use [sc-meta CLI](/developers/meta/sc-meta-cli) to [generate the boilerplate code for your contract interactions](/developers/meta/sc-meta-cli/#calling-snippets). -Though, for writing contract interaction snippets in **JavaScript** or **TypeScript**, please refer to the [`sdk-js` cookbook](/sdk-and-tools/sdk-js/sdk-js-cookbook-v13). If you'd like these snippets to function as system tests of your contract, a choice would be to structure them as Mocha or Jest tests - take the `*.local.net.spec.ts` tests in [`mx-sdk-js-core`](https://github.com/multiversx/mx-sdk-js-core) as examples. For writing contract interaction snippets in **Python**, please refer to the [`sdk-py` cookbook](/sdk-and-tools/sdk-py/sdk-py-cookbook) - if desired, you can shape them as simple scripts, as Python unit tests, or as Jupyter notebooks. +Though, for writing contract interaction snippets in **JavaScript** or **TypeScript**, please refer to the [`sdk-js` cookbook](/sdk-and-tools/sdk-js/sdk-js-cookbook-v14). If you'd like these snippets to function as system tests of your contract, a choice would be to structure them as Mocha or Jest tests - take the `*.local.net.spec.ts` tests in [`mx-sdk-js-core`](https://github.com/multiversx/mx-sdk-js-core) as examples. For writing contract interaction snippets in **Python**, please refer to the [`sdk-py` cookbook](/sdk-and-tools/sdk-py/sdk-py-cookbook) - if desired, you can shape them as simple scripts, as Python unit tests, or as Jupyter notebooks. You might also want to have a look over [**xSuite**](https://xsuite.dev), a toolkit to init, build, test, deploy contracts using JavaScript, made by the [Arda team](https://arda.run). diff --git a/sidebars.js b/sidebars.js index 950311499..5bf1a91fa 100644 --- a/sidebars.js +++ b/sidebars.js @@ -225,7 +225,7 @@ const sidebars = { { label: "Cookbook", type: "doc", - id: "sdk-and-tools/sdk-js/sdk-js-cookbook-v13", + id: "sdk-and-tools/sdk-js/sdk-js-cookbook-v14", }, { type: "category", @@ -235,8 +235,8 @@ const sidebars = { id: "sdk-and-tools/sdk-js/sdk-js-cookbook-versions" }, items: [ - "sdk-and-tools/sdk-js/sdk-js-cookbook-v12", "sdk-and-tools/sdk-js/sdk-js-cookbook-v13", + "sdk-and-tools/sdk-js/sdk-js-cookbook-v14", ] }, "sdk-and-tools/sdk-js/extending-sdk-js", From 8b14cac3f808e91c8212fb6c9332dd63884edf53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Tue, 18 Mar 2025 11:30:21 +0200 Subject: [PATCH 4/6] Update docs wrt. relayed transactions. --- docs/developers/relayed-transactions.md | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/docs/developers/relayed-transactions.md b/docs/developers/relayed-transactions.md index 3060317a6..5c1a0f736 100644 --- a/docs/developers/relayed-transactions.md +++ b/docs/developers/relayed-transactions.md @@ -30,6 +30,10 @@ Once all applications will completely transition to relayed v3 model, v1 and v2 ## Relayed transactions version 1 +:::note +Legacy version. Please use [version 3](#relayed-transactions-version-3), instead. +::: + A relayed transaction version 1 relies on having the inner transaction JSON serialized and given as an argument to the `relayedTx` protocol function. It would look like: @@ -165,6 +169,10 @@ gasLimit = 61040000 // just like the gas limit set in the relayed transaction ## Relayed transactions version 2 +:::note +Legacy version. Please use [version 3](#relayed-transactions-version-3), instead. +::: + In contrast with version 1, relayed transactions version 2 have only certain fields of the inner transaction included in the data field, making the payload smaller, therefore the tx fee smaller. It also eliminates the need of calculating the matching gas limit values between the relayed and inner transactions. @@ -249,10 +257,6 @@ Decoding the arguments ([useful resources here](/developers/sc-calls-format/)) w ## Relayed transactions version 3 -:::note -This feature is not yet available on **Mainnet**. See [Spica Protocol Upgrade](https://governance.multiversx.com/proposal/erd1qqqqqqqqqqqqqpgq4qvrwlr2e6ld50f3qfc94am38p8298kthg4s3f0vfn/1). -::: - Relayed transactions v3 feature comes with a change on the entire transaction structure, adding two new optional fields: - `relayer`, which is the relayer address that will pay the fees. - `relayerSignature`, the signature of the relayer that proves the agreement of the relayer. @@ -316,3 +320,11 @@ Here's an example of a relayed v3 transaction. Its intent is to call the `add` m "relayerSignature": "..." } ``` + +### Preparing relayed transactions using the SDKs + +The SDKs have built-in support for relayed transactions. Please follow: + - [mxpy support](/sdk-and-tools/sdk-py/mxpy-cli/#relayed-transactions-v3) + - [sdk-py support](/sdk-and-tools/sdk-py/sdk-py-cookbook/#relayed-transactions) + - [sdk-js v14 support](/sdk-and-tools/sdk-js/sdk-js-cookbook-v14#relayed-transactions) + - [sdk-js v13 support (legacy)](/sdk-and-tools/sdk-js/sdk-js-cookbook-v13#preparing-a-relayed-transaction) From 4befb06219ba1f9b6d42efbec7d6fbda770d9e9d Mon Sep 17 00:00:00 2001 From: danielailie Date: Tue, 18 Mar 2025 11:42:48 +0200 Subject: [PATCH 5/6] Fix content --- .../sdk-js/sdk-js-cookbook-v14.md | 262 +++++++++--------- 1 file changed, 131 insertions(+), 131 deletions(-) diff --git a/docs/sdk-and-tools/sdk-js/sdk-js-cookbook-v14.md b/docs/sdk-and-tools/sdk-js/sdk-js-cookbook-v14.md index f0a505418..2711b7d88 100644 --- a/docs/sdk-and-tools/sdk-js/sdk-js-cookbook-v14.md +++ b/docs/sdk-and-tools/sdk-js/sdk-js-cookbook-v14.md @@ -7,7 +7,7 @@ pagination_next: null [comment]: # (mx-abstract) -# Overview +## Overview This guide walks you through handling common tasks using the MultiversX Javascript SDK (v14, latest stable version). @@ -15,7 +15,7 @@ This guide walks you through handling common tasks using the MultiversX Javascri This cookbook makes use of `sdk-js v14`. In order to migrate from `sdk-js v13.x` to `sdk-js v14`, please also follow [the migration guide](https://github.com/multiversx/mx-sdk-js-core/issues/576). ::: -# Creating an Entrypoint +## Creating an Entrypoint An Entrypoint represents a network client that simplifies access to the most common operations. There is a dedicated entrypoint for each network: `MainnetEntrypoint`, `DevnetEntrypoint`, `TestnetEntrypoint`, `LocalnetEntrypoint`. @@ -45,7 +45,7 @@ const customEntrypoint = new DevnetEntrypoint({ }); ``` -# Creating Accounts +## Creating Accounts You can initialize an account directly from the entrypoint. Keep in mind that the account is network agnostic, meaning it doesn't matter which entrypoint is used. Accounts are used for signing transactions and messages and managing the account's nonce. They can also be saved to a PEM or keystore file for future use. @@ -57,9 +57,9 @@ Accounts are used for signing transactions and messages and managing the account } ``` -## Other Ways to Instantiate an Account +### Other Ways to Instantiate an Account -### From a Secret Key +#### From a Secret Key ```js { const secretKeyHex = "413f42575f7f26fad3317a778771212fdb80245850981e48b58a4f25e344e8f9"; @@ -69,7 +69,7 @@ Accounts are used for signing transactions and messages and managing the account } ``` -### From a PEM file +#### From a PEM file ```js { const filePath = path.join("src", "testdata", "testwallets", "alice.pem"); @@ -77,7 +77,7 @@ Accounts are used for signing transactions and messages and managing the account } ``` -### From a Keystore File +#### From a Keystore File ```js { const keystorePath = path.join("src", "testdata", "testwallets", "alice.json"); @@ -88,21 +88,21 @@ Accounts are used for signing transactions and messages and managing the account } ``` -### From a Mnemonic +#### From a Mnemonic ```js const mnemonic = Mnemonic.generate(); const accountFromMnemonic = Account.newFromMnemonic(mnemonic.getText()); ``` -### From a KeyPair +#### From a KeyPair ```js const keypair = KeyPair.generate(); const accountFromKeyPairs = Account.newFromKeypair(keypair); ``` -## Managing the Account Nonce +### Managing the Account Nonce An account has a `nonce` property that the user is responsible for managing. You can fetch the nonce from the network and increment it after each transaction. @@ -128,7 +128,7 @@ Each transaction must have the correct nonce, otherwise it will fail to execute. For more details, see the [Creating Transactions](#creating-transactions) section. -### Saving the Account to a File +#### Saving the Account to a File Accounts can be saved to either a PEM file or a keystore file. While PEM wallets are less secure for storing secret keys, they are convenient for testing purposes. @@ -160,7 +160,7 @@ Keystore files offer a higher level of security. ``` -## Using a Ledger Device +### Using a Ledger Device You can manage your account with a Ledger device, allowing you to sign both transactions and messages while keeping your keys secure. @@ -169,23 +169,23 @@ Note: **The multiversx-sdk package does not include Ledger support by default. T npm install @multiversx/sdk-hw-provider ``` -### Creating a Ledger Account +#### Creating a Ledger Account This can be done using the dedicated library. You can find more information [here](/sdk-and-tools/sdk-js/sdk-js-signing-providers/#the-hardware-wallet-provider). When signing transactions or messages, the Ledger device will prompt you to confirm the details before proceeding. -## Compatibility with IAccount Interface +### Compatibility with IAccount Interface The `Account` implements the `IAccount` interface, making it compatible with transaction controllers and any other component that expects this interface. -# Calling the Faucet +## Calling the Faucet This functionality is not yet available through the entrypoint, but we recommend using the faucet available within the Web Wallet. For more details about hthe faucet [see this](/wallet/web-wallet/#testnet-and-devnet-faucet). - [Testnet Wallet](https://testnet-wallet.multiversx.com/). - [Devnet Wallet](https://devnet-wallet.multiversx.com/). -## Interacting with the network +### Interacting with the network The entrypoint exposes a few ways to directly interact with the network, such as: @@ -209,7 +209,7 @@ To get the underlying network provider from our entrypoint, we can do as follows } ``` -## Creating a network provider +### Creating a network provider When manually instantiating a network provider, you can provide a configuration to specify the client name and set custom request options. ```js @@ -238,9 +238,9 @@ Both `ApiNetworkProvider` and `ProxyNetworkProvider` implement a common interfac The classes returned by the API expose the most commonly used fields directly for convenience. However, each object also contains a `raw` field that stores the original API response, allowing access to additional fields if needed. -# Fetching data from the network +## Fetching data from the network -## Fetching the network config +### Fetching the network config ```js { @@ -251,7 +251,7 @@ The classes returned by the API expose the most commonly used fields directly fo } ``` -## Fetching the network status +### Fetching the network status ```js { @@ -263,12 +263,12 @@ The classes returned by the API expose the most commonly used fields directly fo } ``` -## Fetching a Block from the Network +### Fetching a Block from the Network To fetch a block, we first instantiate the required arguments and use its hash. The API only supports fetching blocks by hash, whereas the **PROXY** allows fetching blocks by either hash or nonce. When using the **PROXY**, keep in mind that the shard must also be specified in the arguments. -### Fetching a block using the **API** +#### Fetching a block using the **API** ```js { const api = new ApiNetworkProvider("https://devnet-api.multiversx.com"); @@ -286,7 +286,7 @@ Additionally, we can fetch the latest block from the network: } ``` -### Fetching a block using the **PROXY** +#### Fetching a block using the **PROXY** When using the proxy, we have to provide the shard, as well. ```js @@ -307,7 +307,7 @@ By default, the shard will be the metachain, but we can specify a different shar } ``` -## Fetching an Account +### Fetching an Account To fetch an account, we need its address. Once we have the address, we create an `Address` object and pass it as an argument to the method. ```js @@ -319,7 +319,7 @@ To fetch an account, we need its address. Once we have the address, we create an } ``` -## Fetching an Account's Storage +### Fetching an Account's Storage We can also fetch an account's storage, allowing us to retrieve all key-value pairs saved for that account. ```js @@ -342,7 +342,7 @@ If we only want to fetch a specific key, we can do so as follows: } ``` -## Waiting for an Account to Meet a Condition +### Waiting for an Account to Meet a Condition There are times when we need to wait for a specific condition to be met before proceeding with an action. For example, let's say we want to send 7 EGLD from Alice to Bob, but this can only happen once Alice's balance reaches at least 7 EGLD. This approach is useful in scenarios where you're waiting for external funds to be sent to Alice, enabling her to transfer the required amount to another recipient. @@ -363,10 +363,10 @@ Keep in mind that this method has a default timeout, which can be adjusted using } ``` -## Sending and Simulating Transactions +### Sending and Simulating Transactions To execute transactions, we use the network providers to broadcast them to the network. Keep in mind that for transactions to be processed, they must be signed. -### Sending a Transaction +#### Sending a Transaction ```js { @@ -389,7 +389,7 @@ To execute transactions, we use the network providers to broadcast them to the n } ``` -### Sending multiple transactions +#### Sending multiple transactions ```js { const entrypoint = new DevnetEntrypoint(); @@ -429,7 +429,7 @@ To execute transactions, we use the network providers to broadcast them to the n } ``` -### Simulating transactions +#### Simulating transactions A transaction can be simulated before being sent for processing by the network. This is primarily used for smart contract calls, allowing you to preview the results produced by the smart contract. ```js @@ -452,7 +452,7 @@ A transaction can be simulated before being sent for processing by the network. } ``` -### Estimating the gas cost of a transaction +#### Estimating the gas cost of a transaction Before sending a transaction to the network for processing, you can retrieve the estimated gas limit required for the transaction to be executed. ```js @@ -478,7 +478,7 @@ Before sending a transaction to the network for processing, you can retrieve the } ``` -## Waiting for transaction completion +### Waiting for transaction completion After sending a transaction, you may want to wait until it is processed before proceeding with another action. Keep in mind that this method has a default timeout, which can be adjusted using the `AwaitingOptions` class. ```js @@ -491,7 +491,7 @@ After sending a transaction, you may want to wait until it is processed before p } ``` -## Waiting for a Transaction to Satisfy a Condition +### Waiting for a Transaction to Satisfy a Condition Similar to accounts, we can wait until a transaction meets a specific condition. ```js @@ -506,7 +506,7 @@ Similar to accounts, we can wait until a transaction meets a specific condition. } ``` -## Waiting for transaction completion +### Waiting for transaction completion After sending a transaction, you may want to wait until it is processed before proceeding with another action. Keep in mind that this method has a default timeout, which can be adjusted using the `AwaitingOptions` class. ```js @@ -519,7 +519,7 @@ After sending a transaction, you may want to wait until it is processed before p } ``` -## Fetching Transactions from the Network +### Fetching Transactions from the Network After sending a transaction, we can fetch it from the network using the transaction hash, which we receive after broadcasting the transaction. ```js @@ -532,7 +532,7 @@ After sending a transaction, we can fetch it from the network using the transact } ``` -## Fetching a token from an account +### Fetching a token from an account We can fetch a specific token (ESDT, MetaESDT, SFT, NFT) from an account by providing the account's address and the token identifier. ```js @@ -550,7 +550,7 @@ We can fetch a specific token (ESDT, MetaESDT, SFT, NFT) from an account by prov } ``` -## Fetching all fungible tokens of an account +### Fetching all fungible tokens of an account Fetches all fungible tokens held by an account. Note that this method does not handle pagination, but it can be achieved using `doGetGeneric`. ```js @@ -563,7 +563,7 @@ Fetches all fungible tokens held by an account. Note that this method does not h } ``` -## Fetching all non-fungible tokens of an account +### Fetching all non-fungible tokens of an account Fetches all non-fungible tokens held by an account. Note that this method does not handle pagination, but it can be achieved using `doGetGeneric`. ```js @@ -576,7 +576,7 @@ Fetches all non-fungible tokens held by an account. Note that this method does n } ``` -## Fetching token metadata +### Fetching token metadata If we want to fetch the metadata of a token (e.g., owner, decimals, etc.), we can use the following methods: ```js @@ -592,7 +592,7 @@ If we want to fetch the metadata of a token (e.g., owner, decimals, etc.), we ca } ``` -## Querying Smart Contracts +### Querying Smart Contracts Smart contract queries, or view functions, are endpoints that only read data from the contract. To send a query to the observer nodes, we can proceed as follows: ```js @@ -609,7 +609,7 @@ Smart contract queries, or view functions, are endpoints that only read data fro } ``` -## Custom Api/Proxy calls +### Custom Api/Proxy calls The methods exposed by the `ApiNetworkProvider` or `ProxyNetworkProvider` are the most common and widely used. However, there may be times when custom API calls are needed. For these cases, we’ve created generic methods for both GET and POST requests. Let’s assume we want to retrieve all the transactions sent by Alice in which the `delegate` function was called. @@ -625,7 +625,7 @@ Let’s assume we want to retrieve all the transactions sent by Alice in which t } ``` -# Creating transactions +## Creating transactions In this section, we’ll explore how to create different types of transactions. To create transactions, we can use either controllers or factories. Controllers are ideal for quick scripts or network interactions, while factories provide a more granular and lower-level approach, typically required for DApps. @@ -634,7 +634,7 @@ Controllers typically use the same parameters as factories, but they also requir Controllers also include extra functionality, such as waiting for transaction completion and parsing transactions. The same functionality can be achieved for transactions built using factories, and we’ll see how in the sections below. In the next section, we’ll learn how to create transactions using both methods. -## Instantiating Controllers and Factories +### Instantiating Controllers and Factories There are two ways to create controllers and factories: 1. Get them from the entrypoint. 2. Manually instantiate them. @@ -655,9 +655,9 @@ There are two ways to create controllers and factories: } ``` -## Token transfers +### Token transfers We can send both native tokens (EGLD) and ESDT tokens using either the controller or the factory. -### Native Token Transfers Using the Controller +#### Native Token Transfers Using the Controller When using the controller, the transaction will be signed because we’ll be working with an Account. ```js @@ -687,7 +687,7 @@ When using the controller, the transaction will be signed because we’ll be wor If you know you’ll only be sending native tokens, you can create the transaction using the `createTransactionForNativeTokenTransfer` method. -### Native Token Transfers Using the Factory +#### Native Token Transfers Using the Factory When using the factory, only the sender's address is required. As a result, the transaction won’t be signed, and the nonce field won’t be set correctly. You will need to handle these aspects after the transaction is created. @@ -721,7 +721,7 @@ You will need to handle these aspects after the transaction is created. If you know you’ll only be sending native tokens, you can create the transaction using the `createTransactionForNativeTokenTransfer` method. -### Custom token transfers using the controller +#### Custom token transfers using the controller ```js { @@ -755,7 +755,7 @@ If you know you’ll only be sending native tokens, you can create the transacti If you know you'll only send ESDT tokens, the same transaction can be created using createTransactionForEsdtTokenTransfer. -### Custom token transfers using the factory +#### Custom token transfers using the factory When using the factory, only the sender's address is required. As a result, the transaction won’t be signed, and the nonce field won’t be set correctly. These aspects should be handled after the transaction is created. ```js @@ -796,7 +796,7 @@ When using the factory, only the sender's address is required. As a result, the If you know you'll only send ESDT tokens, the same transaction can be created using createTransactionForEsdtTokenTransfer. -### Sending native and custom tokens +#### Sending native and custom tokens Both native and custom tokens can now be sent. If a `nativeAmount` is provided along with `tokenTransfers`, the native token will be included in the `MultiESDTNFTTransfer` built-in function call. We can send both types of tokens using either the `controller` or the `factory`, but for simplicity, we’ll use the controller in this example. @@ -828,9 +828,9 @@ We can send both types of tokens using either the `controller` or the `factory`, } ``` -## Smart Contracts +### Smart Contracts -### Contract ABIs +#### Contract ABIs A contract's ABI (Application Binary Interface) describes the endpoints, data structures, and events that the contract exposes. While interactions with the contract are possible without the ABI, they are much easier to implement when the definitions are available. @@ -897,12 +897,12 @@ If an ABI file isn’t available, but you know the contract’s endpoints and da } ``` -## Smart Contract deployments +### Smart Contract deployments For creating smart contract deployment transactions, we have two options: a controller and a factory. Both function similarly to the ones used for token transfers. When creating transactions that interact with smart contracts, it's recommended to provide the ABI file to the controller or factory if possible. This allows arguments to be passed as native Javascript values. If the ABI is not available, but we know the expected data types, we can pass arguments as typed values (e.g., `BigUIntValue`, `ListValue`, `StructValue`, etc.) or as raw bytes. -### Deploying a Smart Contract Using the Controller +#### Deploying a Smart Contract Using the Controller ```js { @@ -952,7 +952,7 @@ let args = [new U32Value(42), "hello", { foo: "bar" }, new TokenIdentifierValue( ::: -### Parsing contract deployment transactions +#### Parsing contract deployment transactions ```js { @@ -975,7 +975,7 @@ If we want to wait for transaction completion and parse the result in two differ } ``` -### Computing the contract address +#### Computing the contract address Even before broadcasting, at the moment you know the sender's address and the nonce for your deployment transaction, you can (deterministically) compute the (upcoming) address of the smart contract: @@ -991,7 +991,7 @@ Even before broadcasting, at the moment you know the sender's address and the no } ``` -### Deploying a Smart Contract using the factory +#### Deploying a Smart Contract using the factory After the transaction is created the nonce needs to be properly set and the transaction should be signed before broadcasting it. ```js @@ -1043,11 +1043,11 @@ After the transaction is created the nonce needs to be properly set and the tran } ``` -## Smart Contract calls +### Smart Contract calls In this section we'll see how we can call an endpoint of our previously deployed smart contract using both approaches with the `controller` and the `factory`. -### Calling a smart contract using the controller +#### Calling a smart contract using the controller ```js { @@ -1087,7 +1087,7 @@ In this section we'll see how we can call an endpoint of our previously deployed } ``` -### Parsing smart contract call transactions +#### Parsing smart contract call transactions 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. ```js @@ -1098,7 +1098,7 @@ In our case, calling the add endpoint does not return anything, but similar to t } ``` -### Calling a smart contract and sending tokens (transfer & execute) +#### Calling a smart contract and sending tokens (transfer & execute) Additionally, if an endpoint requires a payment when called, we can send tokens to the contract while creating a smart contract call transaction. Both EGLD and ESDT tokens or a combination of both can be sent. This functionality is supported by both the controller and the factory. @@ -1151,7 +1151,7 @@ Both EGLD and ESDT tokens or a combination of both can be sent. This functionali } ``` -### Calling a smart contract using the factory +#### Calling a smart contract using the factory Let's create the same smart contract call transaction, but using the `factory`. ```js @@ -1205,7 +1205,7 @@ Let's create the same smart contract call transaction, but using the `factory`. } ``` -### Parsing transaction outcome +#### Parsing transaction outcome 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: ```js @@ -1219,7 +1219,7 @@ As said before, the `add` endpoint we called does not return anything, but we co } ``` -### Decoding transaction events +#### Decoding transaction events You might be interested into decoding events emitted by a contract. You can do so by using the `TransactionEventsParser`. 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. @@ -1238,7 +1238,7 @@ First, we load the abi file, then we fetch the transaction, we extract the event } ``` -### Decoding transaction events +#### Decoding transaction events Whenever needed, the contract ABI can be used for manually encoding or decoding custom types. Let's encode a struct called EsdtTokenPayment (of [multisig](https://github.com/multiversx/mx-contracts-rs/tree/main/contracts/multisig) contract) into binary data. @@ -1272,7 +1272,7 @@ Now let's decode a struct using the ABI. } ``` -## Smart Contract queries +### Smart Contract queries When querying a smart contract, a **view function** is called. A view function does not modify the state of the contract, so we do not need to send a transaction. To perform this query, we use the **SmartContractController**. While we can use the contract's ABI file to encode the query arguments, we can also use it to parse the result. In this example, we will query the **adder smart contract** by calling its `getSum` endpoint. @@ -1317,11 +1317,11 @@ This approach achieves the same result as the previous example. } ``` -## Upgrading a smart contract +### Upgrading a smart contract Contract upgrade transactions are similar to deployment transactions (see above) because they also require contract bytecode. However, in this case, the contract address is already known. Like deploying a smart contract, we can upgrade a smart contract using either the **controller** or the **factory**. -### Uprgrading a smart contract using the controller +#### Uprgrading a smart contract using the controller ```js { // prepare the account @@ -1368,7 +1368,7 @@ However, in this case, the contract address is already known. Like deploying a s } ``` -## Token management +### Token management In this section, we're going to create transactions to issue fungible tokens, issue semi-fungible tokens, create NFTs, set token roles, but also parse these transactions to extract their outcome (e.g. get the token identifier of the newly issued token). @@ -1378,7 +1378,7 @@ For the factory, the same functionality can be achieved using the `TokenManageme For scripts or quick network interactions, we recommend using the controller. However, for a more granular approach (e.g., DApps), the factory is the better choice. -### Issuing fungible tokens using the controller +#### Issuing fungible tokens using the controller ```js { // create the entrypoint and the token management controller @@ -1420,7 +1420,7 @@ For scripts or quick network interactions, we recommend using the controller. Ho } ``` -### Issuing fungible tokens using the factory +#### Issuing fungible tokens using the factory ```js { // create the entrypoint and the token management transactions factory @@ -1468,7 +1468,7 @@ For scripts or quick network interactions, we recommend using the controller. Ho ``` -### Setting special roles for fungible tokens using the controller +#### Setting special roles for fungible tokens using the controller ```js { // create the entrypoint and the token management controller @@ -1507,7 +1507,7 @@ For scripts or quick network interactions, we recommend using the controller. Ho } ``` -### Setting special roles for fungible tokens using the factory +#### Setting special roles for fungible tokens using the factory ```js { // create the entrypoint and the token management controller @@ -1551,7 +1551,7 @@ For scripts or quick network interactions, we recommend using the controller. Ho } ``` -### Issuing semi-fungible tokens using the controller +#### Issuing semi-fungible tokens using the controller ```js { // create the entrypoint and the token management controller @@ -1591,7 +1591,7 @@ For scripts or quick network interactions, we recommend using the controller. Ho } ``` -### Issuing semi-fungible tokens using the factory +#### Issuing semi-fungible tokens using the factory ```js { // create the entrypoint and the token management controller @@ -1637,7 +1637,7 @@ For scripts or quick network interactions, we recommend using the controller. Ho } ``` -### Issuing NFT collection & creating NFTs using the controller +#### Issuing NFT collection & creating NFTs using the controller ```js { @@ -1703,7 +1703,7 @@ For scripts or quick network interactions, we recommend using the controller. Ho } ``` -### Issuing NFT collection & creating NFTs using the factory +#### Issuing NFT collection & creating NFTs using the factory ```js { // create the entrypoint and the token management transdactions factory @@ -1768,7 +1768,7 @@ For scripts or quick network interactions, we recommend using the controller. Ho // sending the transaction txHash = await entrypoint.sendTransaction(transaction); - // ## wait for transaction to execute, extract the token identifier + // ### wait for transaction to execute, extract the token identifier transactionOnNetwork = await entrypoint.awaitCompletedTransaction(txHash); outcome = parser.parseIssueNonFungible(transactionOnNetwork); @@ -1784,7 +1784,7 @@ These are just a few examples of what you can do using the token management cont - [TokenManagementController](https://multiversx.github.io/mx-sdk-js-core/v14/classes/TokenManagementController.html) - [TokenManagementTransactionsFactory](https://multiversx.github.io/mx-sdk-js-core/v14/classes/TokenManagementTransactionsFactory.html) -## Account management +### Account management The account management controller and factory allow us to create transactions for managing accounts, such as: - Guarding and unguarding accounts @@ -1793,7 +1793,7 @@ The account management controller and factory allow us to create transactions fo To learn more about Guardians, please refer to the [official documentation](/developers/built-in-functions/#setguardian). A guardian can also be set using the WebWallet, which leverages our hosted `Trusted Co-Signer Service`. Follow [this guide](/wallet/web-wallet/#guardian) for step-by-step instructions on guarding an account using the wallet. -### Guarding an account using the controller +#### Guarding an account using the controller ```js { // create the entrypoint and the account controller @@ -1824,7 +1824,7 @@ A guardian can also be set using the WebWallet, which leverages our hosted `Trus } ``` -### Guarding an account using the factory +#### Guarding an account using the factory ```js { // create the entrypoint and the account management factory @@ -1861,7 +1861,7 @@ A guardian can also be set using the WebWallet, which leverages our hosted `Trus Once a guardian is set, we must wait **20 epochs** before it can be activated. After activation, all transactions sent from the account must also be signed by the guardian. -### Activating the guardian using the controller +#### Activating the guardian using the controller ```js { // create the entrypoint and the account controller @@ -1885,7 +1885,7 @@ Once a guardian is set, we must wait **20 epochs** before it can be activated. A } ``` -### Activating the guardian using the factory +#### Activating the guardian using the factory ```js { // create the entrypoint and the account factory @@ -1914,7 +1914,7 @@ Once a guardian is set, we must wait **20 epochs** before it can be activated. A } ``` -### Unguarding the account using the controller +#### Unguarding the account using the controller ```js { // create the entrypoint and the account controller @@ -1943,7 +1943,7 @@ Once a guardian is set, we must wait **20 epochs** before it can be activated. A } ``` -### Unguarding the guardian using the factory +#### Unguarding the guardian using the factory ```js { // create the entrypoint and the account factory @@ -1974,7 +1974,7 @@ Once a guardian is set, we must wait **20 epochs** before it can be activated. A } ``` -### Saving a key-value pair to an account using the controller +#### Saving a key-value pair to an account using the controller You can find more information [here](/developers/account-storage) regarding the account storage. ```js @@ -2006,7 +2006,7 @@ You can find more information [here](/developers/account-storage) regarding the } ``` -### Saving a key-value pair to an account using the factory +#### Saving a key-value pair to an account using the factory ```js { // create the entrypoint and the account factory @@ -2039,7 +2039,7 @@ You can find more information [here](/developers/account-storage) regarding the } ``` -## Delegation management +### Delegation management To learn more about staking providers and delegation, please refer to the official [documentation](/validators/delegation-manager/#introducing-staking-providers). In this section, we'll cover how to: @@ -2054,7 +2054,7 @@ These operations can be performed using both the controller and the **factory**. - [DelegationController](https://multiversx.github.io/mx-sdk-js-core/v14/classes/DelegationController.html) - [DelegationTransactionsFactory](https://multiversx.github.io/mx-sdk-js-core/v14/classes/DelegationTransactionsFactory.html) -### Creating a New Delegation Contract Using the Controller +#### Creating a New Delegation Contract Using the Controller ```js { // create the entrypoint and the delegation controller @@ -2086,7 +2086,7 @@ These operations can be performed using both the controller and the **factory**. } ``` -### Creating a new delegation contract using the factory +#### Creating a new delegation contract using the factory ```js { // create the entrypoint and the delegation factory @@ -2125,7 +2125,7 @@ These operations can be performed using both the controller and the **factory**. } ``` -### Delegating funds to the contract using the Controller +#### Delegating funds to the contract using the Controller We can send funds to a delegation contract to earn rewards. ```js @@ -2155,7 +2155,7 @@ We can send funds to a delegation contract to earn rewards. } ``` -### Delegating funds to the contract using the factory +#### Delegating funds to the contract using the factory ```js { // create the entrypoint and the delegation factory @@ -2186,7 +2186,7 @@ We can send funds to a delegation contract to earn rewards. } ``` -### Redelegating rewards using the Controller +#### Redelegating rewards using the Controller Over time, as rewards accumulate, we may choose to redelegate them to the contract to maximize earnings. ```js @@ -2214,7 +2214,7 @@ Over time, as rewards accumulate, we may choose to redelegate them to the contra } ``` -### Redelegating rewards using the factory +#### Redelegating rewards using the factory ```js { // create the entrypoint and the delegation factory @@ -2244,7 +2244,7 @@ Over time, as rewards accumulate, we may choose to redelegate them to the contra } ``` -### Claiming rewards using the Controller +#### Claiming rewards using the Controller We can also claim our rewards when needed. ```js @@ -2272,7 +2272,7 @@ We can also claim our rewards when needed. } ``` -### Claiming rewards using the factory +#### Claiming rewards using the factory ```js { // create the entrypoint and the delegation factory @@ -2302,7 +2302,7 @@ We can also claim our rewards when needed. } ``` -### Undelegating funds using the Controller +#### Undelegating funds using the Controller By **undelegating**, we signal the contract that we want to retrieve our staked funds. This process requires a **10-epoch unbonding period** before the funds become available. ```js @@ -2331,7 +2331,7 @@ By **undelegating**, we signal the contract that we want to retrieve our staked } ``` -### Undelegating funds using the factory +#### Undelegating funds using the factory ```js { // create the entrypoint and the delegation factory @@ -2362,7 +2362,7 @@ By **undelegating**, we signal the contract that we want to retrieve our staked } ``` -### Withdrawing funds using the Controller +#### Withdrawing funds using the Controller After the `10-epoch unbonding period` is complete, we can proceed with withdrawing our staked funds using the controller. This final step allows us to regain access to the previously delegated funds. ```js @@ -2391,7 +2391,7 @@ After the `10-epoch unbonding period` is complete, we can proceed with withdrawi } ``` -### Withdrawing funds using the factory +#### Withdrawing funds using the factory ```js { // create the entrypoint and the delegation factory @@ -2421,7 +2421,7 @@ After the `10-epoch unbonding period` is complete, we can proceed with withdrawi } ``` -## Relayed transactions +### Relayed transactions We are currently on the `third iteration (V3)` of relayed transactions. V1 and V2 will soon be deactivated, so we will focus on V3. For V3, two new fields have been added to transactions: @@ -2468,7 +2468,7 @@ Let’s see how to create a relayed transaction: } ``` -### Creating relayed transactions using controllers +#### Creating relayed transactions using controllers We can create relayed transactions using any of the available controllers. Each controller includes a relayer argument, which must be set if we want to create a relayed transaction. @@ -2516,7 +2516,7 @@ Let’s issue a fungible token using a relayed transaction: } ``` -### Creating relayed transactions using factories +#### Creating relayed transactions using factories Unlike controllers, `transaction factories` do not have a `relayer` argument. Instead, the **relayer must be set after creating the transaction**. This approach is beneficial because the **transaction is not signed by the sender at the time of creation**, allowing flexibility in setting the relayer before signing. @@ -2569,7 +2569,7 @@ Let’s issue a fungible token using the `TokenManagementTransactionsFactory`: } ``` -## Guarded transactions +### Guarded transactions Similar to relayers, transactions also have two additional fields: - guardian @@ -2581,7 +2581,7 @@ Each controller includes an argument for the guardian. The transaction can eithe Let’s issue a token using a guarded account: -### Creating guarded transactions using controllers +#### Creating guarded transactions using controllers We can create guarded transactions using any of the available controllers. Each controller method includes a guardian argument, which must be set if we want to create a guarded transaction. @@ -2629,7 +2629,7 @@ Let’s issue a fungible token using a relayed transaction: } ``` -### Creating guarded transactions using factories +#### Creating guarded transactions using factories Unlike controllers, `transaction factories` do not have a `guardian` argument. Instead, the **guardian must be set after creating the transaction**. This approach is beneficial because the transaction is **not signed by the sender at the time of creation**, allowing flexibility in setting the guardian before signing. @@ -2698,7 +2698,7 @@ Flow for Creating Guarded Relayed Transactions: 4. Then, the guardian signs. 5. Finally, the relayer signs before broadcasting. -# Addresses +## Addresses Create an `Address` object from a bech32-encoded string: @@ -2727,7 +2727,7 @@ If the HRP (human-readable part) is not provided, the SDK will use the default o } ``` -### Create an address from a raw public key +#### Create an address from a raw public key ``` js { @@ -2736,7 +2736,7 @@ If the HRP (human-readable part) is not provided, the SDK will use the default o } ``` -### Using an AddressFactory to create addresses +#### Using an AddressFactory to create addresses AddressFactory allows creating addresses with a custom HRP, ensuring consistency across your application. ``` js @@ -2749,7 +2749,7 @@ AddressFactory allows creating addresses with a custom HRP, ensuring consistency } ``` -### Getting the shard of an address +#### Getting the shard of an address ``` js const addressComputer = new AddressComputer(); @@ -2763,7 +2763,7 @@ const contractAddress = Address.newFromBech32("erd1qqqqqqqqqqqqqpgquzmh78klkqwt0 console.log("Is contract address:", contractAddress.isSmartContract()); ``` -## Changing the default hrp +### Changing the default hrp The **LibraryConfig** class manages the default **HRP** (human-readable part) for addresses, which is set to `"erd"` by default. You can change the HRP when creating an address or modify it globally in **LibraryConfig**, affecting all newly created addresses. ``` js @@ -2780,9 +2780,9 @@ console.log(testAddress.toBech32()); LibraryConfig.defaultAddressHrp = "erd"; ``` -# Wallets +## Wallets -### Generating a mnemonic +#### Generating a mnemonic Mnemonic generation is based on [bip39](https://www.npmjs.com/package/bip39) and can be achieved as follows: ``` js @@ -2792,7 +2792,7 @@ const words = mnemonic.getWords(); console.log({ words }); ``` -### Saving the mnemonic to a keystore file +#### Saving the mnemonic to a keystore file The mnemonic can be saved to a keystore file: ``` js @@ -2807,7 +2807,7 @@ The mnemonic can be saved to a keystore file: } ``` -### Deriving secret keys from a mnemonic +#### Deriving secret keys from a mnemonic Given a mnemonic, we can derive keypairs: ``` js @@ -2822,7 +2822,7 @@ Given a mnemonic, we can derive keypairs: } ``` -### Saving a secret key to a keystore file +#### Saving a secret key to a keystore file The secret key can also be saved to a keystore file: ``` js @@ -2837,7 +2837,7 @@ The secret key can also be saved to a keystore file: } ``` -### Saving a secret key to a PEM file +#### Saving a secret key to a PEM file 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.* ``` js @@ -2856,7 +2856,7 @@ We can save a secret key to a pem file. *This is not recommended as it is not se } ``` -### Generating a KeyPair +#### Generating a KeyPair 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. ``` js @@ -2869,7 +2869,7 @@ A `KeyPair` is a wrapper over a secret key and a public key. We can create a key } ``` -### Loading a wallet from keystore mnemonic file +#### Loading a wallet from keystore mnemonic file Load a keystore that holds an encrypted mnemonic (and perform wallet derivation at the same time): ``` js @@ -2892,7 +2892,7 @@ Load a keystore that holds an encrypted mnemonic (and perform wallet derivation } ``` -### Loading a wallet from a keystore secret key file +#### Loading a wallet from a keystore secret key file ``` js { @@ -2906,7 +2906,7 @@ Load a keystore that holds an encrypted mnemonic (and perform wallet derivation } ``` -### Loading a wallet from a PEM file +#### Loading a wallet from a PEM file ``` js { @@ -2919,12 +2919,12 @@ Load a keystore that holds an encrypted mnemonic (and perform wallet derivation } ``` -# Signing objects +## Signing objects Signing is done using an account's secret key. To simplify this process, we provide wrappers like [Account](#creating-accounts), which streamline signing operations. First, we'll explore how to sign using an Account, followed by signing directly with a secret key. -### Signing a Transaction using an Account +#### Signing a Transaction using an Account We are going to assume we have an account at this point. If you don't, feel free to check out the [creating an account](#creating-accounts) section. ```js { @@ -2944,7 +2944,7 @@ We are going to assume we have an account at this point. If you don't, feel free } ``` -### Signing a Transaction using a SecretKey +#### Signing a Transaction using a SecretKey ```js { const secretKeyHex = "413f42575f7f26fad3317a778771212fdb80245850981e48b58a4f25e344e8f9"; @@ -2971,7 +2971,7 @@ We are going to assume we have an account at this point. If you don't, feel free } ``` -### Signing a Transaction by hash +#### Signing a Transaction by hash ```js { const filePath = path.join("src", "testdata", "testwallets", "alice.pem"); @@ -3001,7 +3001,7 @@ We are going to assume we have an account at this point. If you don't, feel free } ``` -### Signing a Message using an Account: +#### Signing a Message using an Account: ```js { const filePath = path.join("src", "testdata", "testwallets", "alice.pem"); @@ -3016,7 +3016,7 @@ We are going to assume we have an account at this point. If you don't, feel free } ``` -### Signing a Message using an SecretKey: +#### Signing a Message using an SecretKey: ```js { const secretKeyHex = "413f42575f7f26fad3317a778771212fdb80245850981e48b58a4f25e344e8f9"; @@ -3035,12 +3035,12 @@ We are going to assume we have an account at this point. If you don't, feel free } ``` -# Verifying signatures +## Verifying signatures Signature verification is performed using an account’s public key. To simplify this process, we provide wrappers over public keys that make verification easier and more convenient. -### Verifying Transaction signature using a UserVerifier +#### Verifying Transaction signature using a UserVerifier ```js { @@ -3075,7 +3075,7 @@ To simplify this process, we provide wrappers over public keys that make verific } ``` -### Verifying Message signature using a UserVerifier +#### Verifying Message signature using a UserVerifier ```js { @@ -3105,7 +3105,7 @@ To simplify this process, we provide wrappers over public keys that make verific } ``` -### Verifying a signature using a public key +#### Verifying a signature using a public key ```js { const filePath = path.join("src", "testdata", "testwallets", "alice.pem"); @@ -3137,7 +3137,7 @@ To simplify this process, we provide wrappers over public keys that make verific } ``` -### Sending messages over boundaries +#### Sending messages over boundaries Signed Message objects are typically sent to a remote party (e.g., a service), which can then verify the signature. To prepare a message for transmission, you can use the `MessageComputer.packMessage()` utility method. From d8e829b0bc7fb94725851f3b8e117bf0fc88fe0c Mon Sep 17 00:00:00 2001 From: danielailie Date: Tue, 18 Mar 2025 11:44:52 +0200 Subject: [PATCH 6/6] Fix typo --- docs/sdk-and-tools/sdk-js/sdk-js-cookbook-v14.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sdk-and-tools/sdk-js/sdk-js-cookbook-v14.md b/docs/sdk-and-tools/sdk-js/sdk-js-cookbook-v14.md index 2711b7d88..468c7da41 100644 --- a/docs/sdk-and-tools/sdk-js/sdk-js-cookbook-v14.md +++ b/docs/sdk-and-tools/sdk-js/sdk-js-cookbook-v14.md @@ -180,7 +180,7 @@ The `Account` implements the `IAccount` interface, making it compatible with tra ## Calling the Faucet -This functionality is not yet available through the entrypoint, but we recommend using the faucet available within the Web Wallet. For more details about hthe faucet [see this](/wallet/web-wallet/#testnet-and-devnet-faucet). +This functionality is not yet available through the entrypoint, but we recommend using the faucet available within the Web Wallet. For more details about the faucet [see this](/wallet/web-wallet/#testnet-and-devnet-faucet). - [Testnet Wallet](https://testnet-wallet.multiversx.com/). - [Devnet Wallet](https://devnet-wallet.multiversx.com/).