Cookbook (v12)
This page will guide you through the process of handling common tasks using sdk-js v12 (legacy, previous version).
A newer variant of the sdk-js cookbook is available here.
In order to migrate to the newer sdk-js v13
, please follow the migration guide.
Creating network providers
Creating an API provider:
import { ApiNetworkProvider } from "@multiversx/sdk-network-providers";
const apiNetworkProvider = new ApiNetworkProvider("https://devnet-api.multiversx.com");
Creating a Proxy provider:
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.
Fetching network parameters
const networkConfig = await apiNetworkProvider.getNetworkConfig();
console.log(networkConfig.MinGasPrice);
console.log(networkConfig.ChainID);
Working with accounts
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.
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());
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):
alice.incrementNonce();
console.log("Nonce:", alice.nonce);
Alternatively, you can also use setNonce
on a Transaction
object:
notYetSignedTx.setNonce(alice.getNonceThenIncrement());
For further reference, please see nonce management.
Preparing TokenTransfer
objects
A TokenTransfer
object for EGLD transfers (value movements):
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:
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:
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):
nonce = 7;
transfer = TokenTransfer.nonFungible(identifier, nonce);
A TokenTransfer
object for transferring meta-esdt tokens:
transfer = TokenTransfer.metaEsdtFromAmount(identifier, nonce, "0.1", numDecimals);
Broadcasting transactions
Preparing a simple transaction
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());
Broadcast using a network provider
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.
Note that, for all purposes, we recommend using sdk-dapp instead of integrating the signing providers on your own.
Broadcast using axios
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;
Wait for transaction completion
import { TransactionWatcher } from "@multiversx/sdk-core";
const watcher = new TransactionWatcher(apiNetworkProvider);
const transactionOnNetwork = await watcher.awaitCompleted(tx);
If only the txHash
is available, then:
const transactionOnNetwork = await watcher.awaitCompleted({ getHash: () => txHash });
console.log(transactionOnNetwork);
In order to wait for multiple transactions:
await Promise.all([watcher.awaitCompleted(tx1), watcher.awaitCompleted(tx2), watcher.awaitCompleted(tx3)]);
For a different awaiting strategy, also see extending sdk-js.
Token transfers
First, let's create a TransferTransactionsFactory
.
import { GasEstimator, TransferTransactionsFactory } from "@multiversx/sdk-core";
const factory = new TransferTransactionsFactory(new GasEstimator());
Single ESDT transfer
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"
});
Single NFT transfer
const transfer2 = TokenTransfer.nonFungible("TEST-38f249", 1);
const tx2 = factory.createESDTNFTTransfer({
tokenTransfer: transfer2,
nonce: 8,
sender: addressOfAlice,
destination: addressOfBob,
chainID: "D"
});
Single SFT transfer
const transfer3 = TokenTransfer.semiFungible("SEMI-9efd0f", 1, 5);
const tx3 = factory.createESDTNFTTransfer({
tokenTransfer: transfer3,
nonce: 9,
sender: addressOfAlice,
destination: addressOfBob,
chainID: "D"
});
Multi ESDT / NFT transfer
const transfers = [transfer1, transfer2, transfer3];
const tx4 = factory.createMultiESDTNFTTransfer({
tokenTransfers: transfers,
nonce: 10,
sender: addressOfAlice,
destination: addressOfBob,
chainID: "D"
});
Contract deployments
Load the bytecode from a file
import { Code } from "@multiversx/sdk-core";
import { promises } from "fs";
let buffer = await promises.readFile("../contracts/counter.wasm");
let code = Code.fromBuffer(buffer);
Load the bytecode from an URL
import axios from "axios";
let response = await axios.get("https://github.com/multiversx/mx-sdk-js-core/raw/main/src/testdata/counter.wasm", {
responseType: "arraybuffer",
transformResponse: [],
headers: {
"Accept": "application/wasm"
}
});
buffer = Buffer.from(response.data);
code = Code.fromBuffer(buffer);
Perform a contract deployment
Create a SmartContract
object:
import { SmartContract } from "@multiversx/sdk-core";
let contract = new SmartContract();
Prepare the deploy transaction:
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).
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:
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:
import { TransactionWatcher } from "@multiversx/sdk-core";
await networkProvider.sendTransaction(deployTransaction);
let transactionOnNetwork = await new TransactionWatcher(networkProvider).awaitCompleted(deployTransaction);
In the end, parse the results:
import { ResultsParser } from "@multiversx/sdk-core";
let { returnCode } = new ResultsParser().parseUntypedOutcome(transactionOnNetwork);
console.log("Return code:", returnCode);
ABI
Load the ABI from a file
import { AbiRegistry, Address, SmartContract } from "@multiversx/sdk-core";
import { promises } from "fs";
let abiJson = await promises.readFile("../contracts/counter.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 });
Load the ABI from an URL
import axios from "axios";
const response = await axios.get("https://github.com/multiversx/mx-sdk-js-core/raw/main/src/testdata/counter.abi.json");
abiRegistry = AbiRegistry.create(response.data);
existingContract = new SmartContract({ address: existingContractAddress, abi: abiRegistry });
Contract queries
When the ABI is not available
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));
Using Interaction
, when the ABI is not available
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.
When the ABI is available
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));
Using Interaction
, when the ABI is available
Prepare the interaction, check it, then build the query:
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:
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:
let firstValueAsStruct = <Struct>firstValue;
Contract interactions
When the ABI is not available
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.
Using Interaction
, when the ABI is not available
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.
Using Interaction
, when the ABI is available
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:
import { StringValue, VariadicValue } from "@multiversx/sdk-core";
abiRegistry = AbiRegistry.create({
"endpoints": [
{
"name": "foobar",
"inputs": [],
"outputs": []
},
{
"name": "doSomething",
"inputs": [{
"type": "counted-variadic<utf-8 string>"
},
{
"type": "variadic<u64>"
}],
"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();
Transfer & execute
Given an interaction:
interaction = contract.methods.foobar([]);
One can apply token transfers to the smart contract call, as well.
For single payments, do as follows:
// Fungible token
interaction.withSingleESDTTransfer(TokenTransfer.fungibleFromAmount("FOO-6ce17b", "1.5", 18));
// Non-fungible token
interaction.withSingleESDTNFTTransfer(TokenTransfer.nonFungible("SDKJS-38f249", 1));
For multiple payments:
interaction.withMultiESDTNFTTransfer([
TokenTransfer.fungibleFromAmount("FOO-6ce17b", "1.5", 18),
TokenTransfer.nonFungible("SDKJS-38f249", 1)
]);
Parsing contract results
When the default ResultsParser
misbehaves, please open an issue on GitHub, 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).
When the ABI is not available
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);
When the ABI is available
let endpointDefinition = AbiRegistry.create({
"name": "counter",
"endpoints": [{
"name": "increment",
"inputs": [],
"outputs": [{ "type": "i64" }]
}]
}).getEndpoint("increment");
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:
endpointDefinition = interaction.getEndpoint();
Alternatively, the endpointDefinition
can also be obtained from the SmartContract
object:
let endpointDefinition = smartContract.getEndpoint("myFunction");
For customizing the default parser, also see extending sdk-js.
Contract events
Decode transaction events
Example of decoding a transaction event having the identifier deposit
:
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));
Explicit decoding / encoding of values
Decoding a custom type
Example of decoding a custom type (a structure) called DepositEvent
from binary data:
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:
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));
Signing objects
For dApps, use the available signing providers instead.
Creating a UserSigner
from a JSON wallet:
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:
const pemText = await promises.readFile("../testwallets/alice.pem", { encoding: "utf8" });
signer = UserSigner.fromPem(pemText);
Signing a transaction:
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:
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"));
Verifying signatures
Creating a UserVerifier
:
import { UserVerifier } from "@multiversx/sdk-wallet";
const aliceVerifier = UserVerifier.fromAddress(addressOfAlice);
const bobVerifier = UserVerifier.fromAddress(addressOfBob);
Suppose we have the following transaction:
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:
message = new SignableMessage({ message: Buffer.from("hello") });
serializedMessage = message.serializeForSigning();
messageSignature = Buffer.from("561bc58f1dc6b10de208b2d2c22c9a474ea5e8cabb59c3d3ce06bbda21cc46454aa71a85d5a60442bd7784effa2e062fcb8fb421c521f898abf7f5ec165e5d0f", "hex");
We can verify their signatures as follows:
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));
Decoding transaction metadata
Using the transaction-decoder
In order to decode the metadata (function, arguments, transfers) from a transaction payload, do as follows:
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
});
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 (see above).
However, you can still find the previous implementations at the following location: