From 16c23c2419debce56dea6173833bdb63901aff12 Mon Sep 17 00:00:00 2001 From: bogdan-rosianu Date: Mon, 2 Feb 2026 13:52:16 +0200 Subject: [PATCH 1/4] AI agents docs + llms.txt --- docs/bridge/architecture.md | 1 + docs/bridge/axelar.md | 6 + docs/bridge/token-types.md | 1 + docs/bridge/transfer-flows.md | 1 + docs/bridge/whitelist-requirements.md | 1 + docs/developers/account-storage.md | 1 + .../best-practices/best-practices-basics.md | 1 + .../best-practices/biguint-operations.md | 1 + .../best-practices/managed-decimal.md | 1 + .../best-practices/prepare-sc-supernova.md | 2 +- .../the-dynamic-allocation-problem.md | 1 + docs/developers/best-practices/time-types.md | 2 +- docs/developers/built-in-functions.md | 1 + docs/developers/constants.md | 2 + docs/developers/contract-api-limits.md | 1 + docs/developers/creating-wallets.md | 2 + docs/developers/data/abi.md | 1 + docs/developers/data/code-metadata.md | 1 + docs/developers/data/composite-values.md | 12 +- docs/developers/data/custom-types.md | 2 + docs/developers/data/defaults.md | 2 + docs/developers/data/multi-values.md | 3 +- .../developers/data/serialization-overview.md | 3 +- docs/developers/data/simple-values.md | 12 +- .../developer-reference/sc-annotations.md | 1 + .../developer-reference/sc-api-functions.md | 1 + .../developer-reference/sc-messages.md | 1 + .../developer-reference/sc-modules.md | 1 + .../developer-reference/sc-payments.md | 1 + .../developer-reference/sc-random-numbers.md | 1 + .../developer-reference/sc-to-sc-calls.md | 2 + .../developer-reference/storage-mappers.md | 2 + .../upgrading-smart-contracts.md | 1 + docs/developers/esdt-tokens.md | 1 + .../event-logs/contract-call-events.md | 2 +- .../event-logs/contract-deploy-events.mdx | 1 + docs/developers/event-logs/esdt-events.mdx | 1 + .../event-logs/execution-events.mdx | 1 + .../event-logs/system-delegation-events.md | 2 +- .../developers/gas-and-fees/egld-transfers.md | 1 + docs/developers/gas-and-fees/overview.md | 2 + .../gas-and-fees/system-smart-contracts.md | 1 + .../user-defined-smart-contracts.md | 2 + docs/developers/guard-accounts.md | 1 + .../guidelines/react-development.md | 1 + .../meta/interactor/interactors-example.md | 1 + .../meta/interactor/interactors-overview.md | 1 + docs/developers/meta/rust-version.md | 1 + docs/developers/meta/sc-allocator.md | 1 + docs/developers/meta/sc-build-reference.md | 1 + docs/developers/meta/sc-config.md | 1 + docs/developers/meta/sc-meta-cli.md | 1 + docs/developers/meta/sc-meta.md | 1 + docs/developers/nft-tokens.md | 1 + docs/developers/overview.md | 1 + docs/developers/relayed-transactions.md | 2 + .../reproducible-contract-builds.md | 2 + docs/developers/sc-calls-format.md | 2 + .../setup-local-testnet-advanced.md | 2 + docs/developers/setup-local-testnet.md | 2 + .../signing-programmatically.md | 1 + .../signing-transactions.md | 2 + .../signing-transactions/tools-for-signing.md | 2 + docs/developers/smart-contracts.md | 1 + docs/developers/testing/rust/mandos-trace.md | 1 + .../testing/rust/sc-blackbox-calls.md | 1 + .../testing/rust/sc-blackbox-example.md | 1 + .../testing/rust/sc-test-overview.md | 1 + docs/developers/testing/rust/sc-test-setup.md | 1 + .../whitebox-legacy-functions-reference.md | 1 + .../testing/rust/whitebox-legacy.md | 1 + docs/developers/testing/sc-debugging.md | 1 + docs/developers/testing/scenario/concept.md | 1 + .../testing/scenario/generating-scenarios.md | 2 + .../testing/scenario/running-scenarios.md | 2 + .../testing/scenario/structure-json.md | 2 + .../testing/scenario/values-complex.md | 2 + .../testing/scenario/values-simple.md | 2 + docs/developers/testing/testing-in-go.md | 1 + docs/developers/testing/testing-overview.md | 1 + docs/developers/toolchain-setup.md | 7 +- docs/developers/transactions/tx-data.md | 1 + docs/developers/transactions/tx-env.md | 1 + docs/developers/transactions/tx-examples.md | 1 + docs/developers/transactions/tx-from.md | 1 + docs/developers/transactions/tx-gas.md | 2 +- .../transactions/tx-impl-details.md | 1 + .../transactions/tx-legacy-calls.md | 3 +- docs/developers/transactions/tx-migration.md | 1 + docs/developers/transactions/tx-overview.md | 1 + docs/developers/transactions/tx-payment.md | 1 + docs/developers/transactions/tx-proxies.md | 1 + .../transactions/tx-result-handlers.md | 1 + docs/developers/transactions/tx-run.md | 1 + docs/developers/transactions/tx-to.md | 1 + .../tutorials/chain-simulator-adder.md | 1 + .../tutorials/crowdfunding/crowdfunding-p1.md | 1 + .../tutorials/crowdfunding/crowdfunding-p2.md | 2 + .../tutorials/crowdfunding/crowdfunding-p3.md | 1 + .../tutorials/crowdfunding/final-code.md | 1 + docs/developers/tutorials/dex-walkthrough.md | 1 + docs/developers/tutorials/energy-dao.md | 1 + docs/developers/tutorials/eth-to-mvx.md | 1 + .../developers/tutorials/interactors-guide.md | 5 +- docs/developers/tutorials/staking-contract.md | 1 + .../tutorials/wallet-connect-v2-migration.md | 5 +- docs/developers/tutorials/your-first-dapp.md | 1 + .../tutorials/your-first-microservice.md | 1 + docs/economics/staking-providers-apr.md | 1 + docs/governance/governance-interaction.md | 1 + docs/governance/overview.md | 1 + docs/integrators/accounts-management.md | 1 + .../integrators/advanced-observer-settings.md | 1 + docs/integrators/creating-transactions.md | 1 + docs/integrators/deep-history-squad.md | 2 +- docs/integrators/egld-integration-guide.md | 1 + .../esdt-tokens-integration-guide.md | 1 + docs/integrators/faq.md | 1 + docs/integrators/observing-squad.md | 1 + docs/integrators/overview.md | 1 + docs/integrators/querying-the-blockchain.md | 1 + .../snapshotless-observing-squad.md | 1 + .../walletconnect-json-rpc-methods.md | 1 + docs/learn/EGLD.md | 1 + docs/learn/ai-agents.md | 18 + docs/learn/architecture-overview.md | 1 + docs/learn/chronology.md | 1 + docs/learn/consensus.md | 1 + docs/learn/economics.md | 1 + docs/learn/entities.md | 1 + docs/learn/getting-started.md | 1 + docs/learn/multiversx-ecosystem.md | 1 + docs/learn/sharding.md | 1 + docs/learn/space-vm.md | 1 + docs/learn/transactions.md | 1 + docs/sdk-and-tools/chain-simulator.md | 1 + docs/sdk-and-tools/devcontainers.md | 1 + docs/sdk-and-tools/elastic-indexer.md | 1 + .../elastic-search-wrong-mappings-fix.md | 1 + docs/sdk-and-tools/elastic-search.md | 1 + docs/sdk-and-tools/erdcpp.md | 1 + docs/sdk-and-tools/erdkotlin.md | 1 + docs/sdk-and-tools/google-bigquery.md | 1 + docs/sdk-and-tools/indices/accounts.md | 1 + docs/sdk-and-tools/indices/accountsesdt.md | 1 + .../indices/accountsesdthistory.md | 1 + docs/sdk-and-tools/indices/accountshistory.md | 1 + docs/sdk-and-tools/indices/blocks.md | 1 + docs/sdk-and-tools/indices/delegators.md | 1 + docs/sdk-and-tools/indices/epochinfo.md | 1 + docs/sdk-and-tools/indices/events.md | 1 + docs/sdk-and-tools/indices/logs.md | 1 + docs/sdk-and-tools/indices/miniblocks.md | 1 + docs/sdk-and-tools/indices/operations.md | 1 + docs/sdk-and-tools/indices/rating.md | 1 + docs/sdk-and-tools/indices/receipts.md | 1 + docs/sdk-and-tools/indices/rounds.md | 1 + docs/sdk-and-tools/indices/scdeploys.md | 1 + docs/sdk-and-tools/indices/scresults.md | 1 + docs/sdk-and-tools/indices/tags.md | 1 + docs/sdk-and-tools/indices/tokens.md | 1 + docs/sdk-and-tools/indices/transactions.md | 1 + docs/sdk-and-tools/indices/validators.md | 1 + docs/sdk-and-tools/mxjava.md | 1 + docs/sdk-and-tools/mxpy/installing-mxpy.md | 1 + docs/sdk-and-tools/mxpy/mxpy-cli.md | 1 + .../mxpy/smart-contract-interactions.md | 1 + docs/sdk-and-tools/notifier.md | 1 + docs/sdk-and-tools/overview.md | 1 + docs/sdk-and-tools/proxy.md | 1 + docs/sdk-and-tools/rest-api/addresses.mdx | 1 + docs/sdk-and-tools/rest-api/blocks.mdx | 1 + .../rest-api/gateway-overview.md | 1 + docs/sdk-and-tools/rest-api/iterate-keys.md | 1 + docs/sdk-and-tools/rest-api/multiversx-api.md | 1 + docs/sdk-and-tools/rest-api/network.mdx | 1 + docs/sdk-and-tools/rest-api/nodes.mdx | 1 + docs/sdk-and-tools/rest-api/rest-api.md | 1 + docs/sdk-and-tools/rest-api/transactions.mdx | 11 +- .../rest-api/versions-and-changelog.md | 1 + .../rest-api/virtual-machine.mdx | 1 + .../rest-api/ws-subscriptions.md | 3 +- .../sdk-dapp/internal-processes/guardians.md | 1 + docs/sdk-and-tools/sdk-dapp/sdk-dapp.md | 1 + docs/sdk-and-tools/sdk-go.md | 1 + docs/sdk-and-tools/sdk-js/extending-sdk-js.md | 1 + .../sdk-js/sdk-js-cookbook-v14.md | 1 + .../sdk-js/sdk-js-cookbook-v15.md | 1 + .../sdk-js/sdk-js-signing-providers.md | 1 + docs/sdk-and-tools/sdk-js/sdk-js.md | 1 + .../writing-and-running-sdk-js-snippets.md | 1 + .../sdk-nestjs/sdk-nestjs-auth.md | 1 + .../sdk-nestjs/sdk-nestjs-cache.md | 1 + .../sdk-nestjs/sdk-nestjs-monitoring.md | 1 + docs/sdk-and-tools/sdk-nestjs/sdk-nestjs.md | 1 + docs/sdk-and-tools/sdk-py.md | 1 + docs/sdk-and-tools/sdk-rust.md | 1 + .../troubleshooting/ide-setup.md | 1 + .../troubleshooting/multiplatform.md | 1 + .../troubleshooting/rust-setup.md | 1 + .../troubleshooting/troubleshooting.md | 1 + docs/sovereign/bitcoin-l2.md | 4 + docs/sovereign/concept.md | 4 + docs/sovereign/cross-chain-execution.md | 8 +- docs/sovereign/custom-configurations.md | 4 + docs/sovereign/disclaimer.md | 4 + docs/sovereign/distributed-setup.md | 4 + docs/sovereign/dual-staking.md | 4 + docs/sovereign/ethereum-l2.md | 4 + docs/sovereign/governance.md | 4 + docs/sovereign/header-verifier.md | 4 + docs/sovereign/interoperability.md | 4 + docs/sovereign/key-components.md | 4 + docs/sovereign/local-setup.md | 4 + docs/sovereign/managing-sovereign.md | 4 + docs/sovereign/mvx-esdt-safe.md | 4 + docs/sovereign/one-click-deployment.md | 4 + docs/sovereign/other-vm.md | 4 + docs/sovereign/overview.md | 1 + docs/sovereign/restaking.md | 4 + docs/sovereign/security.md | 4 + docs/sovereign/services.md | 4 + docs/sovereign/software-dependencies.md | 4 + docs/sovereign/solana-l2.md | 4 + docs/sovereign/sov-esdt-safe.md | 4 + docs/sovereign/sovereign-api.md | 4 + docs/sovereign/sovereign-explorer.md | 4 + docs/sovereign/sovereign-wallet.md | 4 + docs/sovereign/standalone-evm.md | 4 + docs/sovereign/system-requirements.md | 4 + docs/sovereign/testing.md | 4 + docs/sovereign/token-economics.md | 4 + docs/sovereign/token-management.md | 4 + docs/sovereign/validators.md | 7 +- docs/sovereign/vm-intro.md | 4 + docs/tokens/fungible-tokens.mdx | 1 + docs/tokens/intro.md | 1 + docs/tokens/nft-tokens.mdx | 1 + docs/utils.mdx | 2 +- docs/validators/delegation-dashboard.md | 1 + docs/validators/delegation-manager.mdx | 1 + docs/validators/faq.md | 1 + docs/validators/import-db.md | 1 + .../key-management/multikey-nodes.md | 1 + .../validators/key-management/protect-keys.md | 1 + .../key-management/validator-keys.md | 5 +- docs/validators/key-management/wallet-keys.md | 1 + docs/validators/mainnet/config-scripts.md | 1 + docs/validators/mainnet/install-update.md | 1 + docs/validators/mainnet/optional-configs.md | 1 + docs/validators/mainnet/use-docker.md | 1 + docs/validators/node-cli.md | 1 + docs/validators/node-configuration.md | 1 + docs/validators/node-databases.md | 1 + docs/validators/node-upgrades.md | 1 + .../nodes-scripts/config-scripts.md | 1 + .../nodes-scripts/install-update.md | 1 + docs/validators/nodes-scripts/manage-node.md | 1 + docs/validators/nodes-scripts/use-docker.md | 1 + docs/validators/operation-modes.md | 1 + docs/validators/overview.md | 1 + docs/validators/rating.md | 1 + docs/validators/redundancy.md | 1 + docs/validators/staking-v4.md | 2 +- ...xisting-validator-into-staking-provider.md | 1 + .../staking/merge-validator-delegation-sc.md | 1 + .../staking/staking-smart-contract.md | 1 + .../staking/staking-unstaking-unjailing.md | 1 + docs/validators/staking/staking.md | 1 + docs/validators/staking/unjailing.md | 1 + docs/validators/system-requirements.md | 1 + docs/validators/useful-links-tools.md | 1 + docs/wallet/keystore.md | 1 + docs/wallet/ledger.md | 1 + docs/wallet/overview.md | 1 + docs/wallet/wallet-extension.md | 1 + docs/wallet/wallet-token.md | 1 + docs/wallet/web-wallet.md | 1 + docs/wallet/webhooks.md | 1 + docs/wallet/xalias.md | 1 + docs/wallet/xportal.md | 1 + docs/welcome/terminology.md | 1 + docs/welcome/welcome-to-multiversx.md | 1 + package.json | 4 +- scripts/generate-llms-txt.js | 371 ++++++++++++++++++ sidebars.js | 1 + static/llms.txt | 290 ++++++++++++++ 287 files changed, 1128 insertions(+), 36 deletions(-) create mode 100644 docs/learn/ai-agents.md create mode 100644 scripts/generate-llms-txt.js create mode 100644 static/llms.txt diff --git a/docs/bridge/architecture.md b/docs/bridge/architecture.md index b45dfc54b..6f9a2ec9e 100644 --- a/docs/bridge/architecture.md +++ b/docs/bridge/architecture.md @@ -1,6 +1,7 @@ --- id: architecture title: Architecture +description: "High-level architecture of the Ad‑Astra Bridge: core contracts on MultiversX and EVM chains, relayer quorum, and how cross‑chain transfers are coordinated." --- import useBaseUrl from '@docusaurus/useBaseUrl'; diff --git a/docs/bridge/axelar.md b/docs/bridge/axelar.md index 53a81be32..62bb9e5a8 100644 --- a/docs/bridge/axelar.md +++ b/docs/bridge/axelar.md @@ -1,3 +1,9 @@ +--- +id: axelar +title: Axelar Amplifier Setup +description: "Set up an Axelar Amplifier verifier for MultiversX: prerequisites, tofnd and ampd services, configuration, and verification steps." +--- + # Axelar Amplifier setup for MultiversX ## Prerequisites diff --git a/docs/bridge/token-types.md b/docs/bridge/token-types.md index 936db2945..9106d11fc 100644 --- a/docs/bridge/token-types.md +++ b/docs/bridge/token-types.md @@ -1,6 +1,7 @@ --- id: token-types title: Token Types +description: "Bridge token configurations across MultiversX and EVM chains: mint/burn, lock/unlock, native vs non‑native, and their operational implications." --- import useBaseUrl from '@docusaurus/useBaseUrl'; diff --git a/docs/bridge/transfer-flows.md b/docs/bridge/transfer-flows.md index b2b3a6ce2..4024e63f1 100644 --- a/docs/bridge/transfer-flows.md +++ b/docs/bridge/transfer-flows.md @@ -1,6 +1,7 @@ --- id: transfer-flows title: Transfer Flows +description: "Step‑by‑step token transfer flows for the MultiversX bridge: EVM↔MultiversX directions, smart‑contract call path, refund scenarios, and relayer processing." --- import useBaseUrl from '@docusaurus/useBaseUrl'; diff --git a/docs/bridge/whitelist-requirements.md b/docs/bridge/whitelist-requirements.md index 0e9093bc2..0500a49fb 100644 --- a/docs/bridge/whitelist-requirements.md +++ b/docs/bridge/whitelist-requirements.md @@ -1,6 +1,7 @@ --- id: whitelist-requirements title: Whitelist Requirements +description: "Whitelist requirements for the Ad‑Astra bridge: prerequisites, token branding, and assigning roles to wrapper or safe contracts." --- [comment]: # (mx-abstract) diff --git a/docs/developers/account-storage.md b/docs/developers/account-storage.md index 9ebb52d68..f6630f826 100644 --- a/docs/developers/account-storage.md +++ b/docs/developers/account-storage.md @@ -1,6 +1,7 @@ --- id: account-storage title: Account storage +description: "Learn about account storage in MultiversX." --- [comment]: # (mx-abstract) diff --git a/docs/developers/best-practices/best-practices-basics.md b/docs/developers/best-practices/best-practices-basics.md index a19592f1e..0158a7af0 100644 --- a/docs/developers/best-practices/best-practices-basics.md +++ b/docs/developers/best-practices/best-practices-basics.md @@ -1,6 +1,7 @@ --- id: best-practices-basics title: Basics +description: "Learn about basics in MultiversX." --- [comment]: # (mx-context-auto) diff --git a/docs/developers/best-practices/biguint-operations.md b/docs/developers/best-practices/biguint-operations.md index 12b5c3a9a..a6b2fb395 100644 --- a/docs/developers/best-practices/biguint-operations.md +++ b/docs/developers/best-practices/biguint-operations.md @@ -1,6 +1,7 @@ --- id: biguint-operations title: BigUint Operations +description: "Learn about biguint operations in MultiversX." --- [comment]: # (mx-context-auto) diff --git a/docs/developers/best-practices/managed-decimal.md b/docs/developers/best-practices/managed-decimal.md index e243337a8..36ed15c44 100644 --- a/docs/developers/best-practices/managed-decimal.md +++ b/docs/developers/best-practices/managed-decimal.md @@ -1,6 +1,7 @@ --- id: managed-decimal title: Managed Decimal +description: "Learn about managed decimal in MultiversX." --- [comment]: # (mx-context-auto) diff --git a/docs/developers/best-practices/prepare-sc-supernova.md b/docs/developers/best-practices/prepare-sc-supernova.md index a16415337..df1cf9788 100644 --- a/docs/developers/best-practices/prepare-sc-supernova.md +++ b/docs/developers/best-practices/prepare-sc-supernova.md @@ -1,9 +1,9 @@ --- id: prepare-sc-supernova title: Preparing SCs for Supernova +description: "Learn about preparing scs for supernova in MultiversX." --- - The MultiversX Supernova upgrade reduces block time from **6 seconds to 0.6 seconds**, enabling sub-second blocks. While this is a major improvement, it can impact existing smart contracts, especially those relying on assumptions about timestamp behavior. This guide explains how to prepare your contracts for Supernova safely. diff --git a/docs/developers/best-practices/the-dynamic-allocation-problem.md b/docs/developers/best-practices/the-dynamic-allocation-problem.md index 45aa3efc6..8a6bdc38c 100644 --- a/docs/developers/best-practices/the-dynamic-allocation-problem.md +++ b/docs/developers/best-practices/the-dynamic-allocation-problem.md @@ -1,6 +1,7 @@ --- id: the-dynamic-allocation-problem title: The dynamic allocation problem +description: "Learn about the dynamic allocation problem in MultiversX." --- [comment]: # (mx-context-auto) diff --git a/docs/developers/best-practices/time-types.md b/docs/developers/best-practices/time-types.md index b9a830e59..03b13f830 100644 --- a/docs/developers/best-practices/time-types.md +++ b/docs/developers/best-practices/time-types.md @@ -1,9 +1,9 @@ --- id: time-types title: Time-related Types +description: "Learn about time-related types in MultiversX." --- - The Supernova release introduces increased block frequency and encourages transitioning to millisecond timestamps, instead of seconds. To support this, the SpaceCraft SDK (starting with `v0.63.0`) provides strong type wrappers for time values to prevent common bugs. diff --git a/docs/developers/built-in-functions.md b/docs/developers/built-in-functions.md index f24a78257..877cc0833 100644 --- a/docs/developers/built-in-functions.md +++ b/docs/developers/built-in-functions.md @@ -1,6 +1,7 @@ --- id: built-in-functions title: Built-In Functions +description: "Learn about built-in functions in MultiversX." --- [comment]: # (mx-abstract) diff --git a/docs/developers/constants.md b/docs/developers/constants.md index 8325814d0..40ee5b733 100644 --- a/docs/developers/constants.md +++ b/docs/developers/constants.md @@ -1,7 +1,9 @@ --- id: constants title: Constants +description: "Learn about constants in MultiversX." --- + [comment]: # (mx-abstract) MultiversX uses some constants, which are specific to each chain (Mainnet, Testnet or Devnet). The updated values can be found at these sources: diff --git a/docs/developers/contract-api-limits.md b/docs/developers/contract-api-limits.md index 6fa4baa6f..e55eb2f05 100644 --- a/docs/developers/contract-api-limits.md +++ b/docs/developers/contract-api-limits.md @@ -1,6 +1,7 @@ --- id: contract-api-limits title: MultiversX Smart Contracts API limits +description: "Learn about multiversx smart contracts api limits in MultiversX." --- [comment]: # (mx-abstract) diff --git a/docs/developers/creating-wallets.md b/docs/developers/creating-wallets.md index 0e03a3431..ddf55490c 100644 --- a/docs/developers/creating-wallets.md +++ b/docs/developers/creating-wallets.md @@ -1,7 +1,9 @@ --- id: creating-wallets title: Creating Wallets +description: "Learn about creating wallets in MultiversX." --- + [comment]: # (mx-abstract) How to create wallets using the CLI or programmatically diff --git a/docs/developers/data/abi.md b/docs/developers/data/abi.md index fca7a4e68..0004333f0 100644 --- a/docs/developers/data/abi.md +++ b/docs/developers/data/abi.md @@ -1,6 +1,7 @@ --- id: abi title: ABI +description: "Learn about abi in MultiversX." --- [comment]: # (mx-abstract) diff --git a/docs/developers/data/code-metadata.md b/docs/developers/data/code-metadata.md index 1bd5a042d..6f14fd7cf 100644 --- a/docs/developers/data/code-metadata.md +++ b/docs/developers/data/code-metadata.md @@ -1,6 +1,7 @@ --- id: code-metadata title: Code Metadata +description: "Learn about code metadata in MultiversX." --- [comment]: # (mx-abstract) diff --git a/docs/developers/data/composite-values.md b/docs/developers/data/composite-values.md index 1232b7718..6c0a28650 100644 --- a/docs/developers/data/composite-values.md +++ b/docs/developers/data/composite-values.md @@ -1,7 +1,9 @@ --- id: composite-values title: Composite Values +description: "Learn about composite values in MultiversX." --- + [comment]: # (mx-abstract) We often need to group simple values into more complex ones, without splitting them into [several arguments](/developers/data/multi-values). @@ -24,16 +26,18 @@ Then, all nested encodings of the items, concatenated. **Examples** | Type | Value | Top-level encoding | Nested encoding | Explanation | -| ---------------- | -------------------- | --------------------- | ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- | +| + +--- + +------------- | -------------------- | --------------------- | ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- | | `Vec` | `vec![1, 2]` | `0x0102` | `0x00000002 0102` | Length = `2` | | `Vec` | `vec![1, 2]` | `0x00010002` | `0x00000002 00010002` | Length = `2` | | `Vec` | `vec![]` | `0x` | `0x00000000` | Length = `0` | | `Vec` | `vec![7]` | `0x00000007` | `0x00000001 00000007` | Length = `1` | | `Vec< Vec>` | `vec![ vec![7]]` | `0x00000001 00000007` | `0x00000001 00000001 00000007` | There is 1 element, which is a vector. In both cases the inner Vec needs to be nested-encoded in the larger Vec. | | `Vec<&[u8]>` | `vec![ &[7u8][..]]` | `0x00000001 07` | `0x00000001 00000001 07` | Same as above, but the inner list is a simple list of bytes. | -| `Vec< BigUint>` | `vec![ 7u32.into()]` | `0x00000001 07` | `0x00000001 00000001 07` | `BigUint`s need to encode their length when nested. The `7` is encoded the same way as a list of bytes of length 1, so the same as above. | - ---- +| `Vec< BigUint>` | `vec![ 7u32.into()]` | `0x00000001 07` | `0x00000001 00000001 07` | `BigUint`s need to encode their length when nested. The `7` is encoded the same way as a list of bytes of length 1, so the same as above. |--- [comment]: # (mx-context-auto) diff --git a/docs/developers/data/custom-types.md b/docs/developers/data/custom-types.md index f879c0c02..105e724fa 100644 --- a/docs/developers/data/custom-types.md +++ b/docs/developers/data/custom-types.md @@ -1,7 +1,9 @@ --- id: custom-types title: Custom Types +description: "Learn about custom types in MultiversX." --- + [comment]: # (mx-abstract) We sometimes create new types that we want to serialize and deserialize directly when interacting with contracts. For `struct`s and `enum`s it is very easy to set them up, with barely any code. diff --git a/docs/developers/data/defaults.md b/docs/developers/data/defaults.md index 7790b4f80..f15c6e85c 100644 --- a/docs/developers/data/defaults.md +++ b/docs/developers/data/defaults.md @@ -1,7 +1,9 @@ --- id: defaults title: Defaults +description: "Learn about defaults in MultiversX." --- + [comment]: # (mx-abstract) Smart contracts occasionally need to interact with uninitialized data. Most notably, whenever a smart contract is deployed, its entire storage will be uninitialized. diff --git a/docs/developers/data/multi-values.md b/docs/developers/data/multi-values.md index 0c6ff9cfc..7e18f37d3 100644 --- a/docs/developers/data/multi-values.md +++ b/docs/developers/data/multi-values.md @@ -1,7 +1,9 @@ --- id: multi-values title: Multi-Values +description: "Learn about multi-values in MultiversX." --- + [comment]: # (mx-abstract) ## Single values vs. multi-values @@ -245,4 +247,3 @@ where ``` To create a custom multi-value type, one needs to manually implement these two traits for the type. Unlike for single values, there is no [equivalent derive syntax](/developers/data/custom-types). - diff --git a/docs/developers/data/serialization-overview.md b/docs/developers/data/serialization-overview.md index 368aa42ef..cb074e70d 100644 --- a/docs/developers/data/serialization-overview.md +++ b/docs/developers/data/serialization-overview.md @@ -1,7 +1,9 @@ --- id: serialization-overview title: The MultiversX Serialization Format +description: "Learn about the multiversx serialization format in MultiversX." --- + [comment]: # (mx-abstract) In MultiversX, there is a specific serialization format for all data that interacts with a smart contract. The serialization format is central to any project because all values entering and exiting a contract are represented as byte arrays that need to be interpreted according to a consistent specification. @@ -48,4 +50,3 @@ This guide is split into several sections: - [The code metadata flag](/developers/data/code-metadata) There is a special section about [uninitialized data and how defaults relate to serialization](/developers/data/defaults). - diff --git a/docs/developers/data/simple-values.md b/docs/developers/data/simple-values.md index 65be11de8..4b8f49971 100644 --- a/docs/developers/data/simple-values.md +++ b/docs/developers/data/simple-values.md @@ -1,7 +1,9 @@ --- id: simple-values title: Simple Values +description: "Learn about simple values in MultiversX." --- + [comment]: # (mx-abstract) We will start by going through the basic types used in smart contracts: @@ -33,7 +35,11 @@ Even when simulating smart contract execution on 64-bit systems, they must still **Examples** | Type | Number | Top-level encoding | Nested encoding | -| ------- | --------------------- | -------------------- | -------------------- | +| + +--- + +---- | --------------------- | -------------------- | -------------------- | | `u8` | `0` | `0x` | `0x00` | | `u8` | `1` | `0x01` | `0x01` | | `u8` | `0x11` | `0x11` | `0x11` | @@ -88,9 +94,7 @@ Even when simulating smart contract execution on 64-bit systems, they must still | `isize` | `-0x11` | `0xEF` | `0xFFFFFFEF` | | `isize` | `-0x1122` | `0xEEDE` | `0xFFFFEEDE` | | `isize` | `-0x112233` | `0xEEDDCD` | `0xFFEEDDCD` | -| `isize` | `-0x11223344` | `0xEEDDCCBC` | `0xEEDDCCBC` | - ---- +| `isize` | `-0x11223344` | `0xEEDDCCBC` | `0xEEDDCCBC` |--- [comment]: # (mx-context-auto) diff --git a/docs/developers/developer-reference/sc-annotations.md b/docs/developers/developer-reference/sc-annotations.md index ba9a7e8f5..3c08a386b 100644 --- a/docs/developers/developer-reference/sc-annotations.md +++ b/docs/developers/developer-reference/sc-annotations.md @@ -1,6 +1,7 @@ --- id: sc-annotations title: Smart contract annotations +description: "Learn about smart contract annotations in MultiversX." --- [comment]: # (mx-abstract) diff --git a/docs/developers/developer-reference/sc-api-functions.md b/docs/developers/developer-reference/sc-api-functions.md index 7ddf50265..56082b6df 100644 --- a/docs/developers/developer-reference/sc-api-functions.md +++ b/docs/developers/developer-reference/sc-api-functions.md @@ -1,6 +1,7 @@ --- id: sc-api-functions title: Smart Contract API Functions +description: "Learn about smart contract api functions in MultiversX." --- [comment]: # (mx-abstract) diff --git a/docs/developers/developer-reference/sc-messages.md b/docs/developers/developer-reference/sc-messages.md index 29d5f0c65..a6dff9b26 100644 --- a/docs/developers/developer-reference/sc-messages.md +++ b/docs/developers/developer-reference/sc-messages.md @@ -1,6 +1,7 @@ --- id: sc-messages title: Messages +description: "Learn about messages in MultiversX." --- [comment]: # (mx-abstract) diff --git a/docs/developers/developer-reference/sc-modules.md b/docs/developers/developer-reference/sc-modules.md index 027def18f..dcd8b8421 100644 --- a/docs/developers/developer-reference/sc-modules.md +++ b/docs/developers/developer-reference/sc-modules.md @@ -1,6 +1,7 @@ --- id: sc-modules title: Smart contract modules +description: "Learn about smart contract modules in MultiversX." --- [comment]: # (mx-abstract) diff --git a/docs/developers/developer-reference/sc-payments.md b/docs/developers/developer-reference/sc-payments.md index bd06db43a..e83fea3fd 100644 --- a/docs/developers/developer-reference/sc-payments.md +++ b/docs/developers/developer-reference/sc-payments.md @@ -1,6 +1,7 @@ --- id: sc-payments title: Smart contract payments +description: "Learn about smart contract payments in MultiversX." --- [comment]: # (mx-abstract) diff --git a/docs/developers/developer-reference/sc-random-numbers.md b/docs/developers/developer-reference/sc-random-numbers.md index 11de4f421..ebe7fb551 100644 --- a/docs/developers/developer-reference/sc-random-numbers.md +++ b/docs/developers/developer-reference/sc-random-numbers.md @@ -1,6 +1,7 @@ --- id: sc-random-numbers title: Random Numbers in Smart Contracts +description: "Learn about random numbers in smart contracts in MultiversX." --- [comment]: # (mx-abstract) diff --git a/docs/developers/developer-reference/sc-to-sc-calls.md b/docs/developers/developer-reference/sc-to-sc-calls.md index f988c15ef..693973ce8 100644 --- a/docs/developers/developer-reference/sc-to-sc-calls.md +++ b/docs/developers/developer-reference/sc-to-sc-calls.md @@ -1,7 +1,9 @@ --- id: sc-to-sc-calls title: SC to SC Calls +description: "Learn about sc to sc calls in MultiversX." --- + [comment]: # (mx-abstract) This guide provides an overview of the different types of smart contract calls that originate from other smart contract calls. diff --git a/docs/developers/developer-reference/storage-mappers.md b/docs/developers/developer-reference/storage-mappers.md index c441392bd..731ec6704 100644 --- a/docs/developers/developer-reference/storage-mappers.md +++ b/docs/developers/developer-reference/storage-mappers.md @@ -1,7 +1,9 @@ --- id: storage-mappers title: Storage Mappers +description: "Learn about storage mappers in MultiversX." --- + [comment]: # (mx-abstract) The Rust framework provides various storage mappers you can use. Deciding which one to use for every situation is critical for performance. There will be a comparison section after each mapper is described. diff --git a/docs/developers/developer-reference/upgrading-smart-contracts.md b/docs/developers/developer-reference/upgrading-smart-contracts.md index a0e36c682..8b8521d77 100644 --- a/docs/developers/developer-reference/upgrading-smart-contracts.md +++ b/docs/developers/developer-reference/upgrading-smart-contracts.md @@ -1,6 +1,7 @@ --- id: upgrading-smart-contracts title: Upgrading smart contracts +description: "Learn about upgrading smart contracts in MultiversX." --- [comment]: # (mx-abstract) diff --git a/docs/developers/esdt-tokens.md b/docs/developers/esdt-tokens.md index 4d2d76430..978455de9 100644 --- a/docs/developers/esdt-tokens.md +++ b/docs/developers/esdt-tokens.md @@ -1,6 +1,7 @@ --- id: esdt-tokens title: ESDT tokens +description: "Learn about esdt tokens in MultiversX." --- The Fungible Tokens documentation has been moved [here](/tokens/fungible-tokens). diff --git a/docs/developers/event-logs/contract-call-events.md b/docs/developers/event-logs/contract-call-events.md index a71f4ff1b..10c08f7ae 100644 --- a/docs/developers/event-logs/contract-call-events.md +++ b/docs/developers/event-logs/contract-call-events.md @@ -1,5 +1,5 @@ --- id: contract-call-events title: Smart Contract Call Events +description: "Learn about smart contract call events in MultiversX." --- - diff --git a/docs/developers/event-logs/contract-deploy-events.mdx b/docs/developers/event-logs/contract-deploy-events.mdx index 3eef4f119..4d0c7d16d 100644 --- a/docs/developers/event-logs/contract-deploy-events.mdx +++ b/docs/developers/event-logs/contract-deploy-events.mdx @@ -1,6 +1,7 @@ --- id: contract-deploy-events title: Smart Contract Deploy Events +description: "Learn about smart contract deploy events in MultiversX." --- import Tabs from '@theme/Tabs'; diff --git a/docs/developers/event-logs/esdt-events.mdx b/docs/developers/event-logs/esdt-events.mdx index ba0c326f8..7531f98ef 100644 --- a/docs/developers/event-logs/esdt-events.mdx +++ b/docs/developers/event-logs/esdt-events.mdx @@ -1,6 +1,7 @@ --- id: esdt-events title: ESDT Operations Events +description: "Learn about esdt operations events in MultiversX." --- import Tabs from '@theme/Tabs'; diff --git a/docs/developers/event-logs/execution-events.mdx b/docs/developers/event-logs/execution-events.mdx index 8af7299c1..a797d050c 100644 --- a/docs/developers/event-logs/execution-events.mdx +++ b/docs/developers/event-logs/execution-events.mdx @@ -1,6 +1,7 @@ --- id: execution-events title: Execution Events +description: "Learn about execution events in MultiversX." --- import Tabs from '@theme/Tabs'; diff --git a/docs/developers/event-logs/system-delegation-events.md b/docs/developers/event-logs/system-delegation-events.md index fa3735201..4449c9dd3 100644 --- a/docs/developers/event-logs/system-delegation-events.md +++ b/docs/developers/event-logs/system-delegation-events.md @@ -1,5 +1,5 @@ --- id: system-delegation-events title: System Delegation Events +description: "Learn about system delegation events in MultiversX." --- - diff --git a/docs/developers/gas-and-fees/egld-transfers.md b/docs/developers/gas-and-fees/egld-transfers.md index a34264d61..4fcc0b8a8 100644 --- a/docs/developers/gas-and-fees/egld-transfers.md +++ b/docs/developers/gas-and-fees/egld-transfers.md @@ -1,6 +1,7 @@ --- id: egld-transfers title: EGLD transfers (move balance transactions) +description: "Learn about egld transfers (move balance transactions) in MultiversX." --- [comment]: # (mx-context-auto) diff --git a/docs/developers/gas-and-fees/overview.md b/docs/developers/gas-and-fees/overview.md index 6d44df10d..1a2864c76 100644 --- a/docs/developers/gas-and-fees/overview.md +++ b/docs/developers/gas-and-fees/overview.md @@ -1,7 +1,9 @@ --- id: overview title: Overview +description: "Learn about overview in MultiversX." --- + [comment]: # (mx-abstract) The processing cost of a MultiversX transaction is determined by its gas limit, the actual gas consumption, and the gas price per gas unit, leading to a processing fee in EGLD that may be subject to a gas refund in certain cases. diff --git a/docs/developers/gas-and-fees/system-smart-contracts.md b/docs/developers/gas-and-fees/system-smart-contracts.md index 25a709d9d..952a8bb7b 100644 --- a/docs/developers/gas-and-fees/system-smart-contracts.md +++ b/docs/developers/gas-and-fees/system-smart-contracts.md @@ -1,6 +1,7 @@ --- id: system-smart-contracts title: System Smart Contracts +description: "Learn about system smart contracts in MultiversX." --- For transactions which call System Smart Contracts, the **actual gas cost** of processing contains the two previously mentioned cost components - and they are easily computable. diff --git a/docs/developers/gas-and-fees/user-defined-smart-contracts.md b/docs/developers/gas-and-fees/user-defined-smart-contracts.md index 36a6527aa..655803a9f 100644 --- a/docs/developers/gas-and-fees/user-defined-smart-contracts.md +++ b/docs/developers/gas-and-fees/user-defined-smart-contracts.md @@ -1,7 +1,9 @@ --- id: user-defined-smart-contracts title: User-defined Smart Contracts +description: "Learn about user-defined smart contracts in MultiversX." --- + [comment]: # (mx-abstract) For user-defined Smart Contract deployments and function calls, the **actual gas consumption** of processing contains both of the previously mentioned cost components - though, while the **value movement and data handling** component is easily computable (using the previously depicted formula), the **contract execution** component is hard to determine precisely _a priori_. Therefore, for this component we have to rely on _simulations_ and _estimations_. diff --git a/docs/developers/guard-accounts.md b/docs/developers/guard-accounts.md index 33130960a..0bd152a1f 100644 --- a/docs/developers/guard-accounts.md +++ b/docs/developers/guard-accounts.md @@ -1,6 +1,7 @@ --- id: guard-accounts title: Guard accounts +description: "Learn about guard accounts in MultiversX." --- [comment]: # (mx-abstract) diff --git a/docs/developers/guidelines/react-development.md b/docs/developers/guidelines/react-development.md index e542f5a7f..ac3c42748 100644 --- a/docs/developers/guidelines/react-development.md +++ b/docs/developers/guidelines/react-development.md @@ -1,6 +1,7 @@ --- id: react-development title: React Development +description: "Learn about react development in MultiversX." --- [comment]: # (mx-abstract) diff --git a/docs/developers/meta/interactor/interactors-example.md b/docs/developers/meta/interactor/interactors-example.md index a0ee35037..727099af2 100644 --- a/docs/developers/meta/interactor/interactors-example.md +++ b/docs/developers/meta/interactor/interactors-example.md @@ -1,6 +1,7 @@ --- id: interactors-example title: Interactors Example +description: "Learn about interactors example in MultiversX." --- [comment]: # (mx-abstract) diff --git a/docs/developers/meta/interactor/interactors-overview.md b/docs/developers/meta/interactor/interactors-overview.md index 0bc278123..7dcee6f79 100644 --- a/docs/developers/meta/interactor/interactors-overview.md +++ b/docs/developers/meta/interactor/interactors-overview.md @@ -1,6 +1,7 @@ --- id: interactors-overview title: Interactors Overview +description: "Learn about interactors in MultiversX." --- [comment]: # (mx-abstract) diff --git a/docs/developers/meta/rust-version.md b/docs/developers/meta/rust-version.md index 050d3bac1..c7686e896 100644 --- a/docs/developers/meta/rust-version.md +++ b/docs/developers/meta/rust-version.md @@ -1,6 +1,7 @@ --- id: rust-version title: Rust Version +description: "Learn about rust version in MultiversX." --- [comment]: # (mx-abstract) diff --git a/docs/developers/meta/sc-allocator.md b/docs/developers/meta/sc-allocator.md index afad109f5..7aaa59d3c 100644 --- a/docs/developers/meta/sc-allocator.md +++ b/docs/developers/meta/sc-allocator.md @@ -1,6 +1,7 @@ --- id: sc-allocator title: Memory allocation +description: "Learn about memory allocation in MultiversX." --- [comment]: # (mx-abstract) diff --git a/docs/developers/meta/sc-build-reference.md b/docs/developers/meta/sc-build-reference.md index 543d2775b..0f3fd688e 100644 --- a/docs/developers/meta/sc-build-reference.md +++ b/docs/developers/meta/sc-build-reference.md @@ -1,6 +1,7 @@ --- id: sc-build-reference title: Build Reference +description: "Learn about build reference in MultiversX." --- [comment]: # (mx-abstract) diff --git a/docs/developers/meta/sc-config.md b/docs/developers/meta/sc-config.md index 2ddbbfaa7..2b8ef338b 100644 --- a/docs/developers/meta/sc-config.md +++ b/docs/developers/meta/sc-config.md @@ -1,6 +1,7 @@ --- id: sc-config title: Configuration +description: "Learn about configuration in MultiversX." --- [comment]: # (mx-abstract) diff --git a/docs/developers/meta/sc-meta-cli.md b/docs/developers/meta/sc-meta-cli.md index 3fc799295..2143dd74e 100644 --- a/docs/developers/meta/sc-meta-cli.md +++ b/docs/developers/meta/sc-meta-cli.md @@ -1,6 +1,7 @@ --- id: sc-meta-cli title: CLI +description: "Learn about cli in MultiversX." --- [comment]: # (mx-context-auto) diff --git a/docs/developers/meta/sc-meta.md b/docs/developers/meta/sc-meta.md index 867581457..d3bb05d34 100644 --- a/docs/developers/meta/sc-meta.md +++ b/docs/developers/meta/sc-meta.md @@ -1,6 +1,7 @@ --- id: sc-meta title: Tooling Overview +description: "Learn about tooling in MultiversX." --- [comment]: # (mx-abstract) diff --git a/docs/developers/nft-tokens.md b/docs/developers/nft-tokens.md index 69d7731bd..2243c0aae 100644 --- a/docs/developers/nft-tokens.md +++ b/docs/developers/nft-tokens.md @@ -1,6 +1,7 @@ --- id: nft-tokens title: NFT tokens +description: "Learn about nft tokens in MultiversX." --- The Semi-Fungible / Non-Fungible Tokens documentation has been moved [here](/tokens/nft-tokens). diff --git a/docs/developers/overview.md b/docs/developers/overview.md index 82707bff5..d12a4a980 100644 --- a/docs/developers/overview.md +++ b/docs/developers/overview.md @@ -1,6 +1,7 @@ --- id: overview title: Developers - Overview +description: "Learn about developers - in MultiversX." --- [comment]: # (mx-abstract) diff --git a/docs/developers/relayed-transactions.md b/docs/developers/relayed-transactions.md index 6bdbbbc59..cd7b91459 100644 --- a/docs/developers/relayed-transactions.md +++ b/docs/developers/relayed-transactions.md @@ -1,7 +1,9 @@ --- id: relayed-transactions title: Relayed Transactions +description: "Learn about relayed transactions in MultiversX." --- + [comment]: # (mx-abstract) On this page, you will find comprehensive information on all aspects of relayed transactions. diff --git a/docs/developers/reproducible-contract-builds.md b/docs/developers/reproducible-contract-builds.md index 0fba270dc..5297567ab 100644 --- a/docs/developers/reproducible-contract-builds.md +++ b/docs/developers/reproducible-contract-builds.md @@ -1,7 +1,9 @@ --- id: reproducible-contract-builds title: Reproducible Builds +description: "Learn about reproducible builds in MultiversX." --- + [comment]: # (mx-abstract) This page will guide you through the process of supporting [reproducible contract builds](https://en.wikipedia.org/wiki/Reproducible_builds), by leveraging Docker and a set of [_frozen_ Docker images available on DockerHub](https://hub.docker.com/r/multiversx/sdk-rust-contract-builder/tags). diff --git a/docs/developers/sc-calls-format.md b/docs/developers/sc-calls-format.md index 288bc16ef..83170c6f4 100644 --- a/docs/developers/sc-calls-format.md +++ b/docs/developers/sc-calls-format.md @@ -1,7 +1,9 @@ --- id: sc-calls-format title: Smart Contract Calls Data Format +description: "Learn about smart contract calls data format in MultiversX." --- + [comment]: # (mx-abstract) This page provides an in-depth examination of the Smart Contract Calls Data Format. diff --git a/docs/developers/setup-local-testnet-advanced.md b/docs/developers/setup-local-testnet-advanced.md index fcae73419..d60d9ab84 100644 --- a/docs/developers/setup-local-testnet-advanced.md +++ b/docs/developers/setup-local-testnet-advanced.md @@ -1,7 +1,9 @@ --- id: setup-local-testnet-advanced title: Set up a Localnet (raw) +description: "Learn about set up a localnet (raw) in MultiversX." --- + [comment]: # (mx-abstract) How to set up a local MultiversX Testnet on a workstation. diff --git a/docs/developers/setup-local-testnet.md b/docs/developers/setup-local-testnet.md index 1c05ed5b1..46f70f9c8 100644 --- a/docs/developers/setup-local-testnet.md +++ b/docs/developers/setup-local-testnet.md @@ -1,7 +1,9 @@ --- id: setup-local-testnet title: Set up a Localnet (mxpy) +description: "Learn about set up a localnet (mxpy) in MultiversX." --- + [comment]: # (mx-abstract) This guide describes how to set up a local mini-testnet - also known as **localnet** - using **mxpy**. The purpose of a localnet is to allow developers experiment with and test their Smart Contracts, in addition to writing unit and integration tests. diff --git a/docs/developers/signing-transactions/signing-programmatically.md b/docs/developers/signing-transactions/signing-programmatically.md index 475f9a66c..68b4c9ae1 100644 --- a/docs/developers/signing-transactions/signing-programmatically.md +++ b/docs/developers/signing-transactions/signing-programmatically.md @@ -1,6 +1,7 @@ --- id: signing-programmatically title: Signing programmatically +description: "Learn about signing programmatically in MultiversX." --- In order to sign a transaction (or a message) using one of the SDKs, follow: diff --git a/docs/developers/signing-transactions/signing-transactions.md b/docs/developers/signing-transactions/signing-transactions.md index ca477a7de..90786ab61 100644 --- a/docs/developers/signing-transactions/signing-transactions.md +++ b/docs/developers/signing-transactions/signing-transactions.md @@ -1,7 +1,9 @@ --- id: signing-transactions title: Signing Transactions +description: "Learn about signing transactions in MultiversX." --- + [comment]: # (mx-abstract) By reading this page you will find out how to serialize and sign the Transaction payload. diff --git a/docs/developers/signing-transactions/tools-for-signing.md b/docs/developers/signing-transactions/tools-for-signing.md index 9bd989848..c62ca35ab 100644 --- a/docs/developers/signing-transactions/tools-for-signing.md +++ b/docs/developers/signing-transactions/tools-for-signing.md @@ -1,7 +1,9 @@ --- id: tools-for-signing title: Tools for signing +description: "Learn about tools for signing in MultiversX." --- + [comment]: # (mx-abstract) In order to sign a transaction without actually dispatching it, several tools can be used. One of the most popular ones is [mxpy](/sdk-and-tools/sdk-py). diff --git a/docs/developers/smart-contracts.md b/docs/developers/smart-contracts.md index c454c0252..18df97635 100644 --- a/docs/developers/smart-contracts.md +++ b/docs/developers/smart-contracts.md @@ -1,6 +1,7 @@ --- id: smart-contracts title: MultiversX Smart Contracts +description: "Learn about multiversx smart contracts in MultiversX." --- [comment]: # (mx-abstract) diff --git a/docs/developers/testing/rust/mandos-trace.md b/docs/developers/testing/rust/mandos-trace.md index 6782dc49b..87774cf7c 100644 --- a/docs/developers/testing/rust/mandos-trace.md +++ b/docs/developers/testing/rust/mandos-trace.md @@ -1,6 +1,7 @@ --- id: mandos-trace title: Mandos traces +description: "Learn about mandos traces in MultiversX." --- [comment]: # "mx-abstract" diff --git a/docs/developers/testing/rust/sc-blackbox-calls.md b/docs/developers/testing/rust/sc-blackbox-calls.md index fea33acd1..d0dc0bd9f 100644 --- a/docs/developers/testing/rust/sc-blackbox-calls.md +++ b/docs/developers/testing/rust/sc-blackbox-calls.md @@ -1,6 +1,7 @@ --- id: sc-blackbox-calls title: Blackbox calls +description: "Learn about blackbox calls in MultiversX." --- [comment]: # "mx-abstract" diff --git a/docs/developers/testing/rust/sc-blackbox-example.md b/docs/developers/testing/rust/sc-blackbox-example.md index 0392f3642..f85d8ce37 100644 --- a/docs/developers/testing/rust/sc-blackbox-example.md +++ b/docs/developers/testing/rust/sc-blackbox-example.md @@ -1,6 +1,7 @@ --- id: sc-blackbox-example title: Blackbox example +description: "Learn about blackbox example in MultiversX." --- [comment]: # (mx-context-auto) diff --git a/docs/developers/testing/rust/sc-test-overview.md b/docs/developers/testing/rust/sc-test-overview.md index 9fde4cea5..773a40d53 100644 --- a/docs/developers/testing/rust/sc-test-overview.md +++ b/docs/developers/testing/rust/sc-test-overview.md @@ -1,6 +1,7 @@ --- id: sc-test-overview title: Overview +description: "Learn about sc test overview in MultiversX." --- [comment]: # "mx-abstract" diff --git a/docs/developers/testing/rust/sc-test-setup.md b/docs/developers/testing/rust/sc-test-setup.md index babe3e1e4..f801309f9 100644 --- a/docs/developers/testing/rust/sc-test-setup.md +++ b/docs/developers/testing/rust/sc-test-setup.md @@ -1,6 +1,7 @@ --- id: sc-test setup title: Test setup +description: "Learn about test setup in MultiversX." --- [comment]: # "mx-abstract" diff --git a/docs/developers/testing/rust/whitebox-legacy-functions-reference.md b/docs/developers/testing/rust/whitebox-legacy-functions-reference.md index 4ecf8ddd8..18b14fbfc 100644 --- a/docs/developers/testing/rust/whitebox-legacy-functions-reference.md +++ b/docs/developers/testing/rust/whitebox-legacy-functions-reference.md @@ -1,6 +1,7 @@ --- id: whitebox-legacy-functions-reference title: Whitebox Functions Reference +description: "Learn about whitebox functions reference in MultiversX." --- [comment]: # (mx-abstract) diff --git a/docs/developers/testing/rust/whitebox-legacy.md b/docs/developers/testing/rust/whitebox-legacy.md index e55d9d379..bc102e469 100644 --- a/docs/developers/testing/rust/whitebox-legacy.md +++ b/docs/developers/testing/rust/whitebox-legacy.md @@ -1,6 +1,7 @@ --- id: whitebox-legacy title: Whitebox Framework +description: "Learn about whitebox framework in MultiversX." --- [comment]: # (mx-abstract) diff --git a/docs/developers/testing/sc-debugging.md b/docs/developers/testing/sc-debugging.md index 84f3ced93..d3335982d 100644 --- a/docs/developers/testing/sc-debugging.md +++ b/docs/developers/testing/sc-debugging.md @@ -1,6 +1,7 @@ --- id: sc-debugging title: Smart Contract Debugging +description: "Learn about smart contract debugging in MultiversX." --- [comment]: # (mx-abstract) diff --git a/docs/developers/testing/scenario/concept.md b/docs/developers/testing/scenario/concept.md index 244d19934..81c0e3d21 100644 --- a/docs/developers/testing/scenario/concept.md +++ b/docs/developers/testing/scenario/concept.md @@ -1,6 +1,7 @@ --- id: concept title: Concept +description: "Learn about concept in MultiversX." --- [comment]: # (mx-context-auto) diff --git a/docs/developers/testing/scenario/generating-scenarios.md b/docs/developers/testing/scenario/generating-scenarios.md index 6b92f077f..decfe508e 100644 --- a/docs/developers/testing/scenario/generating-scenarios.md +++ b/docs/developers/testing/scenario/generating-scenarios.md @@ -1,7 +1,9 @@ --- id: generating-scenarios title: Generating scenarios +description: "Learn about generating scenarios in MultiversX." --- + [comment]: # (mx-abstract) There are currently several ways to generate scenarios. diff --git a/docs/developers/testing/scenario/running-scenarios.md b/docs/developers/testing/scenario/running-scenarios.md index 5d5042269..ec7b2bac7 100644 --- a/docs/developers/testing/scenario/running-scenarios.md +++ b/docs/developers/testing/scenario/running-scenarios.md @@ -1,7 +1,9 @@ --- id: running-scenarios title: Running scenarios +description: "Learn about running scenarios in MultiversX." --- + [comment]: # (mx-abstract) Most of the MultiversX smart contract testing infrastructure is built with scenarios in mind, so there are lots of ways to execute them. diff --git a/docs/developers/testing/scenario/structure-json.md b/docs/developers/testing/scenario/structure-json.md index b9631fe80..3ab43923d 100644 --- a/docs/developers/testing/scenario/structure-json.md +++ b/docs/developers/testing/scenario/structure-json.md @@ -1,7 +1,9 @@ --- id: structure-json title: JSON Structure +description: "Learn about json structure in MultiversX." --- + [comment]: # (mx-abstract) Scenario JSON files are designed to be readable by humans too. This is why you will see diff --git a/docs/developers/testing/scenario/values-complex.md b/docs/developers/testing/scenario/values-complex.md index 13ee4d14e..881ce20fe 100644 --- a/docs/developers/testing/scenario/values-complex.md +++ b/docs/developers/testing/scenario/values-complex.md @@ -1,7 +1,9 @@ --- id: values-complex title: Scenario Complex Values +description: "Learn about scenario complex values in MultiversX." --- + [comment]: # (mx-abstract) We already covered representations of simple types [here](/developers/testing/scenario/values-simple). This is enough for arguments of types like `usize`, `BigUint` or `&[u8]`, but we need to also somehow specify complex types like custom structs or lists of items. diff --git a/docs/developers/testing/scenario/values-simple.md b/docs/developers/testing/scenario/values-simple.md index 4fe537c5f..17acdb7f7 100644 --- a/docs/developers/testing/scenario/values-simple.md +++ b/docs/developers/testing/scenario/values-simple.md @@ -1,7 +1,9 @@ --- id: values-simple title: Scenario Simple Values +description: "Learn about scenario simple values in MultiversX." --- + [comment]: # (mx-abstract) We went through the structure of a scenario, and you might have noticed that in a lot of places values are expressed in diverse ways. diff --git a/docs/developers/testing/testing-in-go.md b/docs/developers/testing/testing-in-go.md index b193e8ccf..00a0392e6 100644 --- a/docs/developers/testing/testing-in-go.md +++ b/docs/developers/testing/testing-in-go.md @@ -1,6 +1,7 @@ --- id: testing-in-go title: Testing in Go +description: "Learn about testing in go in MultiversX." --- [comment]: # (mx-abstract) diff --git a/docs/developers/testing/testing-overview.md b/docs/developers/testing/testing-overview.md index e724b260f..965ab5a39 100644 --- a/docs/developers/testing/testing-overview.md +++ b/docs/developers/testing/testing-overview.md @@ -1,6 +1,7 @@ --- id: testing-overview title: Testing Overview +description: "Learn about testing in MultiversX." --- [comment]: # (mx-abstract) diff --git a/docs/developers/toolchain-setup.md b/docs/developers/toolchain-setup.md index dee54d32e..5e1bddd39 100644 --- a/docs/developers/toolchain-setup.md +++ b/docs/developers/toolchain-setup.md @@ -1,6 +1,7 @@ --- id: toolchain-setup title: Toolchain Setup +description: "Learn about toolchain setup in MultiversX." --- [comment]: # (mx-context-auto) @@ -86,12 +87,12 @@ Default host: x86_64-unknown-linux-gnu rustup home: /home/ubuntu/.rustup installed toolchains --------------------- +--- +----------------- stable-x86_64-unknown-linux-gnu (default) [...] -active toolchain ----------------- +active toolchain---------------- name: stable-x86_64-unknown-linux-gnu installed targets: wasm32-unknown-unknown diff --git a/docs/developers/transactions/tx-data.md b/docs/developers/transactions/tx-data.md index a41cee64f..d717898ee 100644 --- a/docs/developers/transactions/tx-data.md +++ b/docs/developers/transactions/tx-data.md @@ -1,6 +1,7 @@ --- id: tx-data title: Payload (data) +description: "Learn about payload (data) in MultiversX." --- [comment]: # (mx-abstract) diff --git a/docs/developers/transactions/tx-env.md b/docs/developers/transactions/tx-env.md index 4389d6f10..b2479999a 100644 --- a/docs/developers/transactions/tx-env.md +++ b/docs/developers/transactions/tx-env.md @@ -1,6 +1,7 @@ --- id: tx-env title: Environments +description: "Learn about environments in MultiversX." --- [comment]: # (mx-abstract) diff --git a/docs/developers/transactions/tx-examples.md b/docs/developers/transactions/tx-examples.md index 1bf565957..80b0223e1 100644 --- a/docs/developers/transactions/tx-examples.md +++ b/docs/developers/transactions/tx-examples.md @@ -1,6 +1,7 @@ --- id: tx-examples title: Examples (TODO - not currently in sidebar) +description: "Learn about examples (todo - not currently in sidebar) in MultiversX." --- [comment]: # (mx-abstract) diff --git a/docs/developers/transactions/tx-from.md b/docs/developers/transactions/tx-from.md index 50dd3f9b5..c87022d5d 100644 --- a/docs/developers/transactions/tx-from.md +++ b/docs/developers/transactions/tx-from.md @@ -1,6 +1,7 @@ --- id: tx-from title: Sender +description: "Learn about sender in MultiversX." --- [comment]: # (mx-abstract) diff --git a/docs/developers/transactions/tx-gas.md b/docs/developers/transactions/tx-gas.md index 671a43280..fa579bfc0 100644 --- a/docs/developers/transactions/tx-gas.md +++ b/docs/developers/transactions/tx-gas.md @@ -1,6 +1,7 @@ --- id: tx-gas title: Gas +description: "Learn about gas in MultiversX." --- [comment]: # (mx-abstract) @@ -81,4 +82,3 @@ fn deploy_from_source( .sync_call() } ``` - diff --git a/docs/developers/transactions/tx-impl-details.md b/docs/developers/transactions/tx-impl-details.md index bdbc47358..4c973952a 100644 --- a/docs/developers/transactions/tx-impl-details.md +++ b/docs/developers/transactions/tx-impl-details.md @@ -1,6 +1,7 @@ --- id: tx-impl-details title: Implementation details (TODO) +description: "Learn about implementation details (todo) in MultiversX." --- [comment]: # (mx-abstract) diff --git a/docs/developers/transactions/tx-legacy-calls.md b/docs/developers/transactions/tx-legacy-calls.md index 082c562ec..6d2efa1d4 100644 --- a/docs/developers/transactions/tx-legacy-calls.md +++ b/docs/developers/transactions/tx-legacy-calls.md @@ -1,9 +1,9 @@ --- id: tx-legacy-calls title: Legacy SC calls +description: "Learn about legacy sc calls in MultiversX." --- - [comment]: # (mx-abstract) ## Deprecated, kept for backwards compatibility @@ -817,4 +817,3 @@ Note the `.contract(...)` method call. Just like deploy, upgrade also comes in two flavors: - `.upgrade_contract(code, code_metadata)` - upgrades the target contract to the new code and sets the new code metadata. - `.upgrade_from_source(source_address, code_metadata)` - updates the target contract with the same code as the code of the contract at `source_address`. - diff --git a/docs/developers/transactions/tx-migration.md b/docs/developers/transactions/tx-migration.md index ac1c88bef..10ee27883 100644 --- a/docs/developers/transactions/tx-migration.md +++ b/docs/developers/transactions/tx-migration.md @@ -1,6 +1,7 @@ --- id: tx-migration title: Migration +description: "Learn about migration in MultiversX." --- [comment]: # (mx-abstract) diff --git a/docs/developers/transactions/tx-overview.md b/docs/developers/transactions/tx-overview.md index f419b17bb..7c28b738c 100644 --- a/docs/developers/transactions/tx-overview.md +++ b/docs/developers/transactions/tx-overview.md @@ -1,6 +1,7 @@ --- id: tx-overview title: Transaction Overview +description: "Learn about transaction in MultiversX." --- [comment]: # (mx-abstract) diff --git a/docs/developers/transactions/tx-payment.md b/docs/developers/transactions/tx-payment.md index 3eb0a6e68..d07ec04b4 100644 --- a/docs/developers/transactions/tx-payment.md +++ b/docs/developers/transactions/tx-payment.md @@ -1,6 +1,7 @@ --- id: tx-payment title: Payments +description: "Learn about payments in MultiversX." --- [comment]: # (mx-abstract) diff --git a/docs/developers/transactions/tx-proxies.md b/docs/developers/transactions/tx-proxies.md index 275e5d536..b927af3f0 100644 --- a/docs/developers/transactions/tx-proxies.md +++ b/docs/developers/transactions/tx-proxies.md @@ -1,6 +1,7 @@ --- id: tx-proxies title: Proxies +description: "Learn about proxies in MultiversX." --- [comment]: # (mx-abstract) diff --git a/docs/developers/transactions/tx-result-handlers.md b/docs/developers/transactions/tx-result-handlers.md index 17323eafe..966dcb096 100644 --- a/docs/developers/transactions/tx-result-handlers.md +++ b/docs/developers/transactions/tx-result-handlers.md @@ -1,6 +1,7 @@ --- id: tx-result-handlers title: Result Handlers +description: "Learn about result handlers in MultiversX." --- [comment]: # (mx-abstract) diff --git a/docs/developers/transactions/tx-run.md b/docs/developers/transactions/tx-run.md index 70f3414c9..5713b26ab 100644 --- a/docs/developers/transactions/tx-run.md +++ b/docs/developers/transactions/tx-run.md @@ -1,6 +1,7 @@ --- id: tx-run title: Run transactions +description: "Learn about run transactions in MultiversX." --- [comment]: # (mx-abstract) diff --git a/docs/developers/transactions/tx-to.md b/docs/developers/transactions/tx-to.md index 2be30fcbe..3d00b32a4 100644 --- a/docs/developers/transactions/tx-to.md +++ b/docs/developers/transactions/tx-to.md @@ -1,6 +1,7 @@ --- id: tx-to title: Receiver +description: "Learn about receiver in MultiversX." --- [comment]: # "mx-abstract" diff --git a/docs/developers/tutorials/chain-simulator-adder.md b/docs/developers/tutorials/chain-simulator-adder.md index f1a4ad532..afd2e354d 100644 --- a/docs/developers/tutorials/chain-simulator-adder.md +++ b/docs/developers/tutorials/chain-simulator-adder.md @@ -1,6 +1,7 @@ --- id: chain-simulator-adder title: Chain Simulator in Adder - SpaceCraft interactors +description: "Tutorial: Chain Simulator in Adder - SpaceCraft interactors" --- [comment]: # (mx-abstract) diff --git a/docs/developers/tutorials/crowdfunding/crowdfunding-p1.md b/docs/developers/tutorials/crowdfunding/crowdfunding-p1.md index 98005d76d..0ba19bc69 100644 --- a/docs/developers/tutorials/crowdfunding/crowdfunding-p1.md +++ b/docs/developers/tutorials/crowdfunding/crowdfunding-p1.md @@ -1,6 +1,7 @@ --- id: crowdfunding-p1 title: Setup & Basics +description: "Tutorial: Setup & Basics" --- [comment]: # (mx-abstract) diff --git a/docs/developers/tutorials/crowdfunding/crowdfunding-p2.md b/docs/developers/tutorials/crowdfunding/crowdfunding-p2.md index a538c8901..d785241af 100644 --- a/docs/developers/tutorials/crowdfunding/crowdfunding-p2.md +++ b/docs/developers/tutorials/crowdfunding/crowdfunding-p2.md @@ -1,7 +1,9 @@ --- id: crowdfunding-p2 title: Core Logic +description: "Tutorial: Core Logic" --- + [comment]: # (mx-abstract) Define contract arguments, handle storage, process payments, define new types, write better tests diff --git a/docs/developers/tutorials/crowdfunding/crowdfunding-p3.md b/docs/developers/tutorials/crowdfunding/crowdfunding-p3.md index 8500e869e..8fbe8b0d8 100644 --- a/docs/developers/tutorials/crowdfunding/crowdfunding-p3.md +++ b/docs/developers/tutorials/crowdfunding/crowdfunding-p3.md @@ -1,6 +1,7 @@ --- id: crowdfunding-p3 title: Extend to Any Token +description: "Tutorial: Extend to Any Token" --- [comment]: # (mx-abstract) diff --git a/docs/developers/tutorials/crowdfunding/final-code.md b/docs/developers/tutorials/crowdfunding/final-code.md index a76d80ea3..330b77536 100644 --- a/docs/developers/tutorials/crowdfunding/final-code.md +++ b/docs/developers/tutorials/crowdfunding/final-code.md @@ -1,6 +1,7 @@ --- id: final-code title: Final Code +description: "Tutorial: Final Code" --- [comment]: # (mx-abstract) diff --git a/docs/developers/tutorials/dex-walkthrough.md b/docs/developers/tutorials/dex-walkthrough.md index 69ffc3d08..0f9af0524 100644 --- a/docs/developers/tutorials/dex-walkthrough.md +++ b/docs/developers/tutorials/dex-walkthrough.md @@ -1,6 +1,7 @@ --- id: dex-walkthrough title: DEX Walkthrough +description: "Tutorial: DEX Walkthrough" --- [comment]: # (mx-abstract) diff --git a/docs/developers/tutorials/energy-dao.md b/docs/developers/tutorials/energy-dao.md index 3cbab2965..cd5eaab63 100644 --- a/docs/developers/tutorials/energy-dao.md +++ b/docs/developers/tutorials/energy-dao.md @@ -1,6 +1,7 @@ --- id: energy-dao title: Energy DAO SC tutorial +description: "Tutorial: Energy DAO SC tutorial" --- [comment]: # (mx-abstract) diff --git a/docs/developers/tutorials/eth-to-mvx.md b/docs/developers/tutorials/eth-to-mvx.md index 1448d1b87..484caa501 100644 --- a/docs/developers/tutorials/eth-to-mvx.md +++ b/docs/developers/tutorials/eth-to-mvx.md @@ -1,6 +1,7 @@ --- id: eth-to-mvx title: Ethereum to MultiversX migration guide +description: "Tutorial: Ethereum to MultiversX migration guide" --- [comment]: # (mx-abstract) diff --git a/docs/developers/tutorials/interactors-guide.md b/docs/developers/tutorials/interactors-guide.md index 43d1b9a4c..08678b232 100644 --- a/docs/developers/tutorials/interactors-guide.md +++ b/docs/developers/tutorials/interactors-guide.md @@ -1,6 +1,7 @@ --- id: interactors-guide title: Deploy a SC in 5 minutes - SpaceCraft interactors +description: "Tutorial: Deploy a SC in 5 minutes - SpaceCraft interactors" --- [comment]: # (mx-abstract) @@ -270,7 +271,9 @@ test integration_test ... ok successes: ----- integration_test stdout ---- +--- + +- integration_test stdout ---- sender's recalled nonce: 1720 -- tx nonce: 1720 sc deploy tx hash: ca6e69c18acd73b20bfd21142b45be1b530ecbec89d1eb9c374b93f7681dbc38 diff --git a/docs/developers/tutorials/staking-contract.md b/docs/developers/tutorials/staking-contract.md index 262f5694d..2079b3d7d 100644 --- a/docs/developers/tutorials/staking-contract.md +++ b/docs/developers/tutorials/staking-contract.md @@ -1,6 +1,7 @@ --- id: staking-contract title: Staking smart contract +description: "Tutorial: Staking smart contract" --- [comment]: # (mx-abstract) diff --git a/docs/developers/tutorials/wallet-connect-v2-migration.md b/docs/developers/tutorials/wallet-connect-v2-migration.md index 7cf940f8e..712ad7f1e 100644 --- a/docs/developers/tutorials/wallet-connect-v2-migration.md +++ b/docs/developers/tutorials/wallet-connect-v2-migration.md @@ -1,6 +1,7 @@ --- id: wallet-connect-v2-migration title: WalletConnect 2.0 Migration +description: "Tutorial: WalletConnect 2.0 Migration" --- [comment]: # (mx-context-auto) @@ -13,7 +14,9 @@ WalletConnect 2.0 is already integrated, only not enabled by default. Follow [these steps](/sdk-and-tools/sdk-dapp/#walletconnect-setup) to generate and add a `walletConnectV2ProjectId` --------------- +--- + +----------- [comment]: # (mx-context-auto) diff --git a/docs/developers/tutorials/your-first-dapp.md b/docs/developers/tutorials/your-first-dapp.md index 62b2f1271..2778f36bc 100644 --- a/docs/developers/tutorials/your-first-dapp.md +++ b/docs/developers/tutorials/your-first-dapp.md @@ -1,6 +1,7 @@ --- id: your-first-dapp title: Build a dApp in 15 minutes +description: "Tutorial: Build a dApp in 15 minutes" --- [comment]: # (mx-abstract) diff --git a/docs/developers/tutorials/your-first-microservice.md b/docs/developers/tutorials/your-first-microservice.md index eb9d09416..b877b75bf 100644 --- a/docs/developers/tutorials/your-first-microservice.md +++ b/docs/developers/tutorials/your-first-microservice.md @@ -1,6 +1,7 @@ --- id: your-first-microservice title: Build a Microservice for your dApp +description: "Tutorial: Build a Microservice for your dApp" --- [comment]: # (mx-abstract) diff --git a/docs/economics/staking-providers-apr.md b/docs/economics/staking-providers-apr.md index e69071260..8c4991560 100644 --- a/docs/economics/staking-providers-apr.md +++ b/docs/economics/staking-providers-apr.md @@ -1,6 +1,7 @@ --- id: staking-providers-apr title: Staking Providers APR +description: "Learn about staking providers apr in MultiversX." --- [comment]: # (mx-abstract) diff --git a/docs/governance/governance-interaction.md b/docs/governance/governance-interaction.md index 27a01e3ac..7a3a8fecc 100644 --- a/docs/governance/governance-interaction.md +++ b/docs/governance/governance-interaction.md @@ -1,6 +1,7 @@ --- id: governance-interaction title: Governance interaction +description: "Interact with the governance smart contract: submit proposals, vote and query status via VM endpoints and transactions." --- [comment]: # (mx-context-auto) diff --git a/docs/governance/overview.md b/docs/governance/overview.md index df76e7e4f..8ce855c63 100644 --- a/docs/governance/overview.md +++ b/docs/governance/overview.md @@ -1,6 +1,7 @@ --- id: overview title: Governance - Overview +description: "Overview of the on‑chain governance module: proposals, voting, execution and release context." --- [comment]: # (mx-abstract) diff --git a/docs/integrators/accounts-management.md b/docs/integrators/accounts-management.md index 12dc7dc30..e31f974ab 100644 --- a/docs/integrators/accounts-management.md +++ b/docs/integrators/accounts-management.md @@ -1,6 +1,7 @@ --- id: accounts-management title: Accounts Management +description: "Learn about MultiversX Integrators accounts management." --- [comment]: # (mx-abstract) diff --git a/docs/integrators/advanced-observer-settings.md b/docs/integrators/advanced-observer-settings.md index 9d5e1e28d..74b2e8db7 100644 --- a/docs/integrators/advanced-observer-settings.md +++ b/docs/integrators/advanced-observer-settings.md @@ -1,6 +1,7 @@ --- id: advanced-observer-settings title: Advanced Observer Settings +description: "Learn about MultiversX Integrators advanced observer settings." --- [comment]: # (mx-abstract) diff --git a/docs/integrators/creating-transactions.md b/docs/integrators/creating-transactions.md index c72d74fb2..2a6a54ccd 100644 --- a/docs/integrators/creating-transactions.md +++ b/docs/integrators/creating-transactions.md @@ -1,6 +1,7 @@ --- id: creating-transactions title: Creating Transactions +description: "Learn about MultiversX Integrators creating transactions." --- [comment]: # (mx-abstract) diff --git a/docs/integrators/deep-history-squad.md b/docs/integrators/deep-history-squad.md index f3cd74b24..18a8ba6c3 100644 --- a/docs/integrators/deep-history-squad.md +++ b/docs/integrators/deep-history-squad.md @@ -1,6 +1,7 @@ --- id: deep-history-squad title: Deep History Squad +description: "Access deep historical blockchain data via the Deep History Squad: use cases, endpoints and integration guidance." --- [comment]: # (mx-abstract) @@ -363,4 +364,3 @@ The squad and the proxy can be started using the command: Alternatively, you can set up a squad using any other known approach, **but make sure to apply the proper `operation-mode`** described in the section [**Observer installation and configuration**](#observer-installation-and-configuration). **Congratulations!** You've set up a deep-history observing squad; the gateway should be ready to resolve historical account (state) queries :rocket: - diff --git a/docs/integrators/egld-integration-guide.md b/docs/integrators/egld-integration-guide.md index de26e99b4..af26eca2d 100644 --- a/docs/integrators/egld-integration-guide.md +++ b/docs/integrators/egld-integration-guide.md @@ -1,6 +1,7 @@ --- id: egld-integration-guide title: EGLD integration guide +description: "Integrate EGLD into exchanges and wallets: addresses, transactions, fees, confirmations and best practices." --- [comment]: # (mx-abstract) diff --git a/docs/integrators/esdt-tokens-integration-guide.md b/docs/integrators/esdt-tokens-integration-guide.md index 970efe130..fbf46682a 100644 --- a/docs/integrators/esdt-tokens-integration-guide.md +++ b/docs/integrators/esdt-tokens-integration-guide.md @@ -1,6 +1,7 @@ --- id: esdt-tokens-integration-guide title: ESDT tokens integration guide +description: "Learn about MultiversX Integrators esdt tokens integration guide." --- [comment]: # (mx-abstract) diff --git a/docs/integrators/faq.md b/docs/integrators/faq.md index b0405bb72..66f617806 100644 --- a/docs/integrators/faq.md +++ b/docs/integrators/faq.md @@ -1,6 +1,7 @@ --- id: faq title: Frequently Asked Questions +description: "Common integration questions: accounts, consensus, sharding, network parameters and API usage." --- [comment]: # "mx-abstract" diff --git a/docs/integrators/observing-squad.md b/docs/integrators/observing-squad.md index 68e56c947..59e29e65a 100644 --- a/docs/integrators/observing-squad.md +++ b/docs/integrators/observing-squad.md @@ -1,6 +1,7 @@ --- id: observing-squad title: Observing Squad +description: "Observing Squad service: high‑availability access to network data for integrators and data providers." --- [comment]: # (mx-abstract) diff --git a/docs/integrators/overview.md b/docs/integrators/overview.md index 49de4d50d..0a356fe35 100644 --- a/docs/integrators/overview.md +++ b/docs/integrators/overview.md @@ -1,6 +1,7 @@ --- id: overview title: Integrators - Overview +description: "Learn about MultiversX Integrators integrators -." --- [comment]: # (mx-abstract) diff --git a/docs/integrators/querying-the-blockchain.md b/docs/integrators/querying-the-blockchain.md index 5c92efde3..2cbfb148c 100644 --- a/docs/integrators/querying-the-blockchain.md +++ b/docs/integrators/querying-the-blockchain.md @@ -1,6 +1,7 @@ --- id: querying-the-blockchain title: Querying the Blockchain +description: "Learn about MultiversX Integrators querying the blockchain." --- [comment]: # (mx-abstract) diff --git a/docs/integrators/snapshotless-observing-squad.md b/docs/integrators/snapshotless-observing-squad.md index 357798c83..1bf5abca2 100644 --- a/docs/integrators/snapshotless-observing-squad.md +++ b/docs/integrators/snapshotless-observing-squad.md @@ -1,6 +1,7 @@ --- id: snapshotless-observing-squad title: Snapshotless Observing Squad +description: "Learn about MultiversX Integrators snapshotless observing squad." --- [comment]: # (mx-abstract) diff --git a/docs/integrators/walletconnect-json-rpc-methods.md b/docs/integrators/walletconnect-json-rpc-methods.md index e41f402ab..78a118dc9 100644 --- a/docs/integrators/walletconnect-json-rpc-methods.md +++ b/docs/integrators/walletconnect-json-rpc-methods.md @@ -1,6 +1,7 @@ --- id: walletconnect-json-rpc-methods title: WalletConnect JSON-RPC Methods +description: "WalletConnect v2 JSON‑RPC methods for MultiversX: available calls, parameters and usage patterns." --- [comment]: # (mx-abstract) diff --git a/docs/learn/EGLD.md b/docs/learn/EGLD.md index b624ab882..440304eb8 100644 --- a/docs/learn/EGLD.md +++ b/docs/learn/EGLD.md @@ -1,6 +1,7 @@ --- id: EGLD title: What is EGLD? +description: "Overview of egld in MultiversX." --- EGLD is the native cryptocurrency of MultiversX, functioning as digital, global money. It's the primary asset used within the MultiversX ecosystem. diff --git a/docs/learn/ai-agents.md b/docs/learn/ai-agents.md new file mode 100644 index 000000000..b3fa6d768 --- /dev/null +++ b/docs/learn/ai-agents.md @@ -0,0 +1,18 @@ +--- +id: ai-agents +title: AI agents +description: "Learn how to use AI agents for your MultiversX projects" +--- + +## **llms.txt** + +MultiversX publishes an `llms.txt` file that supplies context enabling LLMs to effectively reference the full MultiversX documentation during inference. +https://docs.multiversx.org/llms.txt + +Read more about `llms.txt`: https://llmstxt.org + +## **Skills** + +We have a GitHub repository that provides a structured collection of specialized "Skills", "Global Workflows" (Roles), and "Documentation" designed to equip AI agents with the deep technical knowledge required to build, audit, and optimize on MultiversX. + +https://github.com/multiversx/mx-ai-skills diff --git a/docs/learn/architecture-overview.md b/docs/learn/architecture-overview.md index a7c079220..e703358d7 100644 --- a/docs/learn/architecture-overview.md +++ b/docs/learn/architecture-overview.md @@ -1,6 +1,7 @@ --- id: architecture-overview title: Architecture Overview +description: "Overview of architecture in MultiversX." --- MultiversX is a high-throughput public blockchain aimed at providing security, efficiency, scalability and interoperability, beyond the current state-of-the-art. The two most important features that set MultiversX apart are Adaptive State Sharding and the Secure Proof of Stake consensus mechanism. diff --git a/docs/learn/chronology.md b/docs/learn/chronology.md index 5380b4bcb..0656e8ea9 100644 --- a/docs/learn/chronology.md +++ b/docs/learn/chronology.md @@ -1,6 +1,7 @@ --- id: chronology title: Chronology +description: "Time structure in MultiversX: rounds, epochs and how they govern block production and finality." --- Following the common Proof-of-Stake principles, the MultiversX network organizes time into rounds and epochs, where a fixed number of consecutive rounds form an epoch. The first round of the first epoch ever is called the _genesis round_, which contains the bootstrapping phase of the network. diff --git a/docs/learn/consensus.md b/docs/learn/consensus.md index 73ac40b67..94bcc718a 100644 --- a/docs/learn/consensus.md +++ b/docs/learn/consensus.md @@ -1,6 +1,7 @@ --- id: consensus title: Consensus +description: "Secure Proof‑of‑Stake (SPoS) in MultiversX: validator selection, randomness, BLS aggregation and single‑block finality." --- # Consensus (Secure Proof‑of‑Stake – SPoS) diff --git a/docs/learn/economics.md b/docs/learn/economics.md index a6d3eb5f8..b2ba57fea 100644 --- a/docs/learn/economics.md +++ b/docs/learn/economics.md @@ -1,6 +1,7 @@ --- id: economics title: Economics +description: "Economics of MultiversX: token supply, fees, rewards, inflation and distribution mechanics." --- [comment]: # (mx-abstract) diff --git a/docs/learn/entities.md b/docs/learn/entities.md index e6fb2bff6..80ac054f9 100644 --- a/docs/learn/entities.md +++ b/docs/learn/entities.md @@ -1,6 +1,7 @@ --- id: entities title: Entities +description: "Core entities in MultiversX: users, accounts, nodes and how they interact on‑chain." --- There are two primary entities in MultiversX: users and nodes. diff --git a/docs/learn/getting-started.md b/docs/learn/getting-started.md index c23e2a462..1ae71a056 100644 --- a/docs/learn/getting-started.md +++ b/docs/learn/getting-started.md @@ -1,6 +1,7 @@ --- id: getting-started title: Getting Started +description: "Overview of getting started in MultiversX." --- MultiversX is a high-performance, sharded smart contract blockchain with Proof of Stake (SPoS) consensus, perfectly designed for the Web3 era with capacity to serve Web2 needs. At the moment of writing, it achieves over 30,000 TPS with adaptive scalability, offering fast and low-cost transactions. The xPortal money app, with embedded DeFi and cross-chain operations, empowers users to interact and own their digital assets. Developers benefit from integrated tools and 30% gas royalties, while validators earn ~8% APR. MultiversX offers sovereign chains for flexible, interoperable blockchain solutions, serving enterprise needs, driving the Web3 vision forward. It is a public blockchain similar to Ethereum, users own their accounts and tokens and they are free to move between [more than 500 different apps](https://multiversx.com/ecosystem). diff --git a/docs/learn/multiversx-ecosystem.md b/docs/learn/multiversx-ecosystem.md index 719019842..2c564ffb4 100644 --- a/docs/learn/multiversx-ecosystem.md +++ b/docs/learn/multiversx-ecosystem.md @@ -1,6 +1,7 @@ --- id: multiversx-ecosystem title: What is MultiversX? +description: "What MultiversX is and how the ecosystem fits together: networks, apps, tooling and core concepts." --- [comment]: # (mx-abstract) diff --git a/docs/learn/sharding.md b/docs/learn/sharding.md index f5e684e2d..cdb33395d 100644 --- a/docs/learn/sharding.md +++ b/docs/learn/sharding.md @@ -1,6 +1,7 @@ --- id: sharding title: Sharding +description: "Overview of sharding in MultiversX." --- Sharding was first used in databases and is a method for distributing data across multiple machines. This makes it a _scaling technique_, and can be used by blockchain networks to partition states and transaction processing, so that each node of the network would only need to process a fraction of all the transactions. Moreover, sharding allows for the parallel processing of transactions. As long as there is a sufficient number of nodes verifying each transaction, ensuring high reliability and security, then splitting a blockchain into shards will allow it to process far more transactions by means of parallelization, and thus greatly improving transaction throughput and efficiency. Moreover, sharding promises to increase the throughput of the network as it expands and the number of validator grows - a property called _horizontal scaling_. diff --git a/docs/learn/space-vm.md b/docs/learn/space-vm.md index 39cedea9f..329e0fa28 100644 --- a/docs/learn/space-vm.md +++ b/docs/learn/space-vm.md @@ -1,6 +1,7 @@ --- id: space-vm title: MultiversX Virtual Machine (SpaceVM) +description: "The MultiversX Virtual Machine (SpaceVM): WASM execution, Rust framework support and performance characteristics." --- The execution of SmartContracts plays a central role in modern blockchain networks. MultiversX built a fast and secure virtual machine for this purpose. diff --git a/docs/learn/transactions.md b/docs/learn/transactions.md index c28185db4..145488984 100644 --- a/docs/learn/transactions.md +++ b/docs/learn/transactions.md @@ -1,6 +1,7 @@ --- id: transactions title: Transactions +description: "Overview of transactions in MultiversX." --- A blockchain transaction is a cryptographically signed instruction sent from one account to another, aiming to update the state of the blockchain network. Each transaction contains details such as the nonce, sender, receiver, amount of value transferred (if it is the case), data, signature (and others). Validators process and verify these transactions, ensuring they are legitimate and adhere to the network's protocol. Once verified, the transaction is recorded in a block and added to the blockchain, making it immutable and transparent. diff --git a/docs/sdk-and-tools/chain-simulator.md b/docs/sdk-and-tools/chain-simulator.md index 246087a90..0d3be1ae6 100644 --- a/docs/sdk-and-tools/chain-simulator.md +++ b/docs/sdk-and-tools/chain-simulator.md @@ -1,6 +1,7 @@ --- id: chain-simulator title: Chain simulator +description: "Overview of MultiversX SDK and Tools chain simulator." --- [comment]: # (mx-context-auto) diff --git a/docs/sdk-and-tools/devcontainers.md b/docs/sdk-and-tools/devcontainers.md index 88743db4d..d16fa9cc6 100644 --- a/docs/sdk-and-tools/devcontainers.md +++ b/docs/sdk-and-tools/devcontainers.md @@ -1,6 +1,7 @@ --- id: devcontainers title: Devcontainers +description: "Overview of MultiversX SDK and Tools devcontainers." --- [comment]: # (mx-abstract) diff --git a/docs/sdk-and-tools/elastic-indexer.md b/docs/sdk-and-tools/elastic-indexer.md index f1c5102fa..c5768f5ba 100644 --- a/docs/sdk-and-tools/elastic-indexer.md +++ b/docs/sdk-and-tools/elastic-indexer.md @@ -1,6 +1,7 @@ --- id: indexer title: Elasticindexer service +description: "Overview of MultiversX SDK and Tools elasticindexer service." --- [comment]: # (mx-abstract) diff --git a/docs/sdk-and-tools/elastic-search-wrong-mappings-fix.md b/docs/sdk-and-tools/elastic-search-wrong-mappings-fix.md index 22a0282c8..cf897a2d4 100644 --- a/docs/sdk-and-tools/elastic-search-wrong-mappings-fix.md +++ b/docs/sdk-and-tools/elastic-search-wrong-mappings-fix.md @@ -1,6 +1,7 @@ --- id: es-index-wrong-mapping title: How to fix Elasticsearch mapping errors +description: "Overview of MultiversX SDK and Tools how to fix elasticsearch mapping errors." --- Starting with the February 2023 mainnet upgrade new constrains for Elasticsearch indices were added. Therefore, one can notice diff --git a/docs/sdk-and-tools/elastic-search.md b/docs/sdk-and-tools/elastic-search.md index 46960d009..55386b5e3 100644 --- a/docs/sdk-and-tools/elastic-search.md +++ b/docs/sdk-and-tools/elastic-search.md @@ -1,6 +1,7 @@ --- id: elastic-search title: Elasticsearch +description: "Overview of MultiversX SDK and Tools elasticsearch." --- [comment]: # (mx-abstract) diff --git a/docs/sdk-and-tools/erdcpp.md b/docs/sdk-and-tools/erdcpp.md index f08c3ecba..b7e6bfffd 100644 --- a/docs/sdk-and-tools/erdcpp.md +++ b/docs/sdk-and-tools/erdcpp.md @@ -1,6 +1,7 @@ --- id: erdcpp title: C++ SDK +description: "Overview of MultiversX SDK and Tools c++ sdk." --- [comment]: # (mx-abstract) diff --git a/docs/sdk-and-tools/erdkotlin.md b/docs/sdk-and-tools/erdkotlin.md index c2a51a7e3..3c9f687eb 100644 --- a/docs/sdk-and-tools/erdkotlin.md +++ b/docs/sdk-and-tools/erdkotlin.md @@ -1,6 +1,7 @@ --- id: erdkotlin title: Kotlin SDK +description: "Overview of MultiversX SDK and Tools kotlin sdk." --- [comment]: # (mx-abstract) diff --git a/docs/sdk-and-tools/google-bigquery.md b/docs/sdk-and-tools/google-bigquery.md index 29229ff35..fb4e7165f 100644 --- a/docs/sdk-and-tools/google-bigquery.md +++ b/docs/sdk-and-tools/google-bigquery.md @@ -1,6 +1,7 @@ --- id: google-bigquery title: Google BigQuery +description: "Overview of MultiversX SDK and Tools google bigquery." --- [comment]: # "mx-abstract" diff --git a/docs/sdk-and-tools/indices/accounts.md b/docs/sdk-and-tools/indices/accounts.md index 23a4040e1..efdd0e4c7 100644 --- a/docs/sdk-and-tools/indices/accounts.md +++ b/docs/sdk-and-tools/indices/accounts.md @@ -1,6 +1,7 @@ --- id: es-index-accounts title: accounts +description: "Overview of MultiversX SDK and Tools accounts." --- [comment]: # (mx-abstract) diff --git a/docs/sdk-and-tools/indices/accountsesdt.md b/docs/sdk-and-tools/indices/accountsesdt.md index fe8a54104..fb75ed74a 100644 --- a/docs/sdk-and-tools/indices/accountsesdt.md +++ b/docs/sdk-and-tools/indices/accountsesdt.md @@ -1,6 +1,7 @@ --- id: es-index-accountsesdt title: accountsesdt +description: "Overview of MultiversX SDK and Tools accountsesdt." --- [comment]: # (mx-abstract) diff --git a/docs/sdk-and-tools/indices/accountsesdthistory.md b/docs/sdk-and-tools/indices/accountsesdthistory.md index 942eb2927..5b43ab3b6 100644 --- a/docs/sdk-and-tools/indices/accountsesdthistory.md +++ b/docs/sdk-and-tools/indices/accountsesdthistory.md @@ -1,6 +1,7 @@ --- id: es-index-accountsesdthistory title: accountsesdthistory +description: "Overview of MultiversX SDK and Tools accountsesdthistory." --- [comment]: # (mx-abstract) diff --git a/docs/sdk-and-tools/indices/accountshistory.md b/docs/sdk-and-tools/indices/accountshistory.md index 6c46b34d4..22d0f8c68 100644 --- a/docs/sdk-and-tools/indices/accountshistory.md +++ b/docs/sdk-and-tools/indices/accountshistory.md @@ -1,6 +1,7 @@ --- id: es-index-accountshistory title: accountshistory +description: "Overview of MultiversX SDK and Tools accountshistory." --- [comment]: # (mx-abstract) diff --git a/docs/sdk-and-tools/indices/blocks.md b/docs/sdk-and-tools/indices/blocks.md index 6d4a9ac84..7fa75eb62 100644 --- a/docs/sdk-and-tools/indices/blocks.md +++ b/docs/sdk-and-tools/indices/blocks.md @@ -1,6 +1,7 @@ --- id: es-index-blocks title: blocks +description: "Overview of MultiversX SDK and Tools blocks." --- [comment]: # (mx-abstract) diff --git a/docs/sdk-and-tools/indices/delegators.md b/docs/sdk-and-tools/indices/delegators.md index d3fe04576..76465e8b8 100644 --- a/docs/sdk-and-tools/indices/delegators.md +++ b/docs/sdk-and-tools/indices/delegators.md @@ -1,6 +1,7 @@ --- id: es-index-delegators title: delegators +description: "Overview of MultiversX SDK and Tools delegators." --- [comment]: # (mx-abstract) diff --git a/docs/sdk-and-tools/indices/epochinfo.md b/docs/sdk-and-tools/indices/epochinfo.md index 0c8e856e2..455cee532 100644 --- a/docs/sdk-and-tools/indices/epochinfo.md +++ b/docs/sdk-and-tools/indices/epochinfo.md @@ -1,6 +1,7 @@ --- id: es-index-epochinfo title: epochinfo +description: "Overview of MultiversX SDK and Tools epochinfo." --- [comment]: # (mx-abstract) diff --git a/docs/sdk-and-tools/indices/events.md b/docs/sdk-and-tools/indices/events.md index 5f47d870f..8635d32ea 100644 --- a/docs/sdk-and-tools/indices/events.md +++ b/docs/sdk-and-tools/indices/events.md @@ -1,6 +1,7 @@ --- id: es-index-events title: events +description: "Overview of MultiversX SDK and Tools events." --- [comment]: # (mx-abstract) diff --git a/docs/sdk-and-tools/indices/logs.md b/docs/sdk-and-tools/indices/logs.md index 62613f6fb..04bbdc02a 100644 --- a/docs/sdk-and-tools/indices/logs.md +++ b/docs/sdk-and-tools/indices/logs.md @@ -1,6 +1,7 @@ --- id: es-index-logs title: logs +description: "Overview of MultiversX SDK and Tools logs." --- [comment]: # (mx-abstract) diff --git a/docs/sdk-and-tools/indices/miniblocks.md b/docs/sdk-and-tools/indices/miniblocks.md index 2b291cd90..53b96f410 100644 --- a/docs/sdk-and-tools/indices/miniblocks.md +++ b/docs/sdk-and-tools/indices/miniblocks.md @@ -1,6 +1,7 @@ --- id: es-index-miniblocks title: miniblocks +description: "Overview of MultiversX SDK and Tools miniblocks." --- [comment]: # (mx-abstract) diff --git a/docs/sdk-and-tools/indices/operations.md b/docs/sdk-and-tools/indices/operations.md index 5ec2ce0f1..04103a4a4 100644 --- a/docs/sdk-and-tools/indices/operations.md +++ b/docs/sdk-and-tools/indices/operations.md @@ -1,6 +1,7 @@ --- id: es-index-operations title: operations +description: "Overview of MultiversX SDK and Tools operations." --- [comment]: # (mx-abstract) diff --git a/docs/sdk-and-tools/indices/rating.md b/docs/sdk-and-tools/indices/rating.md index 7d467e20c..b26728fc0 100644 --- a/docs/sdk-and-tools/indices/rating.md +++ b/docs/sdk-and-tools/indices/rating.md @@ -1,6 +1,7 @@ --- id: es-index-rating title: rating +description: "Overview of MultiversX SDK and Tools rating." --- [comment]: # (mx-abstract) diff --git a/docs/sdk-and-tools/indices/receipts.md b/docs/sdk-and-tools/indices/receipts.md index 0fff1d65d..db974c9e7 100644 --- a/docs/sdk-and-tools/indices/receipts.md +++ b/docs/sdk-and-tools/indices/receipts.md @@ -1,6 +1,7 @@ --- id: es-index-receipts title: receipts +description: "Overview of MultiversX SDK and Tools receipts." --- [comment]: # (mx-abstract) diff --git a/docs/sdk-and-tools/indices/rounds.md b/docs/sdk-and-tools/indices/rounds.md index 557b2a4b1..5e0a668f0 100644 --- a/docs/sdk-and-tools/indices/rounds.md +++ b/docs/sdk-and-tools/indices/rounds.md @@ -1,6 +1,7 @@ --- id: es-index-rounds title: rounds +description: "Overview of MultiversX SDK and Tools rounds." --- [comment]: # (mx-abstract) diff --git a/docs/sdk-and-tools/indices/scdeploys.md b/docs/sdk-and-tools/indices/scdeploys.md index 6c9e6990f..a3f4ef80d 100644 --- a/docs/sdk-and-tools/indices/scdeploys.md +++ b/docs/sdk-and-tools/indices/scdeploys.md @@ -1,6 +1,7 @@ --- id: es-index-scdeploys title: scdeploys +description: "Overview of MultiversX SDK and Tools scdeploys." --- [comment]: # (mx-abstract) diff --git a/docs/sdk-and-tools/indices/scresults.md b/docs/sdk-and-tools/indices/scresults.md index cf685c872..e506a4395 100644 --- a/docs/sdk-and-tools/indices/scresults.md +++ b/docs/sdk-and-tools/indices/scresults.md @@ -1,6 +1,7 @@ --- id: es-index-scresults title: scresults +description: "Overview of MultiversX SDK and Tools scresults." --- [comment]: # (mx-abstract) diff --git a/docs/sdk-and-tools/indices/tags.md b/docs/sdk-and-tools/indices/tags.md index 80cae9f93..d9613e3d1 100644 --- a/docs/sdk-and-tools/indices/tags.md +++ b/docs/sdk-and-tools/indices/tags.md @@ -1,6 +1,7 @@ --- id: es-index-tags title: tags +description: "Overview of MultiversX SDK and Tools tags." --- [comment]: # (mx-abstract) diff --git a/docs/sdk-and-tools/indices/tokens.md b/docs/sdk-and-tools/indices/tokens.md index 21817eae8..25066028f 100644 --- a/docs/sdk-and-tools/indices/tokens.md +++ b/docs/sdk-and-tools/indices/tokens.md @@ -1,6 +1,7 @@ --- id: es-index-tokens title: tokens +description: "Overview of MultiversX SDK and Tools tokens." --- [comment]: # (mx-abstract) diff --git a/docs/sdk-and-tools/indices/transactions.md b/docs/sdk-and-tools/indices/transactions.md index 3b59135cb..07bbaf3ac 100644 --- a/docs/sdk-and-tools/indices/transactions.md +++ b/docs/sdk-and-tools/indices/transactions.md @@ -1,6 +1,7 @@ --- id: es-index-transactions title: transactions +description: "Overview of MultiversX SDK and Tools transactions." --- [comment]: # (mx-abstract) diff --git a/docs/sdk-and-tools/indices/validators.md b/docs/sdk-and-tools/indices/validators.md index 870155146..4d85a954d 100644 --- a/docs/sdk-and-tools/indices/validators.md +++ b/docs/sdk-and-tools/indices/validators.md @@ -1,6 +1,7 @@ --- id: es-index-validators title: validators +description: "Overview of MultiversX SDK and Tools validators." --- [comment]: # (mx-abstract) diff --git a/docs/sdk-and-tools/mxjava.md b/docs/sdk-and-tools/mxjava.md index 4692909dd..1faf9b99f 100644 --- a/docs/sdk-and-tools/mxjava.md +++ b/docs/sdk-and-tools/mxjava.md @@ -1,6 +1,7 @@ --- id: mxjava title: Java SDK +description: "Overview of MultiversX SDK and Tools java sdk." --- [comment]: # (mx-abstract) diff --git a/docs/sdk-and-tools/mxpy/installing-mxpy.md b/docs/sdk-and-tools/mxpy/installing-mxpy.md index 9514d04bb..2d0f1eb1e 100644 --- a/docs/sdk-and-tools/mxpy/installing-mxpy.md +++ b/docs/sdk-and-tools/mxpy/installing-mxpy.md @@ -1,6 +1,7 @@ --- id: installing-mxpy title: Installing mxpy +description: "Overview of MultiversX SDK and Tools installing mxpy." --- [comment]: # (mx-abstract) diff --git a/docs/sdk-and-tools/mxpy/mxpy-cli.md b/docs/sdk-and-tools/mxpy/mxpy-cli.md index 5bf2b970c..8163f6d6f 100644 --- a/docs/sdk-and-tools/mxpy/mxpy-cli.md +++ b/docs/sdk-and-tools/mxpy/mxpy-cli.md @@ -1,6 +1,7 @@ --- id: mxpy-cli title: mxpy CLI cookbook +description: "Overview of MultiversX SDK and Tools mxpy cli cookbook." --- [comment]: # (mx-abstract) diff --git a/docs/sdk-and-tools/mxpy/smart-contract-interactions.md b/docs/sdk-and-tools/mxpy/smart-contract-interactions.md index a1de4e252..3bf641bc7 100644 --- a/docs/sdk-and-tools/mxpy/smart-contract-interactions.md +++ b/docs/sdk-and-tools/mxpy/smart-contract-interactions.md @@ -1,6 +1,7 @@ --- id: smart-contract-interactions title: Smart contract interactions +description: "Overview of MultiversX SDK and Tools smart contract interactions." --- [comment]: # (mx-abstract) diff --git a/docs/sdk-and-tools/notifier.md b/docs/sdk-and-tools/notifier.md index d8e45e688..689772164 100644 --- a/docs/sdk-and-tools/notifier.md +++ b/docs/sdk-and-tools/notifier.md @@ -1,6 +1,7 @@ --- id: notifier title: Events notifier +description: "Overview of MultiversX SDK and Tools events notifier." --- [comment]: # (mx-abstract) diff --git a/docs/sdk-and-tools/overview.md b/docs/sdk-and-tools/overview.md index 3a3082e21..297050778 100644 --- a/docs/sdk-and-tools/overview.md +++ b/docs/sdk-and-tools/overview.md @@ -1,6 +1,7 @@ --- id: overview title: SDKs and Tools - Overview +description: "Overview of MultiversX SDK and Tools sdks and tools -." --- [comment]: # (mx-abstract) diff --git a/docs/sdk-and-tools/proxy.md b/docs/sdk-and-tools/proxy.md index ae2cd3cc4..16787e83e 100644 --- a/docs/sdk-and-tools/proxy.md +++ b/docs/sdk-and-tools/proxy.md @@ -1,6 +1,7 @@ --- id: proxy title: Proxy architecture +description: "Overview of MultiversX SDK and Tools proxy architecture." --- Overview of the MultiversX Proxy diff --git a/docs/sdk-and-tools/rest-api/addresses.mdx b/docs/sdk-and-tools/rest-api/addresses.mdx index 7815aa944..1cfadafef 100644 --- a/docs/sdk-and-tools/rest-api/addresses.mdx +++ b/docs/sdk-and-tools/rest-api/addresses.mdx @@ -1,6 +1,7 @@ --- id: addresses title: Addresses +description: "Overview of MultiversX SDK and Tools addresses." --- ```mdx-code-block diff --git a/docs/sdk-and-tools/rest-api/blocks.mdx b/docs/sdk-and-tools/rest-api/blocks.mdx index 9d2fbf790..614f80ed5 100644 --- a/docs/sdk-and-tools/rest-api/blocks.mdx +++ b/docs/sdk-and-tools/rest-api/blocks.mdx @@ -1,6 +1,7 @@ --- id: blocks title: Blocks +description: "Overview of MultiversX SDK and Tools blocks." --- ```mdx-code-block diff --git a/docs/sdk-and-tools/rest-api/gateway-overview.md b/docs/sdk-and-tools/rest-api/gateway-overview.md index 51e972138..c6ceb41a5 100644 --- a/docs/sdk-and-tools/rest-api/gateway-overview.md +++ b/docs/sdk-and-tools/rest-api/gateway-overview.md @@ -1,6 +1,7 @@ --- id: gateway-overview title: Gateway overview +description: "Overview of MultiversX SDK and Tools gateway." --- [comment]: # (mx-abstract) diff --git a/docs/sdk-and-tools/rest-api/iterate-keys.md b/docs/sdk-and-tools/rest-api/iterate-keys.md index 13b1e7f4a..3b1bc50e2 100644 --- a/docs/sdk-and-tools/rest-api/iterate-keys.md +++ b/docs/sdk-and-tools/rest-api/iterate-keys.md @@ -1,6 +1,7 @@ --- id: iterate-keys title: Iterate keys +description: "Overview of MultiversX SDK and Tools iterate keys." --- [comment]: # (mx-abstract) diff --git a/docs/sdk-and-tools/rest-api/multiversx-api.md b/docs/sdk-and-tools/rest-api/multiversx-api.md index 34fc30d07..306000d99 100644 --- a/docs/sdk-and-tools/rest-api/multiversx-api.md +++ b/docs/sdk-and-tools/rest-api/multiversx-api.md @@ -1,6 +1,7 @@ --- id: multiversx-api title: MultiversX API +description: "Overview of MultiversX SDK and Tools multiversx api." --- [comment]: # (mx-abstract) diff --git a/docs/sdk-and-tools/rest-api/network.mdx b/docs/sdk-and-tools/rest-api/network.mdx index 43243f3f5..5e340de7a 100644 --- a/docs/sdk-and-tools/rest-api/network.mdx +++ b/docs/sdk-and-tools/rest-api/network.mdx @@ -1,6 +1,7 @@ --- id: network title: Network +description: "Overview of MultiversX SDK and Tools network." --- ```mdx-code-block diff --git a/docs/sdk-and-tools/rest-api/nodes.mdx b/docs/sdk-and-tools/rest-api/nodes.mdx index 6e0be228f..5a9f708b2 100644 --- a/docs/sdk-and-tools/rest-api/nodes.mdx +++ b/docs/sdk-and-tools/rest-api/nodes.mdx @@ -1,6 +1,7 @@ --- id: nodes title: Nodes +description: "Overview of MultiversX SDK and Tools nodes." --- ```mdx-code-block diff --git a/docs/sdk-and-tools/rest-api/rest-api.md b/docs/sdk-and-tools/rest-api/rest-api.md index 47de8ed0b..7ccd376b0 100644 --- a/docs/sdk-and-tools/rest-api/rest-api.md +++ b/docs/sdk-and-tools/rest-api/rest-api.md @@ -1,6 +1,7 @@ --- id: rest-api title: REST API overview +description: "Overview of MultiversX SDK and Tools rest api." --- [comment]: # (mx-abstract) diff --git a/docs/sdk-and-tools/rest-api/transactions.mdx b/docs/sdk-and-tools/rest-api/transactions.mdx index 38c2ebee6..2fd9de28b 100644 --- a/docs/sdk-and-tools/rest-api/transactions.mdx +++ b/docs/sdk-and-tools/rest-api/transactions.mdx @@ -1,6 +1,7 @@ --- id: transactions title: Transactions +description: "Overview of MultiversX SDK and Tools transactions." --- ```mdx-code-block @@ -31,7 +32,11 @@ values={[ Body Parameters | Param | Required | Type | Description | -| ---------------- | ----------------------------------------- | -------- | -------------------------------------------------------------------------------- | +| + +--- + +------------- | ----------------------------------------- | -------- | -------------------------------------------------------------------------------- | | nonce | REQUIRED | `number` | The Nonce of the Sender. | | value | REQUIRED | `string` | The Value to transfer, as a string representation of a Big Integer (can be "0"). | | receiver | REQUIRED | `string` | The Address (bech32) of the Receiver. | @@ -264,9 +269,7 @@ _SimulationResults_ | receipts | []ApiReceipt | an array of the receipts (if any) | | hash | string | the hash of the transaction | -❕ Note that fields that are empty won't be included in the response. This can be seen in the examples below - ---- +❕ Note that fields that are empty won't be included in the response. This can be seen in the examples below--- 🟢 200: OK diff --git a/docs/sdk-and-tools/rest-api/versions-and-changelog.md b/docs/sdk-and-tools/rest-api/versions-and-changelog.md index 8297055bf..62a6f027a 100644 --- a/docs/sdk-and-tools/rest-api/versions-and-changelog.md +++ b/docs/sdk-and-tools/rest-api/versions-and-changelog.md @@ -1,6 +1,7 @@ --- id: versions-and-changelog title: Versions and Changelog +description: "Overview of MultiversX SDK and Tools versions and changelog." --- [comment]: # (mx-abstract) diff --git a/docs/sdk-and-tools/rest-api/virtual-machine.mdx b/docs/sdk-and-tools/rest-api/virtual-machine.mdx index 8517156eb..1f11241f6 100644 --- a/docs/sdk-and-tools/rest-api/virtual-machine.mdx +++ b/docs/sdk-and-tools/rest-api/virtual-machine.mdx @@ -1,6 +1,7 @@ --- id: virtual-machine title: Virtual Machine +description: "Overview of MultiversX SDK and Tools virtual machine." --- ```mdx-code-block diff --git a/docs/sdk-and-tools/rest-api/ws-subscriptions.md b/docs/sdk-and-tools/rest-api/ws-subscriptions.md index 9b68c691b..45ea54540 100644 --- a/docs/sdk-and-tools/rest-api/ws-subscriptions.md +++ b/docs/sdk-and-tools/rest-api/ws-subscriptions.md @@ -1,6 +1,7 @@ --- id: multiversx-api-ws title: MultiversX API WebSocket +description: "Overview of MultiversX SDK and Tools multiversx api websocket." --- ## MultiversX WebSocket Subscription API @@ -727,4 +728,4 @@ socket.on("error", (errorData) => { - Errors are emitted via the standard `error` event. - Uses `socket.io-client`. -This document contains everything required to use MultiversX WebSocket Subscriptions effectively. \ No newline at end of file +This document contains everything required to use MultiversX WebSocket Subscriptions effectively. diff --git a/docs/sdk-and-tools/sdk-dapp/internal-processes/guardians.md b/docs/sdk-and-tools/sdk-dapp/internal-processes/guardians.md index 543d071dd..8df290a1f 100644 --- a/docs/sdk-and-tools/sdk-dapp/internal-processes/guardians.md +++ b/docs/sdk-and-tools/sdk-dapp/internal-processes/guardians.md @@ -1,6 +1,7 @@ --- id: guardians title: Guardians +description: "Overview of MultiversX SDK and Tools guardians." --- [comment]: # (mx-abstract) diff --git a/docs/sdk-and-tools/sdk-dapp/sdk-dapp.md b/docs/sdk-and-tools/sdk-dapp/sdk-dapp.md index 31e5f3c70..b26b5c04e 100644 --- a/docs/sdk-and-tools/sdk-dapp/sdk-dapp.md +++ b/docs/sdk-and-tools/sdk-dapp/sdk-dapp.md @@ -1,6 +1,7 @@ --- id: sdk-dapp title: sdk-dapp +description: "Overview of MultiversX SDK and Tools sdk-dapp." --- Library used to build React dApps on MultiversX Network. diff --git a/docs/sdk-and-tools/sdk-go.md b/docs/sdk-and-tools/sdk-go.md index f2ff45c54..af72cea7f 100644 --- a/docs/sdk-and-tools/sdk-go.md +++ b/docs/sdk-and-tools/sdk-go.md @@ -1,6 +1,7 @@ --- id: sdk-go title: Go SDK +description: "Overview of MultiversX SDK and Tools go sdk." --- [comment]: # (mx-abstract) diff --git a/docs/sdk-and-tools/sdk-js/extending-sdk-js.md b/docs/sdk-and-tools/sdk-js/extending-sdk-js.md index 63d24e38c..50e637292 100644 --- a/docs/sdk-and-tools/sdk-js/extending-sdk-js.md +++ b/docs/sdk-and-tools/sdk-js/extending-sdk-js.md @@ -2,6 +2,7 @@ id: extending-sdk-js title: Extending sdk-js pagination_prev: sdk-and-tools/sdk-js/sdk-js +description: "Overview of MultiversX SDK and Tools extending sdk-js." --- [comment]: # (mx-abstract) 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 8571ce1d8..39ee4974b 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 @@ -3,6 +3,7 @@ id: sdk-js-cookbook-v14 title: Cookbook (v14) pagination_prev: sdk-and-tools/sdk-js/sdk-js pagination_next: null +description: "Overview of MultiversX SDK and Tools cookbook (v14)." --- [comment]: # (mx-abstract) diff --git a/docs/sdk-and-tools/sdk-js/sdk-js-cookbook-v15.md b/docs/sdk-and-tools/sdk-js/sdk-js-cookbook-v15.md index a84683128..1dc949a2c 100644 --- a/docs/sdk-and-tools/sdk-js/sdk-js-cookbook-v15.md +++ b/docs/sdk-and-tools/sdk-js/sdk-js-cookbook-v15.md @@ -3,6 +3,7 @@ id: sdk-js-cookbook title: Cookbook (v15) pagination_prev: sdk-and-tools/sdk-js/sdk-js-cookbook-v14 pagination_next: null +description: "Overview of MultiversX SDK and Tools cookbook (v15)." --- [comment]: # (mx-abstract) diff --git a/docs/sdk-and-tools/sdk-js/sdk-js-signing-providers.md b/docs/sdk-and-tools/sdk-js/sdk-js-signing-providers.md index a90d52a8f..c8ce1f850 100644 --- a/docs/sdk-and-tools/sdk-js/sdk-js-signing-providers.md +++ b/docs/sdk-and-tools/sdk-js/sdk-js-signing-providers.md @@ -1,6 +1,7 @@ --- id: sdk-js-signing-providers title: Signing Providers for dApps +description: "Overview of MultiversX SDK and Tools signing providers for dapps." --- [comment]: # (mx-abstract) diff --git a/docs/sdk-and-tools/sdk-js/sdk-js.md b/docs/sdk-and-tools/sdk-js/sdk-js.md index 9d744a104..2c57ecaa4 100644 --- a/docs/sdk-and-tools/sdk-js/sdk-js.md +++ b/docs/sdk-and-tools/sdk-js/sdk-js.md @@ -1,6 +1,7 @@ --- id: sdk-js title: sdk-js +description: "Overview of MultiversX SDK and Tools sdk-js." --- [comment]: # (mx-abstract) 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 75bbfba79..1e911227e 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 @@ -1,6 +1,7 @@ --- id: writing-and-testing-sdk-js-interactions title: Writing and testing interactions +description: "Overview of MultiversX SDK and Tools writing and testing interactions." --- [comment]: # (mx-exclude-file) diff --git a/docs/sdk-and-tools/sdk-nestjs/sdk-nestjs-auth.md b/docs/sdk-and-tools/sdk-nestjs/sdk-nestjs-auth.md index 9796033db..50887eb1b 100644 --- a/docs/sdk-and-tools/sdk-nestjs/sdk-nestjs-auth.md +++ b/docs/sdk-and-tools/sdk-nestjs/sdk-nestjs-auth.md @@ -1,6 +1,7 @@ --- id: sdk-nestjs-auth title: NestJS SDK Auth utilities +description: "Overview of MultiversX SDK and Tools nestjs sdk auth utilities." --- NPM Version diff --git a/docs/sdk-and-tools/sdk-nestjs/sdk-nestjs-cache.md b/docs/sdk-and-tools/sdk-nestjs/sdk-nestjs-cache.md index fd28cd8a5..40f302c79 100644 --- a/docs/sdk-and-tools/sdk-nestjs/sdk-nestjs-cache.md +++ b/docs/sdk-and-tools/sdk-nestjs/sdk-nestjs-cache.md @@ -1,6 +1,7 @@ --- id: sdk-nestjs-cache title: NestJS SDK Cache utilities +description: "Overview of MultiversX SDK and Tools nestjs sdk cache utilities." --- NPM Version diff --git a/docs/sdk-and-tools/sdk-nestjs/sdk-nestjs-monitoring.md b/docs/sdk-and-tools/sdk-nestjs/sdk-nestjs-monitoring.md index 4e3351df1..68c15d1a4 100644 --- a/docs/sdk-and-tools/sdk-nestjs/sdk-nestjs-monitoring.md +++ b/docs/sdk-and-tools/sdk-nestjs/sdk-nestjs-monitoring.md @@ -1,6 +1,7 @@ --- id: sdk-nestjs-monitoring title: NestJS SDK Monitoring utilities +description: "Overview of MultiversX SDK and Tools nestjs sdk monitoring utilities." --- NPM Version diff --git a/docs/sdk-and-tools/sdk-nestjs/sdk-nestjs.md b/docs/sdk-and-tools/sdk-nestjs/sdk-nestjs.md index 1e5ad8b9f..e9c92301b 100644 --- a/docs/sdk-and-tools/sdk-nestjs/sdk-nestjs.md +++ b/docs/sdk-and-tools/sdk-nestjs/sdk-nestjs.md @@ -1,6 +1,7 @@ --- id: sdk-nestjs title: NestJS SDK +description: "Overview of MultiversX SDK and Tools nestjs sdk." --- MultiversX NestJS Microservice Utilities diff --git a/docs/sdk-and-tools/sdk-py.md b/docs/sdk-and-tools/sdk-py.md index a457b4ac9..2978130f4 100644 --- a/docs/sdk-and-tools/sdk-py.md +++ b/docs/sdk-and-tools/sdk-py.md @@ -3,6 +3,7 @@ id: sdk-py title: Python SDK pagination_prev: developers/testing/testing-in-go pagination_next: sdk-and-tools/mxpy/installing-mxpy +description: "Overview of MultiversX SDK and Tools python sdk." --- [comment]: # (mx-abstract) diff --git a/docs/sdk-and-tools/sdk-rust.md b/docs/sdk-and-tools/sdk-rust.md index 04da596f5..69354a319 100644 --- a/docs/sdk-and-tools/sdk-rust.md +++ b/docs/sdk-and-tools/sdk-rust.md @@ -1,6 +1,7 @@ --- id: sdk-rust title: Rust SDK +description: "Overview of MultiversX SDK and Tools rust sdk." --- [comment]: # (mx-abstract) diff --git a/docs/sdk-and-tools/troubleshooting/ide-setup.md b/docs/sdk-and-tools/troubleshooting/ide-setup.md index 21a317793..e93434f94 100644 --- a/docs/sdk-and-tools/troubleshooting/ide-setup.md +++ b/docs/sdk-and-tools/troubleshooting/ide-setup.md @@ -1,6 +1,7 @@ --- id: ide-setup title: Fix IDEs configuration +description: "Overview of MultiversX SDK and Tools fix ides configuration." --- [comment]: # (mx-abstract) diff --git a/docs/sdk-and-tools/troubleshooting/multiplatform.md b/docs/sdk-and-tools/troubleshooting/multiplatform.md index a15b77b3a..6b2a99b0b 100644 --- a/docs/sdk-and-tools/troubleshooting/multiplatform.md +++ b/docs/sdk-and-tools/troubleshooting/multiplatform.md @@ -1,6 +1,7 @@ --- id: multiplatform title: MultiversX tools on multiple platforms +description: "Overview of MultiversX SDK and Tools multiversx tools on multiple platforms." --- Generally speaking, the MultiversX tools should work on all platforms. However, platform-specific issues can occur. This page aims to be an entry point for troubleshooting platform-specific issues, in regards to the MultiversX toolset. diff --git a/docs/sdk-and-tools/troubleshooting/rust-setup.md b/docs/sdk-and-tools/troubleshooting/rust-setup.md index 40f7a5983..6d218ef3e 100644 --- a/docs/sdk-and-tools/troubleshooting/rust-setup.md +++ b/docs/sdk-and-tools/troubleshooting/rust-setup.md @@ -1,6 +1,7 @@ --- id: fix-rust-setup title: Fix Rust installation +description: "Overview of MultiversX SDK and Tools fix rust installation." --- [comment]: # (mx-abstract) diff --git a/docs/sdk-and-tools/troubleshooting/troubleshooting.md b/docs/sdk-and-tools/troubleshooting/troubleshooting.md index 8e507b000..69529a8a5 100644 --- a/docs/sdk-and-tools/troubleshooting/troubleshooting.md +++ b/docs/sdk-and-tools/troubleshooting/troubleshooting.md @@ -1,6 +1,7 @@ --- id: troubleshooting title: Overview +description: "Overview of MultiversX SDK and Tools troubleshooting." --- [comment]: # (mx-abstract) diff --git a/docs/sovereign/bitcoin-l2.md b/docs/sovereign/bitcoin-l2.md index 6476a9ea1..94bdfce77 100644 --- a/docs/sovereign/bitcoin-l2.md +++ b/docs/sovereign/bitcoin-l2.md @@ -1,3 +1,7 @@ +--- +description: "Learn about MultiversX Sovereign bitcoin l2." +--- + # Bitcoin L2 :::note diff --git a/docs/sovereign/concept.md b/docs/sovereign/concept.md index 619f9c033..f343bb16c 100644 --- a/docs/sovereign/concept.md +++ b/docs/sovereign/concept.md @@ -1,3 +1,7 @@ +--- +description: "Learn about MultiversX Sovereign concept." +--- + # Concept :::note diff --git a/docs/sovereign/cross-chain-execution.md b/docs/sovereign/cross-chain-execution.md index 6b804075b..116603103 100644 --- a/docs/sovereign/cross-chain-execution.md +++ b/docs/sovereign/cross-chain-execution.md @@ -1,3 +1,7 @@ +--- +description: "Learn about MultiversX Sovereign cross chain execution." +--- + # Introduction When we take a look at the blockchain industry, we observe a segregated ecosystem lacking cohesion, interoperability and teamwork. The vision lead to the Blockchain Revolution, knows as “Web3” — a new era of the internet that is user-centered, emphasizing data ownership and decentralized trust. @@ -56,7 +60,7 @@ The source for the smart contracts can be found at the official [MultiversX Sove ## Sovereign Bridge Service This feature facilitates the execution of outgoing operations. This service is an application that receives Sovereign operations. After that, it will call the `execute_operation` endpoint from the `Mvx-ESDT-Safe` smart contract. The registration and execution of operations looks like this: -- For N operations there is only one [register transaction](from-sovereign.md#registering-a-set-of-operations) inside the Header-Verifier smart contract. -- N transactions for the [execution](from-sovereign.md#executing-an-operation) of N operations inside the ESDT-Safe smart contract, one execution transaction per operation. +- For N operations there is only one [register transaction](mvx-esdt-safe.md#registering-a-set-of-operations) inside the Header-Verifier smart contract. +- N transactions for the [execution](mvx-esdt-safe.md#executing-an-operation) of N operations inside the ESDT-Safe smart contract, one execution transaction per operation. > There can be one or more services deployed in the network at the same time. diff --git a/docs/sovereign/custom-configurations.md b/docs/sovereign/custom-configurations.md index 02a807fd9..45695f505 100644 --- a/docs/sovereign/custom-configurations.md +++ b/docs/sovereign/custom-configurations.md @@ -1,3 +1,7 @@ +--- +description: "Learn about MultiversX Sovereign custom configurations." +--- + # Custom Configurations ## Sovereign network customisations diff --git a/docs/sovereign/disclaimer.md b/docs/sovereign/disclaimer.md index 4450fe936..8e260c18a 100644 --- a/docs/sovereign/disclaimer.md +++ b/docs/sovereign/disclaimer.md @@ -1,3 +1,7 @@ +--- +description: "Learn about MultiversX Sovereign disclaimer." +--- + # Disclaimer :::note diff --git a/docs/sovereign/distributed-setup.md b/docs/sovereign/distributed-setup.md index 86d856e0f..060943c6d 100644 --- a/docs/sovereign/distributed-setup.md +++ b/docs/sovereign/distributed-setup.md @@ -1,3 +1,7 @@ +--- +description: "Learn about MultiversX Sovereign distributed setup." +--- + # Distributed Setup ## Create distributed Sovereign Chain configuration diff --git a/docs/sovereign/dual-staking.md b/docs/sovereign/dual-staking.md index 35e953f21..a84450115 100644 --- a/docs/sovereign/dual-staking.md +++ b/docs/sovereign/dual-staking.md @@ -1,3 +1,7 @@ +--- +description: "Learn about MultiversX Sovereign dual staking." +--- + # Dual Staking diff --git a/docs/sovereign/ethereum-l2.md b/docs/sovereign/ethereum-l2.md index 7cecca426..9c77c21c6 100644 --- a/docs/sovereign/ethereum-l2.md +++ b/docs/sovereign/ethereum-l2.md @@ -1,3 +1,7 @@ +--- +description: "Learn about MultiversX Sovereign ethereum l2." +--- + # Ethereum L2 :::note diff --git a/docs/sovereign/governance.md b/docs/sovereign/governance.md index 48baab590..7f111a6f1 100644 --- a/docs/sovereign/governance.md +++ b/docs/sovereign/governance.md @@ -1,3 +1,7 @@ +--- +description: "Learn about MultiversX Sovereign governance." +--- + # Governance diff --git a/docs/sovereign/header-verifier.md b/docs/sovereign/header-verifier.md index a3ca0fb37..e00328ee9 100644 --- a/docs/sovereign/header-verifier.md +++ b/docs/sovereign/header-verifier.md @@ -1,3 +1,7 @@ +--- +description: "Learn about MultiversX Sovereign header verifier." +--- + # Header-Verifier As mentioned in the [Introduction](cross-chain-execution.md) the `Header-Verifier` smart contract is responsible to verify signatures, store the *BLS Keys* of the validators and register incoming *Operations*. diff --git a/docs/sovereign/interoperability.md b/docs/sovereign/interoperability.md index dfbf9a937..59dea830f 100644 --- a/docs/sovereign/interoperability.md +++ b/docs/sovereign/interoperability.md @@ -1,3 +1,7 @@ +--- +description: "Learn about MultiversX Sovereign interoperability." +--- + # Interoperability and Modular Design Sovereign Chains are designed to serve as an interoperability layer between different ecosystems. Ecosystem partners and builders have the opportunity to create a set of components that can be activated or deactivated based on the specific needs of a particular Sovereign Chain. But how will sovereign chains achieve that? diff --git a/docs/sovereign/key-components.md b/docs/sovereign/key-components.md index 73d4163f2..67c9298da 100644 --- a/docs/sovereign/key-components.md +++ b/docs/sovereign/key-components.md @@ -1,3 +1,7 @@ +--- +description: "Learn about MultiversX Sovereign key components." +--- + # Key Components :::note diff --git a/docs/sovereign/local-setup.md b/docs/sovereign/local-setup.md index 988f0ef89..8c2ce4fe3 100644 --- a/docs/sovereign/local-setup.md +++ b/docs/sovereign/local-setup.md @@ -1,3 +1,7 @@ +--- +description: "Learn about MultiversX Sovereign local setup." +--- + # Full Local Setup ## Deploy local Sovereign Chain diff --git a/docs/sovereign/managing-sovereign.md b/docs/sovereign/managing-sovereign.md index cc5380569..6ca66a2d1 100644 --- a/docs/sovereign/managing-sovereign.md +++ b/docs/sovereign/managing-sovereign.md @@ -1,3 +1,7 @@ +--- +description: "Learn about MultiversX Sovereign managing sovereign." +--- + # Managing Sovereign Chains :::note diff --git a/docs/sovereign/mvx-esdt-safe.md b/docs/sovereign/mvx-esdt-safe.md index dd6198163..6872c2b01 100644 --- a/docs/sovereign/mvx-esdt-safe.md +++ b/docs/sovereign/mvx-esdt-safe.md @@ -1,3 +1,7 @@ +--- +description: "Learn about MultiversX Sovereign mvx esdt safe." +--- + # Mvx-ESDT-Safe ![To Sovereign](../../static/sovereign/to-sovereign.png) diff --git a/docs/sovereign/one-click-deployment.md b/docs/sovereign/one-click-deployment.md index 623ae75d3..e38bd0b40 100644 --- a/docs/sovereign/one-click-deployment.md +++ b/docs/sovereign/one-click-deployment.md @@ -1,3 +1,7 @@ +--- +description: "Learn about MultiversX Sovereign one click deployment." +--- + # One-Click Deployment ## One-click local sovereign deployment in Digital Ocean diff --git a/docs/sovereign/other-vm.md b/docs/sovereign/other-vm.md index 3e676785d..42aee291c 100644 --- a/docs/sovereign/other-vm.md +++ b/docs/sovereign/other-vm.md @@ -1,3 +1,7 @@ +--- +description: "Learn about MultiversX Sovereign other vm." +--- + # Other-VM :::note diff --git a/docs/sovereign/overview.md b/docs/sovereign/overview.md index a0f05ef42..5aaa98233 100644 --- a/docs/sovereign/overview.md +++ b/docs/sovereign/overview.md @@ -1,6 +1,7 @@ --- id: overview title: Sovereign - Overview +description: "Learn about MultiversX Sovereign sovereign -." --- [comment]: # (mx-context-auto) diff --git a/docs/sovereign/restaking.md b/docs/sovereign/restaking.md index b73932e70..4bb3c3c4b 100644 --- a/docs/sovereign/restaking.md +++ b/docs/sovereign/restaking.md @@ -1,3 +1,7 @@ +--- +description: "Learn about MultiversX Sovereign restaking." +--- + # Restaking ## Introduction diff --git a/docs/sovereign/security.md b/docs/sovereign/security.md index 5cc548391..c85d6a126 100644 --- a/docs/sovereign/security.md +++ b/docs/sovereign/security.md @@ -1,3 +1,7 @@ +--- +description: "Learn about MultiversX Sovereign security." +--- + # Security Considerations diff --git a/docs/sovereign/services.md b/docs/sovereign/services.md index 98ffcac97..3f0dd7819 100644 --- a/docs/sovereign/services.md +++ b/docs/sovereign/services.md @@ -1,3 +1,7 @@ +--- +description: "Learn about MultiversX Sovereign services." +--- + # Introduction ## Sovereign services diff --git a/docs/sovereign/software-dependencies.md b/docs/sovereign/software-dependencies.md index 2ee16ac24..b84b156fa 100644 --- a/docs/sovereign/software-dependencies.md +++ b/docs/sovereign/software-dependencies.md @@ -1,3 +1,7 @@ +--- +description: "Learn about MultiversX Sovereign software dependencies." +--- + # Software Dependencies Understanding and managing software dependencies is crucial for the successful deployment and maintenance of Sovereign Chains. Dependencies ensure that all components of your blockchain network work seamlessly together. This page outlines the key dependencies required for building and operating Sovereign Chains, including software libraries, frameworks, and tools. diff --git a/docs/sovereign/solana-l2.md b/docs/sovereign/solana-l2.md index a59a5617e..17b0299cc 100644 --- a/docs/sovereign/solana-l2.md +++ b/docs/sovereign/solana-l2.md @@ -1,3 +1,7 @@ +--- +description: "Learn about MultiversX Sovereign solana l2." +--- + # Solana L2 :::note diff --git a/docs/sovereign/sov-esdt-safe.md b/docs/sovereign/sov-esdt-safe.md index eb6b7516b..b06644586 100644 --- a/docs/sovereign/sov-esdt-safe.md +++ b/docs/sovereign/sov-esdt-safe.md @@ -1,3 +1,7 @@ +--- +description: "Learn about MultiversX Sovereign sov esdt safe." +--- + # Sov-ESDT-Safe ![From Sovereign](../../static/sovereign/from-sovereign.png) diff --git a/docs/sovereign/sovereign-api.md b/docs/sovereign/sovereign-api.md index c789160ca..46f1be88e 100644 --- a/docs/sovereign/sovereign-api.md +++ b/docs/sovereign/sovereign-api.md @@ -1,3 +1,7 @@ +--- +description: "Learn about MultiversX Sovereign sovereign api." +--- + # API service ## Deploy Sovereign Proxy service diff --git a/docs/sovereign/sovereign-explorer.md b/docs/sovereign/sovereign-explorer.md index 4d0909162..bf93dc582 100644 --- a/docs/sovereign/sovereign-explorer.md +++ b/docs/sovereign/sovereign-explorer.md @@ -1,3 +1,7 @@ +--- +description: "Learn about MultiversX Sovereign sovereign explorer." +--- + # Explorer service ## Deploy Explorer diff --git a/docs/sovereign/sovereign-wallet.md b/docs/sovereign/sovereign-wallet.md index 4a0e2f99c..4f28cdaae 100644 --- a/docs/sovereign/sovereign-wallet.md +++ b/docs/sovereign/sovereign-wallet.md @@ -1,3 +1,7 @@ +--- +description: "Learn about MultiversX Sovereign sovereign wallet." +--- + # Wallet service ## Deploy Lite Wallet diff --git a/docs/sovereign/standalone-evm.md b/docs/sovereign/standalone-evm.md index 520f860bc..b580ff2b9 100644 --- a/docs/sovereign/standalone-evm.md +++ b/docs/sovereign/standalone-evm.md @@ -1,3 +1,7 @@ +--- +description: "Learn about MultiversX Sovereign standalone evm." +--- + # Standalone EVM ## EVM as example diff --git a/docs/sovereign/system-requirements.md b/docs/sovereign/system-requirements.md index 62da26137..82da89193 100644 --- a/docs/sovereign/system-requirements.md +++ b/docs/sovereign/system-requirements.md @@ -1,3 +1,7 @@ +--- +description: "Learn about MultiversX Sovereign system requirements." +--- + # System Requirements :::note diff --git a/docs/sovereign/testing.md b/docs/sovereign/testing.md index 73b703d66..60b6fabdf 100644 --- a/docs/sovereign/testing.md +++ b/docs/sovereign/testing.md @@ -1,3 +1,7 @@ +--- +description: "Learn about MultiversX Sovereign testing." +--- + # Testing and Validation :::note diff --git a/docs/sovereign/token-economics.md b/docs/sovereign/token-economics.md index 6f3c9bfa0..d70827ce8 100644 --- a/docs/sovereign/token-economics.md +++ b/docs/sovereign/token-economics.md @@ -1,3 +1,7 @@ +--- +description: "Learn about MultiversX Sovereign token economics." +--- + # Token Economics ## Introduction diff --git a/docs/sovereign/token-management.md b/docs/sovereign/token-management.md index 114fa695c..88abf635b 100644 --- a/docs/sovereign/token-management.md +++ b/docs/sovereign/token-management.md @@ -1,3 +1,7 @@ +--- +description: "Learn about MultiversX Sovereign token management." +--- + # Sovereign Deposit Tokens Guide ## Main Chain -> Sovereign Chain diff --git a/docs/sovereign/validators.md b/docs/sovereign/validators.md index aef64bdbd..1d1cacf65 100644 --- a/docs/sovereign/validators.md +++ b/docs/sovereign/validators.md @@ -1,7 +1,12 @@ +--- +id: validators +title: Validators +description: "Validators in Sovereign Chains: roles, selection, staking and security considerations for independent networks." +--- + # How to become a validator :::note This version of the documentation focuses solely on the essential steps required to set up and deploy a sovereign chain on either a local or remote computer. More content will be added as it is accepted and discussed on [Agora](https://agora.multiversx.com/), or once it is implemented and available for production. ::: - diff --git a/docs/sovereign/vm-intro.md b/docs/sovereign/vm-intro.md index ebe3eb0e4..a2bd86cc3 100644 --- a/docs/sovereign/vm-intro.md +++ b/docs/sovereign/vm-intro.md @@ -1,3 +1,7 @@ +--- +description: "Learn about MultiversX Sovereign vm intro." +--- + # Introduction The MultiversX protocol is designed so that integrating a new executor, a new processor, or even a completely new VM is straightforward. In essence, any new VM only needs to implement the `VMExecutionHandler` interface. Currently, there are two VMs running on MultiversX: diff --git a/docs/tokens/fungible-tokens.mdx b/docs/tokens/fungible-tokens.mdx index 21866f907..9bfd97795 100644 --- a/docs/tokens/fungible-tokens.mdx +++ b/docs/tokens/fungible-tokens.mdx @@ -1,6 +1,7 @@ --- id: fungible-tokens title: Fungible tokens +description: "ESDT fungible tokens: issuance, configuration properties, permissions, and on‑chain usage." --- ```mdx-code-block diff --git a/docs/tokens/intro.md b/docs/tokens/intro.md index a579a71a2..8bc3642f0 100644 --- a/docs/tokens/intro.md +++ b/docs/tokens/intro.md @@ -1,6 +1,7 @@ --- id: intro title: Intro to ESDT +description: "Introduction to ESDT token standards on MultiversX: fungible, non‑fungible and semi‑fungible basics." --- Fungible tokens, such as cryptocurrencies, are interchangeable and have the same value as other tokens of the same type. Non-fungible tokens (NFTs) are unique digital assets that are assigned unique identification codes and metadata, making them one-of-a-kind. Semi-fungible tokens are a combination of the two, offering both interchangeable and unique properties. For us they are ESDTs. diff --git a/docs/tokens/nft-tokens.mdx b/docs/tokens/nft-tokens.mdx index c97674ee6..63d5f2489 100644 --- a/docs/tokens/nft-tokens.mdx +++ b/docs/tokens/nft-tokens.mdx @@ -1,6 +1,7 @@ --- id: nft-tokens title: NFT & SFT tokens +description: "NFTs and SFTs on MultiversX: issuance, roles, properties, transfer mechanics and branding best practices." --- ```mdx-code-block diff --git a/docs/utils.mdx b/docs/utils.mdx index c09d6cabc..37d93b62c 100644 --- a/docs/utils.mdx +++ b/docs/utils.mdx @@ -1,6 +1,7 @@ --- id: utils title: Markdown Examples +description: "Learn about markdown examples in MultiversX." --- import Tabs from "@theme/Tabs"; @@ -41,7 +42,6 @@ _italicized text_ `code` ### Horizontal Rule - --- ### Link diff --git a/docs/validators/delegation-dashboard.md b/docs/validators/delegation-dashboard.md index 62630303b..79329dd9c 100644 --- a/docs/validators/delegation-dashboard.md +++ b/docs/validators/delegation-dashboard.md @@ -1,6 +1,7 @@ --- id: delegation-dashboard title: Delegation Dashboard +description: "Manage delegation smart contracts with a visual dashboard: staking, rewards, and provider operations." --- [comment]: # (mx-abstract) diff --git a/docs/validators/delegation-manager.mdx b/docs/validators/delegation-manager.mdx index e1fa471d3..7e975f9ac 100644 --- a/docs/validators/delegation-manager.mdx +++ b/docs/validators/delegation-manager.mdx @@ -1,6 +1,7 @@ --- id: delegation-manager title: Staking Providers +description: "Guidance for MultiversX validators: staking providers." --- ```mdx-code-block diff --git a/docs/validators/faq.md b/docs/validators/faq.md index 025d19f18..6bd0eb8ef 100644 --- a/docs/validators/faq.md +++ b/docs/validators/faq.md @@ -1,6 +1,7 @@ --- id: faq title: FAQs +description: "Frequently asked questions for validators and node operators: setup, operations, staking and troubleshooting." --- [comment]: # (mx-abstract) diff --git a/docs/validators/import-db.md b/docs/validators/import-db.md index 2516ed239..ab2791385 100644 --- a/docs/validators/import-db.md +++ b/docs/validators/import-db.md @@ -1,6 +1,7 @@ --- id: import-db title: Import DB +description: "Run a node in import‑db mode to rebuild state and reprocess historical blocks and transactions." --- [comment]: # (mx-abstract) diff --git a/docs/validators/key-management/multikey-nodes.md b/docs/validators/key-management/multikey-nodes.md index 001887d54..f02088922 100644 --- a/docs/validators/key-management/multikey-nodes.md +++ b/docs/validators/key-management/multikey-nodes.md @@ -1,6 +1,7 @@ --- id: multikey-nodes title: Multikey nodes management +description: "Manage multiple validator keys across node groups: architecture, setup and operational procedures." --- [comment]: # (mx-abstract) diff --git a/docs/validators/key-management/protect-keys.md b/docs/validators/key-management/protect-keys.md index f4b556a7c..269e29ca0 100644 --- a/docs/validators/key-management/protect-keys.md +++ b/docs/validators/key-management/protect-keys.md @@ -1,6 +1,7 @@ --- id: protect-keys title: Protecting your keys +description: "Best practices for securing validator and wallet keys: storage, access control, rotation and recovery." --- [comment]: # (mx-abstract) diff --git a/docs/validators/key-management/validator-keys.md b/docs/validators/key-management/validator-keys.md index c8b488132..55aba44c7 100644 --- a/docs/validators/key-management/validator-keys.md +++ b/docs/validators/key-management/validator-keys.md @@ -1,6 +1,7 @@ --- id: validator-keys title: Validator Keys +description: "Validator keys: purpose, generation, secure handling and usage in consensus." --- [comment]: # (mx-abstract) @@ -19,8 +20,8 @@ The **Validator Keys** are located in the `validatorKey.pem` file, which is gene Below you can find their anatomy and how to extract the information from them Example: - ------BEGIN PRIVATE KEY for _45e7131ba37e05c5de3f8862b4d8294812f004a5b660abb793e89b65816dbff2b02f54c25f139359c9c98be0fa657d0bf1ae4115dcf6fdbf5f3a470f1d251f769610b48fe34eeab59e82ac1cc0336d1d9109a14b768b97ccb4db4c2431629688_----- +--- +--BEGIN PRIVATE KEY for _45e7131ba37e05c5de3f8862b4d8294812f004a5b660abb793e89b65816dbff2b02f54c25f139359c9c98be0fa657d0bf1ae4115dcf6fdbf5f3a470f1d251f769610b48fe34eeab59e82ac1cc0336d1d9109a14b768b97ccb4db4c2431629688_----- **YmRiNmViOGYzMmQ3OWY0YjE4ODJjMzE1ODA4YjQyZmZjODhiZDQxNzMwNmE5MTRiZjQ4OTAyNjM0MTcyNjMzMw==** diff --git a/docs/validators/key-management/wallet-keys.md b/docs/validators/key-management/wallet-keys.md index 24bfe2143..742858119 100644 --- a/docs/validators/key-management/wallet-keys.md +++ b/docs/validators/key-management/wallet-keys.md @@ -1,6 +1,7 @@ --- id: wallet-keys title: Wallet Keys +description: "Wallet keys used for staking and node management: types, usage and security considerations." --- [comment]: # (mx-abstract) diff --git a/docs/validators/mainnet/config-scripts.md b/docs/validators/mainnet/config-scripts.md index 246536823..eb4e95132 100644 --- a/docs/validators/mainnet/config-scripts.md +++ b/docs/validators/mainnet/config-scripts.md @@ -1,6 +1,7 @@ --- id: config-scripts title: Scripts & User config +description: "Scripts and user configuration for installing and maintaining MultiversX nodes on mainnet, testnet and devnet." --- :::caution diff --git a/docs/validators/mainnet/install-update.md b/docs/validators/mainnet/install-update.md index 0ee088005..7cd75c85e 100644 --- a/docs/validators/mainnet/install-update.md +++ b/docs/validators/mainnet/install-update.md @@ -1,6 +1,7 @@ --- id: install-update title: Installing a Validator Node +description: "Install and update a MultiversX validator node using the provided scripts (section deprecated in favor of unified scripts)." --- :::caution diff --git a/docs/validators/mainnet/optional-configs.md b/docs/validators/mainnet/optional-configs.md index ac9700a64..111cb5a2a 100644 --- a/docs/validators/mainnet/optional-configs.md +++ b/docs/validators/mainnet/optional-configs.md @@ -1,6 +1,7 @@ --- id: optional-configs title: Optional Configurations +description: "Optional validator node configuration tweaks and parameters (deprecated; see unified scripts)." --- :::caution diff --git a/docs/validators/mainnet/use-docker.md b/docs/validators/mainnet/use-docker.md index f517073dd..ca07e6889 100644 --- a/docs/validators/mainnet/use-docker.md +++ b/docs/validators/mainnet/use-docker.md @@ -1,6 +1,7 @@ --- id: use-docker title: How to use the Docker Image +description: "Run validator nodes using the official Docker image and scripts (deprecated; see unified scripts)." --- :::caution diff --git a/docs/validators/node-cli.md b/docs/validators/node-cli.md index e247de479..215272ff4 100644 --- a/docs/validators/node-cli.md +++ b/docs/validators/node-cli.md @@ -1,6 +1,7 @@ --- id: node-cli title: Node CLI +description: "Node command‑line flags and utilities from mx‑chain‑go: startup options, configuration, debugging and maintenance." --- [comment]: # (mx-abstract) diff --git a/docs/validators/node-configuration.md b/docs/validators/node-configuration.md index 1ffd14e8c..04dd6e68c 100644 --- a/docs/validators/node-configuration.md +++ b/docs/validators/node-configuration.md @@ -1,6 +1,7 @@ --- id: node-configuration title: Node Configuration +description: "Configure MultiversX validator nodes: key configuration files, what they control, safe parameters to adjust, and constraints tied to network consensus." --- [comment]: # (mx-context-auto) diff --git a/docs/validators/node-databases.md b/docs/validators/node-databases.md index ba2da372c..04b1bb4b7 100644 --- a/docs/validators/node-databases.md +++ b/docs/validators/node-databases.md @@ -1,6 +1,7 @@ --- id: node-databases title: Node Databases +description: "Overview of node databases: key‑value stores, what data they contain, and how they are used by the protocol." --- [comment]: # (mx-abstract) diff --git a/docs/validators/node-upgrades.md b/docs/validators/node-upgrades.md index 56dc3829f..78b553db2 100644 --- a/docs/validators/node-upgrades.md +++ b/docs/validators/node-upgrades.md @@ -1,6 +1,7 @@ --- id: node-upgrades title: MultiversX Node upgrades +description: "Plan and execute MultiversX node upgrades safely: scheduling, compatibility considerations, and operational steps." --- [comment]: # (mx-abstract) diff --git a/docs/validators/nodes-scripts/config-scripts.md b/docs/validators/nodes-scripts/config-scripts.md index 5f01edd5b..ca5ff91ff 100644 --- a/docs/validators/nodes-scripts/config-scripts.md +++ b/docs/validators/nodes-scripts/config-scripts.md @@ -1,6 +1,7 @@ --- id: config-scripts title: Scripts & User config +description: "Install and configure MultiversX nodes using mx‑chain‑scripts: prerequisites, configuration and setup." --- [comment]: # (mx-abstract) diff --git a/docs/validators/nodes-scripts/install-update.md b/docs/validators/nodes-scripts/install-update.md index 366fec782..d7419432c 100644 --- a/docs/validators/nodes-scripts/install-update.md +++ b/docs/validators/nodes-scripts/install-update.md @@ -1,6 +1,7 @@ --- id: install-update title: Installing a Validator Node +description: "Install and update validator nodes via the script menu: installation, upgrades and maintenance actions." --- [comment]: # (mx-abstract) diff --git a/docs/validators/nodes-scripts/manage-node.md b/docs/validators/nodes-scripts/manage-node.md index f658412c1..60e7a04fa 100644 --- a/docs/validators/nodes-scripts/manage-node.md +++ b/docs/validators/nodes-scripts/manage-node.md @@ -1,6 +1,7 @@ --- id: manage-node title: Manage a validator node +description: "Manage validator nodes with scripts: start/stop, upgrades, database operations and routine maintenance." --- Your node will start as an observer. In order to make it into a validator, you will need to have 2500 xEGLD tokens. You can reach out to an admin in our [Telegram community](https://t.me/MultiversXValidators) who will gladly help. diff --git a/docs/validators/nodes-scripts/use-docker.md b/docs/validators/nodes-scripts/use-docker.md index 3874776fd..af124ea00 100644 --- a/docs/validators/nodes-scripts/use-docker.md +++ b/docs/validators/nodes-scripts/use-docker.md @@ -1,6 +1,7 @@ --- id: use-docker title: How to use the Docker Image +description: "Deploy and manage MultiversX nodes with Docker using the provided scripts and images." --- [comment]: # (mx-abstract) diff --git a/docs/validators/operation-modes.md b/docs/validators/operation-modes.md index c70d72f8d..b90d769e6 100644 --- a/docs/validators/operation-modes.md +++ b/docs/validators/operation-modes.md @@ -1,6 +1,7 @@ --- id: node-operation-modes title: Node operation modes +description: "Supported node operation modes and configuration patterns for observers, validators, and specialized roles." --- [comment]: # (mx-abstract) diff --git a/docs/validators/overview.md b/docs/validators/overview.md index 452648bd9..9a98e2526 100644 --- a/docs/validators/overview.md +++ b/docs/validators/overview.md @@ -1,6 +1,7 @@ --- id: overview title: Validators - Overview +description: "Overview of MultiversX validator nodes, responsibilities, lifecycle, tooling and operational workflows." --- [comment]: # (mx-abstract) diff --git a/docs/validators/rating.md b/docs/validators/rating.md index 3c3e46f61..0a02bb6fe 100644 --- a/docs/validators/rating.md +++ b/docs/validators/rating.md @@ -1,6 +1,7 @@ --- id: rating title: Rating +description: "Validator rating system: how scores are computed, how they influence consensus selection, and how to recover from penalties." --- [comment]: # (mx-abstract) diff --git a/docs/validators/redundancy.md b/docs/validators/redundancy.md index 3a81147b7..8c17a595b 100644 --- a/docs/validators/redundancy.md +++ b/docs/validators/redundancy.md @@ -1,6 +1,7 @@ --- id: redundancy title: Node redundancy +description: "High‑availability setups for validator nodes: standby instances, failover strategies, and configuration guidance." --- [comment]: # (mx-abstract) diff --git a/docs/validators/staking-v4.md b/docs/validators/staking-v4.md index a1760a68b..d13825252 100644 --- a/docs/validators/staking-v4.md +++ b/docs/validators/staking-v4.md @@ -1,6 +1,7 @@ --- id: staking-v4 title: Staking v4 +description: "What’s new in Staking v4: protocol changes, validator impact, reward mechanics and migration notes." --- [comment]: # (mx-context-auto) @@ -375,4 +376,3 @@ The specific parameters, including the initial limit and `NodeLimitPercentage`, vote. This ensures community involvement in determining the rules governing node ownership. The actual limit is 50 nodes per provider with the calculation details from the 'systemSmartContractsConfig.toml' [here](https://github.com/multiversx/mx-chain-mainnet-config/blob/2ca2da07427c5a802202d1ed364a923f0e366f13/systemSmartContractsConfig.toml#L15) - diff --git a/docs/validators/staking/convert-existing-validator-into-staking-provider.md b/docs/validators/staking/convert-existing-validator-into-staking-provider.md index f581eb36d..90053c00e 100644 --- a/docs/validators/staking/convert-existing-validator-into-staking-provider.md +++ b/docs/validators/staking/convert-existing-validator-into-staking-provider.md @@ -1,6 +1,7 @@ --- id: convert-existing-validator-into-staking-provider title: Convert An Existing Validator Into A Staking Provider +description: "Convert a validator into a staking provider under Staking Phase 3.5: deploy a delegation contract and migrate without losing validator slots." --- Staking Phase 3.5 introduced the ability for an existing Validator to create a new delegation smart contract and have their validator node(s) added in the delegation smart contract directly. This is different from before, when in order to do this, a Validator node was to be unstaked, and then placed at the back of the queue. With Staking Phase 3.5, Validators can retain the place inside the 3,200 Validator nodes, and start accepting non-custodial delegations. diff --git a/docs/validators/staking/merge-validator-delegation-sc.md b/docs/validators/staking/merge-validator-delegation-sc.md index 7876e0a6c..8eff439df 100644 --- a/docs/validators/staking/merge-validator-delegation-sc.md +++ b/docs/validators/staking/merge-validator-delegation-sc.md @@ -1,6 +1,7 @@ --- id: merge-validator-delegation-sc title: Merging A Validator Into An Existing Delegation Smart Contract +description: "Merge a standalone validator into an existing delegation smart contract: whitelist, merge transaction and operational notes." --- Introduced in Staking Phase 3.5, the ability of merging one or more existing standalone validator node into a staking provider gives more flexibility for staking provider operators. diff --git a/docs/validators/staking/staking-smart-contract.md b/docs/validators/staking/staking-smart-contract.md index 84282e769..7a7de68cb 100644 --- a/docs/validators/staking/staking-smart-contract.md +++ b/docs/validators/staking/staking-smart-contract.md @@ -1,6 +1,7 @@ --- id: staking-smart-contract title: The Staking Smart Contract +description: "Operations of the Staking system smart contract: staking, parameters, and example transactions for common tasks." --- [comment]: # (mx-abstract) diff --git a/docs/validators/staking/staking-unstaking-unjailing.md b/docs/validators/staking/staking-unstaking-unjailing.md index 95031e437..8ab90c004 100644 --- a/docs/validators/staking/staking-unstaking-unjailing.md +++ b/docs/validators/staking/staking-unstaking-unjailing.md @@ -1,6 +1,7 @@ --- id: staking-unstaking-unjailing title: Staking, unstaking and unjailing +description: "Pointers to staking & unstaking and unjailing procedures, with links to the detailed guides." --- The content of this page was moved to [Staking & Unstaking](/validators/staking) and [Unjailing](/validators/staking/unjailing). diff --git a/docs/validators/staking/staking.md b/docs/validators/staking/staking.md index 0ea9ab0df..0fca9ca1b 100644 --- a/docs/validators/staking/staking.md +++ b/docs/validators/staking/staking.md @@ -1,6 +1,7 @@ --- id: staking title: Staking & Unstaking +description: "Stake and unstake validator nodes: prerequisites, transactions, reward addresses and operational workflow." --- [comment]: # (mx-abstract) diff --git a/docs/validators/staking/unjailing.md b/docs/validators/staking/unjailing.md index c368758b4..1e4274ace 100644 --- a/docs/validators/staking/unjailing.md +++ b/docs/validators/staking/unjailing.md @@ -1,6 +1,7 @@ --- id: unjailing title: Unjailing +description: "Guidance for MultiversX validators: unjailing." --- [comment]: # (mx-abstract) diff --git a/docs/validators/system-requirements.md b/docs/validators/system-requirements.md index af5f14441..7c6a224a9 100644 --- a/docs/validators/system-requirements.md +++ b/docs/validators/system-requirements.md @@ -1,6 +1,7 @@ --- id: system-requirements title: System Requirements +description: "Hardware, OS, storage and network requirements for running MultiversX validator nodes in production." --- [comment]: # (mx-abstract) diff --git a/docs/validators/useful-links-tools.md b/docs/validators/useful-links-tools.md index 0395e4ee1..4ce9704e4 100644 --- a/docs/validators/useful-links-tools.md +++ b/docs/validators/useful-links-tools.md @@ -1,6 +1,7 @@ --- id: useful-links title: Useful Links & Tools +description: "Curated links, documentation and utilities helpful for MultiversX validators and node operators." --- [comment]: # (mx-abstract) diff --git a/docs/wallet/keystore.md b/docs/wallet/keystore.md index 1a5314260..7771d0230 100644 --- a/docs/wallet/keystore.md +++ b/docs/wallet/keystore.md @@ -1,6 +1,7 @@ --- id: keystore title: Keystore files +description: "Create, import and use keystore files safely with the MultiversX Web Wallet and tooling." --- [comment]: # (mx-abstract) diff --git a/docs/wallet/ledger.md b/docs/wallet/ledger.md index ad496f165..5f8f36351 100644 --- a/docs/wallet/ledger.md +++ b/docs/wallet/ledger.md @@ -1,6 +1,7 @@ --- id: ledger title: Ledger +description: "Set up and use a Ledger device with MultiversX: install the app, connect securely, and sign transactions safely." --- [comment]: # (mx-abstract) diff --git a/docs/wallet/overview.md b/docs/wallet/overview.md index fafec9f0e..cb483576a 100644 --- a/docs/wallet/overview.md +++ b/docs/wallet/overview.md @@ -1,6 +1,7 @@ --- id: overview title: Wallets - Overview +description: "Overview of MultiversX wallets: Web Wallet, xPortal, Ledger and DeFi Wallet, with setup and usage guides." --- [comment]: # (mx-abstract) diff --git a/docs/wallet/wallet-extension.md b/docs/wallet/wallet-extension.md index 5ce0c211e..19375044f 100644 --- a/docs/wallet/wallet-extension.md +++ b/docs/wallet/wallet-extension.md @@ -1,6 +1,7 @@ --- id: wallet-extension title: MultiversX DeFi Wallet +description: "Browser extension for MultiversX: install and set up the DeFi Wallet, manage multiple accounts, and interact with dApps." --- [comment]: # (mx-abstract) diff --git a/docs/wallet/wallet-token.md b/docs/wallet/wallet-token.md index bb3d02bbc..269641d73 100644 --- a/docs/wallet/wallet-token.md +++ b/docs/wallet/wallet-token.md @@ -1,6 +1,7 @@ --- id: create-a-fungible-token title: Web Wallet Tokens +description: "Issue and manage ESDT tokens in the MultiversX Web Wallet: prerequisites, creation steps, configuration properties, and transferring tokens." --- [comment]: # (mx-context-auto) diff --git a/docs/wallet/web-wallet.md b/docs/wallet/web-wallet.md index abb157e19..d09f47421 100644 --- a/docs/wallet/web-wallet.md +++ b/docs/wallet/web-wallet.md @@ -1,6 +1,7 @@ --- id: web-wallet title: Web Wallet +description: "Create or import accounts, send and receive EGLD and tokens, stake, and securely manage assets using the MultiversX Web Wallet." --- [comment]: # (mx-abstract) diff --git a/docs/wallet/webhooks.md b/docs/wallet/webhooks.md index fd9eb3a8d..1d8515c0d 100644 --- a/docs/wallet/webhooks.md +++ b/docs/wallet/webhooks.md @@ -1,6 +1,7 @@ --- id: webhooks title: Webhooks +description: "Integrate with the Web Wallet via URL webhooks to request login and pre‑filled transactions, with callback redirects." --- [comment]: # (mx-abstract) diff --git a/docs/wallet/xalias.md b/docs/wallet/xalias.md index 10e6d5db9..dc5e9ab1a 100644 --- a/docs/wallet/xalias.md +++ b/docs/wallet/xalias.md @@ -1,6 +1,7 @@ --- id: xalias title: xAlias +description: "xAlias single sign‑on for MultiversX: Google‑based onboarding, self‑custody with later conversion, and Web Wallet‑compatible integration hooks." --- [comment]: # (mx-abstract) diff --git a/docs/wallet/xportal.md b/docs/wallet/xportal.md index 9f1e54fd7..51119bc36 100644 --- a/docs/wallet/xportal.md +++ b/docs/wallet/xportal.md @@ -1,6 +1,7 @@ --- id: xportal title: xPortal +description: "xPortal mobile app: secure wallet, token swaps, payments, missions, AI avatar creation and upcoming debit card features." --- [comment]: # (mx-abstract) diff --git a/docs/welcome/terminology.md b/docs/welcome/terminology.md index ff1e5a152..122dcf1d2 100644 --- a/docs/welcome/terminology.md +++ b/docs/welcome/terminology.md @@ -1,6 +1,7 @@ --- id: terminology title: Terminology +description: "Learn about terminology in MultiversX." --- **Metachain**: the blockchain that runs in a special shard, where the main responsibilities are not processing transactions, diff --git a/docs/welcome/welcome-to-multiversx.md b/docs/welcome/welcome-to-multiversx.md index bb92a301f..08f1118af 100644 --- a/docs/welcome/welcome-to-multiversx.md +++ b/docs/welcome/welcome-to-multiversx.md @@ -1,6 +1,7 @@ --- id: welcome-to-multiversx title: Welcome to MultiversX +description: "Learn about welcome to multiversx in MultiversX." --- [comment]: # (mx-abstract) diff --git a/package.json b/package.json index a0711e8d6..6eb3b8f76 100644 --- a/package.json +++ b/package.json @@ -12,13 +12,15 @@ "scripts": { "docusaurus": "docusaurus", "start": "docusaurus start", + "prebuild": "node scripts/generate-llms-txt.js", "build": "docusaurus build", "swizzle": "docusaurus swizzle", "deploy": "docusaurus deploy", "clear": "docusaurus clear", "serve": "docusaurus serve", "write-translations": "docusaurus write-translations", - "write-heading-ids": "docusaurus write-heading-ids" + "write-heading-ids": "docusaurus write-heading-ids", + "generate:llms": "node scripts/generate-llms-txt.js" }, "dependencies": { "@docusaurus/core": "3.8.1", diff --git a/scripts/generate-llms-txt.js b/scripts/generate-llms-txt.js new file mode 100644 index 000000000..b16aebb85 --- /dev/null +++ b/scripts/generate-llms-txt.js @@ -0,0 +1,371 @@ +#!/usr/bin/env node +/* + generate-llms-txt.js + + Scans the Docusaurus sidebar and docs to produce a categorized llms.txt + in a human-friendly format similar to llmstxt.org examples: a header + with site title and description, followed by sections (## Category) + and bullet links "- [Title](URL): Description". The output is written + to static/llms.txt so it is served at /llms.txt. + + Usage: node scripts/generate-llms-txt.js +*/ + +const fs = require('fs'); +const fsp = require('fs/promises'); +const path = require('path'); + +const ROOT = path.join(__dirname, '..'); +const DOCS_DIR = path.join(ROOT, 'docs'); +const STATIC_DIR = path.join(ROOT, 'static'); +const OUTPUT_FILE = path.join(STATIC_DIR, 'llms.txt'); + +function safeRequire(p) { + try { + return require(p); + } catch (e) { + return null; + } +} + +// Read site meta from docusaurus.config.js (best effort) +async function getSiteMeta() { + const configPath = path.join(ROOT, 'docusaurus.config.js'); + try { + const content = await fsp.readFile(configPath, 'utf8'); + const titleMatch = content.match(/\btitle:\s*["']([^"']+)["']/); + const taglineMatch = content.match(/\btagline:\s*\n\s*["']([\s\S]*?)["'],/); + const urlMatch = content.match(/\burl:\s*["']([^"']+)["']/); + const baseMatch = content.match(/\bbaseUrl:\s*["']([^"']+)["']/); + const url = urlMatch ? urlMatch[1] : ''; + const base = baseMatch ? baseMatch[1] : '/'; + const baseNorm = base.startsWith('/') ? base : `/${base}`; + const siteUrl = url ? `${url.replace(/\/$/, '')}${baseNorm}`.replace(/\/$/, '') : ''; + return { + siteUrl, + title: (titleMatch ? titleMatch[1] : 'Documentation').trim(), + tagline: (taglineMatch ? taglineMatch[1] : '').trim(), + }; + } catch { + return { siteUrl: '', title: 'Documentation', tagline: '' }; + } +} + +function readFrontmatterSlug(mdContent) { + // Extract slug from YAML frontmatter if present + // Very light parser: only first frontmatter block + if (!mdContent.startsWith('---')) return null; + const end = mdContent.indexOf('\n---', 3); + if (end === -1) return null; + const block = mdContent.slice(3, end); + const m = block.match(/^\s*slug:\s*(["']?)(.+?)\1\s*$/m); + return m ? m[2].trim() : null; +} + +async function resolveDocPath(docId) { + // 1) Direct mapping + const directMd = path.join(DOCS_DIR, `${docId}.md`); + const directMdx = path.join(DOCS_DIR, `${docId}.mdx`); + if (fs.existsSync(directMd)) return directMd; + if (fs.existsSync(directMdx)) return directMdx; + + // 2) Try kebab-casing the last segment (spaces -> '-') + const dir = path.join(DOCS_DIR, path.dirname(docId)); + const base = path.basename(docId); + const kebab = base.replace(/\s+/g, '-'); + const kebabMd = path.join(dir, `${kebab}.md`); + const kebabMdx = path.join(dir, `${kebab}.mdx`); + if (fs.existsSync(kebabMd)) return kebabMd; + if (fs.existsSync(kebabMdx)) return kebabMdx; + + // 3) Scan directory for file whose frontmatter id matches the base + try { + const entries = await fsp.readdir(dir, { withFileTypes: true }); + for (const e of entries) { + if (!e.isFile()) continue; + if (!/\.(md|mdx)$/i.test(e.name)) continue; + const full = path.join(dir, e.name); + try { + const content = await fsp.readFile(full, 'utf8'); + if (content.startsWith('---')) { + const end = content.indexOf('\n---', 3); + if (end !== -1) { + const block = content.slice(3, end); + const idm = block.match(/^\s*id:\s*(["']?)(.+?)\1\s*$/m); + const fid = idm ? idm[2].trim() : null; + if (fid && fid === base) { + return full; + } + } + } + } catch {} + } + } catch {} + + return null; +} + +async function computeUrlForDoc(docId, siteUrl) { + // Try to resolve the actual file path + const filePath = await resolveDocPath(docId); + let defaultPath = `/${docId}`; + if (filePath) { + const rel = path.relative(DOCS_DIR, filePath).replace(/\\/g, '/'); + defaultPath = `/${rel.replace(/\.(md|mdx)$/i, '')}`; + } + // If file has slug starting with "/", prefer that + if (filePath) { + try { + const content = await fsp.readFile(filePath, 'utf8'); + const slug = readFrontmatterSlug(content); + if (slug && slug.startsWith('/')) { + return siteUrl ? `${siteUrl}${slug}` : slug; + } + } catch {} + } + return siteUrl ? `${siteUrl}${defaultPath}` : defaultPath; +} + +function isString(x) { + return typeof x === 'string' || x instanceof String; +} + +function collectDocIdsFromItems(items, acc) { + if (!items) return; + for (const it of items) { + if (isString(it)) { + acc.add(String(it)); + continue; + } + if (it && typeof it === 'object') { + if (it.type === 'category') { + // Include linked doc for category, if present + if (it.link && it.link.type === 'doc' && it.link.id) { + acc.add(String(it.link.id)); + } + collectDocIdsFromItems(it.items, acc); + } else if (it.type === 'doc' && it.id) { + acc.add(String(it.id)); + } else if (it.id) { + acc.add(String(it.id)); + } + } + } +} + +function titleCaseFromSlug(slug) { + const name = slug.replace(/^.*\//, '').replace(/[-_]+/g, ' ').trim(); + return name + .split(' ') + .filter(Boolean) + .map((w) => w.charAt(0).toUpperCase() + w.slice(1)) + .join(' '); +} + +const SPECIAL_TITLE_SEGMENTS = new Map([ + ['rest-api', 'Rest API'], + ['sdk-and-tools', 'SDK and Tools'], +]); + +function humanizeSegmentForTitle(seg) { + const s = seg.trim(); + if (SPECIAL_TITLE_SEGMENTS.has(s)) return SPECIAL_TITLE_SEGMENTS.get(s); + return s + .split(/[-_]+/) + .filter(Boolean) + .map((w) => w.charAt(0).toUpperCase() + w.slice(1)) + .join(' '); +} + +function humanizeSegmentForDescription(seg) { + const t = humanizeSegmentForTitle(seg); + return t.toLowerCase(); +} + +// + +function parseFrontmatter(mdContent) { + const meta = {}; + if (!mdContent.startsWith('---')) return meta; + const end = mdContent.indexOf('\n---', 3); + if (end === -1) return meta; + const block = mdContent.slice(3, end); + const pairs = { + id: block.match(/^\s*id:\s*(["']?)(.+?)\1\s*$/m), + title: block.match(/^\s*title:\s*(["']?)(.+?)\1\s*$/m), + slug: block.match(/^\s*slug:\s*(["']?)(.+?)\1\s*$/m), + description: block.match(/^\s*description:\s*(["']?)([\s\S]*?)\1\s*$/m), + }; + if (pairs.id) meta.id = pairs.id[2].trim(); + if (pairs.title) meta.title = pairs.title[2].trim(); + if (pairs.slug) meta.slug = pairs.slug[2].trim(); + if (pairs.description) meta.description = pairs.description[2].trim(); + meta._fmEnd = end + '\n---'.length; + return meta; +} + +function extractFirstParagraph(mdContent) { + // Remove frontmatter + let content = mdContent; + if (mdContent.startsWith('---')) { + const end = mdContent.indexOf('\n---', 3); + if (end !== -1) content = mdContent.slice(end + 4); + } + const lines = content.split(/\r?\n/); + // Simple state to skip admonitions blocks ::: + let inAdmonition = false; + for (let i = 0; i < lines.length; i++) { + let line = lines[i].trim(); + if (!line) continue; + if (line.startsWith('[comment]:')) continue; + if (line.startsWith(':::')) { + inAdmonition = !inAdmonition; // toggle on start and end + continue; + } + if (inAdmonition) continue; + if (line.startsWith('#')) continue; // skip headings + if (line.startsWith('<')) continue; // skip raw html blocks + // collapse markdown links to text + line = line.replace(/\[([^\]]+)\]\([^\)]+\)/g, '$1'); + // remove inline code markers + line = line.replace(/`([^`]+)`/g, '$1'); + // compact whitespace + line = line.replace(/\s+/g, ' ').trim(); + if (line) return line; + } + return ''; +} + +async function getDocMeta(docId) { + const filePath = await resolveDocPath(docId); + let meta = { + id: docId, + title: titleCaseFromSlug(docId), + description: '', + filePath: null, + source: 'none', + }; + if (!filePath) return meta; + try { + const content = await fsp.readFile(filePath, 'utf8'); + const fm = parseFrontmatter(content); + let title = fm.title || meta.title; + const fromFm = !!fm.description; + const description = fm.description || extractFirstParagraph(content); + // Contextualize short/generic titles using parent segments when helpful + const rel = path.relative(DOCS_DIR, filePath).replace(/\\/g, '/').replace(/\.(md|mdx)$/i, ''); + const parts = rel.split('/').filter(Boolean); + if (parts.length >= 2) { + const parent = parts[parts.length - 2]; + const last = parts[parts.length - 1]; + const humanLast = humanizeSegmentForTitle(last); + const CONTEXT_PARENTS = new Set(['rest-api']); + if (CONTEXT_PARENTS.has(parent) && title.trim() === humanLast) { + title = `${humanizeSegmentForTitle(parent)} ${title}`; + } + } + meta = { ...meta, title, description, filePath, source: fromFm ? 'frontmatter' : (description ? 'content' : 'none') }; + } catch { + // ignore + } + return meta; +} + +function isPoorDescription(desc) { + if (!desc) return true; + const d = desc.trim(); + if (!d) return true; + if (d.startsWith('```')) return true; + if (/mdx-code-block/i.test(d)) return true; + if (/^:::/m.test(d)) return true; + if (/please\s+take\s+note/i.test(d)) return true; + // Very short and generic + if (d.length < 20) return true; + return false; +} + +function brandFromTitle(siteTitle) { + if (!siteTitle) return 'MultiversX'; + // Remove trailing words like 'Docs', 'Documentation' + return siteTitle.replace(/\s+(Docs|Documentation)$/i, '').trim(); +} + +// + +function generatedDescriptionFromPath(idOrPath, filePath, brand) { + const ref = filePath + ? path.relative(DOCS_DIR, filePath).replace(/\\/g, '/').replace(/\.(md|mdx)$/i, '') + : idOrPath; + let parts = ref.split('/').filter(Boolean); + parts = parts.filter((p) => p !== 'index'); + if (parts.length >= 2 && parts[parts.length - 1].toLowerCase() === 'overview') { + parts = parts.slice(0, -1); + } + if (parts.length === 0) return `Learn more about ${brand} documentation`; + const phrase = parts.map(humanizeSegmentForDescription).join(' '); + return `Learn more about ${brand} ${phrase}`.trim(); +} + +async function main() { + const site = await getSiteMeta(); + const siteUrl = site.siteUrl; + const brand = brandFromTitle(site.title || 'MultiversX'); + const sidebars = safeRequire(path.join(ROOT, 'sidebars.js')); + if (!sidebars || !sidebars.docs) { + console.error('Could not load sidebars.js or missing "docs" sidebar.'); + process.exit(1); + } + + // In this repository, sidebars.docs is an object of top-level category labels -> items[] + const categories = sidebars.docs; + const outputLines = []; + + // Header with site title and tagline + const headerTitle = site.title || 'Documentation'; + outputLines.push(`# ${headerTitle}`); + if (site.tagline) outputLines.push('', `> ${site.tagline}`); + outputLines.push( + 'MultiversX is a highly scalable, fast and secure blockchain platform. This documentation covers architecture, smart contracts, SDKs, APIs, wallets, validators, and the broader ecosystem.' + ); + outputLines.push( + 'This documentation is organized into major sections. Each section includes tutorials, examples, and detailed technical references.' + ); + outputLines.push(''); + + for (const [categoryLabel, items] of Object.entries(categories)) { + const ids = new Set(); + collectDocIdsFromItems(items, ids); + if (ids.size === 0) continue; + + outputLines.push(`## ${categoryLabel}`); + // Build entries with title + description + url + const all = []; + for (const id of Array.from(ids)) { + // eslint-disable-next-line no-await-in-loop + const meta = await getDocMeta(id); + // eslint-disable-next-line no-await-in-loop + const url = await computeUrlForDoc(id, siteUrl); + all.push({ id, title: meta.title, description: meta.description, url, source: meta.source, filePath: meta.filePath }); + } + all.sort((a, b) => a.title.localeCompare(b.title)); + for (const e of all) { + let desc = (e.description || '').replace(/\s+/g, ' ').trim(); + if (e.source !== 'frontmatter' && isPoorDescription(desc)) { + desc = generatedDescriptionFromPath(e.id, e.filePath, brand); + } + const clipped = desc.length > 400 ? `${desc.slice(0, 397)}...` : desc; + outputLines.push(`- [${e.title}](${e.url})${clipped ? `: ${clipped}` : ''}`); + } + outputLines.push(''); + } + + // Ensure static directory exists + await fsp.mkdir(STATIC_DIR, { recursive: true }); + await fsp.writeFile(OUTPUT_FILE, outputLines.join('\n'), 'utf8'); + console.log(`Wrote ${OUTPUT_FILE}`); +} + +main().catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/sidebars.js b/sidebars.js index a234e5b74..61e0612fe 100644 --- a/sidebars.js +++ b/sidebars.js @@ -43,6 +43,7 @@ const sidebars = { "learn/space-vm", "learn/transactions", "learn/economics", + "learn/ai-agents", ], Developers: [ "developers/overview", diff --git a/static/llms.txt b/static/llms.txt new file mode 100644 index 000000000..097a4e83d --- /dev/null +++ b/static/llms.txt @@ -0,0 +1,290 @@ +# MultiversX Docs + +> A highly scalable, fast and secure blockchain platform for distributed apps, enterprise use cases and the new internet economy. +MultiversX is a highly scalable, fast and secure blockchain platform. This documentation covers architecture, smart contracts, SDKs, APIs, wallets, validators, and the broader ecosystem. +This documentation is organized into major sections. Each section includes tutorials, examples, and detailed technical references. + +## Welcome to MultiversX +- [Welcome to MultiversX](https://docs.multiversx.com/welcome/welcome-to-multiversx): Learn about welcome to multiversx in MultiversX. + +## Learn about MultiversX +- [AI agents](https://docs.multiversx.com/learn/ai-agents): Learn how to use AI agents for your MultiversX projects +- [Architecture Overview](https://docs.multiversx.com/learn/architecture-overview): Overview of architecture in MultiversX. +- [Chronology](https://docs.multiversx.com/learn/chronology): Time structure in MultiversX: rounds, epochs and how they govern block production and finality. +- [Consensus](https://docs.multiversx.com/learn/consensus): Secure Proof‑of‑Stake (SPoS) in MultiversX: validator selection, randomness, BLS aggregation and single‑block finality. +- [Economics](https://docs.multiversx.com/learn/economics): Economics of MultiversX: token supply, fees, rewards, inflation and distribution mechanics. +- [Entities](https://docs.multiversx.com/learn/entities): Core entities in MultiversX: users, accounts, nodes and how they interact on‑chain. +- [Getting Started](https://docs.multiversx.com/learn/getting-started): Overview of getting started in MultiversX. +- [MultiversX Virtual Machine (SpaceVM)](https://docs.multiversx.com/learn/space-vm): The MultiversX Virtual Machine (SpaceVM): WASM execution, Rust framework support and performance characteristics. +- [Sharding](https://docs.multiversx.com/learn/sharding): Overview of sharding in MultiversX. +- [Transactions](https://docs.multiversx.com/learn/transactions): Overview of transactions in MultiversX. +- [What is EGLD?](https://docs.multiversx.com/learn/EGLD): Overview of egld in MultiversX. +- [What is MultiversX?](https://docs.multiversx.com/learn/multiversx-ecosystem): What MultiversX is and how the ecosystem fits together: networks, apps, tooling and core concepts. + +## Developers +- [ABI](https://docs.multiversx.com/developers/data/abi): Learn about abi in MultiversX. +- [Account storage](https://docs.multiversx.com/developers/account-storage): Learn about account storage in MultiversX. +- [accounts](https://docs.multiversx.com/sdk-and-tools/indices/accounts): Overview of MultiversX SDK and Tools accounts. +- [accountsesdt](https://docs.multiversx.com/sdk-and-tools/indices/accountsesdt): Overview of MultiversX SDK and Tools accountsesdt. +- [accountsesdthistory](https://docs.multiversx.com/sdk-and-tools/indices/accountsesdthistory): Overview of MultiversX SDK and Tools accountsesdthistory. +- [accountshistory](https://docs.multiversx.com/sdk-and-tools/indices/accountshistory): Overview of MultiversX SDK and Tools accountshistory. +- [Basics](https://docs.multiversx.com/developers/best-practices/best-practices-basics): Learn about basics in MultiversX. +- [BigUint Operations](https://docs.multiversx.com/developers/best-practices/biguint-operations): Learn about biguint operations in MultiversX. +- [Blackbox calls](https://docs.multiversx.com/developers/testing/rust/sc-blackbox-calls): Learn about blackbox calls in MultiversX. +- [Blackbox example](https://docs.multiversx.com/developers/testing/rust/sc-blackbox-example): Learn about blackbox example in MultiversX. +- [blocks](https://docs.multiversx.com/sdk-and-tools/indices/blocks): Overview of MultiversX SDK and Tools blocks. +- [Build a dApp in 15 minutes](https://docs.multiversx.com/developers/tutorials/your-first-dapp): Tutorial: Build a dApp in 15 minutes +- [Build a Microservice for your dApp](https://docs.multiversx.com/developers/tutorials/your-first-microservice): Tutorial: Build a Microservice for your dApp +- [Build Reference](https://docs.multiversx.com/developers/meta/sc-build-reference): Learn about build reference in MultiversX. +- [Built-In Functions](https://docs.multiversx.com/developers/built-in-functions): Learn about built-in functions in MultiversX. +- [C++ SDK](https://docs.multiversx.com/sdk-and-tools/erdcpp): Overview of MultiversX SDK and Tools c++ sdk. +- [Chain simulator](https://docs.multiversx.com/sdk-and-tools/chain-simulator): Overview of MultiversX SDK and Tools chain simulator. +- [Chain Simulator in Adder - SpaceCraft interactors](https://docs.multiversx.com/developers/tutorials/chain-simulator-adder): Tutorial: Chain Simulator in Adder - SpaceCraft interactors +- [CLI](https://docs.multiversx.com/developers/meta/sc-meta-cli): Learn about cli in MultiversX. +- [Code Metadata](https://docs.multiversx.com/developers/data/code-metadata): Learn about code metadata in MultiversX. +- [Composite Values](https://docs.multiversx.com/developers/data/composite-values): Learn about composite values in MultiversX. +- [Concept](https://docs.multiversx.com/developers/testing/scenario/concept): Learn about concept in MultiversX. +- [Configuration](https://docs.multiversx.com/developers/meta/sc-config): Learn about configuration in MultiversX. +- [Constants](https://docs.multiversx.com/developers/constants): Learn about constants in MultiversX. +- [Cookbook (v14)](https://docs.multiversx.com/sdk-and-tools/sdk-js/sdk-js-cookbook-v14): Overview of MultiversX SDK and Tools cookbook (v14). +- [Cookbook (v15)](https://docs.multiversx.com/sdk-and-tools/sdk-js/sdk-js-cookbook-v15): Overview of MultiversX SDK and Tools cookbook (v15). +- [Core Logic](https://docs.multiversx.com/developers/tutorials/crowdfunding/crowdfunding-p2): Tutorial: Core Logic +- [Creating Wallets](https://docs.multiversx.com/developers/creating-wallets): Learn about creating wallets in MultiversX. +- [Custom Types](https://docs.multiversx.com/developers/data/custom-types): Learn about custom types in MultiversX. +- [Defaults](https://docs.multiversx.com/developers/data/defaults): Learn about defaults in MultiversX. +- [delegators](https://docs.multiversx.com/sdk-and-tools/indices/delegators): Overview of MultiversX SDK and Tools delegators. +- [Deploy a SC in 5 minutes - SpaceCraft interactors](https://docs.multiversx.com/developers/tutorials/interactors-guide): Tutorial: Deploy a SC in 5 minutes - SpaceCraft interactors +- [Devcontainers](https://docs.multiversx.com/sdk-and-tools/devcontainers): Overview of MultiversX SDK and Tools devcontainers. +- [Developers - Overview](https://docs.multiversx.com/developers/overview): Learn about developers - in MultiversX. +- [DEX Walkthrough](https://docs.multiversx.com/developers/tutorials/dex-walkthrough): Tutorial: DEX Walkthrough +- [EGLD transfers (move balance transactions)](https://docs.multiversx.com/developers/gas-and-fees/egld-transfers): Learn about egld transfers (move balance transactions) in MultiversX. +- [Elasticindexer service](https://docs.multiversx.com/sdk-and-tools/elastic-indexer): Overview of MultiversX SDK and Tools elasticindexer service. +- [Elasticsearch](https://docs.multiversx.com/sdk-and-tools/elastic-search): Overview of MultiversX SDK and Tools elasticsearch. +- [Energy DAO SC tutorial](https://docs.multiversx.com/developers/tutorials/energy-dao): Tutorial: Energy DAO SC tutorial +- [Environments](https://docs.multiversx.com/developers/transactions/tx-env): Learn about environments in MultiversX. +- [epochinfo](https://docs.multiversx.com/sdk-and-tools/indices/epochinfo): Overview of MultiversX SDK and Tools epochinfo. +- [ESDT Operations Events](https://docs.multiversx.com/developers/event-logs/esdt-events): Learn about esdt operations events in MultiversX. +- [Ethereum to MultiversX migration guide](https://docs.multiversx.com/developers/tutorials/eth-to-mvx): Tutorial: Ethereum to MultiversX migration guide +- [events](https://docs.multiversx.com/sdk-and-tools/indices/events): Overview of MultiversX SDK and Tools events. +- [Events notifier](https://docs.multiversx.com/sdk-and-tools/notifier): Overview of MultiversX SDK and Tools events notifier. +- [Execution Events](https://docs.multiversx.com/developers/event-logs/execution-events): Learn about execution events in MultiversX. +- [Extend to Any Token](https://docs.multiversx.com/developers/tutorials/crowdfunding/crowdfunding-p3): Tutorial: Extend to Any Token +- [Extending sdk-js](https://docs.multiversx.com/sdk-and-tools/sdk-js/extending-sdk-js): Overview of MultiversX SDK and Tools extending sdk-js. +- [Final Code](https://docs.multiversx.com/developers/tutorials/crowdfunding/final-code): Tutorial: Final Code +- [Fix IDEs configuration](https://docs.multiversx.com/sdk-and-tools/troubleshooting/ide-setup): Overview of MultiversX SDK and Tools fix ides configuration. +- [Fix Rust installation](https://docs.multiversx.com/sdk-and-tools/troubleshooting/rust-setup): Overview of MultiversX SDK and Tools fix rust installation. +- [Fungible tokens](https://docs.multiversx.com/tokens/fungible-tokens): ESDT fungible tokens: issuance, configuration properties, permissions, and on‑chain usage. +- [Gas](https://docs.multiversx.com/developers/transactions/tx-gas): Learn about gas in MultiversX. +- [Gateway overview](https://docs.multiversx.com/sdk-and-tools/rest-api/gateway-overview): Overview of MultiversX SDK and Tools gateway. +- [Generating scenarios](https://docs.multiversx.com/developers/testing/scenario/generating-scenarios): Learn about generating scenarios in MultiversX. +- [Go SDK](https://docs.multiversx.com/sdk-and-tools/sdk-go): Overview of MultiversX SDK and Tools go sdk. +- [Google BigQuery](https://docs.multiversx.com/sdk-and-tools/google-bigquery): Overview of MultiversX SDK and Tools google bigquery. +- [Guard accounts](https://docs.multiversx.com/developers/guard-accounts): Learn about guard accounts in MultiversX. +- [Guardians](https://docs.multiversx.com/sdk-and-tools/sdk-dapp/internal-processes/guardians): Overview of MultiversX SDK and Tools guardians. +- [How to fix Elasticsearch mapping errors](https://docs.multiversx.com/sdk-and-tools/elastic-search-wrong-mappings-fix): Overview of MultiversX SDK and Tools how to fix elasticsearch mapping errors. +- [Installing mxpy](https://docs.multiversx.com/sdk-and-tools/mxpy/installing-mxpy): Overview of MultiversX SDK and Tools installing mxpy. +- [Interactors Example](https://docs.multiversx.com/developers/meta/interactor/interactors-example): Learn about interactors example in MultiversX. +- [Interactors Overview](https://docs.multiversx.com/developers/meta/interactor/interactors-overview): Learn about interactors in MultiversX. +- [Intro to ESDT](https://docs.multiversx.com/tokens/intro): Introduction to ESDT token standards on MultiversX: fungible, non‑fungible and semi‑fungible basics. +- [Iterate keys](https://docs.multiversx.com/sdk-and-tools/rest-api/iterate-keys): Overview of MultiversX SDK and Tools iterate keys. +- [Java SDK](https://docs.multiversx.com/sdk-and-tools/mxjava): Overview of MultiversX SDK and Tools java sdk. +- [JSON Structure](https://docs.multiversx.com/developers/testing/scenario/structure-json): Learn about json structure in MultiversX. +- [Kotlin SDK](https://docs.multiversx.com/sdk-and-tools/erdkotlin): Overview of MultiversX SDK and Tools kotlin sdk. +- [Legacy SC calls](https://docs.multiversx.com/developers/transactions/tx-legacy-calls): Learn about legacy sc calls in MultiversX. +- [logs](https://docs.multiversx.com/sdk-and-tools/indices/logs): Overview of MultiversX SDK and Tools logs. +- [Managed Decimal](https://docs.multiversx.com/developers/best-practices/managed-decimal): Learn about managed decimal in MultiversX. +- [Memory allocation](https://docs.multiversx.com/developers/meta/sc-allocator): Learn about memory allocation in MultiversX. +- [Messages](https://docs.multiversx.com/developers/developer-reference/sc-messages): Learn about messages in MultiversX. +- [Migration](https://docs.multiversx.com/developers/transactions/tx-migration): Learn about migration in MultiversX. +- [miniblocks](https://docs.multiversx.com/sdk-and-tools/indices/miniblocks): Overview of MultiversX SDK and Tools miniblocks. +- [Multi-Values](https://docs.multiversx.com/developers/data/multi-values): Learn about multi-values in MultiversX. +- [MultiversX API](https://docs.multiversx.com/sdk-and-tools/rest-api/multiversx-api): Overview of MultiversX SDK and Tools multiversx api. +- [MultiversX API WebSocket](https://docs.multiversx.com/sdk-and-tools/rest-api/ws-subscriptions): Overview of MultiversX SDK and Tools multiversx api websocket. +- [MultiversX Smart Contracts](https://docs.multiversx.com/developers/smart-contracts): Learn about multiversx smart contracts in MultiversX. +- [MultiversX Smart Contracts API limits](https://docs.multiversx.com/developers/contract-api-limits): Learn about multiversx smart contracts api limits in MultiversX. +- [MultiversX tools on multiple platforms](https://docs.multiversx.com/sdk-and-tools/troubleshooting/multiplatform): Overview of MultiversX SDK and Tools multiversx tools on multiple platforms. +- [mxpy CLI cookbook](https://docs.multiversx.com/sdk-and-tools/mxpy/mxpy-cli): Overview of MultiversX SDK and Tools mxpy cli cookbook. +- [NestJS SDK](https://docs.multiversx.com/sdk-and-tools/sdk-nestjs/sdk-nestjs): Overview of MultiversX SDK and Tools nestjs sdk. +- [NestJS SDK Auth utilities](https://docs.multiversx.com/sdk-and-tools/sdk-nestjs/sdk-nestjs-auth): Overview of MultiversX SDK and Tools nestjs sdk auth utilities. +- [NestJS SDK Cache utilities](https://docs.multiversx.com/sdk-and-tools/sdk-nestjs/sdk-nestjs-cache): Overview of MultiversX SDK and Tools nestjs sdk cache utilities. +- [NestJS SDK Monitoring utilities](https://docs.multiversx.com/sdk-and-tools/sdk-nestjs/sdk-nestjs-monitoring): Overview of MultiversX SDK and Tools nestjs sdk monitoring utilities. +- [NFT & SFT tokens](https://docs.multiversx.com/tokens/nft-tokens): NFTs and SFTs on MultiversX: issuance, roles, properties, transfer mechanics and branding best practices. +- [operations](https://docs.multiversx.com/sdk-and-tools/indices/operations): Overview of MultiversX SDK and Tools operations. +- [Overview](https://docs.multiversx.com/developers/testing/rust/sc-test-overview): Learn about sc test overview in MultiversX. +- [Overview](https://docs.multiversx.com/sdk-and-tools/troubleshooting/troubleshooting): Overview of MultiversX SDK and Tools troubleshooting. +- [Overview](https://docs.multiversx.com/developers/gas-and-fees/overview): Learn about overview in MultiversX. +- [Payload (data)](https://docs.multiversx.com/developers/transactions/tx-data): Learn about payload (data) in MultiversX. +- [Payments](https://docs.multiversx.com/developers/transactions/tx-payment): Learn about payments in MultiversX. +- [Preparing SCs for Supernova](https://docs.multiversx.com/developers/best-practices/prepare-sc-supernova): Learn about preparing scs for supernova in MultiversX. +- [Proxies](https://docs.multiversx.com/developers/transactions/tx-proxies): Learn about proxies in MultiversX. +- [Proxy architecture](https://docs.multiversx.com/sdk-and-tools/proxy): Overview of MultiversX SDK and Tools proxy architecture. +- [Python SDK](https://docs.multiversx.com/sdk-and-tools/sdk-py): Overview of MultiversX SDK and Tools python sdk. +- [Random Numbers in Smart Contracts](https://docs.multiversx.com/developers/developer-reference/sc-random-numbers): Learn about random numbers in smart contracts in MultiversX. +- [rating](https://docs.multiversx.com/sdk-and-tools/indices/rating): Overview of MultiversX SDK and Tools rating. +- [React Development](https://docs.multiversx.com/developers/guidelines/react-development): Learn about react development in MultiversX. +- [receipts](https://docs.multiversx.com/sdk-and-tools/indices/receipts): Overview of MultiversX SDK and Tools receipts. +- [Receiver](https://docs.multiversx.com/developers/transactions/tx-to): Learn about receiver in MultiversX. +- [Relayed Transactions](https://docs.multiversx.com/developers/relayed-transactions): Learn about relayed transactions in MultiversX. +- [Reproducible Builds](https://docs.multiversx.com/developers/reproducible-contract-builds): Learn about reproducible builds in MultiversX. +- [Rest API Addresses](https://docs.multiversx.com/sdk-and-tools/rest-api/addresses): Overview of MultiversX SDK and Tools addresses. +- [Rest API Blocks](https://docs.multiversx.com/sdk-and-tools/rest-api/blocks): Overview of MultiversX SDK and Tools blocks. +- [Rest API Network](https://docs.multiversx.com/sdk-and-tools/rest-api/network): Overview of MultiversX SDK and Tools network. +- [Rest API Nodes](https://docs.multiversx.com/sdk-and-tools/rest-api/nodes): Overview of MultiversX SDK and Tools nodes. +- [REST API overview](https://docs.multiversx.com/sdk-and-tools/rest-api/rest-api): Overview of MultiversX SDK and Tools rest api. +- [Rest API Transactions](https://docs.multiversx.com/sdk-and-tools/rest-api/transactions): Overview of MultiversX SDK and Tools transactions. +- [Rest API Virtual Machine](https://docs.multiversx.com/sdk-and-tools/rest-api/virtual-machine): Overview of MultiversX SDK and Tools virtual machine. +- [Result Handlers](https://docs.multiversx.com/developers/transactions/tx-result-handlers): Learn about result handlers in MultiversX. +- [rounds](https://docs.multiversx.com/sdk-and-tools/indices/rounds): Overview of MultiversX SDK and Tools rounds. +- [Run transactions](https://docs.multiversx.com/developers/transactions/tx-run): Learn about run transactions in MultiversX. +- [Running scenarios](https://docs.multiversx.com/developers/testing/scenario/running-scenarios): Learn about running scenarios in MultiversX. +- [Rust SDK](https://docs.multiversx.com/sdk-and-tools/sdk-rust): Overview of MultiversX SDK and Tools rust sdk. +- [Rust Version](https://docs.multiversx.com/developers/meta/rust-version): Learn about rust version in MultiversX. +- [SC to SC Calls](https://docs.multiversx.com/developers/developer-reference/sc-to-sc-calls): Learn about sc to sc calls in MultiversX. +- [scdeploys](https://docs.multiversx.com/sdk-and-tools/indices/scdeploys): Overview of MultiversX SDK and Tools scdeploys. +- [Scenario Complex Values](https://docs.multiversx.com/developers/testing/scenario/values-complex): Learn about scenario complex values in MultiversX. +- [Scenario Simple Values](https://docs.multiversx.com/developers/testing/scenario/values-simple): Learn about scenario simple values in MultiversX. +- [scresults](https://docs.multiversx.com/sdk-and-tools/indices/scresults): Overview of MultiversX SDK and Tools scresults. +- [sdk-dapp](https://docs.multiversx.com/sdk-and-tools/sdk-dapp/sdk-dapp): Overview of MultiversX SDK and Tools sdk-dapp. +- [sdk-js](https://docs.multiversx.com/sdk-and-tools/sdk-js/sdk-js): Overview of MultiversX SDK and Tools sdk-js. +- [SDKs and Tools - Overview](https://docs.multiversx.com/sdk-and-tools/overview): Overview of MultiversX SDK and Tools sdks and tools -. +- [Sender](https://docs.multiversx.com/developers/transactions/tx-from): Learn about sender in MultiversX. +- [Set up a Localnet (mxpy)](https://docs.multiversx.com/developers/setup-local-testnet): Learn about set up a localnet (mxpy) in MultiversX. +- [Set up a Localnet (raw)](https://docs.multiversx.com/developers/setup-local-testnet-advanced): Learn about set up a localnet (raw) in MultiversX. +- [Setup & Basics](https://docs.multiversx.com/developers/tutorials/crowdfunding/crowdfunding-p1): Tutorial: Setup & Basics +- [Signing programmatically](https://docs.multiversx.com/developers/signing-transactions/signing-programmatically): Learn about signing programmatically in MultiversX. +- [Signing Providers for dApps](https://docs.multiversx.com/sdk-and-tools/sdk-js/sdk-js-signing-providers): Overview of MultiversX SDK and Tools signing providers for dapps. +- [Signing Transactions](https://docs.multiversx.com/developers/signing-transactions/signing-transactions): Learn about signing transactions in MultiversX. +- [Simple Values](https://docs.multiversx.com/developers/data/simple-values): Learn about simple values in MultiversX. +- [Smart contract annotations](https://docs.multiversx.com/developers/developer-reference/sc-annotations): Learn about smart contract annotations in MultiversX. +- [Smart Contract API Functions](https://docs.multiversx.com/developers/developer-reference/sc-api-functions): Learn about smart contract api functions in MultiversX. +- [Smart Contract Call Events](https://docs.multiversx.com/developers/event-logs/contract-call-events): Learn about smart contract call events in MultiversX. +- [Smart Contract Calls Data Format](https://docs.multiversx.com/developers/sc-calls-format): Learn about smart contract calls data format in MultiversX. +- [Smart Contract Debugging](https://docs.multiversx.com/developers/testing/sc-debugging): Learn about smart contract debugging in MultiversX. +- [Smart Contract Deploy Events](https://docs.multiversx.com/developers/event-logs/contract-deploy-events): Learn about smart contract deploy events in MultiversX. +- [Smart contract interactions](https://docs.multiversx.com/sdk-and-tools/mxpy/smart-contract-interactions): Overview of MultiversX SDK and Tools smart contract interactions. +- [Smart contract modules](https://docs.multiversx.com/developers/developer-reference/sc-modules): Learn about smart contract modules in MultiversX. +- [Smart contract payments](https://docs.multiversx.com/developers/developer-reference/sc-payments): Learn about smart contract payments in MultiversX. +- [Staking smart contract](https://docs.multiversx.com/developers/tutorials/staking-contract): Tutorial: Staking smart contract +- [Storage Mappers](https://docs.multiversx.com/developers/developer-reference/storage-mappers): Learn about storage mappers in MultiversX. +- [System Delegation Events](https://docs.multiversx.com/developers/event-logs/system-delegation-events): Learn about system delegation events in MultiversX. +- [System Smart Contracts](https://docs.multiversx.com/developers/gas-and-fees/system-smart-contracts): Learn about system smart contracts in MultiversX. +- [tags](https://docs.multiversx.com/sdk-and-tools/indices/tags): Overview of MultiversX SDK and Tools tags. +- [Test setup](https://docs.multiversx.com/developers/testing/rust/sc-test-setup): Learn about test setup in MultiversX. +- [Testing in Go](https://docs.multiversx.com/developers/testing/testing-in-go): Learn about testing in go in MultiversX. +- [Testing Overview](https://docs.multiversx.com/developers/testing/testing-overview): Learn about testing in MultiversX. +- [The dynamic allocation problem](https://docs.multiversx.com/developers/best-practices/the-dynamic-allocation-problem): Learn about the dynamic allocation problem in MultiversX. +- [The MultiversX Serialization Format](https://docs.multiversx.com/developers/data/serialization-overview): Learn about the multiversx serialization format in MultiversX. +- [Time-related Types](https://docs.multiversx.com/developers/best-practices/time-types): Learn about time-related types in MultiversX. +- [tokens](https://docs.multiversx.com/sdk-and-tools/indices/tokens): Overview of MultiversX SDK and Tools tokens. +- [Toolchain Setup](https://docs.multiversx.com/developers/toolchain-setup): Learn about toolchain setup in MultiversX. +- [Tooling Overview](https://docs.multiversx.com/developers/meta/sc-meta): Learn about tooling in MultiversX. +- [Tools for signing](https://docs.multiversx.com/developers/signing-transactions/tools-for-signing): Learn about tools for signing in MultiversX. +- [Transaction Overview](https://docs.multiversx.com/developers/transactions/tx-overview): Learn about transaction in MultiversX. +- [transactions](https://docs.multiversx.com/sdk-and-tools/indices/transactions): Overview of MultiversX SDK and Tools transactions. +- [Upgrading smart contracts](https://docs.multiversx.com/developers/developer-reference/upgrading-smart-contracts): Learn about upgrading smart contracts in MultiversX. +- [User-defined Smart Contracts](https://docs.multiversx.com/developers/gas-and-fees/user-defined-smart-contracts): Learn about user-defined smart contracts in MultiversX. +- [validators](https://docs.multiversx.com/sdk-and-tools/indices/validators): Overview of MultiversX SDK and Tools validators. +- [Versions and Changelog](https://docs.multiversx.com/sdk-and-tools/rest-api/versions-and-changelog): Overview of MultiversX SDK and Tools versions and changelog. +- [WalletConnect 2.0 Migration](https://docs.multiversx.com/developers/tutorials/wallet-connect-v2-migration): Tutorial: WalletConnect 2.0 Migration +- [Whitebox Framework](https://docs.multiversx.com/developers/testing/rust/whitebox-legacy): Learn about whitebox framework in MultiversX. +- [Whitebox Functions Reference](https://docs.multiversx.com/developers/testing/rust/whitebox-legacy-functions-reference): Learn about whitebox functions reference in MultiversX. +- [Writing and testing interactions](https://docs.multiversx.com/sdk-and-tools/sdk-js/writing-and-running-sdk-js-snippets): Overview of MultiversX SDK and Tools writing and testing interactions. + +## Validators +- [Convert An Existing Validator Into A Staking Provider](https://docs.multiversx.com/validators/staking/convert-existing-validator-into-staking-provider): Convert a validator into a staking provider under Staking Phase 3.5: deploy a delegation contract and migrate without losing validator slots. +- [FAQs](https://docs.multiversx.com/validators/faq): Frequently asked questions for validators and node operators: setup, operations, staking and troubleshooting. +- [How to use the Docker Image](https://docs.multiversx.com/validators/nodes-scripts/use-docker): Deploy and manage MultiversX nodes with Docker using the provided scripts and images. +- [Import DB](https://docs.multiversx.com/validators/import-db): Run a node in import‑db mode to rebuild state and reprocess historical blocks and transactions. +- [Installing a Validator Node](https://docs.multiversx.com/validators/nodes-scripts/install-update): Install and update validator nodes via the script menu: installation, upgrades and maintenance actions. +- [Manage a validator node](https://docs.multiversx.com/validators/nodes-scripts/manage-node): Manage validator nodes with scripts: start/stop, upgrades, database operations and routine maintenance. +- [Merging A Validator Into An Existing Delegation Smart Contract](https://docs.multiversx.com/validators/staking/merge-validator-delegation-sc): Merge a standalone validator into an existing delegation smart contract: whitelist, merge transaction and operational notes. +- [Multikey nodes management](https://docs.multiversx.com/validators/key-management/multikey-nodes): Manage multiple validator keys across node groups: architecture, setup and operational procedures. +- [MultiversX Node upgrades](https://docs.multiversx.com/validators/node-upgrades): Plan and execute MultiversX node upgrades safely: scheduling, compatibility considerations, and operational steps. +- [Node CLI](https://docs.multiversx.com/validators/node-cli): Node command‑line flags and utilities from mx‑chain‑go: startup options, configuration, debugging and maintenance. +- [Node Configuration](https://docs.multiversx.com/validators/node-configuration): Configure MultiversX validator nodes: key configuration files, what they control, safe parameters to adjust, and constraints tied to network consensus. +- [Node Databases](https://docs.multiversx.com/validators/node-databases): Overview of node databases: key‑value stores, what data they contain, and how they are used by the protocol. +- [Node operation modes](https://docs.multiversx.com/validators/operation-modes): Supported node operation modes and configuration patterns for observers, validators, and specialized roles. +- [Node redundancy](https://docs.multiversx.com/validators/redundancy): High‑availability setups for validator nodes: standby instances, failover strategies, and configuration guidance. +- [Protecting your keys](https://docs.multiversx.com/validators/key-management/protect-keys): Best practices for securing validator and wallet keys: storage, access control, rotation and recovery. +- [Rating](https://docs.multiversx.com/validators/rating): Validator rating system: how scores are computed, how they influence consensus selection, and how to recover from penalties. +- [Scripts & User config](https://docs.multiversx.com/validators/nodes-scripts/config-scripts): Install and configure MultiversX nodes using mx‑chain‑scripts: prerequisites, configuration and setup. +- [Staking & Unstaking](https://docs.multiversx.com/validators/staking/staking): Stake and unstake validator nodes: prerequisites, transactions, reward addresses and operational workflow. +- [Staking Providers](https://docs.multiversx.com/validators/delegation-manager): Guidance for MultiversX validators: staking providers. +- [Staking Providers APR](https://docs.multiversx.com/economics/staking-providers-apr): Learn about staking providers apr in MultiversX. +- [Staking v4](https://docs.multiversx.com/validators/staking-v4): What’s new in Staking v4: protocol changes, validator impact, reward mechanics and migration notes. +- [System Requirements](https://docs.multiversx.com/validators/system-requirements): Hardware, OS, storage and network requirements for running MultiversX validator nodes in production. +- [The Staking Smart Contract](https://docs.multiversx.com/validators/staking/staking-smart-contract): Operations of the Staking system smart contract: staking, parameters, and example transactions for common tasks. +- [Unjailing](https://docs.multiversx.com/validators/staking/unjailing): Guidance for MultiversX validators: unjailing. +- [Useful Links & Tools](https://docs.multiversx.com/validators/useful-links-tools): Curated links, documentation and utilities helpful for MultiversX validators and node operators. +- [Validator Keys](https://docs.multiversx.com/validators/key-management/validator-keys): Validator keys: purpose, generation, secure handling and usage in consensus. +- [Validators - Overview](https://docs.multiversx.com/validators/overview): Overview of MultiversX validator nodes, responsibilities, lifecycle, tooling and operational workflows. +- [Wallet Keys](https://docs.multiversx.com/validators/key-management/wallet-keys): Wallet keys used for staking and node management: types, usage and security considerations. + +## Integrators +- [Accounts Management](https://docs.multiversx.com/integrators/accounts-management): Learn about MultiversX Integrators accounts management. +- [Advanced Observer Settings](https://docs.multiversx.com/integrators/advanced-observer-settings): Learn about MultiversX Integrators advanced observer settings. +- [Creating Transactions](https://docs.multiversx.com/integrators/creating-transactions): Learn about MultiversX Integrators creating transactions. +- [Deep History Squad](https://docs.multiversx.com/integrators/deep-history-squad): Access deep historical blockchain data via the Deep History Squad: use cases, endpoints and integration guidance. +- [EGLD integration guide](https://docs.multiversx.com/integrators/egld-integration-guide): Integrate EGLD into exchanges and wallets: addresses, transactions, fees, confirmations and best practices. +- [ESDT tokens integration guide](https://docs.multiversx.com/integrators/esdt-tokens-integration-guide): Learn about MultiversX Integrators esdt tokens integration guide. +- [Frequently Asked Questions](https://docs.multiversx.com/integrators/faq): Common integration questions: accounts, consensus, sharding, network parameters and API usage. +- [Integrators - Overview](https://docs.multiversx.com/integrators/overview): Learn about MultiversX Integrators integrators -. +- [Observing Squad](https://docs.multiversx.com/integrators/observing-squad): Observing Squad service: high‑availability access to network data for integrators and data providers. +- [Querying the Blockchain](https://docs.multiversx.com/integrators/querying-the-blockchain): Learn about MultiversX Integrators querying the blockchain. +- [Snapshotless Observing Squad](https://docs.multiversx.com/integrators/snapshotless-observing-squad): Learn about MultiversX Integrators snapshotless observing squad. +- [WalletConnect JSON-RPC Methods](https://docs.multiversx.com/integrators/walletconnect-json-rpc-methods): WalletConnect v2 JSON‑RPC methods for MultiversX: available calls, parameters and usage patterns. + +## Advanced +- [Architecture](https://docs.multiversx.com/bridge/architecture): High-level architecture of the Ad‑Astra Bridge: core contracts on MultiversX and EVM chains, relayer quorum, and how cross‑chain transfers are coordinated. +- [Axelar Amplifier Setup](https://docs.multiversx.com/bridge/axelar): Set up an Axelar Amplifier verifier for MultiversX: prerequisites, tofnd and ampd services, configuration, and verification steps. +- [Bitcoin L2](https://docs.multiversx.com/sovereign/bitcoin-l2): Learn about MultiversX Sovereign bitcoin l2. +- [Concept](https://docs.multiversx.com/sovereign/concept): Learn about MultiversX Sovereign concept. +- [Cross Chain Execution](https://docs.multiversx.com/sovereign/cross-chain-execution): Learn about MultiversX Sovereign cross chain execution. +- [Custom Configurations](https://docs.multiversx.com/sovereign/custom-configurations): Learn about MultiversX Sovereign custom configurations. +- [Disclaimer](https://docs.multiversx.com/sovereign/disclaimer): Learn about MultiversX Sovereign disclaimer. +- [Distributed Setup](https://docs.multiversx.com/sovereign/distributed-setup): Learn about MultiversX Sovereign distributed setup. +- [Dual Staking](https://docs.multiversx.com/sovereign/dual-staking): Learn about MultiversX Sovereign dual staking. +- [Ethereum L2](https://docs.multiversx.com/sovereign/ethereum-l2): Learn about MultiversX Sovereign ethereum l2. +- [Governance](https://docs.multiversx.com/sovereign/governance): Learn about MultiversX Sovereign governance. +- [Governance - Overview](https://docs.multiversx.com/governance/overview): Overview of the on‑chain governance module: proposals, voting, execution and release context. +- [Governance interaction](https://docs.multiversx.com/governance/governance-interaction): Interact with the governance smart contract: submit proposals, vote and query status via VM endpoints and transactions. +- [Header Verifier](https://docs.multiversx.com/sovereign/header-verifier): Learn about MultiversX Sovereign header verifier. +- [Interoperability](https://docs.multiversx.com/sovereign/interoperability): Learn about MultiversX Sovereign interoperability. +- [Key Components](https://docs.multiversx.com/sovereign/key-components): Learn about MultiversX Sovereign key components. +- [Keystore files](https://docs.multiversx.com/wallet/keystore): Create, import and use keystore files safely with the MultiversX Web Wallet and tooling. +- [Ledger](https://docs.multiversx.com/wallet/ledger): Set up and use a Ledger device with MultiversX: install the app, connect securely, and sign transactions safely. +- [Local Setup](https://docs.multiversx.com/sovereign/local-setup): Learn about MultiversX Sovereign local setup. +- [Managing Sovereign](https://docs.multiversx.com/sovereign/managing-sovereign): Learn about MultiversX Sovereign managing sovereign. +- [MultiversX DeFi Wallet](https://docs.multiversx.com/wallet/wallet-extension): Browser extension for MultiversX: install and set up the DeFi Wallet, manage multiple accounts, and interact with dApps. +- [Mvx Esdt Safe](https://docs.multiversx.com/sovereign/mvx-esdt-safe): Learn about MultiversX Sovereign mvx esdt safe. +- [One Click Deployment](https://docs.multiversx.com/sovereign/one-click-deployment): Learn about MultiversX Sovereign one click deployment. +- [Other Vm](https://docs.multiversx.com/sovereign/other-vm): Learn about MultiversX Sovereign other vm. +- [Restaking](https://docs.multiversx.com/sovereign/restaking): Learn about MultiversX Sovereign restaking. +- [Security](https://docs.multiversx.com/sovereign/security): Learn about MultiversX Sovereign security. +- [Services](https://docs.multiversx.com/sovereign/services): Learn about MultiversX Sovereign services. +- [Software Dependencies](https://docs.multiversx.com/sovereign/software-dependencies): Learn about MultiversX Sovereign software dependencies. +- [Solana L2](https://docs.multiversx.com/sovereign/solana-l2): Learn about MultiversX Sovereign solana l2. +- [Sov Esdt Safe](https://docs.multiversx.com/sovereign/sov-esdt-safe): Learn about MultiversX Sovereign sov esdt safe. +- [Sovereign - Overview](https://docs.multiversx.com/sovereign/overview): Learn about MultiversX Sovereign sovereign -. +- [Sovereign Api](https://docs.multiversx.com/sovereign/sovereign-api): Learn about MultiversX Sovereign sovereign api. +- [Sovereign Explorer](https://docs.multiversx.com/sovereign/sovereign-explorer): Learn about MultiversX Sovereign sovereign explorer. +- [Sovereign Wallet](https://docs.multiversx.com/sovereign/sovereign-wallet): Learn about MultiversX Sovereign sovereign wallet. +- [Standalone Evm](https://docs.multiversx.com/sovereign/standalone-evm): Learn about MultiversX Sovereign standalone evm. +- [System Requirements](https://docs.multiversx.com/sovereign/system-requirements): Learn about MultiversX Sovereign system requirements. +- [Testing](https://docs.multiversx.com/sovereign/testing): Learn about MultiversX Sovereign testing. +- [Token Economics](https://docs.multiversx.com/sovereign/token-economics): Learn about MultiversX Sovereign token economics. +- [Token Management](https://docs.multiversx.com/sovereign/token-management): Learn about MultiversX Sovereign token management. +- [Token Types](https://docs.multiversx.com/bridge/token-types): Bridge token configurations across MultiversX and EVM chains: mint/burn, lock/unlock, native vs non‑native, and their operational implications. +- [Transfer Flows](https://docs.multiversx.com/bridge/transfer-flows): Step‑by‑step token transfer flows for the MultiversX bridge: EVM↔MultiversX directions, smart‑contract call path, refund scenarios, and relayer processing. +- [Validators](https://docs.multiversx.com/sovereign/validators): Validators in Sovereign Chains: roles, selection, staking and security considerations for independent networks. +- [Vm Intro](https://docs.multiversx.com/sovereign/vm-intro): Learn about MultiversX Sovereign vm intro. +- [Wallets - Overview](https://docs.multiversx.com/wallet/overview): Overview of MultiversX wallets: Web Wallet, xPortal, Ledger and DeFi Wallet, with setup and usage guides. +- [Web Wallet](https://docs.multiversx.com/wallet/web-wallet): Create or import accounts, send and receive EGLD and tokens, stake, and securely manage assets using the MultiversX Web Wallet. +- [Web Wallet Tokens](https://docs.multiversx.com/wallet/wallet-token): Issue and manage ESDT tokens in the MultiversX Web Wallet: prerequisites, creation steps, configuration properties, and transferring tokens. +- [Webhooks](https://docs.multiversx.com/wallet/webhooks): Integrate with the Web Wallet via URL webhooks to request login and pre‑filled transactions, with callback redirects. +- [Whitelist Requirements](https://docs.multiversx.com/bridge/whitelist-requirements): Whitelist requirements for the Ad‑Astra bridge: prerequisites, token branding, and assigning roles to wrapper or safe contracts. +- [xAlias](https://docs.multiversx.com/wallet/xalias): xAlias single sign‑on for MultiversX: Google‑based onboarding, self‑custody with later conversion, and Web Wallet‑compatible integration hooks. +- [xPortal](https://docs.multiversx.com/wallet/xportal): xPortal mobile app: secure wallet, token swaps, payments, missions, AI avatar creation and upcoming debit card features. + +## Terminology +- [Terminology](https://docs.multiversx.com/welcome/terminology): Learn about terminology in MultiversX. From d4c5ba82a8bf232902e54c0ae7f8feebda06aaa9 Mon Sep 17 00:00:00 2001 From: bogdan-rosianu Date: Mon, 2 Feb 2026 14:07:06 +0200 Subject: [PATCH 2/4] fixes --- docs/developers/data/composite-values.md | 11 +++++------ docs/developers/data/simple-values.md | 11 +++++------ docs/developers/toolchain-setup.md | 6 +++--- docs/developers/tutorials/interactors-guide.md | 4 +--- docs/sdk-and-tools/rest-api/transactions.mdx | 9 ++++----- docs/utils.mdx | 1 + docs/validators/key-management/validator-keys.md | 4 ++-- 7 files changed, 21 insertions(+), 25 deletions(-) diff --git a/docs/developers/data/composite-values.md b/docs/developers/data/composite-values.md index 6c0a28650..44f5732b9 100644 --- a/docs/developers/data/composite-values.md +++ b/docs/developers/data/composite-values.md @@ -26,18 +26,17 @@ Then, all nested encodings of the items, concatenated. **Examples** | Type | Value | Top-level encoding | Nested encoding | Explanation | -| - ---- - -------------- | -------------------- | --------------------- | ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- | +| +| ---------------- | -------------------- | --------------------- | ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- | | `Vec` | `vec![1, 2]` | `0x0102` | `0x00000002 0102` | Length = `2` | | `Vec` | `vec![1, 2]` | `0x00010002` | `0x00000002 00010002` | Length = `2` | | `Vec` | `vec![]` | `0x` | `0x00000000` | Length = `0` | | `Vec` | `vec![7]` | `0x00000007` | `0x00000001 00000007` | Length = `1` | | `Vec< Vec>` | `vec![ vec![7]]` | `0x00000001 00000007` | `0x00000001 00000001 00000007` | There is 1 element, which is a vector. In both cases the inner Vec needs to be nested-encoded in the larger Vec. | | `Vec<&[u8]>` | `vec![ &[7u8][..]]` | `0x00000001 07` | `0x00000001 00000001 07` | Same as above, but the inner list is a simple list of bytes. | -| `Vec< BigUint>` | `vec![ 7u32.into()]` | `0x00000001 07` | `0x00000001 00000001 07` | `BigUint`s need to encode their length when nested. The `7` is encoded the same way as a list of bytes of length 1, so the same as above. |--- +| `Vec< BigUint>` | `vec![ 7u32.into()]` | `0x00000001 07` | `0x00000001 00000001 07` | `BigUint`s need to encode their length when nested. The `7` is encoded the same way as a list of bytes of length 1, so the same as above. | + +--- [comment]: # (mx-context-auto) diff --git a/docs/developers/data/simple-values.md b/docs/developers/data/simple-values.md index 4b8f49971..aaefcf889 100644 --- a/docs/developers/data/simple-values.md +++ b/docs/developers/data/simple-values.md @@ -35,11 +35,8 @@ Even when simulating smart contract execution on 64-bit systems, they must still **Examples** | Type | Number | Top-level encoding | Nested encoding | -| - ---- - ----- | --------------------- | -------------------- | -------------------- | +| +| ------- | --------------------- | -------------------- | -------------------- | | `u8` | `0` | `0x` | `0x00` | | `u8` | `1` | `0x01` | `0x01` | | `u8` | `0x11` | `0x11` | `0x11` | @@ -94,7 +91,9 @@ Even when simulating smart contract execution on 64-bit systems, they must still | `isize` | `-0x11` | `0xEF` | `0xFFFFFFEF` | | `isize` | `-0x1122` | `0xEEDE` | `0xFFFFEEDE` | | `isize` | `-0x112233` | `0xEEDDCD` | `0xFFEEDDCD` | -| `isize` | `-0x11223344` | `0xEEDDCCBC` | `0xEEDDCCBC` |--- +| `isize` | `-0x11223344` | `0xEEDDCCBC` | `0xEEDDCCBC` | + +--- [comment]: # (mx-context-auto) diff --git a/docs/developers/toolchain-setup.md b/docs/developers/toolchain-setup.md index 5e1bddd39..f464084d3 100644 --- a/docs/developers/toolchain-setup.md +++ b/docs/developers/toolchain-setup.md @@ -87,12 +87,12 @@ Default host: x86_64-unknown-linux-gnu rustup home: /home/ubuntu/.rustup installed toolchains ---- ------------------ +-------------------- stable-x86_64-unknown-linux-gnu (default) [...] -active toolchain---------------- +active toolchain +---------------- name: stable-x86_64-unknown-linux-gnu installed targets: wasm32-unknown-unknown diff --git a/docs/developers/tutorials/interactors-guide.md b/docs/developers/tutorials/interactors-guide.md index 08678b232..cab11923a 100644 --- a/docs/developers/tutorials/interactors-guide.md +++ b/docs/developers/tutorials/interactors-guide.md @@ -271,9 +271,7 @@ test integration_test ... ok successes: ---- - -- integration_test stdout ---- +---- integration_test stdout ---- sender's recalled nonce: 1720 -- tx nonce: 1720 sc deploy tx hash: ca6e69c18acd73b20bfd21142b45be1b530ecbec89d1eb9c374b93f7681dbc38 diff --git a/docs/sdk-and-tools/rest-api/transactions.mdx b/docs/sdk-and-tools/rest-api/transactions.mdx index 2fd9de28b..34d171a06 100644 --- a/docs/sdk-and-tools/rest-api/transactions.mdx +++ b/docs/sdk-and-tools/rest-api/transactions.mdx @@ -33,10 +33,7 @@ Body Parameters | Param | Required | Type | Description | | - ---- - -------------- | ----------------------------------------- | -------- | -------------------------------------------------------------------------------- | +| ---------------- | ----------------------------------------- | -------- | -------------------------------------------------------------------------------- | | nonce | REQUIRED | `number` | The Nonce of the Sender. | | value | REQUIRED | `string` | The Value to transfer, as a string representation of a Big Integer (can be "0"). | | receiver | REQUIRED | `string` | The Address (bech32) of the Receiver. | @@ -269,7 +266,9 @@ _SimulationResults_ | receipts | []ApiReceipt | an array of the receipts (if any) | | hash | string | the hash of the transaction | -❕ Note that fields that are empty won't be included in the response. This can be seen in the examples below--- +❕ Note that fields that are empty won't be included in the response. This can be seen in the examples below + +--- 🟢 200: OK diff --git a/docs/utils.mdx b/docs/utils.mdx index 37d93b62c..b0466d019 100644 --- a/docs/utils.mdx +++ b/docs/utils.mdx @@ -42,6 +42,7 @@ _italicized text_ `code` ### Horizontal Rule + --- ### Link diff --git a/docs/validators/key-management/validator-keys.md b/docs/validators/key-management/validator-keys.md index 55aba44c7..ed2ad5a8b 100644 --- a/docs/validators/key-management/validator-keys.md +++ b/docs/validators/key-management/validator-keys.md @@ -20,8 +20,8 @@ The **Validator Keys** are located in the `validatorKey.pem` file, which is gene Below you can find their anatomy and how to extract the information from them Example: ---- ---BEGIN PRIVATE KEY for _45e7131ba37e05c5de3f8862b4d8294812f004a5b660abb793e89b65816dbff2b02f54c25f139359c9c98be0fa657d0bf1ae4115dcf6fdbf5f3a470f1d251f769610b48fe34eeab59e82ac1cc0336d1d9109a14b768b97ccb4db4c2431629688_----- + +-----BEGIN PRIVATE KEY for _45e7131ba37e05c5de3f8862b4d8294812f004a5b660abb793e89b65816dbff2b02f54c25f139359c9c98be0fa657d0bf1ae4115dcf6fdbf5f3a470f1d251f769610b48fe34eeab59e82ac1cc0336d1d9109a14b768b97ccb4db4c2431629688_----- **YmRiNmViOGYzMmQ3OWY0YjE4ODJjMzE1ODA4YjQyZmZjODhiZDQxNzMwNmE5MTRiZjQ4OTAyNjM0MTcyNjMzMw==** From 1f70a8ad81a048ecfe4d0471812908617b4c53f0 Mon Sep 17 00:00:00 2001 From: bogdan-rosianu Date: Tue, 3 Feb 2026 10:30:39 +0200 Subject: [PATCH 3/4] add llms-full.txt --- docs/learn/ai-agents.md | 2 +- scripts/generate-llms-txt.js | 69 + static/llms-full.txt | 60335 +++++++++++++++++++++++++++++++++ 3 files changed, 60405 insertions(+), 1 deletion(-) create mode 100644 static/llms-full.txt diff --git a/docs/learn/ai-agents.md b/docs/learn/ai-agents.md index b3fa6d768..3b88ebbec 100644 --- a/docs/learn/ai-agents.md +++ b/docs/learn/ai-agents.md @@ -7,7 +7,7 @@ description: "Learn how to use AI agents for your MultiversX projects" ## **llms.txt** MultiversX publishes an `llms.txt` file that supplies context enabling LLMs to effectively reference the full MultiversX documentation during inference. -https://docs.multiversx.org/llms.txt +https://docs.multiversx.com/llms.txt Read more about `llms.txt`: https://llmstxt.org diff --git a/scripts/generate-llms-txt.js b/scripts/generate-llms-txt.js index b16aebb85..91d56629d 100644 --- a/scripts/generate-llms-txt.js +++ b/scripts/generate-llms-txt.js @@ -19,6 +19,7 @@ const ROOT = path.join(__dirname, '..'); const DOCS_DIR = path.join(ROOT, 'docs'); const STATIC_DIR = path.join(ROOT, 'static'); const OUTPUT_FILE = path.join(STATIC_DIR, 'llms.txt'); +const OUTPUT_FULL_FILE = path.join(STATIC_DIR, 'llms-full.txt'); function safeRequire(p) { try { @@ -236,6 +237,43 @@ function extractFirstParagraph(mdContent) { return ''; } +function stripFrontmatter(mdContent) { + if (mdContent.startsWith('---')) { + const end = mdContent.indexOf('\n---', 3); + if (end !== -1) { + return mdContent.slice(end + 4); + } + } + return mdContent; +} + +function trimLeadingTitle(mdContent, title) { + const lines = mdContent.split(/\r?\n/); + if (lines.length === 0) return mdContent; + const first = lines[0].trim(); + const m = first.match(/^#+\s*(.+)$/); + if (m) { + const text = (m[1] || '').trim(); + if (text.toLowerCase() === (title || '').trim().toLowerCase()) { + // remove heading and following optional blank line + let idx = 1; + if (lines[idx] !== undefined && lines[idx].trim() === '') idx++; + return lines.slice(idx).join('\n'); + } + } + return mdContent; +} + +function removeInternalComments(mdContent) { + // Remove single-line Docusaurus comment markers like: + // [comment]: # (mx-abstract) + // [comment]: # (mx-context-auto) + return mdContent + .split(/\r?\n/) + .filter((line) => !/^\s*\[comment\]:\s*#\s*\(/.test(line.trim())) + .join('\n'); +} + async function getDocMeta(docId) { const filePath = await resolveDocPath(docId); let meta = { @@ -319,6 +357,7 @@ async function main() { // In this repository, sidebars.docs is an object of top-level category labels -> items[] const categories = sidebars.docs; const outputLines = []; + const fullLines = []; // Header with site title and tagline const headerTitle = site.title || 'Documentation'; @@ -332,12 +371,21 @@ async function main() { ); outputLines.push(''); + // Header for full file + fullLines.push(`# ${headerTitle}`); + if (site.tagline) fullLines.push('', `> ${site.tagline}`); + fullLines.push( + 'This file contains the full content of documentation pages, grouped by category, for AI consumption.' + ); + fullLines.push(''); + for (const [categoryLabel, items] of Object.entries(categories)) { const ids = new Set(); collectDocIdsFromItems(items, ids); if (ids.size === 0) continue; outputLines.push(`## ${categoryLabel}`); + fullLines.push(`## ${categoryLabel}`); // Build entries with title + description + url const all = []; for (const id of Array.from(ids)) { @@ -355,6 +403,24 @@ async function main() { } const clipped = desc.length > 400 ? `${desc.slice(0, 397)}...` : desc; outputLines.push(`- [${e.title}](${e.url})${clipped ? `: ${clipped}` : ''}`); + + // Build full content entry + if (e.filePath) { + try { + const raw = await fsp.readFile(e.filePath, 'utf8'); + let body = stripFrontmatter(raw); + body = trimLeadingTitle(body, e.title); + body = removeInternalComments(body); + fullLines.push(`### ${e.title}`); + fullLines.push(''); + const cleaned = body.trim(); + if (cleaned) fullLines.push(cleaned); + // Add a consistent separator between pages + fullLines.push(''); + fullLines.push('---'); + fullLines.push(''); + } catch {} + } } outputLines.push(''); } @@ -363,6 +429,9 @@ async function main() { await fsp.mkdir(STATIC_DIR, { recursive: true }); await fsp.writeFile(OUTPUT_FILE, outputLines.join('\n'), 'utf8'); console.log(`Wrote ${OUTPUT_FILE}`); + + await fsp.writeFile(OUTPUT_FULL_FILE, fullLines.join('\n'), 'utf8'); + console.log(`Wrote ${OUTPUT_FULL_FILE}`); } main().catch((err) => { diff --git a/static/llms-full.txt b/static/llms-full.txt new file mode 100644 index 000000000..61f098c5b --- /dev/null +++ b/static/llms-full.txt @@ -0,0 +1,60335 @@ +# MultiversX Docs + +> A highly scalable, fast and secure blockchain platform for distributed apps, enterprise use cases and the new internet economy. +This file contains the full content of documentation pages, grouped by category, for AI consumption. + +## Welcome to MultiversX +### Welcome to MultiversX + +## **New to Blockchain?** + +If you are brand new to blockchain and don't know where to start, we have provided some beginner resources that help you start your journey: + + - [What is Blockchain?](https://www.youtube.com/watch?v=tv6OBimIX98&list=PLQVcheGWwBRWFjgEGLx1Fv2qF_6UVpSXX) + - [What is a wallet?](/wallet/overview) + - [What is a Smart Contract?](https://youtu.be/BALVrahGeJ8?si=CaCyHlQF14CD2e01) + - [What is Gas?](/developers/gas-and-fees/overview) + - [What is a dApp?](https://www.youtube.com/watch?v=CQRO3YKQoFQ) + - [What is a cryptocurrency, coin or token?](https://www.youtube.com/watch?v=kyPMo6LPMc4) + - [What is an explorer?](https://youtu.be/tv6OBimIX98?si=CVvlHv4TyalQzKHt&t=1452) + +## **Get started with MultiversX!** + +### **At a glance** + +MultiversX is a [sharded](/learn/sharding) blockchain network, with a [PoS-based consensus mechanism](/learn/consensus). It supports [smart contracts](/developers/smart-contracts), scales via an industry-first adaptive state sharding mechanism and is therefore capable of horizontal scaling whilst having [low-end hardware requirements](/validators/system-requirements) for nodes. + +#### **Highlights:** + + - Performance and Throughput: + - 10,000 TPS per transaction shard + - 30,000 current max TPS + - 6s block time + - $0.002 tx cost + - can scale beyond 100,000 TPS + - Highest TPS achieved: 263,000 in a public testnet with 50 shards in 2020 + - Full horizontal scalability + - [xPortal](https://xportal.com) money app with progressive security, embedded DeFi, on- and off-ramps, cards and a vast array of features + - [Tokens](/tokens/fungible-tokens) (FT, SFT, NFT) natively embedded into the protocol for maximum safety + - [Developers](/developers/overview): + - Royalties: 30% SC gas returned to SC authors + - [Rust framework with debugger](/developers/smart-contracts) + - [Validators](/validators/overview): + - ~10% APR for running nodes + - low system requirements + - very easy to setup and run + - one-click-setup nodes available at Google Cloud or Tencent Cloud + - [Network setup](https://explorer.multiversx.com/validators): + - 5000+ nodes (3200+ validator nodes) + - 4 shards (1 Metachain, 3 transaction shards) + - more shards can be added if needed + - [Sovereign Chains](/sovereign/overview): + - DEMO - ["voyager" sovereign chain](https://explorer.voyager1.dev/) was launched as a test sovereign chain and reached a max TPS of >77k TPS with 1s block time + - Multi-VM - compatible with [Bitcoin](https://cdn.prod.website-files.com/6597cc7be68d63ec0c8ce338/664f6e01f2dd839286e20536_BitcoinX-litepaper.pdf), [Ethereum](https://cdn.prod.website-files.com/6597cc7be68d63ec0c8ce338/664f6e03efeeeb4e8c2efaf8_EthereumX-litepaper.pdf), [Solana](https://cdn.prod.website-files.com/6597cc7be68d63ec0c8ce338/664f6e026748935c069bd7a5_SolanaX-litepaper.pdf) and much more. + - Interoperability Layer - hyper-scalable, composable and configurable technological tree designed for flexibility and interoperability. + +### **How is MultiversX different?** + +The MultiversX network is the first of it's kind to present a viable solution where all three aspects of sharding, namely state, network and transactions have been fully implemented at once. Combining this with the unique "Adaptive" mechanism that allows adding or removing shards easily, this novel architecture allows the network to scale with demand whilst maintaining it's high level of security. + +In addition to horizontal scaling through sharding, MultiversX also approaches the consensus problem with a mechanism called Secure Proof of Stake, which mitigates potential attack vectors when compared to Proof of Work, while also enabling large throughput and fast execution. At the same time it manages to eliminate potential security issues found in sharded networks, by randomly shuffling validators after every epoch among all shards, provably mitigating shard takeover attacks. + +By solving some of the hardest consensus and sharding problems in the blockchain space, MultiversX is able to provide a very high level of performance on a network made of inexpensive computers, resulting in a very low cost per transaction whilst maintaining high levels of security and incentivizing decentralization. In addition to performance and cost, MultiversX also stands out through the quality of the developer experience and the resulting boost in usability on the end-user side. + + +## [**Roadmap and current status**](https://multiversx.com/roadmap) + +--- + +## Learn about MultiversX +### AI agents + +## **llms.txt** + +MultiversX publishes an `llms.txt` file that supplies context enabling LLMs to effectively reference the full MultiversX documentation during inference. +https://docs.multiversx.com/llms.txt + +Read more about `llms.txt`: https://llmstxt.org + +## **Skills** + +We have a GitHub repository that provides a structured collection of specialized "Skills", "Global Workflows" (Roles), and "Documentation" designed to equip AI agents with the deep technical knowledge required to build, audit, and optimize on MultiversX. + +https://github.com/multiversx/mx-ai-skills + +--- + +### Architecture Overview + +MultiversX is a high-throughput public blockchain aimed at providing security, efficiency, scalability and interoperability, beyond the current state-of-the-art. The two most important features that set MultiversX apart are Adaptive State Sharding and the Secure Proof of Stake consensus mechanism. + +MultiversX is a complete redesign of blockchain architecture with the aim to achieve global scalability and near instant transaction speed. MultiversX's architecture rests on the following key innovations: + +1. [**Adaptive State Sharding**](consensus) on all levels: transaction, data and network. The dynamically adaptive sharding mechanism will perform shard merging and shard splitting while taking into consideration both the number of available validator nodes and also the network usage. +2. [**Secure Proof of Stake**](/learn/consensus) **Consensus**, completed in just two communication steps, using modified Boneh–Lynn–Shacham ("BLS") multi-signatures among the validators of the consensus group. Moreover, nodes inside the shard are randomly selected for the consensus group with no possibility of knowing the group's composition more than one round in advance. +3. **High resiliency** to malicious attacks due to periodical node reshuffling across shards. Every epoch, up to 1/3 of the nodes in every shard are reshuffled to other shards in order to prevent collusion. +4. **Secure randomness source** with BLS signing, which makes it non-biasable and unpredictable. +5. The [**MultiversX WASM VM**](/learn/space-vm), an exceptionally fast virtual machine for executing smart contracts written in _any programming language_ that can compile to WebAssembly. +6. **Smart contracts on a sharded state architecture**, with balanced load on shards. This is a requirement for a high-throughput blockchain platform. Balancing smart contracts across shards allows MultiversX to run multiple SCs in parallel, while the cross-shard calls are handled by an asynchronous [cross-shard execution process](/learn/transactions). +7. **Fast finality for cross-shard transactions** in mere seconds. Having a very high TPS is required for a high throughput blockchain solution, but TPS is only half the picture: fast finality for cross-shard transactions is of crucial importance. Most existing state-of-the-art blockchain architectures refuse to mention this aspect, but from a user standpoint it is extremely important. Fast cross-shard finality is naturally handled by MultiversX at the protocol level, using a dispatching algorithm and a routing protocol. + +--- + +### Chronology + +Following the common Proof-of-Stake principles, the MultiversX network organizes time into rounds and epochs, where a fixed number of consecutive rounds form an epoch. The first round of the first epoch ever is called the _genesis round_, which contains the bootstrapping phase of the network. + + +## **Rounds** + +Each round has a fixed time duration, consistent across the network, currently decided to be 6 seconds. In [Architecture overview](/learn/architecture-overview) we mentioned that the MultiversX network is sharded. Because all shards process transactions in parallel and _in lock-step_, it means that in each round, inside a shard, at most one block may be added to the shard's blockchain. There may be rounds where no block is added to the blockchain, for example when consensus is not reached or when the designated consensus group leader is offline and cannot propose a block. + + +## **Epochs** + +An epoch is a sequence of consecutive rounds during which the configuration of the network does not change. The number of rounds in an epoch is initially calculated to produce epochs of 24 hours in length. + +The moment between epochs is used by the network to adapt its topology according to the processing load and its size, to compute rewards for the validator nodes and to perform other tasks to close the previous epoch and prepare for the new. Read more about how the network reconfigures its topology and how it prevents node collusion in [Adaptive State Sharding](/learn/sharding). + +--- + +### Consensus + +# Consensus (Secure Proof‑of‑Stake – SPoS) + +## 1. How Consensus Works in MultiversX (post-Andromeda) + +Consensus is the protocol that keeps every shard and the Metachain in lock‑step, ensuring all honest participants share **one canonical history**. MultiversX uses a bespoke scheme called **Secure Proof‑of‑Stake (SPoS)**, a blend of stake‑weighted validator selection, VRF‑style randomness, and a high‑speed BLS multisignature algorithm that guarantees _single‑block finality_. + +Below is a step‑by‑step walk‑through of *one* six‑second block cycle (round `r`). The same engine runs independently in **every execution shard** and in the **Metachain**. + +### 1.1 Validator set and epochs + +* **Validators.** Nodes that stake a minimum of 2500EGLD and run the MultiversX node client software. Each validator holds a long‑lived BLS key that is rotated only when they unbond/unstake, anyway the keys are shuffled among shards each epoch. +* **Epoch.** A period of 24 hours (≈ 14 400 blocks on a 6 s cadence-round). At the start of every epoch the 400 validators in each shard are reshuffled using a deterministic permutation seeded with verifiable randomness. The composition of a shard remains fixed for the whole epoch. + +### 1.2 Round randomness + +``` +newRandSeed = currentProposer.Sign(lastRandSeed) +randomness = hash(newRandSeed, currentRound) +``` +* `newRandSeed` - VUF (Verifiable Unpredictable Function). It allows the generate seeds that are verifiable, unpredictable but not uniform. +* `randomness` - VRF (Verifiable Random Function) + +### 1.3 Proposer election + +Each validator hashes together its public key and `rand_r`: + +``` +score_i = Hash(PK_i ‖ rand_r) +``` + +The lowest score wins and becomes the **proposer** for round `r`. Ties are astronomically unlikely. + +### 1.4 Two‑phase BLS consensus + +Once chosen, the proposer assembles a block and broadcasts it to the other 399 validators. Consensus then runs in **two BLS rounds**—`prepare` and `commit`—similar to PBFT but with aggregated signatures: + +1. **Prepare.** Validators check the block (state root, signatures, gas limits). If valid they sign its hash and return a 96‑byte BLS share. +2. **Commit.** The proposer (or any validator) aggregates ≥ ⅔ of the shares into a single 96‑byte signature and broadcasts a **commit message**. + +Because **all 400 validators** participate (post‑Andromeda), a share threshold of 268 guarantees Byzantine safety. + +### 1.5 Equivalent Consensus Proof (ECP) + +The final artefact attached to the block header is an **ECP**: + +``` +ECP = (agg_signature, bitmap_400) +``` + +* `agg_signature` – aggregated BLS signature from ≥ 268 validators. +* `bitmap_400` – 400‑bit field indicating which keys took part. + +Any validator can perform the aggregation; there is no single point of failure. Because the validator set and aggregation order are deterministic inside the epoch, *exactly one* valid ECP can exist for a given block—making equivocation impossible. + +### 1.6 Notarisation and finality + +* **Execution shards**: A block is **final the moment its ECP is broadcast**. No confirmation block is needed. +* **Metachain**: Collects hashes of all shard blocks plus their ECPs, notarises them in the metablock of the next round, and itself reaches finality via the same BLS protocol. + +**Latency:** ~6 s for intra‑shard txs, ~18 s for cross‑shard (three‑block path). + +## 2.  Consensus group size: 63 → 400 + +| Era | Execution‑shard consensus group | Metachain | +|-----|---------------------------------|-----------| +| **Pre‑Andromeda** | 63 of 400 eligible validators, shuffled _every round_ | 400 validators | +| **Andromeda** | **All 400 validators** every round (fixed for the whole epoch) | **Unchanged** (400) | + +Using the full validator set removes the need for a confirmation block; a single consensus proves majority acceptance. + +## 3.  Consensus flow (post‑Andromeda) + +```mermaid +sequenceDiagram + participant P as Proposer + participant V as Validators (399) + participant M as Metachain + + %% Round r + P->>V: ① Block proposal (header + txs) + V->>P: ② Prepare signatures (first BLS round) + P->>V: ③ Commit message (aggregated sig ≥ ⅔) + V->>M: ④ Broadcast final block + ECP + M--)P: ⑤ Block notarised (instant finality) +``` + +**Equivalent Consensus Proof (ECP)** +Any validator can aggregate ≥ ⅔ BLS shares into an ECP. Because aggregation order is fixed and the validator set is constant across the epoch, the proof is uniquely determined – _equivocation is impossible_. + +## 4.  Confirmation blocks removed + +*Pre‑Andromeda*: each execution shard produced a **block** and, one round later, a **confirmation block** to seal it. The Metachain applied the same pattern to eliminate double‑spend risk. +*Andromeda*: one block is enough; once its ECP is broadcast the block is final, so no confirmation blocks are produced. This halves average time‑to‑finality. + +## 5.  Validator rating & selection + +* **Rating** still measures historical performance and influences shard assignment probability at the **epoch shuffle** step. +* Because every validator now participates in every round, rating no longer affects _per‑round_ selection but continues to impact rewards via uptime and signature participation. + +## 6.  Security considerations + +* **Byzantine tolerance**: With 400 signatures per block the network tolerates up to 133 malicious validators per shard (≈ ⅓). +* **Rogue‑key protection**: Implemented via KOSK and deterministic aggregation order – enabling fast verification of 400‑party BLS signatures. +* **Proposer failure**: Because ECP finalisation is permissionless, the network is no longer sensitive to a single proposer going offline. + +## TL;DR + +* **400/400 validators** now sign every shard block. +* **Equivalent Consensus Proofs (ECP)** replace leader‑only finalisation – _any_ validator can finalise. +* **Confirmation blocks are gone**: a block is final the moment ≥ ⅔ signatures are aggregated. +* Randomness & proposer selection remain **per‑round** and still depend on the previous block hash. +* Validator **rating** and **stake** continue to influence shard assignment and rewards – unchanged. + +--- + +### Economics + +In this page you can find more about the MultiversX economics, the EGLD token supply, how fees are collected, how rewards and distributed and so on. + + +## **Economics Overview** + +The MultiversX blockchain economy is designed around eGold (EGLD) - a powerful digital currency positioned for global adoption. + +eGold, or EGLD, is the native coin of the network. + +- It is **paid** by users and developers to transfer value and assets, create and manage tokens, deploy and call smart contracts, and other similar operations + +- It is **earned** by validators, the set of internet computers that run and secure the network and help it reach consensus + +This circular digital economy revolves around EGLD, which has a maximum theoretical supply of 31,415,926 EGLD. + +In the bootstrapping phase of the MultiversX blockchain 20,000,000 EGLD coins were initially minted. +Additional EGLD are gradually released into circulation over a period of 10 years until the maximum theoretical limit is reached. + +The EGLD minting - or “inflation” - is offset by the sum of the transaction fees. As an example, if 100,000 EGLD are to be minted as per the inflation schedule in Year X, but the network-wide sum of the transaction fees for Year X are 75,000 EGLD, only 25,000 new EGLD will enter into circulation. + +As a result, the powerful economic attribute of scarcity is designed to scale with adoption. + +![img](/economics/egld-supply.png) + +You can learn more about the EGLD token and MultiversX economy from the resources below: +- https://multiversx.com/blog/egold-a-powerful-digital-currency-positioned-for-global-adoption/ +- https://multiversx.com/blog/the-wealth-of-crypto-networks-elrond-economics-paper + +--- + +### Entities + +There are two primary entities in MultiversX: users and nodes. + +A **user** is anyone holding one or more pairs of keys (one secret, one public). Using a pair of keys, the user can submit signed transactions to the network. The MultiversX network treats each pair of keys as an _account_, and each account implicitly has an associated amount of EGLD tokens, called the _balance_ of the account. Moreover, an account also has an associated mapping storage, which holds arbitrary values. + +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). + +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. + +--- + +### Getting Started + +MultiversX is a high-performance, sharded smart contract blockchain with Proof of Stake (SPoS) consensus, perfectly designed for the Web3 era with capacity to serve Web2 needs. At the moment of writing, it achieves over 30,000 TPS with adaptive scalability, offering fast and low-cost transactions. The xPortal money app, with embedded DeFi and cross-chain operations, empowers users to interact and own their digital assets. Developers benefit from integrated tools and 30% gas royalties, while validators earn ~8% APR. MultiversX offers sovereign chains for flexible, interoperable blockchain solutions, serving enterprise needs, driving the Web3 vision forward. It is a public blockchain similar to Ethereum, users own their accounts and tokens and they are free to move between [more than 500 different apps](https://multiversx.com/ecosystem). + +:::note +If you're not on MultiversX, get started by creating your wallet with [xPortal](https://xportal.com/app) or [xWallet](https://wallet.multiversx.com/create). +::: + +## **Learn** + +If you want to learn more, get started by diving into these concepts: + +- [**MultiversX Blockchain Basics**](https://www.youtube.com/watch?v=tv6OBimIX98&list=PLQVcheGWwBRWFjgEGLx1Fv2qF_6UVpSXX) - Learn about the basics of the blockchain, cryptocurrencies, consensus models, MultiversX explorer, wallet and a live demo of how to interact with the network. +- [**Architecture**](/learn/architecture-overview) - A breakdown of Multiversx's onchain system. +- [**Sovereign Chains**](/sovereign/overview) - Learn what Sovereign Chains represent for MultiversX. + +## **Build** + +If you are a developer, you've come to the right place. The development act is one of the easiest and most straight-forward that you will find in the entire space, with lots of tools and support offered by the foundation and community fellow developers. + +- [**Get EGLD**](/learn/EGLD.md#getting-egld) - You need EGLD or xEGLD in order to be able to interact with the MultiversX Network. +- [**Tutorials**](https://multiversx.com/builders/build-your-first-multiversx-dapp-in-30-minutes) - A great start for anyone looking to learn what it means to develop on the MultiversX blockchain. +- [**Cryptozombies**](https://cryptozombies.io/en/multiversx) - Interactive tutorials for learning how to write MultiversX Smart Contracts. +- [**Tools and resources**](https://multiversx.com/builders/builder-tools-resources) - You can access the best set of tools that you may need for running your project. +- [**SC development Framework**](/developers/smart-contracts) - The MultiversX Blockchain's VM executes WebAssembly which means that it can execute smart contracts written in any programming language that can be compiled to WASM bytecode. Though, at the time of writing, we only provide support for Rust, with Swift being planned for the end of the year 2024. +- [**Backend**](https://www.youtube.com/watch?v=6m4o_NkLP8o&list=PLQVcheGWwBRWFjgEGLx1Fv2qF_6UVpSXX&index=8) - So you've written your smart contract, but now you need to interact with it. Learn from this first-hand experience of interacting with smart contracts. +- [**Frontend**](https://www.youtube.com/watch?v=eMNIN5ip2w0&list=PLQVcheGWwBRWFjgEGLx1Fv2qF_6UVpSXX&index=5) - Because UI/UX is a big deal for MultiversX, having fully functional frontends is of utmost importance for your users and also for anyone interacting with blockchain. +- [**Support**](https://t.me/MultiversXDevelopers) - We encourage everyone to ask for support or seek answers for their questions if there are any. You can find us on [Telegram](https://t.me/MultiversXDevelopers), [Discord](https://discord.gg/multiversxbuilders) or [StackOverflow](https://stackoverflow.com/questions/tagged/multiversx). + +## **Contribute** + +MultiversX highly values and encourages contributions from the community, especially those who make their code public. To stay engaged with the latest developments and connect with other developers, consider joining a developer session. + +:::note +Contributing to the [MultiversX electric-capital repository](https://github.com/electric-capital/crypto-ecosystems/blob/master/data/ecosystems/m/multiversx-elrond.toml) by adding your product github repo, not only enhances visibility in the blockchain community but also showcases the dynamic nature of our builder group. +::: + +### **Repositories** + +| Repository | Description | +|------------|------------| +| [Protocol](https://github.com/multiversx/mx-chain-go)| Implementation of the protocol| +| [Proxy](https://github.com/multiversx/mx-chain-proxy-go)| Implementation of the proxy| +| [Documentation](https://github.com/multiversx/mx-docs)| Official documentation source, please open an issue or create PR at this repo| +| [Smart Contract Dev Framework](https://github.com/multiversx/mx-sdk-rs)| Recommended SC Dev Framework| +| [VSCode IDE](https://github.com/multiversx/mx-ide-vscode) | Microsoft VSCode IDE| +| [MultiversX Organization](https://github.com/multiversx/) | Official MultiversX Github Organization| + +### **Developer Sessions** + +We host a bi-monthly developer session to discuss upcoming changes to the protocol, SDKs and Tools. The call is open to anyone and we expect any builder to join and get involved. + +- [Sessions Calendar](https://multiversx.com/events/sessions) - Calendar of all planned Developer and Validators Sessions. +- [Recordings](https://www.youtube.com/@MultiversXOfficial/playlists) - watch recordings of previous calls. + +--- + +### MultiversX Virtual Machine (SpaceVM) + +The execution of SmartContracts plays a central role in modern blockchain networks. MultiversX built a fast and secure virtual machine for this purpose. + +Because the MultiversX VM executes [WebAssembly](https://en.wikipedia.org/wiki/WebAssembly), it also means that it can execute smart contracts written in _any programming language_ that can be compiled to WASM bytecode. **Though, we only provide support for writing contracts in Rust.** + +Developers are encouraged to use Rust for their smart contracts, however. MultiversX provides a [Rust framework](https://github.com/multiversx/mx-sdk-rs) which allows for unusually clean and efficient code in smart contracts, a rarity in the blockchain field. A declarative testing framework is bundled as well. + + +## **Features** + +The MultiversX VM was built to be as fast and secure as possible, but without adding unneeded restrictions to what smart contracts can do: the API provided by the VM, called the MultiversX Environment Interface, is comprehensive and easy to use. + + +## **Statelessness** + +The MultiversX WASM VM is a stateless VM. When a smart contract is being executed, it is not allowed to write directly neither to the blockchain, nor to the storage. This is an important design decision, because it obviates the need for reverting operations. So, instead of writing directly to the state, the API will accumulate the changes introduced by the smart contract execution into a _transient data structure_, which is then applied to the storage and / or blockchain, but only at the end of the execution, and only in case of success. Reading the global state is, of course, permitted at any time. + +In effect, the global state remains unaffected until the execution ends. + + +## **Fast execution engine** + +The MultiversX VM executes code using Wasmer as an execution engine, which operates as a just-in-time streaming compiler. Due to the design of Wasmer, the smart contracts are executed at near-native speed. + +The version of Wasmer that is embedded in the VM has been modified to add accurate metering with configurable cost per individual WASM opcode. But apart from metering, MultiversX has also modified Wasmer to allow for preemptive execution control at runtime, whereby a smart contract can be stopped immediately by the VM, if needed. Moreover, the compilation efficiency has been improved, and floating-point operations have been forbidden, to ensure strict determinism. + + +## **Asynchronous calls between contracts** + +Smart contracts may call each other using the VM's asynchronous API. Because the MultiversX Network is sharded adaptively, it may happen that a smart contract will end up calling another smart contract stored by a different shard. This is handled easily by the MultiversX VM, and the smart contract developer never has to care about shards. + +In case a contract calls another, and they are both in the same shard, the execution is effectively synchronous, and both contracts are executed without even leaving the VM. + +If the contracts happen to be in different shards, no worries - the execution will be automatically switched to an asynchronous mode, the call is sent to its destination shard, executed there, and then the flow finally returns to the caller. + +Both the synchronous and asynchronous modes are invisible to the smart contract developer: the API is the same for both, and the switch happens at runtime, when needed. + +--- + +### Sharding + +Sharding was first used in databases and is a method for distributing data across multiple machines. This makes it a _scaling technique_, and can be used by blockchain networks to partition states and transaction processing, so that each node of the network would only need to process a fraction of all the transactions. Moreover, sharding allows for the parallel processing of transactions. As long as there is a sufficient number of nodes verifying each transaction, ensuring high reliability and security, then splitting a blockchain into shards will allow it to process far more transactions by means of parallelization, and thus greatly improving transaction throughput and efficiency. Moreover, sharding promises to increase the throughput of the network as it expands and the number of validator grows - a property called _horizontal scaling_. + + +## **Sharding types** + +We emphasize the three main types of sharding: network sharding, transaction sharding and state sharding, described in the next paragraphs. + +**Network sharding** handles the way the nodes are grouped into shards and can be used to optimize communication, as message propagation inside a shard can be done much faster than propagation to the entire network. This is the first challenge in every sharding approach and the mechanism that maps nodes to shards has to take into consideration the possible attacks from an attacker that gains control over a specific shard. + +**Transaction sharding** handles the way the transactions are mapped to the shards where they will be processed. In an account-based system, the transactions could be assigned to shards based on the sender's address. + +**State sharding** is the most challenging approach. In contrast to the previously described sharding mechanisms, where all nodes store the entire state, in the case of state-sharded blockchains each shard maintains only a portion of the state. If the accounts involved in a transaction reside in different shards, executing that transaction will require the state to be updated in both shards and will involve the exchange of messages between the nodes of the two shards. In order to increase resiliency to malicious attacks, the nodes in the shards have to be reshuffled from time to time. However, moving nodes between shards introduces synchronization overheads, that is, the time taken for the newly added nodes to download the latest state from their new shard. Thus, it is imperative that only a subset of all nodes should be redistributed during each epoch, to prevent down times during the synchronization process. + + +## **Sharding directions** + +Some sharding proposals attempt to focus on transaction sharding or state sharding alone, which increases transaction's throughput, either by forcing every node to store lots of state data or each node to be a supercomputer. + +Sharding introduces some new challenges, such as the single-shard takeover attack, potentially intensive cross-shard communication, overall data availability and also the need of an abstraction layer that hides the shards. However, given that the above problems are addressed correctly, state sharding brings considerable overall improvements: transaction throughput will increase significantly due to parallel transaction processing and transaction fees will be considerably reduced. These two criteria are widely considered to be the main obstacles against mainstream adoption of blockchain technology. MultiversX has undertaken the task of transforming these obstacles into advantages and incentives towards massive mainstream adoption. + + +## **The MultiversX sharding approach** + + +## **Goals** + +Sharding in the MultiversX network was designed from the ground up to address the complexity of combining **network sharding**, **transaction sharding** and **state sharding**. The result is a cohesive protocol design, which not only achieves full sharding, but attains the following goals as well: + +1. **Scalability without affecting availability**, which requires that increasing or decreasing the number of shards should only affect a negligibly small vicinity of nodes without causing down-times, or minimizing them while updating states. +2. **Fast dispatching and instant traceability**, which requires that computing the destination shard of a transaction must be deterministic and also trivial to calculate, eliminating the need for communication rounds. +3. **Efficiency and adaptability**, which requires that the shards should be as balanced as possible at any given time. + +A trivial step-by-step example of how it works is depicted in the animation below: + +![img](/technology/sharding.gif) + +Adaptive State Sharding workflow + + +## Node shuffling + +To prevent collusion, the configuration of each shard needs to change regularly. The MultiversX network does this by shuffling nodes between shards at the end of each epoch. While reshuffling all of the nodes in every epoch would provide the highest security level, it would have a non-negligible impact on the liveness of the system, due to additional latencies that appear when nodes are resynchronizing with their new shards. To avoid these latencies, a carefully controlled proportion of eligible validators belonging to a shard will be redistributed non-deterministically and uniformly to the other shards at the end of each epoch. + +Shuffled nodes will be placed in the new shards in a _waiting list_, meaning that they must spend this epoch performing resynchronization with the new shard. Only after spending an entire epoch in the waiting list of the new shard is the node allowed to become an _eligible validator_ and join the shard in full. + +The unpredictability of the shuffling process is important for the security of the network. For this reason, at the end of each epoch, it is the metachain which computes a list of nodes which must leave their shards and move to new ones. These nodes are selected using the randomness source calculated in the preceding metachain block, which means that the selection and redistribution cannot be known in advance. This computation is deterministic, therefore it requires no additional communication. + +![img](/technology/node-shuffling.png) + +Node reshuffling diagram + +In the above diagram, _e_ represents the upcoming network, for which nodes must be shuffled, while _e - 1_ represents the epoch that is ending. + +The node shuffling process must take multiple aspects into account: + +- Nodes must be shuffled among shards while also maintaining the shards balanced; +- Nodes that have already spent an epoch synchronizing inside the waiting list of a shard must be promoted to _eligible validator_ status in the respective shard. +- There may be new nodes that have registered to the network and are waiting to join. They have been placed into a _network-wide waiting list_ and remain unassigned to shards until the end of the current epoch. +- There may be nodes that have signaled their intention to leave +- The network topology might need changes. If processing load has been uneven across shards, or if many nodes are joining or exiting the network, then the number of shards must change. + +To produce a shard configuration for the new epoch, the metachain performs the following steps: + +1. The number of nodes that must be shuffled out of each shard is calculated, then these nodes are _unassigned_ from their shards; +2. The new number of shards for the entire network is computed, based on the processing load in shards and number of nodes that will form the network in the upcoming epoch, and also taking into account nodes that are joining or leaving; +3. For each shard, the nodes that have previously spent an epoch synchronizing in the waiting list are promoted to _eligible validators_ in that shard; +4. The nodes in the network-wide waiting list (including those that have been unassigned from their shards at step 1) are redistributed randomly and uniformly to all shards and put into their waiting lists, where they'll spend the next epoch synchronizing with the new shard. + +As described before, the reconfiguration of shards at the start of epochs and the arbitrary selection of validators within rounds both discourage the creation of unfair coalitions and diminish the possibility of DDoS and bribery attacks, while maintaining decentralization and a high transactions throughput. + +--- + +### Transactions + +A blockchain transaction is a cryptographically signed instruction sent from one account to another, aiming to update the state of the blockchain network. Each transaction contains details such as the nonce, sender, receiver, amount of value transferred (if it is the case), data, signature (and others). Validators process and verify these transactions, ensuring they are legitimate and adhere to the network's protocol. Once verified, the transaction is recorded in a block and added to the blockchain, making it immutable and transparent. + +## **What's a Transaction?** + +A MultiversX transaction is an action initiated by an account managed by a user. For example, when Bob sends Alice 1 EGLD, Bob's account is debited and Alice's is credited. This state-changing action occurs within a transaction. + +Transactions update the network's state and are broadcast to the entire network. Validators execute and validate these transactions, ensuring they adhere to the network's rules. Each transaction includes details like sender and receiver addresses, the amount of EGLD transferred, and associated fees. These transactions are cryptographically signed to ensure authenticity and security. Transactions require a fee and must be included in a validated block. To make this overview simpler we'll cover [gas fees](/developers/gas-and-fees/overview) and validation elsewhere. + +Here’s what a typical MultiversX transaction might include: + +- ```Nonce```: A counter indicating the transaction number from the account. +- ```Value```: The amount of EGLD being transferred. +- ```Receiver```: The recipient's address. +- ```Sender```: The sender's address. +- ```Gas Price```: The price paid in EGLD for each gas unit. +- ```Gas Limit```: The maximum gas units the transaction can consume. +- ```Data```: Optional field for additional data. +- ```Chain ID```: The ID of the chain where the transaction has to be processed (Mainnet, Testnet, Sovereign Chain A etc.). +- ```Version```: The version of the transaction type. +- ```Signature```: Generated by the sender’s private key to authorize the transaction. + +A ready-to-broadcast transaction is structured as follows: + +```json +{ + "nonce": 42, + "value": "100000000000000000", + "receiver": "erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r", + "sender": "erd1ylzm22ngxl2tspgvwm0yth2myr6dx9avtx83zpxpu7rhxw4qltzs9tmjm9", + "gasPrice": 1000000000, + "gasLimit": 70000, + "data": "Zm9vZCBmb3IgY2F0cw==", + "chainID": "1", + "version": 1, + "signature": "5845301de8ca3a8576166fb3b7dd25124868ce54b07eec7022ae3ffd8d4629540dbb7d0ceed9455a259695e2665db614828728d0f9b0fb1cc46c07dd669d2f0e" +} +``` + +## Cross‑Shard Transactions + +### Prerequisites + +> **Note** +> To get the most out of this chapter we recommend reading **[Sharding](https://docs.multiversx.com/learn/sharding)** and our **[Introduction to MultiversX](https://docs.multiversx.com/learn/multiversx-ecosystem)** first. + +### Overview + +With the **Andromeda (v1.9.6)** upgrade, cross‑shard execution has been streamlined: + +* **Confirmation blocks have been removed** – every shard block is final as soon as it is signed by ≥ 2⁄3 of the 400‑validator consensus group. +* The end‑to‑end path for a cross‑shard transaction is now **three blocks (≈ 18 s on mainnet)**, down from six. +* The underlying data structures – blocks, miniblocks and metablocks – remain unchanged, so no SDK or API schema adjustments are required. + +To illustrate the new flow we reduce the network to **two execution shards** (`shard 0`, `shard 1`) and the **Metachain**. A user in `shard 0` sends EGLD to another user in `shard 1`. + +### Block structure recap + +Each shard block contains: + +| Component | Description | +|-----------|-------------| +| **Header** | nonce, round, proposer, timestamp, gas summary, Merkle roots, etc. | +| **Miniblocks** | ordered lists of transactions, grouped by `(sender shard → receiver shard)` pair. | + +In `shard 0` a typical block might include three miniblocks: + +| Miniblock | Contents | +|-----------|----------| +| **0** | Intra‑shard txs (`shard 0 → shard 0`). | +| **1** | Cross‑shard txs **from `shard 0` to `shard 1`** (our example). | +| **2** | Cross‑shard txs **from `shard 1` to `shard 0`** that were already executed in `shard 1`; they will be finalised here. | + +> There is no limit to how many miniblocks with the same sender/receiver pair can appear in one block. + +### Processing a cross‑shard transaction (post‑Andromeda) + +The atomic unit of cross‑shard execution remains the **miniblock**: either the entire miniblock is processed, or none of its transactions are applied and it is retried in the next round. + +MultiversX continues to use an **asynchronous model**: + +1. **Validation & execution in the sender’s shard.** + `shard 0` fully verifies the transaction (nonce, signatures, balance, etc.) and places it in miniblock 1. The block is produced and immediately final (single‑block finality). +2. **Notarisation by the Metachain.** + The Metachain includes the header & miniblock hashes of the new `shard 0` block in metablock `n + 1`, together with an **Equivalent Consensus Proof (ECP)** – a compressed BLS aggregate signature from ≥ 2⁄3 of the validator set. +3. **Execution in the receiver’s shard.** + Nodes in `shard 1` read the metablock, fetch miniblock 1 from `shard 0`, verify the ECP, check for replay, and apply the state changes (credit the destination address). The resulting `shard 1` block (nonce `n + 2`) is finalised with its own consensus proof and notarised in the next metablock. + +The sequence diagram below highlights the three‑round flow: + +```mermaid +sequenceDiagram + participant S0 as Shard 0 + participant MC as Metachain + participant S1 as Shard 1 + + S0->>MC: Block n (miniblock 1) + MC-->>S0: Notarisation + ECP + MC->>S1: Proven miniblock header + S1->>MC: Block n+2 (exec miniblock 1) +``` + +#### Rounds required + +The transaction is considered **final** once miniblock 1 is executed and notarised in `shard 1`. + +| Round | Block location | Event | +|-------|----------------|-------| +| `n` | `shard 0` | Tx validated & executed (miniblock 1). | +| `n + 1` | Metachain | Notarises `shard 0` block (`ECP`). | +| `n + 2` | `shard 1` | Executes & finalises the mirrored miniblock. | + +Total latency ≈ 18 seconds on the actual 6 s‑block‑time mainnet. + +### Why did cross‑shard execution need six rounds before Andromeda? + +Before v1.9.6 a cross‑shard transfer had to survive **at least six separate blocks** to guarantee it could never be reverted elsewhere: + +| Step | Round | Chain | Purpose | +|------|-------|-------|---------| +| 1 | `k` | Source shard | Produce block **Bk** containing the cross‑shard tx. | +| 2 | `k + 1` | Source shard | **Confirmation block** Ck+1 proving Bk. | +| 3 | `k + 2` | Metachain | Notarise Bk (only after it sees Ck+1). | +| 4 | `k + 3` | Metachain | Confirmation metablock Mk+3 proving Mk+2. | +| 5 | `k + 4` | Destination shard | Execute mirrored miniblock, produce Dk+4. | +| 6 | `k + 5` | Destination shard | Confirmation block Dk+5; metachain can now notarise Dk+4. | + +These guard‑rails were essential while only **63 of 400 validators** signed each shard block—an equivocating proposer could, in theory, create conflicting blocks until the confirmation arrived. + +With **Andromeda**, every block reaches finality immediately (400 signatures, fixed order), so the two confirmation stages are obsolete and the path collapses to three blocks. + +### Cross‑chain proof verification made simpler + +| Pre‑Andromeda | Post‑Andromeda | +|---------------|----------------| +| **63 / 400 validators** per block; membership shuffled each round. | **400 / 400 validators** sign every block for the entire epoch. | +| Verifier had to reconstruct the 63‑node subset and their signature order for every proof. | Verifier only needs the epoch‑wide validator list once; proofs are a single aggregated BLS signature. | +| Large metadata overhead in every cross‑chain proof. | Proofs are smaller, easier to verify, cheaper to transmit. | + +> **Effect:** Cross‑chain proofs are now lighter and faster to validate while preserving the same security guarantees. + +--- + +### What is EGLD? + +EGLD is the native cryptocurrency of MultiversX, functioning as digital, global money. It's the primary asset used within the MultiversX ecosystem. + +### Key Features of EGLD + +- **Ownership:** EGLD allows you to be your own bank, with full control over your funds. +- **Security:** Protected by cryptography, exists under your own account, ensuring the safety of your funds and transactions. +- **Decentralized:** No central authority controls EGLD, making it a truly decentralized currency. +- **Accessibility:** All you need is an internet connection and a wallet to use EGLD. +- **Flexibility:** EGLD can be divided into small units (as small as 0.000000000000000001), allowing for transactions of any size. +- **Payments:** Sending EGLD without any intermediary, everything directly on-chain, is like being face-to-face and handing cash over. + +### Unique Aspects of EGLD + +:::important +EGLD is the lifeblood of MultiversX. When you send EGLD or use a MultiversX application, you'll pay a fee in EGLD to use the network. This fee incentivizes validators to process and verify transactions. + +Validators, the record-keepers of MultiversX, ensure no one is cheating. They are selected to propose blocks of transactions and are rewarded with EGLD. + +The work validators do, and the capital they stake, keeps MultiversX secure and decentralized. **EGLD powers MultiversX.** + +When you stake your EGLD, you help secure the network and earn rewards. The threat of losing staked EGLD deters attackers. [More on staking](/validators/staking). + +::: + +- **Fueling MultiversX:** EGLD powers the MultiversX network, paying for transactions fees and incentivizing network validators. +- **Staking Rewards:** By staking EGLD, users help secure the network and earn rewards, promoting a robust and decentralized ecosystem. +- **Collateral Use:** EGLD can be used as collateral within decentralized finance (DeFi) applications, enabling borrowing, lending, and earning interest. + +### Value Proposition + +- **Transaction Fees:** EGLD is essential for paying transaction fees within the MultiversX network. +- **Store of Value:** With a fixed supply, EGLD serves as a digital store of value. +- **Investment:** EGLD's role in the growing MultiversX ecosystem makes it a valuable investment, similar to Bitcoin or other cryptocurrencies. + +### Getting EGLD + +:::important +EGLD can be purchased through various exchanges and stored in compatible wallets. **Always ensure to use trusted platforms for transactions.** +::: + +You can earn EGLD, receive it from your peers, friends or buy it from exchanges and various other apps. + +- **Centralized exchanges (CEXs)**: Exchanges are platforms that offer you the service of buying EGLD using traditional currencies. **They have custody over any EGLD you buy until you send it to a [wallet](/wallet/overview) you control.** +- **Receive EGLD from your peers and friends:**: [Once you have a MultiversX account](/wallet/web-wallet), all you need to do is to share your public address to start sending and receiving EGLD. +- **Wallets**: [xPortal](https://xportal.com/) wallet lets you buy EGLD with a debit/credit card, directly from the dApp. +- **Staking Rewards**: You can [play the role](/learn/entities) of a delegator (user) or validator (node operator) and start earning EGLD by delegating to a staking provider or by simply [running a validator node](/validators/delegation-manager). +- **Earn EGLD:** You can earn EGLD by participating in [hackathons, campaigns, coding challenges](https://multiversx.com/events), airdrops, bug bounties etc. + +:::note +To interact with the [Testnet](https://testnet-explorer.multiversx.com) or [Devnet](https://devnet-explorer.multiversx.com), you'll need xEGLD, which can be obtained from one of the available [faucets](/wallet/web-wallet.md#testnet-and-devnet-faucet). +::: + +--- + +### What is MultiversX? + +## **Summary** + +:::important + +MultiversX is a global network of interconnected computers that adhere to the [**MultiversX Protocol**](https://github.com/multiversx/mx-chain-go), providing a robust foundation for communities, applications, organizations, and digital assets. This decentralized platform enables users to create and manage their MultiversX accounts from anywhere in the world, at any time. + +With **MultiversX**, you can explore a vast ecosystem of applications or develop your own, all without relying on a central authority that could alter the rules or limit your access. The core innovation of MultiversX lies in its trustless environment, ensuring that the network remains open, transparent, and secure for all participants. + +::: + + +## **What is MultiversX?** + +MultiversX is a distributed transactional computation protocol that relies on a sharded state architecture and a [Secure Proof of Stake](/learn/consensus) consensus mechanism. While most other blockchain networks require custom hardware and high energy consumption, MultiversX runs on [average computers](/validators/system-requirements). + +By employing [_sharding_](/learn/sharding), a method of parallelizing data & transactions processing, MultiversX’s performance will scale up with the number of computers joining the network, reaching more than 100,000 transactions per second while growing increasingly decentralized. + +:::important +**What is a blockchain?** + +A blockchain is a database of transactions that is continually updated and shared across numerous computers in a network. Each new set of transactions is grouped into a "block," which is added to the chain of previous blocks, hence the name blockchain. Public blockchains like MultiversX enable anyone to add data, but not remove it (**immutable**). To alter any information or manipulate the system, one would need to compromise the majority of computers on the network, which is an incredibly challenging task. This inherent difficulty makes decentralized blockchains like MultiversX highly secure and trustworthy. +::: + + +## **Why would I use MultiversX?** + +MultiversX is a collaborative story written by all of us, inviting any builder to discover the digital universes that can be built together no matter whether it is in DeFi, Gaming, or Payments area. MultiversX nodes are distributed to more than 125 different staking providers, ensuring a robust and secure network. It becomes invaluable for individuals facing uncertainty regarding the security, stability, or mobility of their assets due to external forces beyond their control, and thanks to the ESDT standard and guardians that enhance its security. + +With transaction costs averaging just $0.002, MultiversX allows anyone to send value at very low costs, making it highly accessible and economical. Whether you are an individual or an enterprise, the low transaction fees ensure that transferring value is affordable and efficient. More on that, if you are a company that does not need any tokens or gas to be paid but only the benefits of a decentralized network, MultiversX offers Sovereign Chains. This solution acts as a response to the appchains movement in the industry. Proven to be the fastest, cheapest, and most composable solutions available, providing enterprises and institutions the opportunity to keep all their operations on-chain. Whether they are permissioned or permissionless, those chains are seamlessly connected to the entire ecosystem of blockchains, thanks to the multi-VM feature, opening up endless possibilities for secure, scalable, and interconnected digital operations. + +**Important achievements:** + +- First live blockchain architecture with state sharding on mainnet, running for >4y with 0s downtime +- **MainChain:** +30,000 TPS(scales > 100kTPS), 6s latency, $0.002/tx +- **Sovereign Chains:** +78,000 TPS, 1s latency +- xPortal App: buy, store, earn, pay EGLD, BTC, ETH, BNB with more than 1.5M users and >100k WAU (weekly active users) +- Smart Contracts, Staking & Delegation, Tokens, Bridges, Oracles +- Validated through multiple audits from Trail of Bits, RuntimeVerification, Certik & others +- More than 400 dApps already launched on Mainnet and many more in development on Devnet + + +The official roadmap can be found here: https://multiversx.com/roadmap + +--- + +## Developers +### ABI + +## ABI Overview + +To interact with a smart contract it is essential to understand its inputs and outputs. This is valid both for on-chain calls, and for off-chain tools, and can in most cases also tell us a lot about what the smart contract does and how it does it. + +For this reason, blockchain smart contracts have so-called ABIs, expressed in a platform-agnostic language - JSON in our case. + +Note that the name ABI is short for _Application Binary Interface_, which is a concept borrowed from low-level and systems programming. The word _binary_ does not refer to its representation, but rather to the fact that it describes the binary encoding of inputs and outputs. + +--- + + +## Minimal example + +At its base minimum, an ABI contains: +- Some general build information, mostly used by humans, rather than tools: + - the compiler version; + - the name and version of the contract crate; + - the framework version used. +- The name of the contract crate and the Rust docs associated to it (again, mostly for documentation purposes). +- Under it, the list of all endpoints. For each endpoint we get: + - The Rust docs associated to them; + - Mutability, meaning whether or not the endpoint can modify smart contract state. At this point in the evolution of the framework, this mutability is purely cosmetic, not enforced. It can be viewed as a form of documentation, or declaration of intent. This might change, though, in the future into a hard guarantee. + - Whether the endpoint is payable. + - The list of all inputs, with their corresponding names and types. + - The list of all outputs, with their corresponding types. It is rare but possible to have more than one declared output value. It is also rare but possible to have output values named. + +```json +{ + "buildInfo": { + "rustc": { + "version": "1.71.0-nightly", + "commitHash": "a2b1646c597329d0a25efa3889b66650f65de1de", + "commitDate": "2023-05-25", + "channel": "Nightly", + "short": "rustc 1.71.0-nightly (a2b1646c5 2023-05-25)" + }, + "contractCrate": { + "name": "adder", + "version": "0.0.0", + "gitVersion": "v0.43.2-5-gfe62c37d2" + }, + "framework": { + "name": "multiversx-sc", + "version": "0.43.2" + } + }, + "docs": [ + "One of the simplest smart contracts possible,", + "it holds a single variable in storage, which anyone can increment." + ], + "name": "Adder", + "constructor": { + "inputs": [ + { + "name": "initial_value", + "type": "BigUint" + } + ], + "outputs": [] + }, + "endpoints": [ + { + "name": "getSum", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "BigUint" + } + ] + }, + { + "docs": [ + "Add desired amount to the storage variable." + ], + "name": "add", + "mutability": "mutable", + "inputs": [ + { + "name": "value", + "type": "BigUint" + } + ], + "outputs": [] + } + ], + "events": [], + "esdtAttributes": [], + "hasCallback": false, + "types": {} +} +``` + +--- + + +## Data types + +Smart contract inputs and outputs almost universally use the [standard MultiversX serialization format](/developers/data/serialization-overview). + +The ABI is supposed to contain enough information, so that, knowing this standard, a developer can write an encoder or decoder for the data of a smart contract in any language. + +:::important Important +Please note that the type names are not necessarily the ones from Rust, we are trying to keep this language-agnostic to some extent. +::: + + +### Basic types + +First off, there are a number of [basic types](/developers/data/simple-values) that are known, and which have a universal representation: + +- Numerical types: `BigUint`, `BigInt`, `u64`, `i32`, etc. +- Booleans: `bool`. +- Raw byte arrays are all specified as `bytes`, irrespective of the underlying implementation in the contract. Someone who just interacts with the contract does not care whether the contracts works with `ManagedBuffer`, `Vec`, or something else, it's all the same to the exterior. +- Text: `utf-8 string`. +- 32 byte account address: `Address`. +- ESDT token identifier: `TokenIdentifier`. Encoded the same as `bytes`, but with more specific semantics. + + +### Composite types + +Then, there are several standard [composite types](/developers/data/composite-values). They also have type arguments that describe the type of their content: + +- Variable-length lists: `List`, where `T` can be any type; e.g. `List`. +- Fixed-length arrays: `arrayN`, where `N` is a number and `T` can be any type; e.g. `array5` represents 5 bytes. +- Heterogeneous fixed-length tuples, `tuple`, no spaces, where `T1`, `T2` , ... , `TN` can be any types; e.g. `tuple`. +- Optional data, `Option`, where `T` can be any type. Represented as either nothing, or a byte of `0x01` followed by the serialized contents. + + +### Custom types: overview + +All the types until here were standard and it is expected that any project using the ABI knows about them. + +But here it gets interesting: the ABI also needs to describe types that are defined the smart contract itself. + +There is simply not enough room to do it inline with the arguments, so a separate section is necessary, which contains all these descriptions. This section is called `"types"`, and it can describe `struct` and `enum` types. + +Have a look at this example with custom types. + +Let's take the following `enum` and `struct`: + +```rust +#[type_abi] +pub struct MyAbiStruct { + pub field1: BigUint, + pub field2: ManagedVec>, + pub field3: (bool, i32) +} + +#[type_abi] +pub enum MyAbiEnum { + Nothing, + Something(i32), + SomethingMore(u8, MyAbiStruct), +} +``` + +And this is their json representation: + +```json +{ + "buildInfo": {}, + "docs": [ + "Struct & Enum example" + ], + "name": "TypesExample", + "constructor": { + "inputs": [], + "outputs": [] + }, + "endpoints": [ + { + "name": "doSomething", + "onlyOwner": true, + "mutability": "mutable", + "inputs": [ + { + "name": "s", + "type": "MyAbiStruct" + } + ], + "outputs": [ + { + "type": "MyAbiEnum" + } + ] + } + ], + "events": [], + "esdtAttributes": [], + "hasCallback": false, + "types": { + "MyAbiStruct": { + "type": "struct", + "docs": [ + "ABI example of a struct." + ], + "fields": [ + { + "docs": [ + "Fields can also have docs." + ], + "name": "field1", + "type": "BigUint" + }, + { + "name": "field2", + "type": "List>" + }, + { + "name": "field3", + "type": "tuple" + } + ] + }, + "MyAbiEnum": { + "type": "enum", + "docs": [ + "ABI example of an enum." + ], + "variants": [ + { + "name": "Nothing", + "discriminant": 0 + }, + { + "name": "Something", + "discriminant": 1, + "fields": [ + { + "name": "0", + "type": "i32" + } + ] + }, + { + "name": "SomethingMore", + "discriminant": 2, + "fields": [ + { + "name": "0", + "type": "u8" + }, + { + "name": "1", + "type": "MyAbiStruct" + } + ] + } + ] + } + } +} +``` + + +### Custom types: struct + +ABI [structures](/developers/data/custom-types#custom-structures) are defined by: +- __Name__; +- __Docs__ (optionally); +- A list of ___fields___. Each field has: + - __Name__; + - __Docs__ (optionally); + - The __type__ of the field. Any type is allowed, so: + - simple types, + - composite types, + - other custom types, + - even the type itself (if you manage to pull that off). + +In the example above, we are declaring a structure called `MyAbiStruct`, with 3 fields, called `field1`, `field2`, and `field3`. + + +### Custom types: enum + +Similarly, [enums](/developers/data/custom-types#custom-enums) are defined by: +- __Name__; +- __Docs__ (optionally); +- A list of ___variants___. Each variant has: + - A __name__; + - __Docs__ (optionally); + - The __discriminant__. This is the index of the variant (starts from 0). It is always serialized as the first byte. + - Optionally, __data fields__ associated with the enum. + - It is most common to have single unnamed field, which will pe named `0`. There are, however, other options. Rust syntax allows: + - Tuple variants, named `0`, `1`, `2`, etc. + - Struct-like variants, with named fields. + +You can read more about Rust enums [here](https://doc.rust-lang.org/book/ch06-01-defining-an-enum.html). + +--- + + +## ESDT Attribute ABI + + +### Overview + +The framework will export all data types found in arguments, results, and events, but it doesn't intrinsically know abut the data that we use in SFT and NFT attributes. This is why there is a special annotation to specify this explicitly. + +Starting with the framework version `0.44`, developers can use the new trait annotation `#[esdt_attribute("name", Type)]` in order to export ESDT attributes types in the ABI file. + +The name field is an arbitrary name provided by the developer, to identify the token. Token identifiers are not hard-coded in contracts, but it can make sense to use the ticker here, if known. + +The type field is simply the name of the type, as it would show up in regular smart contract code. + +:::important Important +The annotation can only be used at trait level along with `#[multiversx_sc::contract]` or `#[multiversx_sc::module]` annotations. Using it anywhere else will not work. +::: + +The exported data will end up in 2 places: + +1. In the contract ABI, in a special `"esdt_attributes"` section; +2. In a special ESDT ABI file (`name.esdt-abi.json`), one for each such declared ESDT. + +More examples of this below. + + +### Details + +A new field called `esdtAttributes` was added to the ABI file, where developers can find the structs (name, type) exported using the `esdt_attribute` trait annotation. Additionally, each `esdt_attribute` will create a new json file with the name given by the developer (followed by `.esdt-abi`) and containing its exported structs (names, types and descriptions). + +The name/ticker is just a way to identify the idea of the token because we do not have the exact identifier or the possibility to create it through this annotation. We only use this annotation as a mark up for a specific ESDT, in order to define its fields' attributes type. It is useful to define ESDT attributes' type beforehand in order to get more specific and overall better results fetching data from other services. + + +### Example using basic types + +Let's take a simple contract `SomeContract` as an example and try out the new annotation. + +```rust title=lib.rs +#![no_std] + +multiversx_sc::imports!(); +multiversx_sc::derive_imports!(); + +#[multiversx_sc::contract] +#[esdt_attribute("testBasic", BigUint)] +pub trait SomeContract { + + #[init] + fn init(&self) {} +} +``` + +Adding the `#[esdt_attribute("testBasic", BigUint)]` at trait level along with `#[multiversx_sc::contract]` should export a new structure named `testBasic` with a `BigUint` field type. This structure resembles an ESDT with the ticker `testBasic` and the attributes fields of type `BigUint`. + +The abi can be generated calling `sc-meta all abi` in the contract folder, or by building the contract using `sc-meta all build` (this command also adds the `wasm` file to the `output` folder). + +Building the contract using `sc-meta all build` will generate the following folder structure: + +``` +some_contract +├── output +│ ├── some_contract.abi.json +│ ├── some_contract.imports.json +| ├── some_contract.mxsc.json +| ├── some_contract.wasm +│ ├── testBasic.esdt-abi.json +``` + +Let's check out the `some_contract.abi.json` file first. Here we discover the new `esdtAttributes` field, containing the value mentioned in the annotation. + +```json +{ + "esdtAttributes": [ + { + "ticker": "testBasic", + "type": "BigUint" + } + ] +} + +``` +We can also check the specific json file exported for the newly defined type where we can find information about the type separated from the main abi file. + +```json title=testBasic.esdt-abi.json +{ + "esdtAttribute": { + "ticker": "testBasic", + "type": "BigUint" + } +} +``` + + +### Using more complex types + +Now, let's see what happens when we use other types than basic ones. Let's add a `Vec`, an `Enum (MyAbiEnum)` and an `Option` to our esdt attributes. + +```rust title=lib.rs +#![no_std] + +multiversx_sc::imports!(); +multiversx_sc::derive_imports!(); + +#[multiversx_sc::contract] +#[esdt_attribute("testBasic", BigUint)] +#[esdt_attribute("testEnum", MyAbiEnum)] +#[esdt_attribute("testOption", Option)] +#[esdt_attribute("testVec", ManagedVec)] +pub trait SomeContract { + #[init] + fn init(&self) {} +} +``` + +If we call `sc-meta all abi` (or `sc-meta all build` if we also wish to build the contract), the new attributes will be added to our __some_contract.abi.json__ file and new separate json files will be created for each attribute. Now, our `esdtAttributes` section from our abi file should look like this: + +```json title=some_contract.abi.json +{ + "esdtAttributes": [ + { + "ticker": "testBasic", + "type": "BigUint" + }, + { + "ticker": "testEnum", + "type": "MyAbiEnum" + }, + { + "ticker": "testOption", + "type": "Option" + }, + { + "ticker": "testVec", + "type": "List" + } + ], +} +``` + +Now, if we take a look into the folder structure of the contract, we should see the following updated folder structure containing the newly generated files in `output`: + +``` +some_contract +├── output +│ ├── some_contract.abi.json +│ ├── some_contract.imports.json +| ├── some_contract.mxsc.json +| ├── some_contract.wasm +│ ├── testBasic.esdt-abi.json +│ ├── testEnum.esdt-abi.json +│ ├── testOption.esdt-abi.json +│ ├── testVec.esdt-abi.json +``` + +Each file contains the new struct with its name and the type field's description such as: + +```json title=testOption.esdt-abi.json +{ + "esdtAttribute": { + "ticker": "testOption", + "type": "Option" + } +} +``` + +Let's also add a custom `struct` into the mix. For this example we are going to use `MyAbiStruct` declared above. + +Here is the updated code for __lib.rs:__ + +```rust title=lib.rs +#![no_std] + +multiversx_sc::imports!(); +multiversx_sc::derive_imports!(); + +#[multiversx_sc::contract] +#[esdt_attribute("testBasic", BigUint)] +#[esdt_attribute("testEnum", MyAbiEnum)] +#[esdt_attribute("testOption", Option)] +#[esdt_attribute("testVec", ManagedVec)] +#[esdt_attribute("testStruct", MyAbiStruct)] +pub trait SomeContract { + #[init] + fn init(&self) {} +} +``` + +Same as before, we use `sc-meta all abi` and a new file named `testStruct.esdt-abi.json` shows up in our folder structure: + +``` +some_contract +├── output +│ ├── some_contract.abi.json +│ ├── some_contract.imports.json +| ├── some_contract.mxsc.json +| ├── some_contract.wasm +│ ├── testBasic.esdt-abi.json +│ ├── testEnum.esdt-abi.json +│ ├── testOption.esdt-abi.json +│ ├── testStruct.esdt-abi.json +│ ├── testVec.esdt-abi.json +``` + +As a final check, let's take a look at what changed in the main abi file, `some_contract.abi.json`, after adding multiple new attributes. + +```json title=some_contract.abi.json +{ + "esdtAttributes": [ + { + "ticker": "testBasic", + "type": "BigUint" + }, + { + "ticker": "testEnum", + "type": "MyAbiEnum" + }, + { + "ticker": "testOption", + "type": "Option" + }, + { + "ticker": "testVec", + "type": "List" + }, + { + "ticker": "testStruct", + "type": "MyAbiStruct" + } + ], +} +``` + +You can find more examples containing multiple data types in the `abi-tester` from [here](https://github.com/multiversx/mx-sdk-rs/tree/master/contracts/feature-tests/abi-tester). + +--- + +### Account storage + +## Description + +The MultiversX protocol offers the possibility of storing additional data under an account as key-value pairs. This can be useful for many use cases. + +A wallet owner can store key-value pairs by using the built-in function `SaveKeyValue` that receives any number of key-value pairs. + +:::tip +Keys that begin with `ELROND` will be rejected because they are reserved for protocol usage. +::: + + +## Transaction format + +```rust +SaveKeyValueTransaction { + Sender: + Receiver: + Value: 0 + GasLimit: + Data: "SaveKeyValue" + + "@" + + + "@" + + + "@" + + + "@" + + + ... +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ +The gas used is computed as following: + +```rust +required_gas = save_key_value_cost + + move_balance_cost + + cost_per_byte * length(txData) + + persist_per_byte * length(key) + // repeated if multiple pairs + persist_per_byte * length(value) + // repeated if multiple pairs + store_per_byte * length(value) + // repeated if multiple pairs +``` + +For a real case example, the cost would be: + +`SaveKeyValue@6b657930@76616c756530` would cost `271000` gas units. + +If we break down the gas usage operations, using the costs in the moment of writing, we would get: + +```rust +required_gas = 100000 + // save key value function cost + 50000 + // move balance cost + 1500 * 34 + // cost_per_byte * length(txData) + 1000 * 4 + // persist_per_byte * length(key) + 1000 * 6 + // persist_per_byte * length(value) + 10000 * 6 + // store_per_byte * length(value) + + = 271000 +``` + + +## Example + +Let's save a single key-value pair. Key would be `key0` and the value would be `value0`. + +```rust +SaveKeyValueTransaction { + Sender: + Receiver: + Value: 0 + GasLimit: 271000 + Data: "SaveKeyValue" + + "@" + 6b657930 + // key0 + "@" + 76616c756530 // value0 +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + + +## REST API + +There are two endpoints that can be used for fetching key-value pairs for an account. They are: + +- [Get value for key](/sdk-and-tools/rest-api/addresses/#get-storage-value-for-address) +- [Get all key-value pairs](/sdk-and-tools/rest-api/addresses/#get-all-storage-for-address) + +--- + +### accounts + +This page describes the structure of the `accounts` index (Elasticsearch), and also depicts a few examples of how to query it. + + +## _id + +The `_id` field of this index is represented by a bech32 encoded address. + + +## Fields + + +| Field | Description | +|------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| address | The address field holds the address in bech32 encoding. Should be equal to the _id field. | +| balance | The balance field holds the amount of EGLD the address possesses. It is a string that also includes the number of decimals. Example: "1500000000000000000" (equivalent to 1.5 EGLD). | +| balanceNum | The balanceNum field holds the amount of EGLD the address possesses, in a numeric format. Example: 1.5. | +| nonce | The nonce field represents the sequence number of the address. | +| shardID | The shardID field represents the shard of the account. | +| timestamp | The timestamp field represents the last moment when the address balance was changed. | +| developerRewards | The developerRewards represents the fees that were accumulated after all the smart contract calls. They can be claimed by the owner. | +| currentOwner | The currentOwner field holds the address in a bech32 format of the current owner of the smart contract. This field is populated only for the smart contract addresses. | +| userName | The userName field contains the herotag the address possesses. | + + +## Query examples + + +### Fetch addresses sorted by balance + +``` +curl --request GET \ + --url ${ES_URL}/accounts/_search \ + --header 'Content-Type: application/json' \ + --data '{ + "sort": [ + { + "balanceNum": { + "order": "desc" + } + } + ], + "size":10 +}' +``` + + +### Fetch addresses in a shard, sorted by balance + +``` +curl --request GET \ + --url ${ES_URL}/accounts/_search \ + --header 'Content-Type: application/json' \ + --data '{ + "query": { + "match": { + "shardId": "1" + } + }, + "sort": [ + { + "balanceNum": { + "order": "desc" + } + } + ], + "size":10 +}' +``` + + +### Fetch addresses with username + +``` +curl --request GET \ + --url ${ES_URL}/accounts/_search \ + --header 'Content-Type: application/json' \ + --data '{ + "query": { + "exists": { + "field": "userName" + } + } +}' +``` + +--- + +### accountsesdt + +This page describes the structure of the `accounts-esdt` index (Elasticsearch), and also depicts a few examples of how to query it. + + +## _id + +The `_id` field of this index is composed in this way: `{bech32address}-{tokenIdentifier}-{nonce}` (example: `erd..-abcd-0123-01`). + + +## Fields + + +| Field | Description | +|------------|---------------------------------------------------------------------------------------------------------------------------------------| +| identifier | The identifier field consists of `token` field and the `nonce` field hex encoded (example: `TOKEN-01abdc-01`). | +| address | The address field holds the address in bech32 encoding. | +| balance | The balance field holds the amount of ESDT token the address possesses. It includes the number of decimals. | +| balanceNum | The balanceNum field holds the amount of ESDT tokens the address possesses, in a numeric format. | +| data | The data field is a structure that contains extra data about a token, such as the creator of an NFT. | +| tokenNonce | The tokenNonce field holds the sequence number of the token. This field is empty in the case of `FungibleESDT`. | +| token | The token field holds the name of the token. | +| timestamp | The timestamp field represents the timestamp when the address balance was changed. | +| type | The type field represents the type of the ESDT token. It can be `FungibleESDT`, `NonFungibleESDT`, `SemiFungibleESDT`, or `MetaESDT`. | +| frozen | The frozen field is set to true when the address possesses a current ESDT token that is in a frozen state. | + +Docs with a non-empty `tokenNonce` field will have the `data` field populated with the following structure: + + +| data fields | Description | +|--------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| uris | The uris field holds a list of URIs. | +| creator | The creator field holds the bech32 encoded address of the token's creator. | +| whiteListedStorage | The whiteListedStorage field is true if the token has white-listed storage. An NFT/SFT has white-listed storage if the URI belongs to one of the allowed decentralized storage services, such as IPFS or Pinata. | +| attributes | The attributes field contains the attributes of the token. | +| nonEmptyURIs | The nonEmptyURIs field is true if the token has non empty uris. | + + +## Query examples + + +### Fetch addresses that hold a specific token, sorted by balance (top holders of a token) + +``` +curl --request GET \ + --url ${ES_URL}/accountsesdt/_search \ + --header 'Content-Type: application/json' \ + --data '{ + "query": { + "match": { + "token": { + "query": "MY-TOKEN-aaabbb", + "operator": "AND" + } + } + }, + "sort": [ + { + "balanceNum": { + "order": "desc" + } + } + ], + "size":10 +}' +``` + + +### Fetch all the ESDT tokens in an address wallet + +``` +curl --request GET \ + --url ${ES_URL}/accountsesdt/_search \ + --header 'Content-Type: application/json' \ + --data '{ + "query": { + "match": { + "address":"erd.." + } + } +}' +``` + +--- + +### accountsesdthistory + +This page describes the structure of the `accounts-esdt-history` index (Elasticsearch), and also depicts a few examples of how to query it. + + +## _id + +The `_id` field of this index is composed in this way: `{bech32address}_{tokenIdentifier}_{nonce}_{timestamp}` (example: `erd.._abcd-0123-01`). + + +## Fields + + +| Field | Description | +|-----------------|---------------------------------------------------------------------------------------------------------------------| +| address | The address field holds the address in bech32 encoding. | +| balance | The balance field holds the amount of ESDT tokens the address possesses. | +| token | The token field holds the token name of the token. | +| identifier | The identifier field is composed of the `token` field and the `nonce` field, hex encoded. | +| tokenNonce | The tokenNonce field holds the sequence number of the token. This field can be empty in the case of `FungibleESDT`. | +| timestamp | The timestamp field represents the timestamp when the address balance was changed. | +| isSmartContract | The isSmartContract field is true if the address is a smart contract address. | +| shardID | The shardID field represents the shard of the account. | + + +## Query examples + + +### Fetch the latest 10 entries of an address' tokens sorted by timestamp + +``` +curl --request GET \ + --url ${ES_URL}/accountsesdthistory/_search \ + --header 'Content-Type: application/json' \ + --data '{ + "query": { + "bool": { + "must": [ + { + "match": { + "identifier": { + "query": "MY-TOKEN-aaabbb", + "operator": "AND" + } + } + }, + { + "match": { + "address": "erd..." + } + } + ] + } + }, + "sort": [ + { + "timestamp": { + "order": "desc" + } + } + ], + "size":10 +}' +``` + +--- + +### accountshistory + +This page describes the structure of the `accounts-history` index (Elasticsearch), and also depicts a few examples of how to query it. + + +## _id + +The `_id` field of this index is composed in this way: `{bech32address}_{timestamp}` (example: `erd.._1234`). + + +## Fields + + +| Field | Description | +|------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| address | The address field holds the address in bech32 encoding. | +| balance | The balance field holds the amount of EGLD the address possesses. It is a string that also includes the number of decimals. Example: "1500000000000000000" (equivalent to 1.5 EGLD). | +| isSender | The isSender field is true if the address was the sender when the balance has changed. | +| timestamp | The timestamp field represents the timestamp when the address balance was changed. | +| isSmartContract | The isSmartContract field is true if the address is a smart contract address. | +| shardID | The shardID field represents the shard of the account. | + + +## Query examples + + +### Fetch the latest 10 entries for an address sorted by timestamp + +``` +curl --request GET \ + --url ${ES_URL}/accountshistory/_search \ + --header 'Content-Type: application/json' \ + --data '{ + "query": { + "match": { + "address": "erd..." + } + }, + "sort": [ + { + "timestamp": { + "order": "desc" + } + } + ], + "size":10 +}' +``` + +--- + +### Basics + +## Code Arrangement + +We'll start with something simple: code arrangement. It's best to separate your code into 4 main parts: +- endpoints +- view functions +- private functions +- storage + +This ensures that it's much easier to find what you're looking for, and it's also much easier for everyone else who's working on that smart contract. Additionally, it's also best to split endpoints by their level of access. Some endpoints might be owner-only, some might be usable only by a select few addresses from a whitelist, and some can be called by anyone. + +The recommended order is the one from the list above, but order is not important as long as you clearly separate your code. Even better if you split those into modules. + +## Module Size + +Each module should have a maximum of 200-300 lines. If you ever need more than that, consider splitting. Makes it much easier to find what you're looking for. + +## Function Size + +Each function should be ~30 lines. Any more than that and it's really hard to navigate the file. + +## Code Placement + +The `lib.rs` file should contain only the `init` and `upgrade` functions most of the time. Sometimes it's tempting to bundle a bunch of unrelated functions there, but don't. You'll end up with an unreasonably large `lib.rs` file. + +## Module Placement + +Each module can be placed in its own folder along with other related modules. Sure, splitting the code is nice, but having to navigate through a large number of files, all at the level of `lib.rs`, might be cumbersome. This makes it even easier to search for specific features. + +## Error Messages + +If you have the same error message in multiple places, it's better to declare a static with the message and use that instead of copy-pasting the message. If you have the same condition too, consider having a separate `require_X` function. + +Example: +``` +pub static TOO_MANY_ARGS_ERR_MSG: &[u8] = b"Too many arguments"; + +#[endpoint(myEndpoint)] +fn my_endpoint(&self, args: MultiValueEncoded) { + require!(args.len() < 10, TOO_MANY_ARGS_ERR_MSG); +} +``` + +or: +``` +fn require_less_than_max_args(&self, args: &MultiValueEncoded) { + require!(args.len() < 10, "Too many arguments"); +} +``` + +## Small PRs + +Unless you're mass-upgrading everything, in which case you really have no other choice, it's much better to keep your PRs focused on one specific task. Also much easier for reviewers to spot issues. + +--- + +### BigUint Operations + +## BigUint Operations + +`BigUint` is simply a handle to the actual representation, similar to how system file handles work, so it's simply a struct with an `i32` as member, representing the handle. All the operations have to be done through API functions, passing the handles for result, first operand, second operand. Using Rust's operator overloading feature, we're able to overwrite arithmetic operators and provide an easy way of adding `BigUint`s, just like primitive number types. + +Even so, you might easily reach situations where you want to use the same number multiple times. For instance, let's say you have 4 `BigUint`s: `a`, `b`, `c`, `d`, and you want to do the following operations: +``` +c = a + b; +d = c + a; +``` + +You will quickly come to realize this does not work, due to Rust's ownership system. `a` is consumed after the first operation, and you will likely reach an error like this: +``` +move occurs because `a` has type `::BigUint`, which does not implement the `Copy` trait +``` + +The easiest way to solve this is to simply clone `a`. +``` +c = a.clone() + b; +d = c + a; +``` + +The errors are now gone, but behind the scenes, this is more complex than simply copying the handle. `a.clone()` creates a whole new `BigUint`, copying the bytes from the original `a`. + +This can be solved by borrowing `a`. `+` and the other operations are defined for references of `BigUint`, so this can be rewritten as: +``` +c = &a + &b; +d = c + a; +``` + +Another example of avoiding the creation of additional `BigUint`s is performing operations with multiple arguments: +``` +e = a + b + c + d; +``` +Or, if you want to keep the instances (can't add owned `BigUint` to `&BigUint`, so you have to borrow the results as well): +``` +e = &((&(&a + &b) + &c) + &d; +``` + +In both cases, this would do the following API calls: +``` +temp1 = bigIntNew(); +bigIntAdd(temp1, a, b); + +temp2 = bigIntNew(); +bigIntAdd(temp2, temp1, c); + +temp3 = bigIntNew(); +bigIntAdd(temp3, temp2, d); +``` + +And as such, creating 3 new `BigUints`, one for the result of `a + b`, one for the result of `(a + b) + c` and one for the final result that ends up in `e`. This can be optimized by rewriting the code in the following way: + +``` +e = BigUint::zero(); +e += &a; +e += &b; +e += &c; +e += &d; +``` + +This creates a single `BigUint` instead of 3. + +Of course, these are trivial examples, but we hope this clears up some confusion about how `BigUint`s work and how you can get the most of them. + +--- + +### Blackbox calls + +[comment]: # "mx-abstract" + +## Overview + +With the addition of unified syntax and new proxies, blackbox testing becomes the easiest and most accessible way to write integration tests for smart contracts. We can identify four types of transactions that can be ran in the testing environment: +- transaction - either a simple transfer or a contract call +- query - fetching values from the sc storage +- deploy - deploying a contract +- upgrade - upgrading a contract + +As mentioned in [TxEnv](../../transactions/tx-env#integration-test), in order to be able to create transactions in the testing environment, we need to create an instance of the `ScenarioWorld` struct. In order to actually send the transaction, we need to finish it with `.run()`. + +### Transaction + +```rust title=blackbox_test.rs + world // ScenarioWorld struct + .tx() // tx with testing environment + .from(OWNER_ADDRESS) + .to(ADDER_ADDRESS) + .typed(adder_proxy::AdderProxy) // typed call + .add(1u32) + .run(); // send transaction +``` + +In this example, we create a contract call to the `adder` contract. We are using the proxy (`AdderProxy`) to call the `add` endpoint of the contract and we mark the end of the transaction and the start of the execution by using `.run()`. + +### Query + +```rust title=blackbox_test.rs + world // ScenarioWorld struct + .query() // tx with testing query environment + .to(ADDER_ADDRESS) + .typed(adder_proxy::AdderProxy) + .sum() + .returns(ExpectValue(6u32)) // assert return value equals 6u32 + .run(); // send transaction +``` + +In this example, we are querying the `sum` view of the `adder` contract through the proxy. + +### Deploy + +```rust title=blackbox_test.rs + let new_address = world // ScenarioWorld struct + .tx() // tx with testing environment + .from(OWNER_ADDRESS) + .typed(adder_proxy::AdderProxy) + .init(5u32) // deploy call + .code(CODE_PATH) + .new_address(ADDER_ADDRESS) // custom deploy address (only in tests) + .returns(ReturnsNewAddress) + .run(); // send transaction +``` + +In this example, we are deploying the `adder` contract by calling it's `init` endpoint through the proxy. The `.new_address(ADDER_ADDRESS)` component is only present in tests for deploy transactions and signals that the contract should be deployed at a specific custom test address. + +### Upgrade + +```rust title=blackbox_test.rs + world // ScenarioWorld struct + .tx() // tx with testing environment + .from(OWNER_ADDRESS) + .to(ADDER_ADDRESS) + .typed(adder_proxy::AdderProxy) + .upgrade(100u64) // upgrade call + .code(CODE_PATH) // contract code + .run(); // send transaction +``` + +In order to upgrade the contract inside the testing environment, we need to call the `upgrade` function through the proxy (as if it was an endpoint, similar to the deploy call). The upgrade call needs to know the contract code, so we always need to attach it to the transaction with `.code(...)`. + +--- + +### Blackbox example + +## Example explained + +The best way to dive into the smart contract test framework is by dissecting a minimal example. + +```rust title=adder_blackbox_test.rs +use multiversx_sc_scenario::imports::*; + +use adder::*; + +const OWNER_ADDRESS: TestAddress = TestAddress::new("owner"); +const ADDER_ADDRESS: TestSCAddress = TestSCAddress::new("adder"); +const CODE_PATH: MxscPath = MxscPath::new("output/adder.mxsc.json"); + +fn world() -> ScenarioWorld { + let mut blockchain = ScenarioWorld::new(); + blockchain.register_contract(CODE_PATH, adder::ContractBuilder); + blockchain +} + +#[test] +fn adder_blackbox() { + let mut world = world(); + + world.start_trace(); + + world.account(OWNER_ADDRESS).nonce(1); + + let new_address = world + .tx() + .from(OWNER_ADDRESS) + .typed(adder_proxy::AdderProxy) + .init(5u32) + .code(CODE_PATH) + .new_address(ADDER_ADDRESS) + .returns(ReturnsNewAddress) + .run(); + + assert_eq!(new_address, ADDER_ADDRESS.to_address()); + + world + .query() + .to(ADDER_ADDRESS) + .typed(adder_proxy::AdderProxy) + .sum() + .returns(ExpectValue(5u32)) + .run(); + + world + .tx() + .from(OWNER_ADDRESS) + .to(ADDER_ADDRESS) + .typed(adder_proxy::AdderProxy) + .add(1u32) + .run(); + + world + .query() + .to(ADDER_ADDRESS) + .typed(adder_proxy::AdderProxy) + .sum() + .returns(ExpectValue(6u32)) + .run(); + + world.check_account(OWNER_ADDRESS); + + world + .check_account(ADDER_ADDRESS) + .check_storage("str:sum", "6"); + + world.write_scenario_trace("trace1.scen.json"); +} +``` + + + +### Imports + +```rust +use multiversx_sc_scenario::imports::*; +``` + +Importing everything from `multiversx_sc_scenario` gives us all the tools we need to write integration tests. From data types to methods, the dependency to `multiversx_sc_scenario` is definitely worth it for the majority of developers. + + +### Contract reference + +```rust +use adder::*; +``` + +In this case, the only reason we import the contract files is to gain access to `adder_proxy.rs`, which is located at `adder/src/adder_proxy.rs`. + + +### Constants + +```rust +const OWNER_ADDRESS: TestAddress = TestAddress::new("owner"); +const ADDER_ADDRESS: TestSCAddress = TestSCAddress::new("adder"); +const CODE_PATH: MxscPath = MxscPath::new("output/adder.mxsc.json"); +``` + +The framework provides various types that offer accessibility acting as a wrapper around strings and giving a clearer scope for the constants. Mandos relies heavily on strings, so, eventually, we have to pass strings to mandos. + +- `TestAddress` - creates a test address for a user that is not a smart contract. Using `TestAddress::new("owner")` is the same as using `address:owner`, but wrapper in a type. +- `TestSCAddress` - creates a test address for a smart contract. Same as before, using `TestSCAddress::new("adder")` is equivalent to using `sc:adder`. +- `MxscPath` - creates a path that is specifically used for locating the contract code. + + +### Environment setup function (`world`) + +```rust +fn world() -> ScenarioWorld { + let mut blockchain = ScenarioWorld::new(); + blockchain.register_contract(CODE_PATH, adder::ContractBuilder); + blockchain +} +``` + +In the environment setup function of this example, the first step is to create an instance of the `ScenarioWorld` struct. This `ScenarioWorld` instance gives us access to the majority of the methods from the testing framework. Afterwards, we set the current directory path (used for debugging) and then we register the `adder` contract by providing the code path (path to `adder` root). + + +### Setup mock accounts + +```rust + world.account(OWNER_ADDRESS).nonce(1); +``` + +This snippet initializes a `SetStateBuilder` which helps us set the owner account at a custom address (`OWNER_ADDRESS`), and give it nonce 1. Now we can use the owner account to send transactions, and the account will be visible if checked. + + +### The test itself + +```rust +#[test] +fn adder_blackbox() { + let mut world = world(); + + /// +} +``` + +First thing to do inside the actual test is to initialize the environment using the `world()` function. Afterwards, we can write transactions and check results. + + +### Deploy + +```rust + let new_address = world + .tx() + .from(OWNER_ADDRESS) + .typed(adder_proxy::AdderProxy) + .init(5u32) + .code(CODE_PATH) + .new_address(ADDER_ADDRESS) + .returns(ReturnsNewAddress) + .run(); + + assert_eq!(new_address, ADDER_ADDRESS.to_address()); +``` + +In order to be able to call and query the contract, we must deploy it first. The transaction syntax is consistent through our various environments, so we are able to write a deploy transaction in the testing framework in a similar way to a deploy from a smart contract. + +There are, however, a few differences: +- `.new_address(ADDER_ADDRESS)` - only available in the testing framework inside a `deploy transaction`, indicates that the newly deployed contract should exist at test address `ADDER_ADDRESS`. If not specified, a mock address will be created and automatically assigned to the smart contract and a warning will appear in the console. +- `.run()` - converts tx data into `Step` data and then runs the step. + + +### Query + +```rust + world + .query() + .to(ADDER_ADDRESS) + .typed(adder_proxy::AdderProxy) + .sum() + .returns(ExpectValue(5u32)) + .run(); +``` + +After deploying the contract, we are now free to interact with it by sending transactions at the specified address. In this example, we are querying the `sum` endpoint of the contract and doing an assert on the result. By using `.returns(ExpectValue(5u32))` we indicate that the returned result should be `5u32` and any other result will throw an error. + + +### Regular transactions + +```rust + world + .tx() + .from(OWNER_ADDRESS) + .to(ADDER_ADDRESS) + .typed(adder_proxy::AdderProxy) + .add(1u32) + .run(); +``` + +In this snippet, we are sending a transaction which is a typed contract call for endpoint `add`, with `1u32` as argument. We are using the `AdderProxy` object from the `adder_proxy` file in order to create the call. + + +### Check accounts + +```rust + world.check_account(OWNER_ADDRESS); +``` + +In order to check the current state of the environment, we create a `CheckStateBuilder` using `check_account`. In this case, `.check_account(OWNER_ADDRESS)` checks if there is any account present at address `OWNER_ADDRESS`. In case of inconsistencies, the method throws an error and stops the execution. + + +### Traces + +```rust + world.start_trace(); + + // ... + + world.write_scenario_trace("trace1.scen.json"); +``` + +Traces help us with generating mandos based on blackbox tests. In order to use traces, we should initialize the trace at the beginning of the test, and then write the trace to a custom file at the end. The trace file in this example will be called `trace1.scen.json` and will be found in the root folder of the contract. + +--- + +### blocks + +This page describes the structure of the `blocks` index (Elasticsearch), and also depicts a few examples of how to query it. + + +## _id + +The `_id` field of this index is represented by the block hash, in a hexadecimal encoding. + + +## Fields + + +| Field | Description | +|-----------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| nonce | The nonce field represents the sequence number of the block (block height). | +| round | The round field represents the round when the block was proposed and executed. | +| epoch | The epoch field represents the epoch when the block was proposed and executed. | +| miniBlocksHashes | The miniBlocksHashes field contains an array of the miniblock hashes (hexadecimal encoded) that were included in the block. | +| miniBlocksDetails | The miniBlocksDetails field contains an array of structures indicating processing details of the miniblocks, such as the index of the first processed transaction. | +| notarizedBlocksHashes | The notarizedBlocksHashes field represents the hashes of the blocks that were notarized in the current block. | +| proposer | The proposer field represents the index of the validator that proposed the block. | +| validators | The validators field is an array that contains the indices of the validators that signed the block. | +| pubKeyBitmap | The pubKeyBitmap field represents the pub key bitmap. | +| size | The size field represents the size of the block in bytes. | +| sizeTxs | The sizeTxs field holds the size of the block's transactions in bytes. | +| timestamp | The timestamp field represents the timestamp when the block was proposed and executed. | +| stateRootHash | The stateRootHash field represents the trie's state root hash when the block was proposed and executed. | +| prevHash | The prevHash field represents the hash of the previous block. | +| shardId | The shardId field represents the shard this block belongs to. | +| txCount | The txCount field represents the number of transactions that were executed in the block. | +| notarizedTxsCount | The notarizedTxsCount field represents the number of transactions that were notarized in the block. | +| accumulatedFees | The accumulatedFees field represents the accumulated fees that were paid in the block. | +| developerFees | The developerFees field represents the developer fees that were accumulated in the block. | +| epochStartBlock | The epochStartBlock field is true if the current block is an epoch-start block. | +| epochStartInfo | The epochStartInfo field is a structure that contains economic data, such as total supply. | +| gasProvided | The gasProvided field represents the total gas that was provided in the block. | +| gasRefunded | The gasRefunded field represents the total gas that was refunded in the block. | +| gasPenalized | The gasPenalized field represents the total gas that was penalized in the block. | +| maxGasLimit | The maxGasLimit field represents the total gas that can be provided in the block. | +| scheduledData | The scheduledData field is a structure that contains data about the scheduled execution. | +| epochStartShardsData | The epochStartShardsData field is an array of structures that contains epoch-start data for each shard, such as pending miniblocks. | + +A `metachain` block (`shardId:4294967295`) with field `epochStartBlock:true` will have the field `epochStartInfo` field populated with the next data: + + +| epochStartInfo fields | Description | +|----------------------------------|----------------------------------------------------------------------------------------------------------------------| +| totalSupply | The totalSupply field represents the EGLD supply. | +| totalToDistribute | The totalToDistribute field represents the amount of EGLD that will be distributed to validators/delegators. | +| totalNewlyMinted | The totalNewlyMinted field represents the amount of the newly minted EGLD. | +| rewardsPerBlock | The rewardsPerBlock field represents the amount of EGLD rewards per block. | +| rewardsForProtocolSustainability | The rewardsForProtocolSustainability field represents the amount of rewards for the protocol sustainability address. | +| nodePrice | The nodePrice field represents EGLD amount required to run a validator. | +| prevEpochStartRound | The prevEpochStartRound field represents the round of the previous epoch start block. | +| prevEpochStartHash | The prevEpochStartHash field represents the hash of the previous epoch start block. | + + +## Query examples + + +### Fetch blocks for a shard +In order to fetch the latest blocks from a shard, one has to do a query that matches the field `shardId`. +``` +curl --request GET \ + --url ${ES_URL}/blocks/_search \ + --header 'Content-Type: application/json' \ + --data '{ + "query": { + "match": { + "shardId": "1" + } + }, + "sort": [ + { + "timestamp": { + "order": "desc" + } + } + ] +}' +``` + + +### Fetch the latest 10 blocks for all shards + +``` +curl --request GET \ + --url ${ES_URL}/blocks/_search \ + --header 'Content-Type: application/json' \ + --data '{ + "sort": [ + { + "timestamp": { + "order": "desc" + } + } + ], + "size":10 +}' +``` + +--- + +### Build a dApp in 15 minutes + +Let's build your first decentralized application (dApp) on the MultiversX Blockchain! + +## Prerequisites + +:::important +Before starting this tutorial, make sure you have the following: + +- `stable` Rust version `≥ 1.85.0` (install via [rustup](/docs/developers/toolchain-setup.md#installing-rust-and-sc-meta)) +- `multiversx-sc-meta` (cargo install [multiversx-sc-meta](/docs/developers/meta/sc-meta-cli.md)) +- `Node.js` with version `≥ 20`(guide [here](https://nodejs.org/en/download/package-manager)) +- `pnpm` ([npm install --global pnpm](https://pnpm.io/installation) ) + +::: + +You are going to use `sc-meta` to: + +1. **Create a wallet** to handle your transactions. +2. **Build** and **deploy** a contract. + + +## Description + +![img](/developers/tutorial/dapp-problem.png) + +The **Ping-Pong app** is a very simple decentralized application that allows users to deposit a specific number of tokens to a smart contract address and to lock them for a specific amount of time. After this time interval passes, users can claim back the same amount of tokens. + +Endpoints available: + +- `ping`: sending funds to the contract; +- `pong`: claiming the same amount back. + +Rules: + +- Each user can only `ping` once before they `pong`; +- The `ping` amount must be exactly the specified value - no more, no less; +- `pong` becomes available only after a set waiting period following a `ping`. + + +## Architecture + +![img](/developers/tutorial/dapp-architecture.png) + + +### Application Layer - Frontend + +For the web application, we will have two pages: + +- **Sign in** - The page where you can authenticate using the **xPortal App**, **Ledger**, **DeFi Wallet**, **xAlias**, **Web Wallet**, **Passkey Proxy** or **Metamask Proxy**; +- **Dashboard** - Here, you can either `ping` or `pong`. If you have already deposited, you will see a countdown timer until the time interval resets. + + +### Blockchain Layer - Backend + +You will interact with a smart contract that provides the following features: + +- `ping`: users send tokens to the contract, locking them for a specific period; +- `pong`: users retrieve their funds, but only after the lock period expires. + +The contract also includes several views, storage mappers and one event: + +- `didUserPing`: **view** that tells if a specific user has already `ping`-ed (_true_) or not (_false_); +- `getPongEnableTimestamp`: **view** that provides the timestamp when `pong` will be available for a given address; +- `getTimeToPong`: **view** that shows the remaining time until `pong` is enabled for a specific address; +- `getAcceptedPaymentToken`: **storage mapper** that saves the token type allowed for deposits; +- `getPingAmount`: **storage mapper** that records the total amount of tokens deposited; +- `getDurationTimestamp`: **storage mapper** that saves the lock duration (in milliseconds) before `pong` can be called after a `ping`; +- `getUserPingTimestamp`: **storage mapper** that saves the timestamp of the block where the user `ping`-ed; +- `pongEvent`: **event** that signals a successful `pong` by the user with amount. + +Think of this smart contract as the API for our dApp, handling all the core business logic. + +To test it out, we will use [MultiversX Blockchain Devnet Explorer](https://devnet-explorer.multiversx.com/) - a public test network maintained by our community. + + +## Set up the environment + +Let's set up the environment for getting your first dApp up and running. + + +### Project Structure + +Start by creating a new folder for your project. Let's call it `ping-pong`. + +```bash +mkdir -p ping-pong +cd ping-pong/ +``` + +By the time we are done, our project will have three subfolders: wallet, contract, and dapp. + +![img](/developers/tutorial/folder-structure.png) + + +### Create wallet + +To deploy a smart contract to the blockchain, you will need a wallet: a **PEM file** is recommended for simplicity and ease of testing. + +:::important +Make sure you are in the `ping-pong/` folder before continuing. +::: + +```bash +mkdir -p wallet +sc-meta wallet new --format pem --outfile ./wallet/wallet-owner.pem +``` + +:::tip +PEM wallets are recommended only for testing and experimenting with non-production code. For real applications, always follow best practices and use secure wallets that can be generated [here](https://wallet.multiversx.com). +::: + +To initiate transactions on the blockchain, your wallet needs funds to cover transaction fees, commonly referred to as **gas**. + +The [MultiversX Devnet](https://devnet-wallet.multiversx.com/dashboard) offers a **faucet** where you can claim **5 EGLD**. You can recall the faucet every **24 hours** if your balance is **lower than 1 EGLD**. Here’s how to fund your wallet: + +1. Go to [Devnet Wallet MultiversX](https://devnet-wallet.multiversx.com/unlock/pem) and log in using your newly generated **PEM** file; +2. Once logged in, open the **Faucet** from the **Tools**; +3. Request **5 xEGLD** to top up your wallet with test EGLD. + +![img](/developers/tutorial/faucet-screenshot.png) + + +## The Blockchain Layer + +With the wallet setup complete, let's move on to the backend - the blockchain layer. + +Let's start with the smart contract. You will first clone the Ping-Pong sample contract [repository](https://github.com/multiversx/mx-ping-pong-sc). + +:::important +Make sure you are still in the `ping-pong/` folder. +::: + +```bash +git clone https://github.com/multiversx/mx-ping-pong-sc contract +``` + +This will create a **contract** folder within ping-pong, containing all the necessary files for the Ping-Pong smart contract. + + +### Build the Smart Contract + +Now that you have the source code for the smart contract, you need to compile it into a **binary** that the **MultiversX Virtual Machine** (VM) can execute. Since the VM runs Web Assembly (WASM) code, you need to compile our Rust source code into a WASM file. + +At path `ping-pong/`, run the following command to build the smart contract into a WASM file. + +```sh +cd contract/ping-pong +sc-meta all build +``` + +After running the build command, a WASM file will be created at `output/ping-pong.wasm`. + +This file contains the bytecode for the smart contract, ready to be deployed on the blockchain. + + +### Deploy the Smart Contract + +Next, let's deploy the smart contract to the blockchain. + +:::tip +Ensure that `wallet_owner.pem` is in the `wallet/` folder and that the smart contract is built. +::: + +Before deploying, you will need to modify the wallet from which transactions are made. Currently, they are made from a test wallet. To use the wallet you created [earlier](./your-first-dapp.md#create-wallet), you will need to make the following changes: + +In the file [`interact.rs`](https://github.com/multiversx/mx-ping-pong-sc/blob/e1b3e0e9657e83ad11cc0069c0fc7183f91a2fe1/ping-pong/interactor/src/interact.rs#L84) located at the path `ping-pong/contract/ping-pong/interactor/src`, the variable `wallet_address_1` from `new` function will be modified. + +```rust title="Before" +let wallet_address_1 = interactor.register_wallet(test_wallets::alice()).await; +``` + +```rust title="After" +let wallet_address_1 = interactor + .register_wallet(Wallet::from_pem_file("../../../wallet/wallet-owner.pem").unwrap()) + .await; +``` + +:::note +You need to replace with the relative path from the **interactor crate** to the **created wallet**. + +The interactor crate is located at `ping-pong/contract/ping-pong/interactor`, and the wallet is in a higher-level directory: `ping-pong/wallet`. + +Alternatively, you can add the absolute path of `wallet-owner.pem`. +::: + +This next command deploys the Ping-Pong contract with the following settings: + +- Ping Amount: **1 EGLD**. +- Lock Duration: **180000 milliseconds** (3 minutes). + +:::important +Ensure that you run the next command inside the **interactor** crate, located at: + +`ping-pong/contract/ping-pong/interactor` +::: + +```bash +cargo run deploy --amount 1000000000000000000 --duration 180000 +``` + +```sh title=output + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.48s + Running `/ping-pong/contract/target/debug/ping-pong-interact deploy --amount 1000000000000000000 --duration 180000` +sender's recalled nonce: 0 +-- tx nonce: 0 +sc deploy tx hash: 93ff495b02eb84f0f427f4f02e2c0ce10e667a3511807db8781e1eaed47f9d16 +deploy address: erd1qqqqqqqqqqqqqpgq0dw0cfqeknm43sxzzeg5h5j3ewwu8gyd0vdqq4jutd +new address: erd1qqqqqqqqqqqqqpgq0dw0cfqeknm43sxzzeg5h5j3ewwu8gyd0vdqq4jutd +``` + +Once the command runs, review the log output carefully. Two key details to note: + +- **Contract Address**: in the example presented below, it is erd1qqqqqqqqqqqqqpgq0dw0cfqeknm43sxzzeg5h5j3ewwu8gyd0vdqq4jutd +- **Transaction Hash**: in the example presented below, it is 93ff495b02eb84f0f427f4f02e2c0ce10e667a3511807db8781e1eaed47f9d16 + +We will take a look at the transaction details. Let's check them in the [Devnet Explorer](https://devnet-explorer.multiversx.com). + +![img](/developers/tutorial/transaction_details.png) + +The Devnet Explorer will be your go-to tool for developing dApps on the MultiversX Blockchain. It allows you to test and monitor your deployments on the Devnet, ensuring everything works as expected. + +With the smart contract successfully deployed, you can now interact with it using blockchain transactions to invoke its main functions: `ping` and `pong`. + +The smart contract source code resides in `ping-pong/contract/ping-pong/src/ping_pong.rs`. + +The contract includes several view functions for querying information. These are invoked using the [MultiversX API](https://devnet-gateway.multiversx.com/#/vm-values/post_vm_values_query): + +- `didUserPing`; +- `getPongEnableTimestamp`; +- `getTimeToPong`; +- `getAcceptedPaymentToken`; +- `getPingAmount`; +- `getDurationTimestamp`; +- `getUserPingTimestamp`. + + +## The Application Layer + +Now that the backend is ready, let’s move on to the application layer! + + +### Set Up the dApp Template + +To get started, navigate back to the root `ping-pong` folder. + +Next, clone a simple dApp template that includes the necessary calls to interact with your newly deployed smart contract: + +```sh +git clone https://github.com/multiversx/mx-template-dapp dapp +cd dapp +``` + +Use the preferred editor and customize the Smart Contract address located in `src/config/config.devnet.ts` + +Then edit this instruction and change it to the contract address that you created in the [deploy step](./your-first-dapp.md#deploy-the-smart-contract). + +![img](/developers/tutorial/config-screenshot.png) + +Navigate to the `ping-pong/dapp` folder and install the required dependencies: + +```sh +npm install --global pnpm +pnpm install +``` + +### Start the Development Server + +To test your dApp locally, start a development server with the following command: + +```sh +pnpm start-devnet +``` + +### Running and Accessing the dApp + +If you run the development server on your local machine, simply open [https://localhost:3000](https://localhost:3000) in your browser. + +If the server is hosted on a remote machine, access it using the server's IP address, like [http://ip:3000](http://ip:3000). + +The production build of the app consists only of static files, so you can deploy it on any hosting platform you prefer. + +Once the development server is up and running, seeing the _Template dApp_ screen confirms that your application is live and ready! + +![img](/developers/tutorial/tutorial_dapp_page.png) + + +## Try your Application + +You will log in using the wallet created [previously](./your-first-dapp.md#create-wallet). + +To do this, you will press **Connect** button and then choose **MultiversX Web Wallet** option. + +![img](/developers/tutorial/wallet_connect.png) + +After you access **Web Wallet** connection, you will be forwarded to login on [Multiversx Wallet](https://devnet-wallet.multiversx.com/unlock/pem). + +![img](/developers/tutorial/wallet_login.png) + +You will choose **PEM** option to login. + + +### Ping Feature + +After signing in, you will be directed to the dashboard where the **Ping** button will be visible. + +![img](/developers/tutorial/dapp_dashboard.png) + +Click the **Ping** button and you will be redirected to the authentication page on the web wallet. + +A new transaction will be created, and you will be asked to confirm it. This transaction transfers the balance from your wallet to the smart contract address. Those funds will be locked for a specified period of time. Pay attention to the data field, where you call the smart contract function `ping`. + +After you confirm the transaction, a success message will appear and the funds are locked. You can view the transaction shown in the image [here](https://devnet-explorer.multiversx.com/transactions/e52c0425bb3d004831706fa52cd83c463475fc69a8052951812eef4ee9e57bdf). + +![img](/developers/tutorial/success_tx.png) +You can see **the amount of time** you will have to wait until you can **Pong**. + + +### Pong Feature + +After the time interval has passed, you can claim the funds by clicking the **Pong** button. + +![img](/developers/tutorial/pong_button.png) + +Another blockchain transaction will wait to be processed and again you will be asked to confirm it. This time the amount will be zero, as you only have to invoke the `pong` function (specified in the _data_ field). + +Once the transaction is complete, a **success** message will appear, and your funds will be returned to your wallet. You can view the transaction shown in the image [here](https://devnet-explorer.multiversx.com/transactions/f98c86677b9d62da969afbc1208d073029e3190e5486f70dc057863e1430e7d1). + +![img](/developers/tutorial/pong_tx.png) + + +## Where to go next? + +The purpose of this guide is to provide a starting point for you to discover the MultiversX technology capabilities and devkit. Keep reading the next docs to dive in deeper. + +We welcome your questions and inquiries on Stack Overflow: [https://stackoverflow.com/questions/tagged/multiversx](https://stackoverflow.com/questions/tagged/multiversx). + +Dive into more advanced topics and discover how to extend the smart contract, customize the wallet, and leverage MultiversX tools here: [https://docs.multiversx.com](/). + +--- + +### Build a Microservice for your dApp + +Let's build a microservice for your decentralized App + +:::important +This guide extends the [**Build a dApp in 15 minutes** guide](/developers/tutorials/your-first-dapp), please follow it before following this one. +We'll work on the Devnet, so you should create and manage a web wallet [here](https://devnet-wallet.multiversx.com). +::: + +This guide has been made available in video format as well: + + + + +## Ping Pong Microservice + +This guide extends the decentralized app we have built in our previous guide [**Build a dApp in 15 minutes**](/developers/tutorials/your-first-dapp). If you haven't followed it so far, please do it now. + +In this guide we're going to build a microservice (an API), which is an intermediary layer between the blockchain layer and the app layer. Our app will consume this microservice instead of making requests directly on the blockchain. + + +### Caching + +In our guide, the purpose of this microservice is to cache the values that come from the blockchain (e.g. `get_time_to_pong`), so every subsequent request will get fast results from our microservice. + + +### Transaction processor + +We will also invalidate the cache when a pong transaction will be done. This means that the microservice will listen to all the `pong` transactions on the blockchain that have our smart contract address as the receiver and as soon as one transaction is confirmed, we will invalidate the cache record corresponding to the sender wallet address. + + +### The Microservice + +We're going to use a microservice template based on nestjs, the caching will be done using redis, so the prerequisites for this guide are: nodejs, npm and redis. + +We will extend "Build a dApp in 15 minutes" guide, so let's build on the existing folder structure and create the microservice into a subfolder of the parent project folder: + + +## Prerequisites + +Before we begin, we'll make sure `redis-server` is installed and is running on our development server. + +```sh +sudo apt install redis-server +``` + +Optionally, we can daemonize redis-server, so it'll run in the background. + +```sh +redis-server --daemonize yes +``` + +We want to make sure redis is running, so if we run: + +```sh +ps aux | grep redis +``` + +then, we will have to see a log line like this one: + +`/usr/bin/redis-server 127.0.0.1:6379` + + +## The microservice + +Ok, let's get started with the microservice. First, we'll clone the template provided by the MultiversX team. + +```sh +git clone https://github.com/multiversx/mx-ping-pong-service +``` + +Let's take a look at the app structure: +`config` - here we'll set up the ping pong smart contract address +`src/crons` - transactions processors are defined here +`src/endpoints` - here we will find the code for `/ping-pong/time-to-pong/
` endpoint + + +### Configure the microservice + +We'll find a configuration file specific for every network we want to deploy the microservice on. In our guide we will use the devnet configuration, which will be found here: + +```sh +~ping-pong/microservice/config/config.devnet.yaml +``` + +First we're going to configure the redis server url. If we run a redis-server on the same machine (or on our development machine) then we can leave the default value. + +Now we'll move on to the smart contract address. We can find it in our `dapp` repository (if we followed the previous guide ["Build a dApp in 15 minutes"](/docs/developers/tutorials/your-first-dapp.md)). If you don't have a smart contract deployed on devnet, then we suggest to follow the previous guide first and then get back to this step. + +Set the `contracts.pingPong` key with the value for the smart contract address and we're done with configuring the microservice. + + +### Start the microservice + +We'll install the dependencies using npm + +``` sh +npm install +``` + +and then we will start the microservice for the devnet: + +``` sh +npm run start:devnet +``` + +Now we have our microservice started on port 3001. Let's identify its URL. +The default url is `http://localhost:3001`, but if you run the decentralized application on a different machine, then we should use `http://:3001`. + + +### Revisit "Your First dApp" + +Now it's time to tell the dApp to use the microservice instead of directly reading the values from the blockchain. First we will set up the microservice URL in the dApp configuration file `src/config.devnet.tsx`: +We will add: + +```javascript +export const microserviceAddress = "http://:3001/ping-pong/time-to-pong/"; +``` + +Next, we want to switch from using the vm query to using our newly created microservice. The request to get the time to pong is done in `src/pages/Dashboard/Actions/index.tsx`. + +We will change vm query code: + +```javascript +React.useEffect(() => { + const query = new Query({ + address: new Address(contractAddress), + func: new ContractFunction("getTimeToPong"), + args: [new AddressValue(new Address(address))], + }); + dapp.proxy + .queryContract(query) + .then(({ returnData }) => { + const [encoded] = returnData; + switch (encoded) { + case undefined: + setHasPing(true); + break; + case "": + setSecondsLeft(0); + setHasPing(false); + break; + default: { + const decoded = Buffer.from(encoded, "base64").toString("hex"); + setSecondsLeft(parseInt(decoded, 16)); + setHasPing(false); + break; + } + } + }) + .catch((err) => { + console.error("Unable to call VM query", err); + }); + // eslint-disable-next-line react-hooks/exhaustive-deps +}, []); +``` + +into a generic HTTP request (in our example we use `axios`): + +```javascript +React.useEffect(() => { + axios + .get(`${microserviceAddress}${address}`) + .then(({ data }) => { + const { status, timeToPong } = data; + switch (status) { + case "not_yet_pinged": + setHasPing(true); + break; + case "awaiting_pong": + setSecondsLeft(timeToPong); + setHasPing(false); + break; + } + }) + .catch((err) => { + console.error("Unable to call microservice", err); + }); + // eslint-disable-next-line react-hooks/exhaustive-deps +}, []); +``` + +Of course, don't forget to manage the required imports (`axios` and the microservice address that we defined previously in the configuration file `config.devnet.tsx`). + +```javascript +import axios from "axios"; +import { contractAddress, microserviceAddress } from "config"; +``` + +We can now save index.tsx and let's run the decentralized app one more time. + +```sh +npm run start +``` + +We can now verify that on the dashboard we still have the countdown and the Pong button is shown like it should be. We can refresh the app multiple times and at first the app will take the value (time to `pong` in seconds) from the blockchain. This value is then cached and all subsequent queries will read the value from the cache. + +You can also find the complete code on our public repository for the dApp in the branch `microservice`: + +```sh +https://github.com/multiversx/mx-template-dapp/blob/microservice/src/pages/Dashboard/Actions/index.tsx +``` + + +## Let's deep dive into the microservice code and explain the 2 basic features we implemented + +We want to minimize the number of requests done directly on the blockchain because of the overhead they incur, so we'll first read the time to `pong` from the blockchain, we'll cache that value and all the subsequent reads will be done from the cache. That value won't change over time. It will only reset AFTER we `pong`. + + +### The Cache + +So the caching part is done in + +```sh +ping-pong/microservice/src/endpoints/ping.pong/ping.pong.controller.ts +``` + +which uses + +```sh +ping-pong/microservice/src/endpoints/ping.pong/ping.pong.service.ts +``` + +The number of seconds until the user can `pong` is returned by the function `getTimeToPong` at line 16 in `ping.pong.service.ts`. + +```javascript +async getTimeToPong(address: Address): Promise<{ status: string, timeToPong?: number }> { +``` + +On line 17 we call `this.getPongDeadline` which, on line 33 will set the returned value in cache + +```javascript +return await this.cachingService.getOrSetCache( + `pong:${address}`, + async () => await this.getPongDeadlineRaw(address), + Constants.oneMinute() * 10 +); +``` + +The function `this.getPongDeadlineRaw` will invoke the only read action on the blockchain, then `this.cachingService.getOrSetCache` will set it in cache. + + +### The Transaction Processor + +After the user clicks the `Pong` button and performs the `pong` transaction, we have to invalidate the cache and we will use the transaction processor to identify all the `pong` transactions on the blockchain that have the receiver set to our smart contract address. + +Let's look at the transaction processor source file here: + +```sh +~/ping-pong/microservice/src/crons/transaction.processor.cron.ts +``` + +On line 23 we'll implement the `async handleNewTransactions()` function that has an interesting event: `onTransactionsReceived`. +Whenever new transactions are confirmed on the blockchain, this event will be executed and an array of transactions will be provided as a parameter. +We'll look in that array for a transaction that has the receiver equal to our smart contract address and the data field should be `pong` (as defined in the smart contract). + +```javascript +if ( + transaction.receiver === this.apiConfigService.getPingPongContract() && + transaction.data +) { + let dataDecoded = Buffer.from(transaction.data, "base64").toString(); + if (["ping", "pong"].includes(dataDecoded)) { + await this.cachingService.deleteInCache(`pong:${transaction.sender}`); + } +} +``` + +If we find one, we will invalidate the cache data for the key `pong:`, where we previously stored the time to pong value. We will use `this.cachingService.deleteInCache` function for this. + + +## Conclusion + +So that's all, we created a _microservice_ in order to make our dApp faster and scalable. +This is a generic decentralized application architecture and most of the examples from this guide were the starting point for some of our highly available and massively used products. +Now we provide you a starting point in order to build your ideas and projects. + + +## Where to go next? + +Break down this guide and learn more about how to extend the microservice, the dapp and the smart contract. Learn more on the MultiversX official documentation site here: [https://docs.multiversx.com](/). + +If you have any questions, feel free to ask them using stackoverflow here: +[https://stackoverflow.com/questions/tagged/multiversx](https://stackoverflow.com/questions/tagged/multiversx). + +Share this guide if you found it useful. + +--- + +### Build Reference + +## How to: Basic build + +To build a contract, navigate to the contract folder and run the following command: + +```sh +sc-meta all build +``` + + +## Configuring the build + +The build process is mostly configured using the [build configuration file](/developers/meta/sc-config), currently called `multicontract.toml`, and placed in the contract crate root. + +It is also possible for the build process to produce [more than one contract per project crate](/developers/meta/sc-config#multi-contract-configuration). + +--- + + +## Contract build process deep dive + +This section provides an overview for those who want to understand the system on a deeper level. If you are simply looking to build some contracts, feel free to skip this. + +Building a contract is a complex process, but luckily it gets handled invisibly by the framework. We will follow the components step by step and give some justification for this architecture. + + +### a. The smart contract itself + +The smart contract is defined as a trait without an implementation. This is good, because it means the contract can be executed on various platforms. Some of the implementation gets generated automatically by the `[multiversx_sc::contract]` macro. + +Not everything, though, can be performed here. Notably, macros cannot access data from other modules or crates, all processing is local to the current contract or module. Therefore, we need another mechanism for working with the complete contract data. + + +### b. The (generated) ABI generator + +ABIs are a collection of metadata about the contract. To build an ABI, we also need the data from the modules. The module macros cannot be called from the contract macros (macros are run at compilation, we are not even sure that modules will need to be recompiled!). Modules, however can be called. That is why we are actually generating _ABI generator functions_ for each module, which can call one another to retrieve the composite picture. + +Note: The ABI generator comes as an implementation of trait [ContractAbiProvider](https://docs.rs/multiversx-sc/0.39.0/multiversx_sc/contract_base/trait.ContractAbiProvider.html). + + +### c. Meta crate: generating the ABI + +The next question is how will this function be called. Whenever we compile the WASM contracts, we also produce the ABIs in JSON format. Rust has something called build scripts, which get called _after_ compiling a project, but for reasons that will become apparent later, they are not powerful enough for our usecase. + +Therefore, we have decided to include an extra crate in each smart contract project. It is always found in the `meta` folder in a contract, and it handles the entire build. To minimize boilerplate, it always only contains one line that simply defers execution to the framework: + +```rust +fn main() { + multiversx_sc_meta::cli_main::(); +} +``` + +To produce the ABI, in fact, it is enough to call: + +```sh +cd meta +cargo run abi +``` + +The meta crate has access to the ABI generator, because it always has a dependency to the contract crate. This is the `my_contract_crate::AbiProvider` in the example above. + +This is also the step where the meta crate parses and processes the `multicontract.toml` file. If there are multiple outputs, one ABI will be produced for each. + + +### d. Meta crate: generating wasm crate code + +Each contract must contain at least one `wasm` crate. This is separate from the contract crate because it has a different purpose: it only needs to be the basis for compiling wasm. Please take it as an intermediary step between the contract logic and the Rust to WASM compiler. This is also where the WASM compilation options are specified (e.g. the optimization level). These options can be seen in the `Cargo.toml` file of the `wasm` crate. + +The separation is helpful because, in this way, the smart contract crate can act as a pure Rust crate with no knowledge of WebAssembly. This makes testing and coverage easy, as well as enabling integration with other unrelated technologies. + +The `wasm` crates do not add any meaningful code to the smart contract. Everything they need to do is to provide an adapter to the WASM function syntax. More specifically, they expose an external function for each desired endpoint, which forwards execution to the corresponding smart contract method. + +If we are not careful, we risk adding unwanted endpoints to the contract. A classic example is when we have a crate with multiple modules, of which only one is imported into the smart contract. In some older versions, you might have gotten unwanted endpoints from the other modules of that crate. To avoid this, we are using the ABI to generate a curated list of endpoints in each `wasm` crate. This way, our contracts always have the exact same endpoints as the ones specified in the ABIs. + +This requires code generation. The `meta` crate will handle this code generation too. An example of such generated code lies below: + +```rust +// Code generated by the multiversx-sc multi-contract system. DO NOT EDIT. + +//////////////////////////////////////////////////// +////////////////// AUTO-GENERATED ////////////////// +//////////////////////////////////////////////////// + +// Init: 1 +// Endpoints: 2 +// Async Callback (empty): 1 +// Total number of exported functions: 4 + +#![no_std] +#![feature(alloc_error_handler, lang_items)] + +multiversx_sc_wasm_adapter::allocator!(); +multiversx_sc_wasm_adapter::panic_handler!(); + +multiversx_sc_wasm_adapter::endpoints! { + adder + ( + getSum + add + ) +} + +multiversx_sc_wasm_adapter::empty_callback! {} +``` + +The `multiversx_sc_wasm_adapter` macros help keep even this generated code to a minimum. + +For multi-contract builds, one `wasm` crate needs to be generated for each of the output contracts: + +- The main wasm crate must always reside in the `wasm` folder. The source file is auto-generated, but the `Cargo.toml` must be provided by the developer. +- The other wasm contracts (called "secondary") receive a crate folder starting with `wasm-`, e.g. `wasm-multisig-view`. These crates are fully generated based on data from `multicontract.toml`. The respective `Cargo.toml` files are based on the `Cargo.toml` of the main wasm crate. All configs are taken from there, except for the crate name. +- Warning: Any folders starting with `wasm-` that are unaccounted for will be removed without prompt. This is to keep the folder structure clean in case of renames. + + +### e. Meta crate: the actual WASM build + +The previous two steps happen by just calling `cargo run` in the meta crate, but to perform a build, one must call `cargo run build`. + +With the ABI information and the code generated, the meta crate can now build all the WASM contracts, one for each output contract. + +The rust compiler places the result in the designated `target` folder, but for convenience, the meta crate moves the executables to the project `output` folder and renames them according to the configured names. + +You might have performed this step automatically from mxpy, but mxpy simply calls the meta crate to do this job. This is because at this point only the meta crate has access to the ABIs and can do it easily. + + +### f. Meta crate: build post-processing + +After building the contracts, there are three more operations left to perform, based on the compiled WebAssembly outputs: + +1. All contracts are optimized, using `wasm-opt`. This operation can be disabled (via `--no-wasm-opt`). +2. A WAT file id generated for each contract. Not enabled by default, can be enabled (via `--wat`). The framework simply calls the `wasm2wat` tool to do this. +3. An `.imports.json` file is generated for each contract. Can be disabled (via `--no-imports`). The framework uses the `wasm-objdump` tool to retrieve the imports. It parses the output and saves it as JSON. + + +### g. Cleaning a project + +In order to clean a project and remove all build artifacts, run the following command: + +```sh +sc-meta all clean +``` + + +### Build process summary + +To recap, the build process steps are as follows: + +1. Generate code using the contract/module macros, including the ABI generator; +2. Call the ABI generator, to produce an ABI in memory; +3. Parse the `multicontract.toml` file (if present); +4. Deciding based on that which endpoints go to which output contracts; +5. Save the ABI as JSON in the output folder (one for each output contract); +6. Generate the wasm crates for all output contracts (all sources generated, Cargo.toml contents copied from the main wasm crate); +7. Build all wasm crates; +8. Copy binaries from the `target` folder(s) to `output`. +9. Perform post-processing for each contract: `wasm-opt`, `wasm2wat`, imports; + +Luckily, the framework can do all of this automatically, with a single click. + +--- + +### Built-In Functions + +## **Introduction** + +MultiversX protocol has a set of built-in functions. A built-in function is a special protocol-side function that doesn't +require a specific smart contract address as receiver of the transaction. When such a function is called via a transaction, +built-in handlers are triggered and will execute it. + +Calls to these functions are considered `built-in function calls` and are treated different than other smart contract calls. + +This documentation is subject to change, but at the time of writing, the current built-in functions were: + +- ClaimDeveloperRewards +- ChangeOwnerAddress +- SetUserName +- SaveKeyValue +- ESDTTransfer +- ESDTBurn +- ESDTFreeze +- ESDTUnFreeze +- ESDTWipe +- ESDTPause +- ESDTUnPause +- ESDTSetRole +- ESDTUnSetRole +- ESDTSetLimitedTransfer +- ESDTUnSetLimitedTransfer +- ESDTLocalBurn +- ESDTLocalMint +- ESDTNFTTransfer +- ESDTNFTCreate +- ESDTNFTAddQuantity +- ESDTNFTBurn +- ESDTNFTAddURI +- ESDTNFTUpdateAttributes +- MultiESDTNFTTransfer +- SetGuardian +- GuardAccount +- UnGuardAccount + + +## **ClaimDeveloperRewards** + +This function is to be used by Smart Contract owners in order to claim the fees accumulated during smart contract calls. +Currently, the developer reward is set to 30% of the fee of each smart contract call. + +```rust +ClaimDeveloperRewardsTransaction { + Sender: + Receiver: + Value: 0 + GasLimit: 6_000_000 + Data: "ClaimDeveloperRewards" +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +:::note +The amount of available developer rewards can be viewed via [Get Address](/sdk-and-tools/rest-api/addresses/#get-address) endpoint when using the smart contract as the `bech32Address`. +::: + + +## **ChangeOwnerAddress** + +`ChangeOwnerAddress` is the function to be called by a Smart Contract's owner when a new owner is desired. + +```rust +ChangeOwnerAddressTransaction { + Sender: + Receiver: + Value: 0 + GasLimit: 6_000_000 + Data: "ChangeOwnerAddress" + + "@" + +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + + +## **SetUserName** + +`SetUserName` is used to set an username for a given address. The receiver's address has to be the DNS smart contract +address of the address. + +```rust +SetUserNameTransaction { + Sender: + Receiver: + Value: 0 + GasLimit: 1_200_000 + Data: "SetUserName" + + "@" + +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + + +## **SaveKeyValue** + +`SaveKeyValue` is used to store a given key-value under an address's storage. More details and the transaction's format are +already covered [here](/developers/account-storage). + + +## **ESDT and NFT built-in functions** + +Most of the ESDT and NFT related built-in function are already described in the [ESDT](/tokens/fungible-tokens) and +[NFT](/tokens/nft-tokens) sections. + + +## **SetGuardian** + +`SetGuardian` is used to set a guardian for a given address. + +```rust +SetGuardianTransaction { + Sender: + Receiver: + Value: 0 + GasLimit: 1_000_000 + Data: "SetGuardian" + + "@" + + + "@" + +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +:::note +1. After sending a `SetGuardian` transaction, the user has to wait for **20 epochs** until the chosen guardian becomes `activeGuardian` (`activationEpoch` field will show the epoch when a `pendingGuardian` will become active). Until the `activationEpoch` is reached, the chosen guardian will be seen as `pendingGuardian`. See [Guardian Data](/sdk-and-tools/rest-api/addresses) for more information. +2. In the case an address already has an activeGuardian, and the user wants to change it, a `SetGuardian` transaction has to be sent again and **20 epochs** have to be awaited. +3. If the account is already Guarded (see below `GuardAccount` transaction), a user can _Fast SetGuardian_ (not waiting for the **20 epochs** anymore) by sending a guarded transaction. +::: + + +## **GuardAccount** + +`GuardAccount` is used to guard the account. In other words, after sending this transaction (and if it is successful), all transactions that will be sent afterwards by this address must have `guardian` and `guardianSignature` filled in until a successful `UnGuardAccount` transaction is being sent. + +```rust +GuardAccountTransaction { + Sender: + Receiver: + Value: 0 + GasLimit: 1_000_000 + Data: "GuardAccount" +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +:::note +After sending a successful `GuardAccount` transaction, only co-signed (guarded) transactions can be sent. Any other transaction will not be processed, except a new `SetGuardian` (for which **20 epochs** must pass until the changes will occur). +::: + + +## **UnGuardAccount** + +`UnGuardAccount` is used to unguard the account. After a successful transaction, it is not mandatory anymore for the user to send co-signed (guarded) transactions. + +```rust +UnGuardAccountTransaction { + Nonce: <> + Sender: + Receiver: + Guardian: + Value: 0 + GasLimit: 1_000_000 + Data: "UnGuardAccount" + Signature: + GuardianSignature: + Version: 2 + Options: +} +``` + +:::note +Please be aware that the ```UnGuardAccount``` operation can only be performed through a guarded transaction when you have access to your activated guardian. Otherwise, you will need to change the guardian (and wait the 20 epochs) prior to performing the unguarding process. +::: + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +--- + +### C++ SDK + +MultiversX SDK for C++ + +**erdcpp** consists of C++ helpers and utilities for interacting with the Blockchain. + +The source code be found here: [mx-sdk-erdcpp](https://github.com/multiversx/mx-sdk-erdcpp). For more details, follow [the documentation](https://github.com/multiversx/mx-sdk-erdcpp). + +--- + +### Chain simulator + +## Overview + +Chain simulator is a piece of software that provides all the `mx-chain-proxy-go` endpoints and includes additional endpoints +for specific operations. + +Instead of setting up a local testnet with a proxy and nodes, using chain simulator is a simpler option. +It mimics the behavior of a local testnet, making it easier to test smart contracts in a simulated environment. + +The chain simulator offers faster testing since it eliminates the need for consensus, allowing blocks to be generated +in milliseconds rather than the typical 6-second intervals on a local testnet. + +The chain simulator provides new endpoints that allow developers to replicate the state of a contract from the mainnet to the simulator. +This enables testing with the same state as on the mainnet, facilitating more accurate and realistic development scenarios. + + +## Architectural Overview + +![img](/technology/chainsimulator.png) + +This simulator is designed to replicate the behavior of a local testnet. Unlike a traditional testnet, this simulator +operates without a consensus group, allowing for isolated testing and development. + + + +## Features + +- Implements all `mx-chain-proxy-go` endpoints. +- Extra endpoints for specific operations. +- Simulates the behavior of a local testnet without a consensus group. + +The GitHub repository for the chain simulator and more information +about how to use can be found [here](https://github.com/multiversx/mx-chain-simulator-go). + +--- + +### Chain Simulator in Adder - SpaceCraft interactors + +This tutorial will guide you to interact with **Chain Simulator** using the SpaceCraft interactors in _Adder_ smart contract. + + +## Introduction + +[Chain Simulator](../../sdk-and-tools/chain-simulator.md) mimics the functionality of a local blockchain test network. It offers a convenient, faster, and realistic way to test smart contracts. + +[SpaceCraft interactor](../meta/interactor/interactors-overview.md) allows testing any complex scenario defined in a smart contract using **Chain Simulator**. Rather than going through the full setup of a local testnet, you can get straight to developing and debugging in a streamlined environment. This tool handles the setup details so you can focus on what matters most: **building and testing your contracts**. + +:::important +Before we dive in and explore how easy it can be, make sure you have the following: + +- `stable` Rust version `≥ 1.85.0` (install via [rustup](/docs/developers/toolchain-setup.md#installing-rust-and-sc-meta)): +- `multiversx-sc-meta` version `≥ 0.54.0` (cargo install [multiversx-sc-meta](/docs/developers/meta/sc-meta-cli.md)) + +::: + + +## Step 1: Start from the template + +For this journey, let's start with the _Adder_ template as our base. You can quickly set it up using **sc-meta** to generate it: + +```bash +sc-meta new --template adder --name my-adder +``` + +After running the command, you’ll see that the contract `my-adder` has been generated. The crate will include the following structure: + +```bash +. +├── interactor +├── meta +├── scenarios +├── src +├── target +├── tests +├── sc-config.toml +├── multiversx.json +├── README.md +├── Cargo.toml +└── Cargo.lock +``` + + +## Step 2: What is inside the interactor + +Our main focus will be the `interactor` directory. Let’s take a closer look: + +The directory that makes the connection with Chain Simulator contains the following structures: + +```bash +. +├── Cargo.toml +├── config.toml +├── src +│ ├── basic_interactor_cli.rs +│ ├── basic_interactor_config.rs +│ ├── basic_interactor_main.rs +│ ├── basic_interactor.rs +│ └── basic_interactor_state.rs +└── tests + └── basic_interactor_cs_test.rs + +2 directories, 8 files +``` + + +### Configuration + +To configure your environment as a blockchain simulator, you need to edit the `config.toml` file and add the following lines: + +```toml +chain_type = 'simulator' +gateway_uri = 'http://localhost:8085' +``` + +You can customize two settings in the configuration: + +- **Type of blockchain**; +- **Gateway URI**. + +To use a simulator for your blockchain, set `chain_type` as shown in the example above. + +By default, the simulator runs on `http://localhost:8085`. However, depending on your Docker image settings, the simulator's URI **might have a different port or name**. + +:::important +Make sure to set both `chain_type` and `gateway_uri` for the interactor to work. +::: + +The configuration is parsed by `basic_interactor_config.rs`. This file contains **two** functions that will be important for interacting with Chain Simulator: + +- `use_chain_simulator()`: returns if the chain type is real or simulator; +- `chain_simulator_config()`: initialize the proper configuration for simulator environment; this function is useful for **continuous integration tests**. + + +### Command Line Instructions + +The `basic_interactor_cli.rs` file contains all the commands you can use to interact with the simulator. + +```rust +/// MyAdder Interact CLI Commands +#[derive(Clone, PartialEq, Eq, Debug, Subcommand)] +pub enum InteractCliCommand { + #[command(name = "deploy", about = "Deploy contract")] + Deploy, + #[command(name = "upgrade", about = "Upgrade contract")] + Upgrade(UpgradeArgs), + #[command(name = "sum", about = "Print sum")] + Sum, + #[command(name = "add", about = "Add value")] + Add(AddArgs), +} +``` + +Let's break down what these commands do on Chain Simulator: + +- `deploy`: uploads the contract to the blockchain; +- `upgrade`: upgrades the existing _Adder_ contract with a new version; +- `sum`: queries the contract's storage **getSum**; +- `add`: executes a transaction that calls the `add` endpoint of the _Adder_ contract, essentially adding a value to it; + + +### State Management for Interactions + +Module `basic_interactor_state.rs` defines a State structure to help us keep track of our smart contract’s address. This information is saved in `state.toml` so even when you restart the interaction commands, it remembers the address you have set! + + +### Interaction transactions + +The `basic_interactor.rs` file is where you will find the functions triggered by each command you run from the command line. Each function represents either a **transaction** or a **query**. In the next sections, we will take a closer look at how these work! + + +## Step 3: Initializing an interactor + +In the Chain Simulator, time moves much faster, but nothing happens on its own. Unlike a live blockchain, transactions aren't automatically added to blocks. Instead, you need to manually trigger actions to process transactions and generate blocks. But don’t worry—SpaceCraft takes care of this for you! + +:::info +Each time you start the Chain Simulator, it creates a fresh, new blockchain from scratch. This means your account state starts empty, and the transaction blocks have no data initially. +::: + +```rust +pub async fn new(config: Config) -> Self { + let mut interactor = Interactor::new(config.gateway_uri()) + .await + .use_chain_simulator(config.use_chain_simulator()); + + interactor.set_current_dir_from_workspace("interactor"); + + let adder_owner_address = interactor.register_wallet(test_wallets::heidi()).await; + let wallet_address = interactor.register_wallet(test_wallets::ivan()).await; + + // generate blocks until ESDTSystemSCAddress is enabled + interactor.generate_blocks_until_epoch(1).await.unwrap(); + + Self { + interactor, + adder_owner_address: adder_owner_address.into(), + wallet_address: wallet_address.into(), + state: State::load_state(), + } +} +``` + +Let's find out what is mandatory for initializing the Chain Simulator interactor. + +```rust +let adder_owner_address = interactor.register_wallet(test_wallets::heidi()).await; +``` + +Every time you initialize an interactor, you’ll need to register a wallet. When a wallet is registered in the Chain Simulator, its associated account is automatically credited with a generous amount of EGLD. This way, you don’t have to worry about running out of tokens while testing! + +:::tip +The account has access to a substantial amount of EGLD when a wallet is registered, removing the need to worry about limited resources. +::: + +:::important +Whenever the Chain Simulator stops, the account will be dissolved. +::: + +```rust +interactor.generate_blocks_until_epoch(1).await.unwrap(); +``` + +Node enables `ESDTSystemSCAddress` in **epoch number one**. If you want to use functionality like issuing or minting tokens, it is necessary to generate blocks until the simulator chain reaches **epoch number one**. + + +## Step 4: Create tests that run on Chain Simulator + +One of the best parts about using the Chain Simulator with your interactor is that it lets you create **continuous integration tests in an environment that mirrors the real blockchain**. + +`tests/` holds all your test suites, where you are able to verify your contract’s behaviour effectively. + +```bash +. +├── Cargo.toml +├── config.toml +├── src +│ ├── basic_interactor_cli.rs +│ ├── basic_interactor_config.rs +│ ├── basic_interactor_main.rs +│ ├── basic_interactor.rs +│ └── basic_interactor_state.rs +└── tests + └── basic_interactor_cs_test.rs +``` + +:::important +Before you start writing tests, ensure that your `Cargo.toml` file includes the `chain-simulator-tests` feature flag, which is necessary to run them. +::: + +In the initial template generated in [Step 1](./chain-simulator-adder.md#step-1-start-from-template), you’ll see that the `chain-simulator-tests` feature is already included. So, your interactor crate’s `Cargo.toml` file should look like this: + +```toml +[package] +name = "my-adder" +version = "0.0.0" +publish = false +edition = "2021" +authors = ["you"] + +[lib] +path = "src/my_adder.rs" + +[dependencies.multiversx-sc] +version = "0.54.0" + +[dev-dependencies.multiversx-sc-scenario] +version = "0.54.0" + +[workspace] +members = [ + ".", + "meta", + "interactor", +] + +[features] +chain-simulator-tests = [] +``` + +With this setup in place, you’re ready to start exploring the test building and execution process for your contract! + +In the `basic_interactor_cs_test.rs`, you’ll find a test that goes through the following steps: + +1. **Deploy** the contract. +2. **Add**: call the function that increments a stored number in the contract by 1. +3. **GetSum**: verify the increment, check that the number in storage has indeed increased by 1. +4. **Upgrade** the contract: perform an upgrade of the contract with a new number. +5. **Unauthorized Upgrade**: try upgrading the contract with an unauthorized user to verify that it results in a failed transaction. + + +### 0. The base + +Each test you write should include the `#[tokio::test]` attribute for asynchronous execution and **enable** the `chain-simulator-tests` feature flag. Here’s what the test setup looks like: + +```rust +use basic_interactor::{Config, MyAdderInteract}; + +#[tokio::test] +#[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] +async fn simulator_adder_test() {} +``` + + +### 1. Deploy the contract + +**Deploy** on Chain Simulator _MyAdder_ contract which sets the initial sum with **zero**. + +```rust +use basic_interactor::{Config, MyAdderInteract}; + +#[tokio::test] +#[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] +async fn simulator_adder_test() { + let mut basic_interact = MyAdderInteract::init(Config::chain_simulator_config()).await; + + ///////////////////////1/////////////////////// + basic_interact.deploy().await; +} +``` + + +### 2. Add + +Create a transaction that calls endpoint `add` which successfully **increments** the stored number in the contract by **one**. + +```rust +use basic_interactor::{Config, MyAdderInteract}; + +#[tokio::test] +#[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] +async fn simulator_upgrade_test() { + let mut basic_interact = MyAdderInteract::init(Config::chain_simulator_config()).await; + + basic_interact.deploy().await; + ///////////////////////2/////////////////////// + basic_interact.add(1u32).await; +} +``` + + +### 3. GetSum + +Query the storage where the number is stored. This way, you can verify if the stored value has been incremented. + +```rust +use basic_interactor::{Config, MyAdderInteract}; + +#[tokio::test] +#[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] +async fn simulator_upgrade_test() { + let mut basic_interact = MyAdderInteract::init(Config::chain_simulator_config()).await; + + basic_interact.deploy().await; + basic_interact.add(1u32).await; + + ///////////////////////3/////////////////////// + // Sum will be 1 + let sum = basic_interact.get_sum().await; + assert_eq!(sum, 1u32.into()); +} +``` + + +### 4. Upgrade the contract + +Upgrade successfully _MyAdder_ with **seven** as the new value stored in the `sum` storage mapper. To verify if the number was indeed changed, you need to query the `SingleValueMapper`. + +```rust +use basic_interactor::{Config, MyAdderInteract}; + +#[tokio::test] +#[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] +async fn simulator_upgrade_test() { + let mut basic_interact = AdderInteract::init(Config::chain_simulator_config()).await; + + basic_interact.deploy().await; + basic_interact.add(1u32).await; + + // Sum will be 1 + let sum = basic_interact.get_sum().await; + assert_eq!(sum, 1u32.into()); + + ///////////////////////4/////////////////////// + basic_interact + .upgrade(7u32, &basic_interact.adder_owner_address.clone(), None) + .await; + + // Sum will be the updated value of 7 + let sum = basic_interact.get_sum().await; + assert_eq!(sum, 7u32.into()); +} +``` + + +### 5. Unauthorized Upgrade + +Attempt to upgrade the contract with an unauthorized user to confirm that it results in a failed transaction. To ensure no changes occur, you need to query the storage again to check that the number remains unchanged. + +```rust +use basic_interactor::{Config, MyAdderInteract}; + +#[tokio::test] +#[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] +async fn simulator_upgrade_test() { + let mut basic_interact = MyAdderInteract::init(Config::chain_simulator_config()).await; + + basic_interact.deploy().await; + basic_interact.add(1u32).await; + + // Sum will be 1 + let sum = basic_interact.get_sum().await; + assert_eq!(sum, 1u32.into()); + + basic_interact + .upgrade(7u32, &basic_interact.adder_owner_address.clone(), None) + .await; + + // Sum will be the updated value of 7 + let sum = basic_interact.get_sum().await; + assert_eq!(sum, 7u32.into()); + + ///////////////////////5/////////////////////// + // Upgrade fails + basic_interact + .upgrade( + 10u32, + &basic_interact.wallet_address.clone(), + Some("upgrade is allowed only for owner"), + ) + .await; + + // Sum will remain 7 + let sum = basic_interact.get_sum().await; + assert_eq!(sum, 7u32.into()); +} +``` + + +## Step 5: Run tests on Chain Simulator + + +### Install + +To run the test provided, you will need to install the Docker image that includes the Chain Simulator. + +```bash +my-adder/interactor$ sc-meta cs install +Attempting to install prerequisites for the Chain Simulator... +Successfully pulled the latest Chain Simulator image. +``` + +:::note +If you encounter the following error while installing: + +```bash +Attempting to install prerequisites for the Chain Simulator... +Error: Failed to execute command: permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock +``` + +You will need to run the command with root privileges due to Docker usage: + +```bash +my-adder/interactor$ sudo sc-meta cs install +``` + +If you get this error: + +```bash +sudo: sc-meta: command not found +``` + +You can find the sc-meta path and choose one of these solutions: + +```bash +my-adder/interactor$ which sc-meta +my-path/.cargo/bin/sc-meta +``` + +1. Add the sc-meta path to root privileges: +2. Run sc-meta directly using the full path: + +```bash +sudo my-path/.cargo/bin/sc-meta cs install +``` + +::: + + +### Start + +Once you’ve successfully installed the Docker image, you can start the Chain Simulator. + +```bash +my-adder/interactor$ sudo my-path/.cargo/bin/sc-meta cs start +Attempting to start the Chain Simulator... +Successfully started the Chain Simulator. +INFO [2024-11-11 13:09:15.683] using the override config files files = [./config/nodeOverrideDefault.toml] +WARN [2024-11-11 13:09:15.684] signature bypass = true +INFO [2024-11-11 13:09:15.699] updated config value file = systemSmartContractsConfig.toml path = ESDTSystemSCConfig.BaseIssuingCost value = 50000000000000000 +INFO [2024-11-11 13:09:15.699] updated config value file = config.toml path = Debug.Process.Enabled value = false +INFO [2024-11-11 13:09:15.699] updated config value file = config.toml path = VirtualMachine.Execution.WasmVMVersions value = [map[StartEpoch:0 Version:1.5]] +INFO [2024-11-11 13:09:15.699] updated config value file = config.toml path = VirtualMachine.Querying.WasmVMVersions value = [map[StartEpoch:0 Version:1.5]] +INFO [2024-11-11 13:09:15.699] updated config value file = config.toml path = WebServerAntiflood.WebServerAntifloodEnabled value = false +INFO [2024-11-11 13:09:15.699] updated config value file = config.toml path = WebServerAntiflood.SimultaneousRequests value = 100000 +... +``` + + +### Run + +While Chain Simulator is running, open a new terminal window in parallel. In this new terminal, you will run the test provided in [Step 4](./chain-simulator-adder.md#step-4-create-tests-that-run-on-chain-simulator). + +```bash +my-adder/interactor$ sc-meta test --chain-simulator +``` + +```bash Output +running 1 test +test simulator_upgrade_test ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.09s +``` + + +### Stop + +In the **same** terminal window you ran the tests, **stop** Chain Simulator using the next command: + +```bash +my-adder/interactor$ sc-meta cs stop +Attempting to close the Chain Simulator... +Successfully stopped the Chain Simulator. +``` + +:::note +If you encounter the following error while stopping: + +```bash +Attempting to close the Chain Simulator... +Error: Failed to execute command: permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock +``` + +Solution is presented in [Run](./chain-simulator-adder.md#install) section. +::: + +By following these steps, you've mastered the basics of smart contract development and testing. Now, it's time to explore more advanced techniques and create innovative applications :sparkles: :rocket: + +--- + +### CLI + +## Introduction + +As mentioned [before](/developers/meta/sc-meta#standalone-tool-vs-contract-tool), the meta tool comes in two flavors, one for the individual contract (based on the contract's ABI), and the other as a standalone tool. + +We will go through the CLI of both of these flavors. + + +## Standalone tool CLI + + +### The `all` command + +But first, there is a special feature that needs additional explanation: the `sc-meta all ...` command. + +The standalone and the contract tools are not completely separate: the standalone tool can control multiple smart contract projects in a single command. + +This is where the `all` command comes in: all it does is find all contracts in a given folder and call the contract tool for each of them with the given arguments. + +For example: +- `sc-meta all abi` will generate the ABIs for all contracts; +- `sc-meta all build --locked` will build all contracts, with the crate versions given by Cargo.lock; +- `sc-meta all clean` will clean all projects; +- etc. + +You can call `sc-meta all help` and see that the CLI docs are almost the same as those of the individual contract tool. + +A related command is the `info` command, which just prints a tree with all the contract and contract libraries in a folder, without doing anything to them. + +:::info Note +Since the CLI of the individual contract tool is available in the standalone tool, the following two commands are equivalent: + +``` +cd my-contract +sc-meta all build +``` + +``` +cd my-contract/meta +cargo run build +``` +::: + +Parameters: +- `--path` + - Target directory where to call all contract meta crates. + - _default_: current directory. +- `--ignore` + - Ignore all directories with these names. + - _default_: `target`. +- `--no-abi-git-version` + - Skips loading the Git version into the ABI +- `--target-dir-meta` + - For the meta crates, allows specifying the target directory where the Rust compiler will build the intermediary files. Sharing the same target directory can speed up building multiple contract crates at once. + - _default_: uses the workspace, or builds in `meta/target`. +- `--target-dir-all` + - Overrides both the `--target-dir-meta` and the `--target-dir-wasm` args. + + +### Calling `info` + +The info command prints an overview of the contracts and libraries and residing under a folder. It also prints their framework versions. + +As an example, below is the output of calling it in the example contract folder in the framework: + +![sc-meta info screenshot](/developers/sc-meta/sc-meta-info.png "Result of calling sc-meta info in the example contract folder in the framework") + +Parameters: +- `--path` + - Target directory where to call all contract meta crates. + - _default_: current directory. +- `--ignore` + - Ignore all directories with these names. + - _default_: `target`. + + +### Calling `upgrade` + +Calling `sc-meta upgrade` will try to automatically alter the code of a contract or group of contracts to make it/them compatible with the latest rust framework version. + +The oldest version currently supported is `0.28.0`. Any older than that, and the developer will need to manually upgrade it to `0.28.0`. + +It is especially important when upgrading from `0.38` to `0.39.0`, since a lot of changes happened at that point. + +:::tip +For projects with multiple contract crates, we recommend upgrading all of them at once. The upgrade algorithm goes step by step, version after version. For some of the major versions, it also checks that the project compiles before moving on. This is to give developers the chance to fix issues manually, if necessary, and not have those issues pile up. If there are local dependencies between contracts, the upgrader will not be able to do the check unless all of them are upgraded together. +::: + +:::caution +Generally, we strongly recommend to ensure code versioning or at least a backup of the contract code to avoid the impossibility of reverting permanent changes. This automatic code altering process involved in using `sc-meta upgrade` highly raises this recommendation. +::: + +Parameters: +- `--path` + - Target directory where to call all contract meta crates. + - _default_: current directory. +- `--ignore` + - Ignore all directories with these names. + - _default_: `target`. +- `--to` + - Overrides the version to upgrade to. + - _default_: the last released version. +- `--no-check` + - By default `upgrade` compile checks the project after each major version upgrade. This is to allow developers that upgrade multiple versions to address issues with the upgrade before too many such issues get to accumulate. This feature can be turned off by the `--no-check` flag. + - _default_: project is compiled. + + +### Calling `local-deps` + +Calling `sc-meta local-deps` will create in each contract a report of the local dependencies between contracts and libraries. This helps with the reproducible builds, but might be extended in the future for other uses. + +Example output (abridged): +```json +{ + "root": "/home/user/multiversx/mx-exchange-sc/", + "contractPath": "/home/user/multiversx/mx-exchange-sc/dex/pair", + "commonDependencyPath": "/home/user/multiversx/mx-exchange-sc", + "dependencies": [ + { + "path": "common/common_errors", + "depth": 1 + }, + { + "path": "common/common_structs", + "depth": 1 + }, + { + "path": "common/modules/legacy_token_decode_module", + "depth": 3 + }, + { + "path": "common/modules/locking_module", + "depth": 2 + }, + { + "path": "common/modules/math", + "depth": 2 + } + ] +} +``` + +Parameters: +- `--path` + - Target directory where to call all contract meta crates. + - _default_: current directory. +- `--ignore` + - Ignore all directories with these names. + - _default_: `target`. + + +### Calling `new` + + + +Creates a new smart contract project from a standard template. + +The tool will replace all necessary names in the project, based on the the project name given by the user. These include: +- the crate name, +- the contract trait name, +- the file name of the main source file. + +Parameters: +- `--template` + - The contract template to clone. Available options can be retrieve by using [this](/developers/meta/sc-meta-cli#calling-templates) + - Required. +- `--name` + - The new name the contract is to receive. + - _default_: If missing, the template name will be kept. +- `--tag` + - The framework version on which the contracts should be created. + - _default_: The latest released version. +- `--path` + - Target directory where to create the new contract directory. + - _default_: current directory. + + +### Calling `templates` + +This command lists all available templates. As of framework version 0.43.2, they are: + +``` +crypto-zombies +empty +adder +``` + +Parameter: +- `--tag` + - The framework version on which the contracts should be created. + - _default_: The latest released version. + + +### Calling `test` + +This command is a useful shorthand for running various types of tests. + +Parameters: +- `--path` + - Target directory where to generate contract integration tests. + - _default_: current directory. +- `--go` + - Use this argument to only run the mx-scenario-go tool, directly. It is equivalent to running `mx-scenario-go run`. + - You can find out how to install `mx-scenario-go` [here](/developers/meta/sc-meta-cli#calling-install). + - _default_: `false` +- `--scen` + - This argument causes cargo test to be run with the `multiversx-sc-scenario/run-go-tests` feature, causing tests relying on the mx-scenairo-go tool to also be run. + - _default_: `false` + - If `scen` and `go` are both specified, scen overrides the go argument. +- `--nocapture` + - This argument prints the entire output of the vm. + - _default_: `false` +- `--help` + - Print help +- `--version` + - Print version + + +### Calling `test-gen` + +The `test-gen` tool is used to [generate boilerplate](/developers/testing/scenario/running-scenarios#auto-generating-the-boilerplate) code when [integrating JSON scenario files in a contract's Rust test suite](/developers/testing/scenario/running-scenarios#integration-in-rust). + +In short: + +Contracts often have JSON scenario tests associated with them, which normally reside in the `scenarios` folder, under the contract crate root. + +In order to execute them as part of the CI, it is helpful to generate a Rust test for each of them. The `test-gen` tool does just that. + +These integration tests come in two flavors: +- Rust tests, that exclusively use the Rust debugger infrastructure; +- VM tests that use the Go infrastructure. + +Read more about JSON scenarios in smart contract projects [here](/developers/testing/scenario/running-scenarios). + +Parameters: +- `--path` + - Target directory where to call all contract meta crates. + - _default_: current directory. +- `--ignore` + - Ignore all directories with these names. + - _default_: `target`. +- `--create` + - Creates test files if they don't exist. + + +### Calling `install` + +This command can be used to quickly install other tools needed for smart contract development, interaction and testing. + +Parameters: +- `all` + - Installs all the known tools. +- `mx-scenario-go` + - Installs the `mx-scenario-go` tool. + - Can further specify the framework version on which the contracts should be created by using `--tag` + +--- + + +## Individual contract CLI + + +### Calling `abi` + +ABI generation can be triggered by calling `sc-meta all abi` or `cargo run abi` in the contract root folder. This command generates the main ABI file of the contract (`.abi.json`) along with all the other json files created if `#[esdt_attribute("name", type)]` was used (`.esdt-abi.json`). You can read more about ESDT Attribute ABI [here](/developers/data/abi#esdt-attribute-abi). + +ABI generation will also be triggered for all the other contract commands, such as `build`, `build-dbg`, `update`, etc. The `abi` command is for when we just want to generate the ABI and do nothing else. + +For a simple contract such as: + +```rust title=lib.rs +#[multiversx_sc::contract] +#[esdt_attribute("myTicker", u64)] +pub trait SomeContract { + #[init] + fn init(&self) {} +} +``` + +The produced files are: + +```text +output +├── myTicker.esdt-abi.json +└── some_contract.abi.json +``` + +Arguments: + +- `--path` Used to specify a target directory where to call all contract meta crates. Will be current directory if not specified. +- `--ignore` Followed by a name is used to ignore all directories with these names [default: `target`]. +- `--no-abi-git-version` Skips loading the Git version into the ABI +- `--target-dir-meta` For the meta crates, allows specifying the target directory where the Rust compiler will build the intermediary files. Sharing the same target directory can speed up building multiple contract crates at once. +- `--target-dir-all` Overrides both the `--target-dir-meta` and the `--target-dir-wasm` args. + + +### Calling `build` + +A build can be triggered by calling either `sc-meta all build` or `cargo run build` in the meta crate of the contract. In fact, the standalone `sc-meta` tool simply forwards the command to the contract meta crate itself. + +By default, this command will produce three files for each output contract: the ABI (`.abi.json`), the contract (`.wasm`) and a json file with all the used VM EI imported functions (`.imports.json`). For the multisig example above, the produced files are as follows: + +```text +output +├── multisig-full.abi.json +├── multisig-full.imports.json +├── multisig-full.wasm +├── multisig-view.abi.json +├── multisig-view.imports.json +├── multisig-view.wasm +├── multisig.abi.json +├── multisig.imports.json +└── multisig.wasm +``` + +Arguments: + +- `--locked` Uses the version from `Cargo.lock`, without updating. Required for reproducible builds. +- `--wasm-name` followed by name: Replaces the main contract's name with this one. Does nothing for secondary contracts. +- `--wasm-suffix` followed by a suffix: Adds a dash and this suffix to all produced contracts. E.g. `cargo run build --wasm-suffix dbg` on multisig will produce contracts `multisig-dbg.wasm`, `multisig-view-dbg.wasm` and `multisig-full-dbg.wasm`. +- `--wasm-symbols` Does not optimize away symbols at compile time, retains function names, good for investigating the WAT. +- `--no-wasm-opt` Does not apply `wasm-opt` after the build, this retains function names, good for investigating the WAT. +- `--wat` Also generates a WAT file for each of the contract outputs. It does so by calling `wasm2wat`. +- `--mir` Also emit MIR files when building. +- `--llvm-ir` Also emit LL (LLVM) files when building. +- `--no-abi-git-version` Skips loading the Git version into the ABI. +- `--no-imports` Does not generate an EI imports JSON file for each contract, as is the default. +- `--target-dir-wasm` Allows specifying the target directory where the Rust compiler will build the intermediary files. Sharing the same target directory can speed up building multiple contract crates at once. +- `--target-dir` Synonym of `--wasm-target-dir`, used for backwards compatibility. +- `--twiggy-top` Generate a twiggy top report after building. +- `--twiggy-paths` Generate a twiggy paths report after building. +- `--twiggy-monos` Generate a twiggy monos report after building. +- `--twiggy-dominators` Generate a twiggy dominators report after building. + +Additional parameters are inherited from `all`, when running out of the standalone tool: +- `--target-dir-meta` For the meta crates, allows specifying the target directory where the Rust compiler will build the intermediary files. +- `--target-dir-all` Overrides both the `--target-dir-meta` and the `--target-dir-wasm` args. + + +### Calling `build-dbg` + +There is another command, provided for convenience: `cargo run build-dbg`. Calling this is equivalent to `cargo run build --wasm-symbols --no-wasm-opt --wasm-suffix "dbg" --wat --no-imports`. It is ideal for developers who want to investigate the WebAssembly output produced by the compiler. + +The output for `build-dbg` in the multisig example would be: + +```text +output +├── multisig.abi.json +├── multisig-dbg.wasm +├── multisig-dbg.wat +├── multisig-full.abi.json +├── multisig-full-dbg.wasm +├── multisig-full-dbg.wat +├── multisig-view.abi.json +├── multisig-view-dbg.wasm +└── multisig-view-dbg.wat +``` + +It accepts all the arguments from `build`, so `--target-dir` works here too. + + +### Calling `twiggy` + +This command is similar to `build-dbg`, in that it provides a shorthand for building contracts and analyzing their size. It is equivalent to running `cargo run build-dbg --twiggy-top --twiggy-paths --twiggy-monos --twiggy-dominators`. + + +### Calling `clean` + +Calling `sc-meta all clean` in the contract folder will remove all build artifacts, including the `output` folder. + + +### Calling `snippets` + +Calling `cargo run snippets` in the meta crate or `sc-meta all snippets` in the root crate will create a project called `interactor` in the contract main directory, containing auto-generated boilerplate code for building an interactor for the current contract. + +An interactor is a small tool, meant for developers to interact with the contract on-chain and write system tests. Being written in Rust, it is ideal for quick interactions and tinkering, directly from the contract project. + +Inside the `interactor` project there is the interactor source file called `interact_main.rs`, a `config.toml` state file containing the chain simulator config, a `config.rs` file containing the source code for parsing the config and a newly generated contract proxy (`proxy.rs`). The `sc-config.toml` file of the contract (if existent) will be updated with the newly created proxy path (to the interactor project) or created from scratch if not existent. + +After calling `sc-meta all snippets` in the `factorial` smart contract crate, we get: +![img](/img/snippets_folder_structure.png) + + +Parameters: +- `--overwrite` Override snippets project if it already exists. Rewrites `sc-config.toml` as well, placing only interactor as proxy path. +- `--path` Target directory where to call all contract meta crates. Will be current directory if not specified. +- `--ignore` Ignore all directories with these names. [default: target] +- `--no-abi-git-version` Skips loading the Git version into the ABI. +- `--target-dir-meta` For the meta crates, allows specifying the target directory where the Rust compiler will build the intermediary files. Sharing the same target directory can speed up building multiple contract crates at once. +- `--target-dir-all` Overrides both the --target-dir-meta and the --target-dir-wasm args. + +### Calling `cs` + +Calling `cargo run cs` in the meta crate or `sc-meta cs` in the root crate will start the interaction with the chain simulator. In order to start an action, one must choose between the following subcommands. + +Subcommands: +- `install` Pulls the latest chain simulator docker image available. This command needs Docker to be installed and running on the current machine. +- `start` Starts the chain simulator in verbose mode at `localhost:8085`. +- `stop` Stops the chain simulator. + +--- + +### Code Metadata + +## Introduction + +Code metadata are flags representing the smart contract's allowed actions after deploy, specifically: +- `upgradeable` - if the contract can be upgraded in the future +- `readable` - if the contract's storage can be read by other contracts +- `payable` - if the contract can receive funds without having any endpoint called (just like user accounts). Note: A contract does NOT have to be payable to receive funds in payable endpoints. +- `payable by smart contracts` - just like the `payable` flag, but can only receive funds from other smart contracts. User transfers will be rejected. + +:::important +The code metadata **must be specified** at deploy-time and, if the contract is _upgradeable_, it **must be specified** at upgrade-time, as well. +::: + +:::important +Once a contract is marked as _**not** upgradeable_, its code and code metadata become **immutable, forever**. +::: + + +## Usage + +When deploying (or upgrading) a smart contract using **mxpy**, its default _code metadata flags_ are: `upgradeable`, `readable` and **non-**`payable`. The default values can be overwritten by decorating the command `mxpy contract deploy` (or `mxpy contract upgrade`) as follows: +- `--metadata-not-upgradeable` - mark the contract as **non-** `upgradeable` +- `--metadata-not-readable` - mark the contract as **non-** `readable` +- `--metadata-payable` - mark the contract as `payable` +- `--metadata-payable-by-sc` - mark the contract as `payable by smart contracts` + +For more information, please follow [mxpy CLI](/sdk-and-tools/mxpy/mxpy-cli). + + +## Bit-flag layout + +Internally, the metadata is stored as a 2-byte wide bit-flag. For easier visualization, let's define the flags like this: +```rust +bitflags! { + struct CodeMetadata: u16 { + const UPGRADEABLE = 0b0000_0001_0000_0000; // LSB of first byte + const READABLE = 0b0000_0100_0000_0000; // 3rd LSB of first byte + const PAYABLE = 0b0000_0000_0000_0010; // 2nd LSB of second byte + const PAYABLE_BY_SC = 0b0000_0000_0000_0100; // 3rd LSB of second byte + } +} +``` + +Alternatively, if you prefer hex over binary: +```rust +const UPGRADEABLE: u16 = 0x01_00; +const READABLE: u16 = 0x04_00; +const PAYABLE: u16 = 0x00_02; +const PAYABLE_BY_SC = 0x00_04; +``` + +For example, if we wish to deploy a contract that is payable and upgradeable our metadata would be `0x0102`. + + +## Conclusion + +We hope these flags will make it a lot easier to create and upgrade smart contracts. + +If you want to take a look at some more examples of how Code Metadata is used, take a look [here](https://github.com/multiversx/mx-sdk-rs/tree/master/contracts/examples). + +--- + +### Composite Values + +We often need to group simple values into more complex ones, without splitting them into [several arguments](/developers/data/multi-values). + +Here too we opted for an encoding that is, above all, very easy to read. + + +### Lists of items + +This is an umbrella term for all types of lists or arrays of various item types. They all serialize the same way. + +**Rust types**: `&[T]`, `Vec`, `Box<[T]>`, `LinkedList`, `VecMapper`, etc. + +**Top-encoding**: All nested encodings of the items, concatenated. + +**Nested encoding**: First, the length of the list, encoded on 4 bytes (`usize`/`u32`). +Then, all nested encodings of the items, concatenated. + +**Examples** + +| Type | Value | Top-level encoding | Nested encoding | Explanation | +| +| ---------------- | -------------------- | --------------------- | ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- | +| `Vec` | `vec![1, 2]` | `0x0102` | `0x00000002 0102` | Length = `2` | +| `Vec` | `vec![1, 2]` | `0x00010002` | `0x00000002 00010002` | Length = `2` | +| `Vec` | `vec![]` | `0x` | `0x00000000` | Length = `0` | +| `Vec` | `vec![7]` | `0x00000007` | `0x00000001 00000007` | Length = `1` | +| `Vec< Vec>` | `vec![ vec![7]]` | `0x00000001 00000007` | `0x00000001 00000001 00000007` | There is 1 element, which is a vector. In both cases the inner Vec needs to be nested-encoded in the larger Vec. | +| `Vec<&[u8]>` | `vec![ &[7u8][..]]` | `0x00000001 07` | `0x00000001 00000001 07` | Same as above, but the inner list is a simple list of bytes. | +| `Vec< BigUint>` | `vec![ 7u32.into()]` | `0x00000001 07` | `0x00000001 00000001 07` | `BigUint`s need to encode their length when nested. The `7` is encoded the same way as a list of bytes of length 1, so the same as above. | + +--- + + +### Arrays and tuples + +The only difference between these types and the lists in the previous section is that their length is known at compile time. +Therefore, there is never any need to encode their length. + +**Rust types**: `[T; N]`, `Box<[T; N]>`, `(T1, T2, ... , TN)`. + +**Top-encoding**: All nested encodings of the items, concatenated. + +**Nested encoding**: All nested encodings of the items, concatenated. + +**Examples** + +| Type | Value | Top-level encoding | Nested encoding | +| ---------------- | ------------------- | ------------------ | ------------------ | +| `[u8; 2]` | `[1, 2]` | `0x0102` | `0x0102` | +| `[u16; 2]` | `[1, 2]` | `0x00010002` | `0x00010002` | +| `(u8, u16, u32)` | `[1u8, 2u16, 3u32]` | `0x01000200000003` | `0x01000200000003` | + +--- + + +### Options + +An `Option` represents an optional value: every Option is either `Some` and contains a value, or `None`, and does not. + +**Rust types**: `Option`. + +**Top-encoding**: If `Some`, a `0x01` byte gets encoded, and after it the encoded value. If `None`, nothing gets encoded. + +**Nested encoding**: If `Some`, a `0x01` byte gets encoded, and after it the encoded value. If `None`, a `0x00` byte get encoded. + +**Examples** + +| Type | Value | Top-level encoding | Nested encoding | Explanation | +| ------------------ | ---------------------------------- | -------------------- | -------------------- | -------------------------------------------------------------------------------------------- | +| `Option` | `Some(5)` | `0x010005` | `0x010005` | | +| `Option` | `Some(0)` | `0x010000` | `0x010000` | | +| `Option` | `None` | `0x` | `0x00` | Note that `Some` has different encoding than `None` for any type | +| `Option< BigUint>` | `Some( BigUint::from( 0x1234u32))` | `0x01 00000002 1234` | `0x01 00000002 1234` | The `Some` value is nested-encoded. For a `BigUint` this adds the length, which here is `2`. | + +--- + +--- + +### Concept + +## What is MANDOS? + +MANDOS is short for **M**ultiversX **A**ccount, **N**etwork and **D**ata **O**peration **S**cenarios. We sometimes call them simply "scenarios". + +They were designed to test the VM and the early contracts in a language-agnostic way, hence initially implemented in JSON. + +Initially written manually, they were gradually superseded by Rust tests. The format, however, is still very useful, since it can be generated from a backend, and then re-run on another. + +## Concept + +Let's think for a moment how an interaction with a blockchain might look like. + +The only way to change the blockchain state is by sending transactions. We might also need to query some contracts in between sending these transactions. We might also query the blockchain state (balances, for instance) directly. Let's call these actions "steps". In a simulated environment, we need at least an additional step: initializing the sandbox. + +Several steps form a scenario. + +A scenario is any interaction with a blockchain, composed of one or more steps. These interactions might be: +- real, completed on a real blockchain, a history of sorts; +- programmed to be executed in the future; +- simulated, but using real blockchain data; +- simulated, using absurd or unrealistic data. + +So it doesn't really matter if these steps are real or imagined. All that matters is that they obey the rules of the blockchain. + +Because of their generality, it is natural to think of all blockchain interactions and black-box tests as such scenarios. + + +## Scenario formats + +The concept of scenario is not tied to a specific technology, or language. + +Historically, they started out as JSON tests. But writing a lot of JSON by hand is very inconvenient and unproductive, so we came up with a very similar syntax in Rust. When we created the interactor framework, we found that the scenario model fits naturally to real interactions too. + +:::info important +Nowadays, we think that the JSON scenario format is best used for interoperability and replays, not for writing tests. + +There are several ways to generate a scenario JSON file automatically, and we encourage developers to move away from writing them by hand. +::: + +The greatest benefit of the JSON format is that it is language-agnostic, and so it can be used with any of our backends. + + +## Scenarios as tests + +Scenarios also have syntax for checking transaction outputs and the blockchain state. This means that each scenario is also a test. Any failing check causes the scenario test to fail. + +:::important What kind of tests are they? +They are always **black-box** tests. They model real blockchain interactions, so there is no way for them to peek inside contracts and access their private functions. +::: + + +## Typed vs. untyped scenarios + +Transaction data on the blockchain is not really typed. Arguments, storage, logs, etc. - they are all untyped at blockchain level. The types are only added by developers when writing contracts, to avoid bugs and make them safer to use. + +But this means that scenarios in their most general form are also untyped. This fits JSON well, which is also (mostly) untyped. + +It clearly becomes tiring, as a developer, to only be able to work with untyped and un-annotated data. There are two ways to overcome this: +- Using a typed version of the scenarios. This is what we do when working with scenarios in Rust. +- Using a specialized language to make values easier to read by developers. + +These are the scenario value expressions, and they help us better express numbers, addresses, or even more complex data. Note that this does not add any type checks, it is a purely cosmetic affair. The format is detailed [here](/developers/testing/scenario/values-simple) and [here](/developers/testing/scenario/values-complex). + +--- + +### Configuration + +We like to say that developers don't write smart contracts directly, rather they write _specifications_ for smart contracts, from which an automated process creates the smart contracts themselves. + +This philosophy has two practical implications: + +1. The smart contract code itself has no direct knowledge of the underlying technology or of the blockchain, and can therefore be used to build other products too, such as tests, interactors, services, etc. +2. The build process is its own separate thing, which needs to be configured. + +It is also possible to build different variants of smart contracts from the same code base. These variants can contain only subsets of the endpoints available in code, or they might have different build settings and underlying API. We call this system "multi-contract", and it is explained in greater depth further on. + +In order not to overburden the build CLI, the bulk of the build configuration resides in a configuration file in the contract crate root. This file must necessarily be called `multicontract.toml` at the present moment. There are plans to change this name to something more general, since its contents have become as of late broader in scope. + +--- + + +## Single contract configuration + + +### Specification + +Assume we want to build a single contract from the project, encompassing all of the available functionality. Let's look at all the ways in which we can configure it: + +```toml +[settings] +main = "main" + +[contracts.main] +name = "my-contract" +add-unlabelled = true +panic-message = true +ei = "1.3" +allocator = "leaking" +stack-size = "3 pages" +features = ["example_feature_1", "example_feature_2"] +kill-legacy-callback = true + +[contracts.main.profile] +codegen-units = 1 +opt-level = "z" +lto = true +debug = false +panic = "abort" +overflow-checks = false + +[[proxy]] +path = "src/main_proxy.rs" +override-import = "use new::override::path" +add-unlabelled=false +add-labels=["label_one"] +add-endpoints=["endpoint_one"] +[[proxy.path-rename]] +from = "main" +to = "new::main" +``` + +The settings are as follows: +- `panic-message` + - “Panic with message” is a feature very useful for debugging. It displays messages from regular Rust panics in a contract, at the cost of ~1.5kB of additional contract size. It is disabled by default, we advise against using it in production. + - _values_: `true` | `false` + - _default_: `false` +- `ei` + - Configures the post-processor that checks the environment interface (EI) used by the built smart contract. + - The post-processor currently only emits a warning, but this might become a hard error in the future. + - _values_: + - `"1.3"` - the EI version that comes with VM 1.5; check [release notes](https://multiversx.com/releases) for currently running VM version + - `"1.2"` - the EI version that comes with VM 1.4; check [release notes](https://multiversx.com/releases) for currently running VM version + - `"1.1"` - older version of the EI, here for historical reasons + - `"1.0"` - older version of the EI, here for historical reasons + - _default_: `"1.2"` +- `allocator` + - Read about it in more detail [here](/developers/meta/sc-allocator). + - In short: configures the heap memory allocator to be used inside the compiled contract. + - _values_: + - `"fail"` - execution crashes when any allocation is attempted; + - `"leaking"` - requests pages, allocates in them, but never frees up memory on the heap; + - `"static64k"` - pre-allocated static 2-page buffer is used for the heap; + - `"wee_alloc"` - the `wee_alloc` allocator is used, which must be imported separately to the wasm crate. + - _default_: `"fail"` +- `stack-size` + - Allows adjusting the amount of memory allocated for the stack, in a WebAssembly contract. + - _values_: + - either number of bytes, e.g. `655360`; + - or the same number expressed as kilobytes with the suffix `k`, e.g. `"64k"`, `"128k"`, etc.; + - or the same number expressed in 64k pages, with the suffix `pages`, e.g. `"1 pages"`, `"8 pages"`, etc.; + - spaces are fine; + - there are some restrictions on this number, it can't be arbitrarily small. We advise against anything less than a page. + - _default_: `131072` or `"128k"`, the size of 2 pages of memory in WebAssembly. +- `features` + - Smart contract crates can have feature flags for conditional compilation. These feature flags allow the possibility of having differences between variants of the smart contract and the usage of the code in tools and off-chain projects. + - How it works: the contract will be built with these feature flags activated. + - _values_: + - a list of feature flags, similar to `Cargo.toml`, e.g. `features = ["example_feature_1", "example_feature_2"]` + - _default_: `[]` +- `kill-legacy-callback` + - The framework has no way of knowing whether or not a certain smart contract variant actually needs the async callback code or not, so in rare situations it is necessary to forcefully remove it. + - _values_: `true` | `false` + - _default_: `false` +- `codegen-units` + - Controls the number of "code generation units" a crate will be split into. Splitting a crate into multiple code generation units can have a significant impact on the compile time and code optimization of the crate. + - From our experience it is of no particular impact to smart contract compilation. + - _default_: `1` +- `opt-level` + - Controls the level of optimization that Rust will apply to the code during compilation. + - By default we run with `s` or `z`, since for smart contracts bytecode size optimization is of the essence. + - We also run `wasm-opt` after this optimization phase, so this only refers to part of the optimization. + - _values_: + - `0`: no optimizations; + - `1`: basic optimizations; + - `2`: some optimizations; + - `3`: all optimizations; + - `s`: optimize for binary size; + - `z`: optimize for binary size, but also turn off loop vectorization. + - _default_: `z` +- `lto` + - Enables link-time optimization for the release profile. + - _values_: `true` | `false` + - _default_: `true` +- `debug` + - Controls the amount of debug information included in the compiled binary. + - This setting is not normally used, since wasm-opt erases any debug information anyway. Use the `build-dbg` to get debug information for the compiled contracts. + - _values_: `true` | `false` + - _default_: `false` +- `panic` + - Controls how smart contracts handles panics, which are unexpected errors that occur at runtime. + - Using `"unwind"` is not tested as it makes little sense in a smart contract. + - _values_: + - `"unwind"`: unwind the stack in case of a panic; + - `"abort"`: terminate the execution in case of a panic. + - _default_: `"abort"` +- `overflow_checks` + - Controls whether it performs runtime checks for integer overflow. When enabled, it will insert additional checks into the generated code to detect and prevent integer overflow errors. + - Note that overflow checks are normally turned off in production, but are useful when testing. The overflow checks are enabled by default when testing smart contracts using the debugger. + - _values_: `true` | `false` + - _default_: `false` +- `proxy` + - Sets custom configuration for a generated proxy. More details about proxies are available [here](/developers/transactions/tx-proxies#how-to-set-up-project-to-re-generate-easily) + - `path` + - Set the output path where the generated proxy will be saved. + - _values_: `String` + - _default_: `"output/proxy.rs"` + - `override-import` + - Override the proxy imports: `use multiversx_sc::proxy_imports::*;`. + - _values_: `String` + - _default_: `""` + - `path-rename` + - Rename paths from structures and enumerations in generated proxy. + - _values_: + - `from`: `String` + - `to`: `String` + - _default_: + - `from`: `""` + - `to`: `""` + - `add-unlabelled` + - Specifies that all unlabelled endpoints should be added in the proxy. + - _values_: `true` | `false` + - _default_: `true` + - `add-labels` + - Endpoints labelled with at least one of these tags are added to the proxy. + - _values_: a list of string labels, e.g. `add-labels = ["label1", "label2"]` + - _default_: `[]` + - `add-endpoints` + - A proxy generated only with the endpoints specified in the list. + - _values_: a list of endpoint names, e.g. `add-endpoints = ["endpoint_one", "endpoint_two"]` + - _default_: `[]` +--- + + +### Default configuration + +Just to clarify defaults once again, if there is no configuration file, all defaults will be taken. This is equivalent to the minimal file below: + +```toml +[settings] +main = "main" + +[contracts.main] +name = "my-contract" +add-unlabelled = true +``` + +It is also equivalent to this more verbose version: + +```toml +[settings] +main = "main" + +[contracts.main] +name = "my-contract" +add-unlabelled = true +panic-message = false +ei = "1.2" +allocator = "fail" +stack-size = "2 pages" +features = [] +kill-legacy-callback = false + +[[proxy]] +path = "output/proxy.rs" +override-import = "" +add-unlabelled = true +add-labels = [] +add-endpoints = [] +[[proxy.path-rename]] +from = "" +to = "" +``` + +--- + + +## Multi-contract configuration + + +### Introduction + +Starting with version `0.37`, it is possible to configure a so-called "multi contract build". + +Just as we defined a single output contract (or contract variant) in the examples above, it is possible to define any number of such outputs. + +Currently the most popular use of having multiple contracts built from the same source is having _external view contracts_. + +An external view contract is a contract that allows convenient reading from another contract's storage directly. Its purpose is to off-load some of the logic required for view functions from the main contract to a secondary one. This can in some cases decrease the main contract's size by many kilobytes of binary code. + +We develop this in more depth [a little later on](#the-external-view-contract-explained), but let's start with an example. + +We will use the multisig contract as an example. In this contract we have several endpoints that are never used on-chain: `getPendingActionFullInfo`, `userRole`, `getAllBoardMembers`, `getAllProposers`, `getActionData`, `getActionSigners`, `getActionSignerCount`, `getActionValidSignerCount`. We want to place these contracts in an external view contract. + +In order to make configuration easier, we label them in code, as can be seen in the excerpt below: + +```rust +#[multiversx_sc::module] +pub trait MultisigStateModule { + // ... + + /// Serialized action data of an action with index. + #[label("multisig-external-view")] + #[view(getActionData)] + fn get_action_data(&self, action_id: usize) -> Action { + // ... + } + + /// Gets addresses of all users who signed an action. + #[label("multisig-external-view")] + #[view(getActionSigners)] + fn get_action_signers(&self, action_id: usize) -> ManagedVec { + // ... + } + + // ... +} + +``` + +Labels don't do anything more than provide a handy way to refer to groups of endpoints in `multicontract.toml`. + +Now for the `multicontract.toml` config itself, with explanations in comments: + +```toml +[settings] +# one of the output contracts is considered "multisig-main" +# it can have any id +main = "multisig-main" + +# contracts are identified by a contract id +# this id is only relevant in this file and in test setup +[contracts.multisig-main] +# the contract name is the important one, +# the output will be .wasm/.abi.json +name = "multisig" +# we can choose to add all unlabelled endpoints to a contract +add-unlabelled = true + +# this is the external view contract, here we call it "view" +[contracts.view] +# the output will be multisig-view.wasm/multisig-view.abi.json +name = "multisig-view" +# this is how we signal that this contract will be built as an external view +external-view = true +# we only add the endpoints labelled "multisig-external-view", as in the code snippet above +# any number of labels can be added +add-labels = ["multisig-external-view"] + +# this is how you get a version of the contract with all endpoints +# (main and external view, as defined above), +[contracts.full] +name = "multisig-full" +add-unlabelled = true +add-labels = ["multisig-external-view"] + +[[proxy]] +variant = "full" +path = "src/full_proxy.rs" +``` + + +### The external view contract explained + +The main rationale for _external view contracts_. It is very common for contracts to have certain endpoints that are very useful for grabbing data off-chain, but are very rarely used on-chain, if ever. Their code is basically bloating the main contract, and the idea is to extract them into a separate contract. This second contract (called an "external view contract") works because contracts can read from the storage of other contracts directly. + +The framework does the storage access rerouting automatically behind the scenes. The contract code cannot even tell the difference between a regular view from the same contract and one that has been relegated to an external view. Even more so, the same view endpoint can function both as external view and as regular view in different contract variants. + +An _external view contract_ has a behavior different from that of a regular contract. The framework adds some logic to such a contract, which is invisible to the developer. There are two main points: + +1. Storage access is different. All storage reads are done from the target contract given in the constructor. +2. An external view contract is allowed to write to storage, but it will be its own storage. In general avoid writing in such a contract. This might become an error in the future. +3. The constructor is different. Be mindful of this when deploying the external view contract. + - The original constructor is ignored, [a specific constructor is always provided instead](https://docs.rs/multiversx-sc/0.39.0/multiversx_sc/external_view_contract/fn.external_view_contract_constructor.html). + - This constructor always takes one single argument, which is the address of the target contract to read from. From this on, the target address can no longer be changed. + - The external view constructor ABI is always as follows: + +```json +{ + "constructor": { + "docs": [ + "The external view init prepares a contract that looks in another contract's storage.", + "It takes a single argument, the other contract's address", + "You won't find this constructors' definition in the contract, it gets injected automatically by the framework. See `multiversx_sc::external_view_contract`." + ], + "inputs": [ + { + "name": "target_contract_address", + "type": "Address" + } + ], + "outputs": [] + } +} +``` + +--- + + +### Specification + +- `settings` + - `main` - The contract id of the main wasm crate. The only thing special about this contract's crate is that it is simply called `wasm` and that its `Cargo.toml` is the basis for the `Cargo.toml` configs in all other output contract wasm crates. +- `contracts` map, indexed by contract id. Each contract has: + - `name` (optional) + - The output contract name. + - It forms the basis of all output artifacts, e.g. from `my-contract` we get `my-contract.abi.json`, `my-contract.wasm`, `my-contract.mxsc.json`, etc. + - _values_: any alphanumeric string, dashed and underscores are allowed. Dashes are preferred over underscores. + - _default_: if missing, the contract id will be used. + - `external-view` + - Specifies that a contract should be built as an external view contract. + - _values_: `true` | `false` + - _default_: `false` + - `add-unlabelled` + - Specifies that all unlabelled endpoints should be added to this contract. + - _values_: `true` | `false` + - _default_: `true` + - `add-labels` + - All endpoints labelled with at least one of these labels will be added to the contract. + - _values_: a list of string labels, e.g. `add-labels = ["label1", "label2"]` + - _default_: `[]` + - `add-endpoints` + - A list of endpoint names to be added directly to this contract. + - It bypasses the label system. + - Can be useful if for some reason labels are missing in code or deemed too cumbersome. + - Use the public endpoint names, not the Rust function names. + - _values_: a list of endpoint names, e.g. `add-endpoints = ["myEndpoint1", "myEndpoint1"]` + - _default_: `[]` +- `labels-for-contracts` + - Currently not used in any of our projects, probably better to stay away from this feature. Providing documentation for reference, anyway. + - The idea is that it is also possible to map in reverse, labels to contracts. It contains a mapping from labels to lists of contract ids. + - It can be a little harder to read than the contract to label map, but it can be used. + - There is a special key, `default`, which refers to the unlabelled endpoints. + - Example, equivalent to the labels in : +- `proxy` + - `variant` + - Generates a proxy for a specific variant + - _value_: `String` + - _default_: `main_contract` + +```toml +[settings] +main = "multisig-main" + +[contracts.multisig-main] +name = "multisig" + +[contracts.view] +name = "multisig-view" +external-view = true + +[contracts.full] +name = "multisig-full" + +[labels-for-contracts] +default = ["multisig-main", "full"] +multisig-external-view = ["view", "full"] +``` + +--- + + +## More examples + + +### Example 1 + +Suppose we want to use some unreleased functionality on the devnet, but also want a version of the contract that we can deploy to mainnet. + +We can use a single code base, but produce two contracts from it. + +In this example we use the async promises system, which is unreleased on the mainnet at the time of writing this. + +Excerpts from the contract code, for context: + +```rust +#[multiversx_sc::contract] +pub trait ForwarderQueue { + // ... + + #[endpoint] + #[payable] + fn forward_queued_calls(&self) { + while let Some(node) = self.queued_calls().pop_front() { + // ... + + match call.call_type { + // ... + QueuedCallType::Promise => { + #[cfg(feature = "promises")] + // ... + }, + } + } + } + + #[promises_callback] + #[label("promises-callback")] + fn promises_callback_method(&self) { + // ... + } +} +``` + +And the configuration that comes with it: + +```toml +[settings] +main = "main" + +[contracts.main] +name = "example1-mainnet" +ei = "1.2" +add-unlabelled = true + +[contracts.promises] +name = "example1-devnet" +ei = "1.3" +add-unlabelled = true +add-labels = ["promises-callback"] +features = ["promises"] +``` + +The result: + +``` +output +├── example1-devnet.abi.json +├── example1-devnet.imports.json +├── example1-devnet.mxsc.json +├── example1-devnet.wasm +├── example1-mainnet.abi.json +├── example1-mainnet.imports.json +├── example1-mainnet.mxsc.json +└── example1-mainnet.wasm +``` + + +### Example 2 + +Since framework version 0.43.0, it is possible to have different variants of a contract with different constructors. This might desirable for several reasons: +- Keeping separate versions for upgrades and migrations. +- Optimizing contract size by first deploying just with the constructor, and then immediately upgrading to a version with all the code but without the constructor. + +It is also possible to have different versions of the same endpoint in different contract variants. In the example below you will see that the endpoint `sampleValue` has two different implementations. + +A minimal example: + +```rust +#[multiversx_sc::contract] +pub trait Example2 { + #[init] + fn default_init(&self, sample_value: BigUint) { + self.sample_value().set(sample_value); + } + + #[init] + #[label("alt-init")] + fn alternative_init(&self) -> &'static str { + "alternative init" + } + + #[view(sampleValue)] + #[storage_mapper("sample-value")] + fn sample_value(&self) -> SingleValueMapper; + + #[view(sampleValue)] + #[label("alt-impl")] + fn alternative_sample_value(&self) -> &'static str { + "alternative message instead of sample value" + } +} +``` + +And the configuration that comes with it: + +```toml +[settings] +main = "example2-main" + +[contracts.example2-main] +add-unlabelled = true + +[contracts.example2-alt-impl] +add-labels = ["alt-impl"] +``` + +--- + + +## Testing with multi-contracts + +It is possible (and recommended) to use the contracts in scenario tests as they would be used on-chain. + +The Go scenario runner will work with the produced contract binaries without further ado. Calling an endpoint that is not available in a certain output contract will fail, even if said endpoint exists in the original contract code. + +To achieve the same effect on the Rust scenario runner, configure as in the following snippet. This is an actual excerpt from `multisig_scenario_rs_test.rs`, one of the multisig test files. + +```rust +fn world() -> ScenarioWorld { + // Initialize the blockchain mock, the same as for a regular test. + let mut blockchain = ScenarioWorld::new(); + + // Contracts that have no multi-contract config are provided the same as before. + blockchain.register_contract("file:test-contracts/adder.wasm", adder::ContractBuilder); + + // For multi-contract outputs we need to provide: + // - the ABI, via the generated AbiProvider type + // - a scenario expression to bind to, same as for simple contracts + // - a contract builder, same as for simple contracts + // - the contract name, as specified in multicontract.toml + blockchain.register_partial_contract::( + "file:output/multisig.wasm", + multisig::ContractBuilder, + "multisig", + ); + + // The same goes for the external view contract. + // There is no need to specify here that it is an external view, + // the framework gets all the data from multicontract.toml. + blockchain.register_partial_contract::( + "file:output/multisig-view.wasm", + multisig::ContractBuilder, + "multisig-view", + ); + + blockchain +} +``` + +--- + +### Constants + +MultiversX uses some constants, which are specific to each chain (Mainnet, Testnet or Devnet). The updated values can be found at these sources: + +**Mainnet**: + +- https://gateway.multiversx.com/network/config +- https://github.com/multiversx/mx-chain-mainnet-config + +**Testnet**: + +- https://testnet-gateway.multiversx.com/network/config +- https://github.com/multiversx/mx-chain-testnet-config + +**Devnet**: + +- https://devnet-gateway.multiversx.com/network/config +- https://github.com/multiversx/mx-chain-devnet-config + +:::important +Each transaction requires a `chainID` field that represents the network's identifier. It adds protection so that transactions cannot be replayed from a network to another. +The values are: + +- Mainnet: `1` +- Testnet: `T` +- Devnet: `D` + ::: + +At the time of writing, the most used constants values for mainnet were: + +- Round duration: 6 seconds +- Epoch duration: 14400 rounds, 24 hours +- Min gas price: 1000000000 +- Min gas limit: 50000 +- Chain ID: `1` (`T` for Testnet, `D` for Devnet) +- Min deposit for the creation of a system delegation smart contract: 1250 EGLD +- Min deposit for a validator: 2500 EGLD +- Number of eligible validators per shard: 400 validators +- Number of eligible validators per metachain: 400 validators +- Max number of active (eligible + waiting) validators: 3200 validators +- Consensus group size on shard: 63 validators +- Consensus group size on metachain: 400 validators +- Min deposit for staking provider: 1 EGLD +- Min deposit for legacy delegation: 10 EGLD +- Unbonding duration for legacy delegation: 144000 blocks +- Unbonding duration for staking providers: 10 epochs +- EGLD denomination: 18 + +--- + +### Cookbook (v14) + +## 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("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("https://devnet-gateway.multiversx.com", "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(keystorePath, "password"); +} +``` + +#### From a Mnemonic +```js + +const mnemonic = Mnemonic.generate(); +const accountFromMnemonic = Account.newFromMnemonic(mnemonic.toString()); +``` + +#### 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.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.resolve("keystoreWallet.json"), "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 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/). + +### 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 = networkProvider.getNetworkConfig(); +} +``` + +### Fetching the network status + +```js +{ + const entrypoint = new DevnetEntrypoint(); + const networkProvider = entrypoint.createNetworkProvider(); + + const metaNetworkStatus = networkProvider.getNetworkStatus(); // fetches status from metachain + const networkStatus = networkProvider.getNetworkStatus(); // 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: any) => { + return account.balance >= 7000000000000000000n; // 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: 2n, + }); + + const secondTransaction = new Transaction({ + sender: bob, + receiver: alice, + gasLimit: 50000n, + chainID: "D", + nonce: 1n, + }); + + const thirdTransaction = new Transaction({ + sender: alice, + receiver: alice, + gasLimit: 60000n, + chainID: "D", + nonce: 3n, + 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: 5000000n, + 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: any) => !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.address, { + 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 = await 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.address, { + 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 = await 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("../src/testData/adder.abi.json", { encoding: "utf8" }); + let abiObj = JSON.parse(abiJson); + let abi = Abi.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", + ); + let abi = Abi.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 +{ + let abi = Abi.create({ + endpoints: [ + { + name: "add", + inputs: [], + outputs: [], + }, + ], + }); +} +``` + +```js +{ + let abi = Abi.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("../src/testData/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: any[] = [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 abi = await loadAbiRegistry("../src/testdata/adder.abi.json"); + const entrypoint = new DevnetEntrypoint(); + const controller = entrypoint.createSmartContractController(abi); + 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 entrypoint = new DevnetEntrypoint(); + const controller = entrypoint.createSmartContractController(); + const networkProvider = entrypoint.createNetworkProvider(); + const transactionOnNetwork = await networkProvider.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 entrypoint = new DevnetEntrypoint(); + const factory = entrypoint.createSmartContractTransactionsFactory(); + 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: any[] = [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 = factory.createTransactionForDeploy(alice.address, { + bytecode: bytecode, + gasLimit: 6000000n, + arguments: args, + }); + 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.createSmartContractTransactionsFactory(); + + // load the contract bytecode + const bytecode = await promises.readFile("../src/testData/adder.wasm"); + + // For deploy arguments, use "TypedValue" objects if you haven't provided an ABI to the factory: + let args: any[] = [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(alice.address, { + bytecode: bytecode, + gasLimit: 6000000n, + arguments: args, + }); + + // the developer is responsible for managing the nonce + alice.nonce = await entrypoint.recallAccountNonce(alice.address); + + // set the nonce + deployTransaction.nonce = alice.nonce; + + // sign the transaction + deployTransaction.signature = await alice.signTransaction(deployTransaction); + + // broadcasting the transaction + const txHash = await entrypoint.sendTransaction(deployTransaction); + + // waiting for transaction to complete + const transactionOnNetwork = await entrypoint.awaitCompletedTransaction(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: any[] = [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 +{ + const entrypoint = new DevnetEntrypoint(); + const controller = entrypoint.createSmartContractController(); + const txHash = "b3ae88ad05c464a74db73f4013de05abcfcb4fb6647c67a262a6cfdf330ef4a9"; + // waits for transaction completion and parses the result + const parsedOutcome = await controller.awaitCompletedExecute(txHash); + const values = parsedOutcome.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: any[] = [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: 10n }); + 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); + + // get the smart contracts controller + const controller = entrypoint.createSmartContractTransactionsFactory(); + + const contractAddress = Address.newFromBech32("erd1qqqqqqqqqqqqqpgq7cmfueefdqkjsnnjnwydw902v8pwjqy3d8ssd4meug"); + + // For deploy arguments, use "TypedValue" objects if you haven't provided an ABI to the factory: + let args: any[] = [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: 10n }); + 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(alice.address, { + 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 = new SmartContractTransactionsOutcomeParser({ abi }); + const txHash = "b3ae88ad05c464a74db73f4013de05abcfcb4fb6647c67a262a6cfdf330ef4a9"; + const transactionOnNetwork = await 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 txHash = "b3ae88ad05c464a74db73f4013de05abcfcb4fb6647c67a262a6cfdf330ef4a9"; + const transactionOnNetwork = await entrypoint.getTransaction(txHash); + const events = gatherAllEvents(transactionOnNetwork); + const outcome = parser.parseEvents({ events }); +} +``` + +#### Encoding / decoding custom types +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 codec = new BinaryCodec(); + + 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 codec = new BinaryCodec(); + 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 sender = Account.newFromKeystore(keystorePath, "password"); + // the developer is responsible for managing the nonce + sender.nonce = await entrypoint.recallAccountNonce(sender.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("../src/testData/adder.wasm"); + + // For deploy arguments, use "TypedValue" objects if you haven't provided an ABI to the factory: + let args: any[] = [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 controller.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.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(); + + // 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 controller.awaitCompletedSetSpecialRoleOnFungibleToken(txHash); + + 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 transaction = await factory.createTransactionForIssuingFungible(alice.address, { + tokenName: "TEST", + tokenTicker: "TEST", + initialSupply: 100n, + numDecimals: 0n, + canFreeze: true, + canWipe: true, + canPause: true, + canChangeOwner: true, + canUpgrade: false, + 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); + + 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 controller.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.address, { + 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.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); + + 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 + const outcome = await controller.awaitCompletedIssueNonFungible(txHash); + + const collectionIdentifier = outcome[0].tokenIdentifier; + + // create an NFT + transaction = await 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 + const outcomeNft = await controller.awaitCompletedCreateNft(txHash); + + const identifier = outcomeNft[0].tokenIdentifier; + const nonce = outcomeNft[0].nonce; + const initialQuantity = outcomeNft[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.address, { + 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.address, { + 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; +} +``` + +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.address, { + 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 transaction = await controller.createTransactionForUnguardingAccount( + alice, + alice.getNonceThenIncrement(), + {}, + ); + + // 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 transaction = await factory.createTransactionForUnguardingAccount(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); +} +``` + +#### 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, + alice.getNonceThenIncrement(), + { + totalDelegationCap: 0n, + 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: 0n, + 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 token identifier + const parser = new TokenManagementTransactionsOutcomeParser(); + const outcome = parser.parseIssueFungible(transactionOnNetwork); + const tokenIdentifier = outcome[0].tokenIdentifier; +} +``` + +#### 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, 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, + 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, 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, 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, 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 be deactivated soon, so we'll focus on V3. + +For V3, two new fields have been added on transactions: `relayer` and `relayerSignature`. + +Note that: +1. the sender and the relayer can sign the transaction in any order. +2. before any of the sender or relayer can sign the transaction, the `relayer` field must be set. +3. relayed transactions require an additional `50,000` of gas. +4. the sender and the relayer must be in the same network shard. + +Let’s see how to create a relayed transaction: + +```js +{ + const entrypoint = new DevnetEntrypoint(); + const walletsPath = path.join("../src", "testdata", "testwallets"); + const bob = await Account.newFromPem(path.join(walletsPath, "bob.pem")); + const grace = Address.newFromBech32("erd1r69gk66fmedhhcg24g2c5kn2f2a5k4kvpr6jfw67dn2lyydd8cfswy6ede"); + const mike = await Account.newFromPem(path.join(walletsPath, "mike.pem")); + + // fetch the nonce of the network + bob.nonce = await entrypoint.recallAccountNonce(bob.address); + + const transaction = new Transaction({ + chainID: "D", + sender: bob.address, + receiver: grace, + relayer: mike.address, + gasLimit: 110_000n, + data: Buffer.from("hello"), + nonce: bob.getNonceThenIncrement(), + }); + + // sender signs the transaction + transaction.signature = await bob.signTransaction(transaction); + + // relayer signs the transaction + transaction.relayerSignature = await mike.signTransaction(transaction); + + // broadcast the transaction + 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.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 relayer, that means she is paying the gas for the transaction + const frank = await Account.newFromPem(path.join(walletsPath, "frank.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, + relayer: frank.address, + }); + + // relayer also signs the transaction + transaction.relayerSignature = await frank.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.createTokenManagementTransactionsFactory(); + + // 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 frank = await Account.newFromPem(path.join(walletsPath, "frank.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 = frank.address; + + // sign the transaction + transaction.signature = await alice.signTransaction(transaction); + + // relayer also signs the transaction + transaction.relayerSignature = await frank.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.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")); + + // 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.createTokenManagementTransactionsFactory(); + + // 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. + +### Multisig + +The sdk contains components to interact with the [Multisig Contract](https://github.com/multiversx/mx-contracts-rs/releases/tag/v0.45.5). +We can deploy a multisig smart contract, add members, propose and execute actions and query the contract. +The same as the other components, to interact with a multisig smart contract we can use either the MultisigController or the MultisigTransactionsFactory. + +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: +- [`MultisigController`](https://multiversx.github.io/mx-sdk-js-core/v14/classes/MultisigController.html) +- [`MultisigTransactionsFactory`](https://multiversx.github.io/mx-sdk-js-core/v14/classes/MultisigTransactionsFactory.html) + +#### Deploying a Multisig Smart Contract using the controller +```js +{ + const abi = await loadAbiRegistry("src/testdata/multisig-full.abi.json"); + const bytecode = await loadContractCode("src/testdata/multisig-full.wasm"); + + // create the entrypoint and the multisig controller + const entrypoint = new DevnetEntrypoint(); + const controller = entrypoint.createMultisigController(abi); + + 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.createTransactionForDeploy(alice, alice.getNonceThenIncrement(), { + quorum: 2, + board: [ + alice.address, + Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"), + ], + bytecode: bytecode.valueOf(), + gasLimit: 100000000n, + }); + + // sending the transaction + const txHash = await entrypoint.sendTransaction(transaction); + + // wait for transaction completion, extract multisig contract's address + const outcome = await controller.awaitCompletedDeploy(txHash); + + const contractAddress = outcome[0].contractAddress; +} +``` + +#### Deploying a Multisig Smart Contract using the factory +```js +{ + const abi = await loadAbiRegistry("src/testdata/multisig-full.abi.json"); + const bytecode = await loadContractCode("src/testdata/multisig-full.wasm"); + + // create the entrypoint and the multisig factory + const entrypoint = new DevnetEntrypoint(); + const factory = entrypoint.createMultisigTransactionsFactory(abi); + + 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 = factory.createTransactionForDeploy(alice.address, { + quorum: 2, + board: [ + alice.address, + Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"), + ], + bytecode: bytecode.valueOf(), + gasLimit: 100000000n, + }); + + transaction.nonce = alice.getNonceThenIncrement(); + transaction.signature = await alice.signTransaction(transaction); + // sending the transaction + const txHash = await entrypoint.sendTransaction(transaction); +} +``` + +#### Propose an action using the controller +We'll propose an action to send some EGLD to Carol. After we sent the proposal, we'll also parse the outcome of the transaction to get the `proposal id`. +The id can be used later for signing and performing the proposal. + +```js +{ + // create the entrypoint and the multisig controller + const entrypoint = new DevnetEntrypoint(); + const abi = await loadAbiRegistry("src/testdata/multisig-full.abi.json"); + const controller = entrypoint.createMultisigController(abi); + + 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.createTransactionForProposeTransferExecute( + alice, + alice.getNonceThenIncrement(), + { + multisigContract: contract, + to: Address.newFromBech32("erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8"), + gasLimit: 10000000n, + nativeTokenAmount: 1000000000000000000n, + }, + ); + + // sending the transaction + const txHash = await entrypoint.sendTransaction(transaction); + + // parse the outcome and get the proposal id + const actionId = await controller.awaitCompletedPerformAction(txHash); +} +``` + +#### Propose an action using the factory +Proposing an action for a multisig contract using the MultisigFactory is very similar to using the controller, but in order to get the proposal id, we need to use MultisigTransactionsOutcomeParser. + +```js +{ + const abi = await loadAbiRegistry("src/testdata/multisig-full.abi.json"); + + // create the entrypoint and the multisig factory + const entrypoint = new DevnetEntrypoint(); + const provider = entrypoint.createNetworkProvider(); + const factory = entrypoint.createMultisigTransactionsFactory(abi); + + const filePath = path.join("../src", "testdata", "testwallets", "alice.pem"); + const alice = await Account.newFromPem(filePath); + + const contract = Address.newFromBech32("erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqf8llllswuedva"); + + const transaction = factory.createTransactionForProposeTransferExecute(alice.address, { + multisigContract: contract, + to: Address.newFromBech32("erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8"), + gasLimit: 10000000n, + nativeTokenAmount: 1000000000000000000n, + }); + // 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); + + // wait for the transaction to execute + const transactionAwaiter = new TransactionWatcher(provider); + const transactionOnNetwork = await transactionAwaiter.awaitCompleted(txHash); + + // parse the outcome of the transaction + const parser = new MultisigTransactionsOutcomeParser({ abi }); + const actionId = parser.parseProposeAction(transactionOnNetwork); +} +``` + +#### Querying the Multisig Smart Contract +Unlike creating transactions, querying the multisig can be performed only using the controller. +Let's query the contract to get all board members. + +```js +{ + const abi = await loadAbiRegistry("src/testdata/multisig-full.abi.json"); + + // create the entrypoint and the multisig controller + const entrypoint = new DevnetEntrypoint(); + const controller = entrypoint.createMultisigController(abi); + + const contract = Address.newFromBech32("erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqf8llllswuedva"); + + const boardMembers = await controller.getAllBoardMembers({ multisigAddress: contract.toBech32() }); +} +``` + +### Governance + +We can create transactions for creating a new governance proposal, vote for a proposal or query the governance contract. + +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: +- [`GovernanceController`](https://multiversx.github.io/mx-sdk-js-core/v14/classes/GovernanceController.html) +- [`GovernanceTransactionsFactory`](https://multiversx.github.io/mx-sdk-js-core/v14/classes/GovernanceTransactionsFactory.html) + +#### Creating a new proposal using the controller +```js +{ + // create the entrypoint and the governance controller + const entrypoint = new DevnetEntrypoint(); + const controller = entrypoint.createGovernanceController(); + + 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 commitHash = "1db734c0315f9ec422b88f679ccfe3e0197b9d67"; + + const transaction = await controller.createTransactionForNewProposal(alice, alice.getNonceThenIncrement(), { + commitHash: commitHash, + startVoteEpoch: 10, + endVoteEpoch: 15, + nativeTokenAmount: 500_000000000000000000n, + }); + + // sending the transaction + const txHash = await entrypoint.sendTransaction(transaction); + + // wait for transaction completion, extract proposal's details + const outcome = await controller.awaitCompletedProposeProposal(txHash); + + const proposalNonce = outcome[0].proposalNonce; + const proposalCommitHash = outcome[0].commitHash; + const proposalStartVoteEpoch = outcome[0].startVoteEpoch; + const proposalEndVoteEpoch = outcome[0].endVoteEpoch; +} +``` + +#### Creating a new proposal using the factory +```js +{ + // create the entrypoint and the governance factory + const entrypoint = new DevnetEntrypoint(); + const factory = entrypoint.createGovernanceTransactionsFactory(); + + const filePath = path.join("../src", "testdata", "testwallets", "alice.pem"); + const alice = await Account.newFromPem(filePath); + + const commitHash = "1db734c0315f9ec422b88f679ccfe3e0197b9d67"; + + const transaction = factory.createTransactionForNewProposal(alice.address, { + commitHash: commitHash, + startVoteEpoch: 10, + endVoteEpoch: 15, + nativeTokenAmount: 500_000000000000000000n, + }); + // 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); + + const parser = new GovernanceTransactionsOutcomeParser({}); + const outcome = parser.parseNewProposal(transactionOnNetwork); + const proposalNonce = outcome[0].proposalNonce; + const proposalCommitHash = outcome[0].commitHash; + const proposalStartVoteEpoch = outcome[0].startVoteEpoch; + const proposalEndVoteEpoch = outcome[0].endVoteEpoch; +} +``` + +#### Vote for a proposal using the controller + +```js +{ + // create the entrypoint and the governance controller + const entrypoint = new DevnetEntrypoint(); + const controller = entrypoint.createGovernanceController(); + + 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.createTransactionForVoting(alice, alice.getNonceThenIncrement(), { + proposalNonce: 1, + vote: Vote.YES, + }); + + // sending the transaction + const txHash = await entrypoint.sendTransaction(transaction); + + // wait for transaction completion, extract proposal's details + const outcome = await controller.awaitCompletedVote(txHash); + const proposalNonce = outcome[0].proposalNonce; + const vote = outcome[0].vote; + const voteTotalStake = outcome[0].totalStake; + const voteVotingPower = outcome[0].votingPower; +} +``` + +#### Vote for a proposal using the factory +```js +{ + // create the entrypoint and the governance factory + const entrypoint = new DevnetEntrypoint(); + const factory = entrypoint.createGovernanceTransactionsFactory(); + + const filePath = path.join("../src", "testdata", "testwallets", "alice.pem"); + const alice = await Account.newFromPem(filePath); + + const transaction = factory.createTransactionForVoting(alice.address, { + proposalNonce: 1, + vote: Vote.YES, + }); + // 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); + + // wait for transaction completion, extract proposal's details + const transactionOnNetwork = await entrypoint.awaitCompletedTransaction(txHash); + const parser = new GovernanceTransactionsOutcomeParser({}); + const outcome = parser.parseVote(transactionOnNetwork); + const proposalNonce = outcome[0].proposalNonce; + const vote = outcome[0].vote; + const voteTotalStake = outcome[0].totalStake; + const voteVotingPower = outcome[0].votingPower; +} +``` + +#### Querying the governance contract +Unlike creating transactions, querying the contract is only possible using the controller. Let's query the contract to get more details about a proposal. + +```js +{ + // create the entrypoint and the governance controller + const entrypoint = new DevnetEntrypoint(); + const controller = entrypoint.createGovernanceController(); + + const proposalInfo = await controller.getProposal(1); + console.log({ proposalInfo }); +} +``` + +## 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"); +} +``` + +#### Getting the shard of an address +``` js + +const addressComputer = new AddressComputer(); +const address = Address.newFromHex("0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1"); +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(); +``` + +#### 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.toString(), 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(filePath, "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 publicKey = secretKey.generatePublicKey(); + + const transaction = new Transaction({ + nonce: 90n, + sender: publicKey.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: alice.address, + 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(transaction); + + 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.signTransaction(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 + +```ts +{ + 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.signMessage(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 = await 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.signTransaction(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 = await 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.signMessage(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 filePath = path.join("../src", "testdata", "testwallets", "alice.pem"); + const alice = await Account.newFromPem(filePath); + const messageComputer = new MessageComputer(); + const data = Buffer.from("test"); + + const message = new Message({ + data: data, + address: alice.address, + }); + + message.signature = await alice.signMessage(message); + // restore message + + const packedMessage = messageComputer.packMessage(message); + const unpackedMessage = messageComputer.unpackMessage(packedMessage); + + // verify the signature + const isSignedByAlice = await alice.verifyMessageSignature(unpackedMessage, message.signature); + + console.log("Transaction is signed by Alice: ", isSignedByAlice); +} +``` + +--- + +### Cookbook (v15) + +## 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 v15`. In order to migrate from `sdk-js v14.x` to `sdk-js v15`, please also follow [the migration guide](https://github.com/multiversx/mx-sdk-js-core/issues/648). +::: + +## 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(keystorePath, "password"); +} +``` + +#### From a Mnemonic +```js + +const mnemonic = Mnemonic.generate(); +const accountFromMnemonic = Account.newFromMnemonic(mnemonic.toString()); +``` + +#### 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.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.resolve("keystoreWallet.json"), "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 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/). + +### 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); +} +``` + +Here you can find a full list of available methods for [`ApiNetworkProvider`](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 = networkProvider.getNetworkConfig(); +} +``` + +### Fetching the network status + +```js +{ + const entrypoint = new DevnetEntrypoint(); + const networkProvider = entrypoint.createNetworkProvider(); + + const metaNetworkStatus = networkProvider.getNetworkStatus(); // fetches status from metachain + const networkStatus = networkProvider.getNetworkStatus(); // 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: any) => { + return account.balance >= 7000000000000000000n; // 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: 2n, + }); + + const secondTransaction = new Transaction({ + sender: bob, + receiver: alice, + gasLimit: 50000n, + chainID: "D", + nonce: 1n, + }); + + const thirdTransaction = new Transaction({ + sender: alice, + receiver: alice, + gasLimit: 60000n, + chainID: "D", + nonce: 3n, + 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: 5000000n, + 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: any) => !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 }); +} +``` + +### Estimating the Gas Limit for Transactions +Additionally, when creating transaction factories or controllers, we can pass an additional argument, a **gas limit estimator**. +This gas estimator simulates the transaction before being sent and computes the `gasLimit` that it will require. +The `GasLimitEstimator` can be initialized with a multiplier, so that the estimated value will be multiplied by the specified value. +The gas limit estimator can be provided to any factory or controller available. Let's see how we can create a `GasLimitEstimator` and use it. + +```js +{ + const api = new ApiNetworkProvider("https://devnet-api.multiversx.com"); + let gasEstimator = new GasLimitEstimator({ networkProvider: api }); // create a gas limit estimator with default multiplier of 1.0 + let gasEstimatorWithMultiplier = new GasLimitEstimator({ networkProvider: api, gasMultiplier: 1.5 }); // create a gas limit estimator with a multiplier of 1.5 + + const config = new TransactionsFactoryConfig({ chainID: "D" }); + const transfersFactory = new TransferTransactionsFactory({ + config: config, + gasLimitEstimator: gasEstimatorWithMultiplier, // or `gasEstimator` + }); +} +``` + +### 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 = await factory.createTransactionForTransfer(alice.address, { + 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 = await 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 = await factory.createTransactionForTransfer(alice.address, { + 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 = await 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("../src/testData/adder.abi.json", { encoding: "utf8" }); + let abiObj = JSON.parse(abiJson); + let abi = Abi.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", + ); + let abi = Abi.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 +{ + let abi = Abi.create({ + endpoints: [ + { + name: "add", + inputs: [], + outputs: [], + }, + ], + }); +} +``` + +```js +{ + let abi = Abi.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("../src/testData/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: any[] = [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/v14/classes/SmartContractController.html) or [`SmartContractTransactionsFactory`](https://multiversx.github.io/mx-sdk-js-core/v14/classes/SmartContractTransactionsFactory.html), even if the ABI is available and provided, +you can still use [`TypedValue`](https://multiversx.github.io/mx-sdk-js-core/v14/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/v14/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 abi = await loadAbiRegistry("../src/testdata/adder.abi.json"); + const entrypoint = new DevnetEntrypoint(); + const controller = entrypoint.createSmartContractController(abi); + 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 entrypoint = new DevnetEntrypoint(); + const controller = entrypoint.createSmartContractController(); + const networkProvider = entrypoint.createNetworkProvider(); + const transactionOnNetwork = await networkProvider.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 entrypoint = new DevnetEntrypoint(); + const factory = entrypoint.createSmartContractTransactionsFactory(); + 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: any[] = [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(alice.address, { + bytecode: bytecode, + gasLimit: 6000000n, + arguments: args, + }); + 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.createSmartContractTransactionsFactory(); + + // load the contract bytecode + const bytecode = await promises.readFile("../src/testData/adder.wasm"); + + // For deploy arguments, use "TypedValue" objects if you haven't provided an ABI to the factory: + let args: any[] = [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(alice.address, { + bytecode: bytecode, + gasLimit: 6000000n, + arguments: args, + }); + + // the developer is responsible for managing the nonce + alice.nonce = await entrypoint.recallAccountNonce(alice.address); + + // set the nonce + deployTransaction.nonce = alice.nonce; + + // sign the transaction + deployTransaction.signature = await alice.signTransaction(deployTransaction); + + // broadcasting the transaction + const txHash = await entrypoint.sendTransaction(deployTransaction); + + // waiting for transaction to complete + const transactionOnNetwork = await entrypoint.awaitCompletedTransaction(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: any[] = [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 +{ + const entrypoint = new DevnetEntrypoint(); + const controller = entrypoint.createSmartContractController(); + const txHash = "b3ae88ad05c464a74db73f4013de05abcfcb4fb6647c67a262a6cfdf330ef4a9"; + // waits for transaction completion and parses the result + const parsedOutcome = await controller.awaitCompletedExecute(txHash); + const values = parsedOutcome.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: any[] = [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: 10n }); + 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); + + // get the smart contracts controller + const controller = entrypoint.createSmartContractTransactionsFactory(); + + const contractAddress = Address.newFromBech32("erd1qqqqqqqqqqqqqpgq7cmfueefdqkjsnnjnwydw902v8pwjqy3d8ssd4meug"); + + // For deploy arguments, use "TypedValue" objects if you haven't provided an ABI to the factory: + let args: any[] = [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: 10n }); + 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(alice.address, { + 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 = new SmartContractTransactionsOutcomeParser({ abi }); + const txHash = "b3ae88ad05c464a74db73f4013de05abcfcb4fb6647c67a262a6cfdf330ef4a9"; + const transactionOnNetwork = await 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 txHash = "b3ae88ad05c464a74db73f4013de05abcfcb4fb6647c67a262a6cfdf330ef4a9"; + const transactionOnNetwork = await entrypoint.getTransaction(txHash); + const events = gatherAllEvents(transactionOnNetwork); + const outcome = parser.parseEvents({ events }); +} +``` + +#### Encoding / decoding custom types +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 codec = new BinaryCodec(); + + 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 codec = new BinaryCodec(); + 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 sender = Account.newFromKeystore(keystorePath, "password"); + // the developer is responsible for managing the nonce + sender.nonce = await entrypoint.recallAccountNonce(sender.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("../src/testData/adder.wasm"); + + // For deploy arguments, use "TypedValue" objects if you haven't provided an ABI to the factory: + let args: any[] = [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 controller.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.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(); + + // 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 controller.awaitCompletedSetSpecialRoleOnFungibleToken(txHash); + + 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 transaction = await factory.createTransactionForIssuingFungible(alice.address, { + tokenName: "TEST", + tokenTicker: "TEST", + initialSupply: 100n, + numDecimals: 0n, + canFreeze: true, + canWipe: true, + canPause: true, + canChangeOwner: true, + canUpgrade: false, + 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); + + 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 controller.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.address, { + 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.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); + + 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 + const outcome = await controller.awaitCompletedIssueNonFungible(txHash); + + const collectionIdentifier = outcome[0].tokenIdentifier; + + // create an NFT + transaction = await 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 + const outcomeNft = await controller.awaitCompletedCreateNft(txHash); + + const identifier = outcomeNft[0].tokenIdentifier; + const nonce = outcomeNft[0].nonce; + const initialQuantity = outcomeNft[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.address, { + 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.address, { + 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; +} +``` + +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.address, { + 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 transaction = await controller.createTransactionForUnguardingAccount( + alice, + alice.getNonceThenIncrement(), + {}, + ); + + // 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 transaction = await factory.createTransactionForUnguardingAccount(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); +} +``` + +#### 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, + alice.getNonceThenIncrement(), + { + totalDelegationCap: 0n, + 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: 0n, + 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 token identifier + const parser = new TokenManagementTransactionsOutcomeParser(); + const outcome = parser.parseIssueFungible(transactionOnNetwork); + const tokenIdentifier = outcome[0].tokenIdentifier; +} +``` + +#### 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, 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, + 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, 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, 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, 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 be deactivated soon, so we'll focus on V3. + +For V3, two new fields have been added on transactions: `relayer` and `relayerSignature`. + +Note that: +1. the sender and the relayer can sign the transaction in any order. +2. before any of the sender or relayer can sign the transaction, the `relayer` field must be set. +3. relayed transactions require an additional `50,000` of gas. +4. the sender and the relayer must be in the same network shard. + +Let’s see how to create a relayed transaction: + +```js +{ + const entrypoint = new DevnetEntrypoint(); + const walletsPath = path.join("../src", "testdata", "testwallets"); + const bob = await Account.newFromPem(path.join(walletsPath, "bob.pem")); + const grace = Address.newFromBech32("erd1r69gk66fmedhhcg24g2c5kn2f2a5k4kvpr6jfw67dn2lyydd8cfswy6ede"); + const mike = await Account.newFromPem(path.join(walletsPath, "mike.pem")); + + // fetch the nonce of the network + bob.nonce = await entrypoint.recallAccountNonce(bob.address); + + const transaction = new Transaction({ + chainID: "D", + sender: bob.address, + receiver: grace, + relayer: mike.address, + gasLimit: 110_000n, + data: Buffer.from("hello"), + nonce: bob.getNonceThenIncrement(), + }); + + // sender signs the transaction + transaction.signature = await bob.signTransaction(transaction); + + // relayer signs the transaction + transaction.relayerSignature = await mike.signTransaction(transaction); + + // broadcast the transaction + 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.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 relayer, that means she is paying the gas for the transaction + const frank = await Account.newFromPem(path.join(walletsPath, "frank.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, + relayer: frank.address, + }); + + // relayer also signs the transaction + transaction.relayerSignature = await frank.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.createTokenManagementTransactionsFactory(); + + // 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 frank = await Account.newFromPem(path.join(walletsPath, "frank.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 = frank.address; + + // sign the transaction + transaction.signature = await alice.signTransaction(transaction); + + // relayer also signs the transaction + transaction.relayerSignature = await frank.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.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")); + + // 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.createTokenManagementTransactionsFactory(); + + // 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. + +### Multisig + +The sdk contains components to interact with the [Multisig Contract](https://github.com/multiversx/mx-contracts-rs/releases/tag/v0.45.5). +We can deploy a multisig smart contract, add members, propose and execute actions and query the contract. +The same as the other components, to interact with a multisig smart contract we can use either the MultisigController or the MultisigTransactionsFactory. + +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: +- [`MultisigController`](https://multiversx.github.io/mx-sdk-js-core/v14/classes/MultisigController.html) +- [`MultisigTransactionsFactory`](https://multiversx.github.io/mx-sdk-js-core/v14/classes/MultisigTransactionsFactory.html) + +#### Deploying a Multisig Smart Contract using the controller +```js +{ + const abi = await loadAbiRegistry("src/testdata/multisig-full.abi.json"); + const bytecode = await loadContractCode("src/testdata/multisig-full.wasm"); + + // create the entrypoint and the multisig controller + const entrypoint = new DevnetEntrypoint(); + const controller = entrypoint.createMultisigController(abi); + + 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.createTransactionForDeploy(alice, alice.getNonceThenIncrement(), { + quorum: 2, + board: [ + alice.address, + Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"), + ], + bytecode: bytecode.valueOf(), + gasLimit: 100000000n, + }); + + // sending the transaction + const txHash = await entrypoint.sendTransaction(transaction); + + // wait for transaction completion, extract multisig contract's address + const outcome = await controller.awaitCompletedDeploy(txHash); + + const contractAddress = outcome[0].contractAddress; +} +``` + +#### Deploying a Multisig Smart Contract using the factory +```js +{ + const abi = await loadAbiRegistry("src/testdata/multisig-full.abi.json"); + const bytecode = await loadContractCode("src/testdata/multisig-full.wasm"); + + // create the entrypoint and the multisig factory + const entrypoint = new DevnetEntrypoint(); + const factory = entrypoint.createMultisigTransactionsFactory(abi); + + 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 factory.createTransactionForDeploy(alice.address, { + quorum: 2, + board: [ + alice.address, + Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"), + ], + bytecode: bytecode.valueOf(), + gasLimit: 100000000n, + }); + + transaction.nonce = alice.getNonceThenIncrement(); + transaction.signature = await alice.signTransaction(transaction); + // sending the transaction + const txHash = await entrypoint.sendTransaction(transaction); +} +``` + +#### Propose an action using the controller +We'll propose an action to send some EGLD to Carol. After we sent the proposal, we'll also parse the outcome of the transaction to get the `proposal id`. +The id can be used later for signing and performing the proposal. + +```js +{ + // create the entrypoint and the multisig controller + const entrypoint = new DevnetEntrypoint(); + const abi = await loadAbiRegistry("src/testdata/multisig-full.abi.json"); + const controller = entrypoint.createMultisigController(abi); + + 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.createTransactionForProposeTransferExecute( + alice, + alice.getNonceThenIncrement(), + { + multisigContract: contract, + to: Address.newFromBech32("erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8"), + gasLimit: 10000000n, + nativeTokenAmount: 1000000000000000000n, + }, + ); + + // sending the transaction + const txHash = await entrypoint.sendTransaction(transaction); + + // parse the outcome and get the proposal id + const actionId = await controller.awaitCompletedPerformAction(txHash); +} +``` + +#### Propose an action using the factory +Proposing an action for a multisig contract using the MultisigFactory is very similar to using the controller, but in order to get the proposal id, we need to use MultisigTransactionsOutcomeParser. + +```js +{ + const abi = await loadAbiRegistry("src/testdata/multisig-full.abi.json"); + + // create the entrypoint and the multisig factory + const entrypoint = new DevnetEntrypoint(); + const provider = entrypoint.createNetworkProvider(); + const factory = entrypoint.createMultisigTransactionsFactory(abi); + + const filePath = path.join("../src", "testdata", "testwallets", "alice.pem"); + const alice = await Account.newFromPem(filePath); + + const contract = Address.newFromBech32("erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqf8llllswuedva"); + + const transaction = await factory.createTransactionForProposeTransferExecute(alice.address, { + multisigContract: contract, + to: Address.newFromBech32("erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8"), + gasLimit: 10000000n, + nativeTokenAmount: 1000000000000000000n, + }); + // 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); + + // wait for the transaction to execute + const transactionAwaiter = new TransactionWatcher(provider); + const transactionOnNetwork = await transactionAwaiter.awaitCompleted(txHash); + + // parse the outcome of the transaction + const parser = new MultisigTransactionsOutcomeParser({ abi }); + const actionId = parser.parseProposeAction(transactionOnNetwork); +} +``` + +#### Querying the Multisig Smart Contract +Unlike creating transactions, querying the multisig can be performed only using the controller. +Let's query the contract to get all board members. + +```js +{ + const abi = await loadAbiRegistry("src/testdata/multisig-full.abi.json"); + + // create the entrypoint and the multisig controller + const entrypoint = new DevnetEntrypoint(); + const controller = entrypoint.createMultisigController(abi); + + const contract = Address.newFromBech32("erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqf8llllswuedva"); + + const boardMembers = await controller.getAllBoardMembers({ multisigAddress: contract.toBech32() }); +} +``` + +### Governance + +We can create transactions for creating a new governance proposal, vote for a proposal or query the governance contract. + +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: +- [`GovernanceController`](https://multiversx.github.io/mx-sdk-js-core/v14/classes/GovernanceController.html) +- [`GovernanceTransactionsFactory`](https://multiversx.github.io/mx-sdk-js-core/v14/classes/GovernanceTransactionsFactory.html) + +#### Creating a new proposal using the controller +```js +{ + // create the entrypoint and the governance controller + const entrypoint = new DevnetEntrypoint(); + const controller = entrypoint.createGovernanceController(); + + 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 commitHash = "1db734c0315f9ec422b88f679ccfe3e0197b9d67"; + + const transaction = await controller.createTransactionForNewProposal(alice, alice.getNonceThenIncrement(), { + commitHash: commitHash, + startVoteEpoch: 10, + endVoteEpoch: 15, + nativeTokenAmount: 500_000000000000000000n, + }); + + // sending the transaction + const txHash = await entrypoint.sendTransaction(transaction); + + // wait for transaction completion, extract proposal's details + const outcome = await controller.awaitCompletedProposeProposal(txHash); + + const proposalNonce = outcome[0].proposalNonce; + const proposalCommitHash = outcome[0].commitHash; + const proposalStartVoteEpoch = outcome[0].startVoteEpoch; + const proposalEndVoteEpoch = outcome[0].endVoteEpoch; +} +``` + +#### Creating a new proposal using the factory +```js +{ + // create the entrypoint and the governance factory + const entrypoint = new DevnetEntrypoint(); + const factory = entrypoint.createGovernanceTransactionsFactory(); + + const filePath = path.join("../src", "testdata", "testwallets", "alice.pem"); + const alice = await Account.newFromPem(filePath); + + const commitHash = "1db734c0315f9ec422b88f679ccfe3e0197b9d67"; + + const transaction = await factory.createTransactionForNewProposal(alice.address, { + commitHash: commitHash, + startVoteEpoch: 10, + endVoteEpoch: 15, + nativeTokenAmount: 500_000000000000000000n, + }); + // 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); + + const parser = new GovernanceTransactionsOutcomeParser({}); + const outcome = parser.parseNewProposal(transactionOnNetwork); + const proposalNonce = outcome[0].proposalNonce; + const proposalCommitHash = outcome[0].commitHash; + const proposalStartVoteEpoch = outcome[0].startVoteEpoch; + const proposalEndVoteEpoch = outcome[0].endVoteEpoch; +} +``` + +#### Vote for a proposal using the controller + +```js +{ + // create the entrypoint and the governance controller + const entrypoint = new DevnetEntrypoint(); + const controller = entrypoint.createGovernanceController(); + + 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.createTransactionForVoting(alice, alice.getNonceThenIncrement(), { + proposalNonce: 1, + vote: Vote.YES, + }); + + // sending the transaction + const txHash = await entrypoint.sendTransaction(transaction); + + // wait for transaction completion, extract proposal's details + const outcome = await controller.awaitCompletedVote(txHash); + const proposalNonce = outcome[0].proposalNonce; + const vote = outcome[0].vote; + const voteTotalStake = outcome[0].totalStake; + const voteVotingPower = outcome[0].votingPower; +} +``` + +#### Vote for a proposal using the factory +```js +{ + // create the entrypoint and the governance factory + const entrypoint = new DevnetEntrypoint(); + const factory = entrypoint.createGovernanceTransactionsFactory(); + + const filePath = path.join("../src", "testdata", "testwallets", "alice.pem"); + const alice = await Account.newFromPem(filePath); + + const transaction = await factory.createTransactionForVoting(alice.address, { + proposalNonce: 1, + vote: Vote.YES, + }); + // 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); + + // wait for transaction completion, extract proposal's details + const transactionOnNetwork = await entrypoint.awaitCompletedTransaction(txHash); + const parser = new GovernanceTransactionsOutcomeParser({}); + const outcome = parser.parseVote(transactionOnNetwork); + const proposalNonce = outcome[0].proposalNonce; + const vote = outcome[0].vote; + const voteTotalStake = outcome[0].totalStake; + const voteVotingPower = outcome[0].votingPower; +} +``` + +#### Querying the governance contract +Unlike creating transactions, querying the contract is only possible using the controller. Let's query the contract to get more details about a proposal. + +```js +{ + // create the entrypoint and the governance controller + const entrypoint = new DevnetEntrypoint(); + const controller = entrypoint.createGovernanceController(); + + const proposalInfo = await controller.getProposal(1); + console.log({ proposalInfo }); +} +``` + +## 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"); +} +``` + +#### Getting the shard of an address +``` js + +const addressComputer = new AddressComputer(); +const address = Address.newFromHex("0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1"); +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(); +``` + +#### 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.toString(), 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(filePath, "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 publicKey = secretKey.generatePublicKey(); + + const transaction = new Transaction({ + nonce: 90n, + sender: publicKey.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: alice.address, + 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(transaction); + + 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.signTransaction(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 + +```ts +{ + 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.signMessage(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 = await 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.signTransaction(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 = await 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.signMessage(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/v14/classes/MessageComputer.html#unpackMessage) to reconstruct the message, prior verification: + +```js +{ + const filePath = path.join("../src", "testdata", "testwallets", "alice.pem"); + const alice = await Account.newFromPem(filePath); + const messageComputer = new MessageComputer(); + const data = Buffer.from("test"); + + const message = new Message({ + data: data, + address: alice.address, + }); + + message.signature = await alice.signMessage(message); + // restore message + + const packedMessage = messageComputer.packMessage(message); + const unpackedMessage = messageComputer.unpackMessage(packedMessage); + + // verify the signature + const isSignedByAlice = await alice.verifyMessageSignature(unpackedMessage, message.signature); + + console.log("Transaction is signed by Alice: ", isSignedByAlice); +} +``` + +--- + +### Core Logic + +Define contract arguments, handle storage, process payments, define new types, write better tests + + +## Configuring the contract + +[The previous chapter](crowdfunding-p1.md) left us with a minimal contract as a starting point. + +The first thing we need to do is to configure the desired target amount and the deadline. The deadline will be expressed as the block timestamp (in milliseconds) after which the contract can no longer be funded. We will be adding 2 more storage fields and arguments to the constructor. + +For now, we'll hardcode the contract to only accept EGLD. First, let's add the necessary import at the top of the file: + +```rust +use multiversx_sc::imports::*; +``` + +Now let's add the storage mappers and init function: + +```rust +#[view(getTarget)] +#[storage_mapper("target")] +fn target(&self) -> SingleValueMapper; + +#[view(getDeadline)] +#[storage_mapper("deadline")] +fn deadline(&self) -> SingleValueMapper; + +#[view(getDeposit)] +#[storage_mapper("deposit")] +fn deposit(&self, donor: &ManagedAddress) -> SingleValueMapper; + +#[view(getCrowdfundingTokenId)] +#[storage_mapper("tokenIdentifier")] +fn cf_token_id(&self) -> SingleValueMapper; + +#[init] +fn init(&self, target: BigUint, deadline: TimestampMillis) { + // only support EGLD for now + self.cf_token_id().set(TokenId::egld()); + + require!(target > 0, "Target must be more than 0"); + self.target().set(target); + + require!( + deadline > self.get_current_time_millis(), + "Deadline can't be in the past" + ); + self.deadline().set(deadline); +} + +fn get_current_time_millis(&self) -> TimestampMillis { + self.blockchain().get_block_timestamp_millis() +} +``` + +The `cf_token_id()` storage mapper will hold the token identifier for our crowdfunding campaign. We initialize it to `TokenId::egld()` in the `init` function, hardcoding it to EGLD for now. In Part 3, we'll make this configurable to support any token. + +`TimestampMillis` is a type-safe wrapper for millisecond timestamps, providing better type safety than using raw `u64` values. + +:::note Private functions +Note that `get_current_time_millis()` is not annotated with `#[endpoint]` or `#[view]`. This makes it a **private helper function** that can only be called from within the contract, not from external transactions. Private functions are useful for organizing code and avoiding duplication, but they cannot be called directly by users or other contracts. +::: + +The deadline being a block timestamp can be expressed as a 64-bits unsigned integer `TimestampMillis`. The target, however, being a sum of EGLD cannot. + +:::note + 1 EGLD = 1018 EGLD-wei, also known as atto-EGLD. + +It is the smallest unit of currency, and all payments are expressed in wei. The same applies to ESDT tokens, where the smallest unit depends on the token's number of decimals. +::: + +Even for small payments, the numbers get large. Luckily, the framework offers support for big numbers out of the box. Two types are available: [**BigUint**](/docs/developers/best-practices/biguint-operations.md) and **BigInt**. + +:::tip +Try to **avoid** using the signed version whenever possible, unless negative values are truly needed. There are some caveats with **BigInt** argument serialization that can lead to **subtle bugs**. +::: + +Note that BigUint logic is not implemented within the contract itself but is provided by the MultiversX VM API to keep the contract code lightweight. + +Let's test that initialization works. + +First, navigate to the contract's crate path and rebuild it using: + +```bash +sc-meta all build +``` + +Next, we regenerate the proxy at the same path using: + +```bash +sc-meta all proxy +``` + +Finally, we update the test: + +```rust +#[test] +fn crowdfunding_deploy_test() { + let mut world = world(); + + world.account(OWNER).nonce(0).balance(1000000); + + let crowdfunding_address = world + .tx() + .from(OWNER) + .typed(crowdfunding_proxy::CrowdfundingProxy) + .init(500_000_000_000u64, 123000u64) + .code(CODE_PATH) + .new_address(CROWDFUNDING_ADDRESS) + .returns(ReturnsNewAddress) + .run(); + + assert_eq!(crowdfunding_address, CROWDFUNDING_ADDRESS.to_address()); + + world.check_account(OWNER).balance(1_000_000); + + world + .query() + .to(CROWDFUNDING_ADDRESS) + .typed(crowdfunding_proxy::CrowdfundingProxy) + .target() + .returns(ExpectValue(500_000_000_000u64)) + .run(); + world + .query() + .to(CROWDFUNDING_ADDRESS) + .typed(crowdfunding_proxy::CrowdfundingProxy) + .deadline() + .returns(ExpectValue(123000u64)) + .run(); +} +``` + +Note the added arguments in the deploy call and the additional query for the `deadline` storage. + +Run the test again from the contract crate's path: + +```bash +sc-meta test +``` + + +## Funding the contract + +It is not enough to receive the funds, the contract also needs to keep track of who donated how much. Additionally, we need to validate that the correct token is being sent. + +```rust +#[view(getDeposit)] +#[storage_mapper("deposit")] +fn deposit(&self, donor: &ManagedAddress) -> SingleValueMapper; + +#[endpoint] +#[payable] +fn fund(&self) { + let payment = self.call_value().single(); + + require!( + payment.token_identifier == self.cf_token_id().get(), + "wrong token" + ); + + let caller = self.blockchain().get_caller(); + self.deposit(&caller).update(|deposit| *deposit += payment.amount.as_big_uint()); +} +``` + +:::tip +Every time the contract is modified, you need to rebuild it and regenerate the proxy. +::: + +A few things to unpack: + +1. This storage mapper has an extra argument, for an address. This is how we define a map in the storage. The donor argument will become part of the storage key. Any number of such key arguments can be added, but in this case we only need one. The resulting storage key will be a concatenation of the specified base key `"deposit"` and the serialized argument. +2. We encounter the first payable function. By default, any function in a smart contract is not payable, i.e. sending EGLD to the contract using the function will cause the transaction to be rejected. Payable functions need to be annotated with `#[payable]`. +3. `call_value().single()` gets the payment as a `Payment` structure, which we then validate against our stored EGLD token identifier from `cf_token_id()`. +4. `fund` needs to also be explicitly declared as an endpoint. All `#[payable]` methods need to be marked `#[endpoint]`, but not the other way around. + +To test the function, we will add a new test, in the same `crowdfunding_blackbox_test.rs` file. Let's call it `crowdfunding_fund_test()` . + +To avoid duplicate code, we will put all the deployment and account setup logic into a function called `crowdfunding_deploy()`. This function will return a **ScenarioWorld** response, which gives us the **state of the mocked chain** after setting up an account with the OWNER address and deploying the crowdfunding contract. + +```rust +fn crowdfunding_deploy() -> ScenarioWorld { + let mut world = world(); + + world.account(OWNER).nonce(0).balance(1000000); + + let crowdfunding_address = world + .tx() + .from(OWNER) + .typed(crowdfunding_proxy::CrowdfundingProxy) + .init(500_000_000_000u64, 123000u64) + .code(CODE_PATH) + .new_address(CROWDFUNDING_ADDRESS) + .returns(ReturnsNewAddress) + .run(); + + assert_eq!(crowdfunding_address, CROWDFUNDING_ADDRESS.to_address()); + + world +} +``` + +Now that we've moved the deployment logic to a separate function, let's update the test that checks the deploy endpoint like this: + +```rust +#[test] +fn crowdfunding_deploy_test() { + let mut world = crowdfunding_deploy(); + world.check_account(OWNER).balance(1_000_000); + + world + .query() + .to(CROWDFUNDING_ADDRESS) + .typed(crowdfunding_proxy::CrowdfundingProxy) + .target() + .returns(ExpectValue(500_000_000_000u64)) + .run(); + world + .query() + .to(CROWDFUNDING_ADDRESS) + .typed(crowdfunding_proxy::CrowdfundingProxy) + .deadline() + .returns(ExpectValue(123000u64)) + .run(); +} +``` + +With the code organized, we can now start developing the test for the fund endpoint. + +```rust +const DONOR: TestAddress = TestAddress::new("donor"); + +fn crowdfunding_fund() -> ScenarioWorld { + let mut world = crowdfunding_deploy(); + + world.account(DONOR).nonce(0).balance(400_000_000_000u64); + + world + .tx() + .from(DONOR) + .to(CROWDFUNDING_ADDRESS) + .typed(crowdfunding_proxy::CrowdfundingProxy) + .fund() + .egld(250_000_000_000u64) + .run(); + + world +} + +#[test] +fn crowdfunding_fund_test() { + let mut world = crowdfunding_fund(); + + world.check_account(OWNER).nonce(1).balance(1_000_000u64); + world + .check_account(DONOR) + .nonce(1) + .balance(150_000_000_000u64); + world + .check_account(CROWDFUNDING_ADDRESS) + .nonce(0) + .balance(250_000_000_000u64); + + world + .query() + .to(CROWDFUNDING_ADDRESS) + .typed(crowdfunding_proxy::CrowdfundingProxy) + .target() + .returns(ExpectValue(500_000_000_000u64)) + .run(); + world + .query() + .to(CROWDFUNDING_ADDRESS) + .typed(crowdfunding_proxy::CrowdfundingProxy) + .deadline() + .returns(ExpectValue(123_000u64)) + .run(); + world + .query() + .to(CROWDFUNDING_ADDRESS) + .typed(crowdfunding_proxy::CrowdfundingProxy) + .deposit(DONOR) + .returns(ExpectValue(250_000_000_000u64)) + .run(); +} + +``` + +Explanation: + +1. We need a **donor**, so we add another account using `.account(DONOR)`. +2. The simulated transaction includes: + - [Sender](/docs/developers/transactions/tx-from.md): `.from(DONOR)` + - [Receiver](/docs/developers/transactions/tx-to.md): `.to(CROWDFUNDING_ADDRESS)`. +3. The [payment](/docs/developers/transactions/tx-payment.md) in the transaction is made using `.egld(250_000_000_000u64)`. +4. When checking the state, we see that the **donor's balance is decreased** by the amount paid, and the **contract balance increased** by the same amount. + +Run again the following command in the root of the project to test it: + +```bash +sc-meta test +``` + +You should then see that both tests pass: + +```bash +Running tests in ./ ... +Executing cargo test ... + Compiling crowdfunding v0.0.0 (/home/crowdfunding) + Finished `test` profile [unoptimized + debuginfo] target(s) in 0.22s + Running unittests src/crowdfunding.rs (target/debug/deps/crowdfunding-73d2b98f9e2cff29) + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s + + Running tests/crowdfunding_blackbox_test.rs (target/debug/deps/crowdfunding_blackbox_test-19b9f0d2428bc9f9) + +running 2 tests +test crowdfunding_deploy_test ... ok +test crowdfunding_fund_test ... ok + +test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s + + Doc-tests crowdfunding + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s + +Process finished with: exit status: 0 +``` + + +## Validation + +It doesn't make sense to create a funding that has the target 0 or a negative number, so target needs to be more than 0. Similarly, it’s unreasonable to create a fundraiser with a deadline in the past, so the deadline must be in the future relative to when the contract is deployed. + +```rust +#[init] +fn init(&self, target: BigUint, deadline: TimestampMillis) { + self.cf_token_id().set(TokenId::egld()); + + require!(target > 0, "Target must be more than 0"); + self.target().set(target); + + require!( + deadline > self.get_current_time_millis(), + "Deadline can't be in the past" + ); + self.deadline().set(deadline); +} +``` + +Additionally, it doesn't make sense to accept funding after the deadline has passed, so any fund transactions after a certain block timestamp should be rejected. The idiomatic way to handle this is: + +```rust +#[endpoint] +#[payable] +fn fund(&self) { + let payment = self.call_value().single(); + + require!( + payment.token_identifier == self.cf_token_id().get(), + "wrong token" + ); + + let current_time = self.blockchain().get_block_timestamp_millis(); + require!(current_time < self.deadline().get(), "cannot fund after deadline"); + + let caller = self.blockchain().get_caller(); + self.deposit(&caller).update(|deposit| *deposit += payment.amount.as_big_uint()); +} +``` + +:::tip +The [`require!`](/docs/developers/developer-reference/sc-messages.md#require) macro is used for enforcing conditions. +::: + +We will create another test to verify that the validation works: `crowdfunding_fund_too_late_test()` . + +```rust +#[test] +fn crowdfunding_fund_too_late_test() { + let mut world = crowdfunding_fund(); + + world.current_block().block_timestamp_millis(123_001u64); + + world + .tx() + .from(DONOR) + .to(CROWDFUNDING_ADDRESS) + .typed(crowdfunding_proxy::CrowdfundingProxy) + .fund() + .egld(10_000_000_000u64) + .with_result(ExpectError(4, "cannot fund after deadline")) + .run(); +} +``` + +Now the same donor wants to donate, again, but in the meantime the current block timestamp has become `123_001`, one block later than the deadline. + +The transaction **fails** with status 4. The testing environment allows us to also check that the proper error message was returned. + +:::info +Status 4 indicates a user error. All errors originating within the contract will return this status. +::: + +By testing the contract again, you should see that all three tests pass: + +```bash +Running tests/crowdfunding_blackbox_test.rs (target/debug/deps/crowdfunding_blackbox_test-19b9f0d2428bc9f9) + +running 3 tests +test crowdfunding_deploy_test ... ok +test crowdfunding_fund_test ... ok +test crowdfunding_fund_too_late_test ... ok + +test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s +``` + + +## Querying for the contract status + +The contract status can be known by anyone by looking into the storage and on the blockchain, but it is really inconvenient right now. + +Let's create an endpoint that gives this status directly. The status will be one of: `FundingPeriod`, `Successful` or `Failed`. + +We could use a number to represent it in code, but the nice way to do it is with an enum. We will take this opportunity to show how to create a serializable type that can be taken as argument, returned as result or saved in storage. + +This is the enum: + +```rust +#[type_abi] +#[derive(TopEncode, TopDecode, PartialEq, Clone, Copy)] +pub enum Status { + FundingPeriod, + Successful, + Failed, +} +``` + +Make sure to add it outside the contract trait. + +Don't forget to add the import for the derive types. This can be place on top off the file, replacing `use multiversx_sc::imports::*;` with: + +```rust +use multiversx_sc::{derive_imports::*, imports::*}; +``` + +The `#[derive]` keyword in Rust allows you to automatically implement certain traits for your type. `TopEncode` and `TopDecode` mean that objects of this type are serializable, which means they can be interpreted from/to a string of bytes. + +`#[type_abi]` is needed to export the type when you want to interact with the already deployed contract. This is out of scope of this tutorial though. + +`PartialEq`, `Clone` and `Copy` are Rust traits. `PartialEq` trait allows your type instances to be compared with the `==` operator. `Clone` and `Copy` traits allow your object instances to be clone/copied respectively. + +We can now use the type **Status** just like we use the other types, so we can write the following method in the contract trait: + +```rust +#[view] +fn status(&self) -> Status { + if self.get_current_time_millis() < self.deadline().get() { + Status::FundingPeriod + } else if self.get_current_funds() >= self.target().get() { + Status::Successful + } else { + Status::Failed + } +} + +#[view(getCurrentFunds)] +fn get_current_funds(&self) -> BigUint { + let token = self.cf_token_id().get(); + self.blockchain().get_sc_balance(&token, 0) +} +``` + +We will also modify the `require` condition in the `fund` endpoint to ensure that the deposit can only be made during the **FundingPeriod**. + +```rust +#[endpoint] +#[payable] +fn fund(&self) { + let payment = self.call_value().single(); + + require!( + payment.token_identifier == self.cf_token_id().get(), + "wrong token" + ); + + require!( + self.status() == Status::FundingPeriod, + "cannot fund after deadline" + ); + + let caller = self.blockchain().get_caller(); + self.deposit(&caller) + .update(|deposit| *deposit += payment.amount.as_big_uint()); +} +``` + +To test `status` method, we update the last test we worked on, `crowdfunding_fund_too_late_test()`: + +```rust +use crowdfunding::crowdfunding_proxy::{self, Status}; + +#[test] +fn crowdfunding_fund_too_late_test() { + /* + Code before updating + */ + + world + .query() + .to(CROWDFUNDING_ADDRESS) + .typed(crowdfunding_proxy::CrowdfundingProxy) + .status() + .returns(ExpectValue(Status::Failed)) + .run(); +} +``` + +:::info +The return response checked in the query is an enum defined in the crowdfunding proxy, thus you have to import `crowdfunding_proxy::Status`. +::: + + +## Claim functionality + +Finally, let's add the `claim` method. The `status` method we just implemented helps us keep the code tidy: + +```rust +#[endpoint] +fn claim(&self) { + match self.status() { + Status::FundingPeriod => sc_panic!("cannot claim before deadline"), + Status::Successful => { + let caller = self.blockchain().get_caller(); + require!( + caller == self.blockchain().get_owner_address(), + "only owner can claim successful funding" + ); + + let token_identifier = self.cf_token_id().get(); + let sc_balance = self.get_current_funds(); + + if let Some(sc_balance_non_zero) = sc_balance.into_non_zero() { + self.tx() + .to(&caller) + .payment(Payment::new(token_identifier, 0, sc_balance_non_zero)) + .transfer(); + } + }, + Status::Failed => { + let caller = self.blockchain().get_caller(); + let deposit = self.deposit(&caller).get(); + + if deposit > 0u32 { + let token_identifier = self.cf_token_id().get(); + self.deposit(&caller).clear(); + + if let Some(deposit_non_zero) = deposit.into_non_zero() { + self.tx() + .to(&caller) + .payment(Payment::new(token_identifier, 0, deposit_non_zero)) + .transfer(); + } + } + }, + } +} +``` + +[`sc_panic!`](/docs/developers/developer-reference/sc-messages.md) has the same functionality as [`panic!`](https://doc.rust-lang.org/std/macro.panic.html) from Rust, with the difference that it works in a no_std environment. + +We use the modern [transaction syntax](/developers/transactions/tx-overview) with `.tx()` to send tokens. We convert amounts to `NonZeroUsize` to ensure we only transfer when there's actually something to send, preventing unnecessary transactions with zero amounts. + + +## Conclusion + +Congratulations! You've successfully built a crowdfunding smart contract with: + +- EGLD-based funding mechanism +- Time-based campaign management +- Status tracking (FundingPeriod, Successful, Failed) +- Claim functionality for both successful campaigns and refunds +- Comprehensive testing + +As an exercise, try to add some more tests, especially ones involving the claim function under different scenarios. + + +## Next Steps + +- **Part 3**: In the [next chapter](crowdfunding-p3.md), we'll generalize the contract to accept any fungible token, not just EGLD +- **View the complete code**: Check out the [final contract code](final-code.md) with detailed explanations +- **Explore more examples**: Visit the [MultiversX contracts repository](https://github.com/multiversx/mx-contracts-rs) for more smart contract examples + +--- + +### Creating Wallets + +How to create wallets using the CLI or programmatically + +Although wallets are commonly created through the [MultiversX Web Wallet](https://wallet.multiversx.com/) or the [MultiversX Ledger App](/wallet/ledger), one can also use the CLI or the SDK. + + +## **Generate a new mnemonic** + +Using [mxjs-wallet](https://www.npmjs.com/package/@multiversx/sdk-wallet-cli), a mnemonic phrase (24 words) can be generated as follows: + +```bash +mxjs-wallet new-mnemonic --mnemonic-file=mnemonicOfAlice.txt +``` + +Programmatically using [sdk-core](https://www.npmjs.com/package/@multiversx/sdk-core), the same can be achieved through: + +```js +import { Mnemonic } from "@multiversx/sdk-core/mnemonic"; + +let mnemonic = Mnemonic.generate(); +let words = mnemonic.getWords(); +console.log(words); +``` + + +## **Deriving a JSON key-file (from mnemonic)** + +Using [mxjs-wallet](https://www.npmjs.com/package/@multiversx/sdk-wallet-cli), a JSON key-file can be obtained as follows: + +```bash +mxjs-wallet derive-key --mnemonic-file=mnemonicOfAlice.txt \ + --account-index=0 \ + --key-file=keyOfAlice.json --password-file=passwordOfAlice.txt +``` + +Programmatically using [sdk-core](https://www.npmjs.com/package/@multiversx/sdk-core), the same can be achieved through: + +```js +const mnemonic = Mnemonic.generate(); +const password = "insert a password here"; +const addressIndex = 0; + +const secretKey = mnemonic.deriveKey(addressIndex); +const userWallet = UserWallet.fromSecretKey({ secretKey, password }); +const jsonFileContent = userWallet.toJSON(); + +fs.writeFileSync("myKeyFile.json", JSON.stringify(jsonFileContent)); +``` + +--- + +### Custom Types + +We sometimes create new types that we want to serialize and deserialize directly when interacting with contracts. For `struct`s and `enum`s it is very easy to set them up, with barely any code. + + +### Custom structures + +Any structure defined in a contract of library can become serializable if it is annotated with either or all of: `TopEncode`, `TopDecode`, `NestedEncode`, `NestedDecode`. + +**Example implementation:** + +```rust +#[derive(TopEncode, TopDecode, NestedEncode, NestedDecode)] +pub struct Struct { + pub int: u16, + pub seq: Vec, + pub another_byte: u8, + pub uint_32: u32, + pub uint_64: u64, +} +``` + +**Top-encoding**: All fields nested-encoded one after the other. + +**Nested encoding**: The same, all fields nested-encoded one after the other. + +**Example value** + +```rust +Struct { + int: 0x42, + seq: vec![0x1, 0x2, 0x3, 0x4, 0x5], + another_byte: 0x6, + uint_32: 0x12345, + uint_64: 0x123456789, +} +``` + +It will be encoded (both top-encoding and nested encoding) as: `0x004200000005010203040506000123450000000123456789`. + +Explanation: + +``` +[ +/* int */ 0, 0x42, +/* seq length */ 0, 0, 0, 5, +/* seq contents */ 1, 2, 3, 4, 5, +/* another_byte */ 6, +/* uint_32 */ 0x00, 0x01, 0x23, 0x45, +/* uint_64 */ 0x00, 0x00, 0x00, 0x01, 0x23, 0x45, 0x67, 0x89 +] +``` + +--- + + +### Custom enums + +Any enum defined in a contract of library can become serializable if it is annotated with either or all of: `TopEncode`, `TopDecode`, `NestedEncode`, `NestedDecode`. + +**A simple enum example:** + +_Example taken from the multiversx-sc-codec tests._ + +```rust +#[derive(TopEncode, TopDecode, NestedEncode, NestedDecode)] +enum DayOfWeek { + Monday, + Tuesday, + Wednesday, + Thursday, + Friday, + Saturday, + Sunday, +} +``` + +**A more complex enum example:** + +```rust +#[derive(TopEncode, TopDecode, NestedEncode, NestedDecode)] +enum EnumWithEverything { + Default, + Today(DayOfWeek), + Write(Vec, u16), + Struct { + int: u16, + seq: Vec, + another_byte: u8, + uint_32: u32, + uint_64: u64, + }, +} +``` + +**Nested encoding**: +First, the discriminant is encoded. The discriminant is the index of the variant, starting with `0`. +Then the fields in that variant (if any) get nested-encoded one after the other. + +**Top-encoding**: Same as nested-encoding, but with an additional rule: +if the discriminant is `0` (first variant) and there are no fields, nothing is encoded. + +**Example values** + +_The examples below are taken from the multiversx-sc-codec tests._ + + + +export const ExampleTable = () => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ValueTop-encoding bytesNested encoding bytes
+ +
 
+            DayOfWeek::Monday
+          
+
+
+ +
 
+            /* nothing */
+          
+
+
+ +
 
+            /* discriminant */ 0,
+          
+
+
+ +
 
+          DayOfWeek::Tuesday
+        
+
+
+ +
 
+            /* discriminant */ 1,
+          
+
+
+ +
 
+            EnumWithEverything::Default
+          
+
+
+ +
 
+            /* nothing */
+          
+
+
+ +
 
+            /* discriminant */ 0,
+          
+
+
+ +
 
+            EnumWithEverything::Today(
+            
DayOfWeek::Monday +
) +
+
+
+ +
 
+            
/* discriminant */ 1, +
/* DayOfWeek discriminant */ 0 +
+
+
+ +
 
+            EnumWithEverything::Today(
+            
DayOfWeek::Friday +
) +
+
+
+ +
 
+            
/* discriminant */ 1, +
/* DayOfWeek discriminant */ 4 +
+
+
+ +
 
+            EnumWithEverything::Write(
+            
Vec::new(), +
0, +
) +
+
+
+ +
 
+            
/* discriminant */ 2, +
/* vec length */ 0, 0, 0, 0, +
/* u16 */ 0, 0, +
+
+
+ +
 
+            EnumWithEverything::Write(
+            
[1, 2, 3].to_vec(), +
4 +
) +
+
+
+ +
 
+            
/* discriminant */ 2, +
/* vec length */ 0, 0, 0, 3, +
/* vec contents */ 1, 2, 3, +
/* an extra 16 */ 0, 4, +
+
+
+ +
 
+            EnumWithEverything::Struct (
+            
int: 0x42, +
seq: vec![0x1, 0x2, 0x3, 0x4, 0x5], +
another_byte: 0x6, +
uint_32: 0x12345, +
uint_64: 0x123456789, +
); +
+
+
+ +
 
+            
/* discriminant */ 3, +
/* int */ 0, 0x42, +
/* seq length */ 0, 0, 0, 5, +
/* seq contents */ 1, 2, 3, 4, 5, +
/* another_byte */ 6, +
/* uint_32 */ 0x00, 0x01, 0x23, 0x45, +
/* uint_64 */ 0x00, 0x00, 0x00, 0x01, +
0x23, 0x45, 0x67, 0x89, +
+
+
+); + +--- + +### Defaults + +Smart contracts occasionally need to interact with uninitialized data. Most notably, whenever a smart contract is deployed, its entire storage will be uninitialized. + +Uninitialized storage is indistinguishable from empty storage values, or storage that has been deleted. It always acts like + + +## Built-in defaults + +The serialization format naturally supports defaults for most types. + +The default value of a type is the value that we receive when deserializing an empty buffer. The same value will serialize to an empty buffer. We like having easy access to empty serialized buffers, because that way we can easily clear storage and we minimize gas costs in transactions. + +For instance, for all numeric types, zero is the default value, because we represent it as an empty buffer. + +| Type | Default value | +| ----------------------------------------- | ------------------------------ | +| `u8` | `0` | +| `u16` | `0` | +| `u32` | `0` | +| `u64` | `0` | +| `usize` | `0` | +| `BigUint` | `0` | +| `i8` | `0` | +| `i16` | `0` | +| `i32` | `0` | +| `i64` | `0` | +| `isize` | `0` | +| `BigInt` | `0` | +| `bool` | `false` | +| `Option` | `None` | +| `ManagedBuffer` | `ManagedBuffer::empty()` | +| `Vec` | `Vec::new()` | +| `String` | `"".to_string()` | +| `DayOfWeek` (see example above) | `DayOfWeek::Monday` | +| `EnumWithEverything` (see example above) | `EnumWithEverything::Default` | + + +### Types that have no defaults + +Certain types have no values that can be represented as an empty buffer, and therefore they have no default value. + +When trying to decode any of these types from an empty buffer, we will receive a deserialization error. + +Examples: +- `(usize, usize)` always gets serialized as exactly 8 bytes, no less; +- `(usize, usize, usize)` always gets serialized as exactly 12 bytes; +- `[u8; 20]` always gets serialized as exactly 20 bytes. + +The same goes for any custom `struct`, its representation is the concatenation of the nested encoding of its components, which is fixed size. + +In some cases a custom `enum` faces the same problem. If the first variant has no additional data, the default is simply the first variant. We saw two examples above: +- `DayOfWeek` is a simple enum top to bottom, so `DayOfWeek::Monday` is naturally its default; +- `EnumWithEverything` has data in some of the variants, but not in the first, so in a similar manner, `EnumWithEverything::Default` works as its default. + +However, if we were to define the enum: +```rust +#[derive(TopEncode, TopDecode)] +enum Either { + Something(u32), + SomethingElse(u64), +} +``` +... there is no way to find a natural default value for it. Both variants are represented as non-empty buffers. + +If you need the default, one workaround is to place these structures inside an `Option`. Options always have the default `None`, no matter the contents. + +There is, however, another way to do it: for custom structures it is possible to define custom defaults, as we will see in the next section. + + +## Custom defaults + +A structure that does not have a natural default value can receive one via custom code. First of all this applies to structures, but it can also be useful for some enums. + +To do so, instead of deriving `TopEncode` and `TopDecode`, we will derive `TopEncodeOrDefault` and `TopDecodeOrDefault`, respectively. + +We need to also specify what we want that default value to be, both when encoding and decoding. For this, we need to explicitly implement traits `EncodeDefault` and `DecodeDefault` for our structure. + +Let's look at an example: + +```rust +#[derive(TopEncodeOrDefault, TopDecodeOrDefault)] +pub struct StructWithDefault { + pub first_field: u16, + pub seq: Vec, + pub another_byte: u8, + pub uint_32: u32, + pub uint_64: u64, +} + +impl EncodeDefault for StructWithDefault { + fn is_default(&self) -> bool { + self.first_field == 5 + } +} + +impl DecodeDefault for StructWithDefault { + fn default() -> Self { + StructWithDefault { + first_field: 5, + seq: vec![], + another_byte: 0, + uint_32: 0, + uint_64: 0, + } + } +} +``` + +We just specified the following: +- `is_default`:whenever the `first_field` field is equal to 5, the other fields don't matter anymore and we save the structure as an empty buffer; +- `default`: whenever we try to decode an empty buffer, we yield a structure that has the `first_field` set to 5, and all the other fields empty or zero. + +It should always be the case that `::is_default(::default())` is true. The framework does not enforce this in any way, but it should be common sense. + +Other than that, there are no constraints on what the default value should be. + +We can do the same for an enum: + +```rust +#[derive(TopEncodeOrDefault, TopDecodeOrDefault)] +enum Either { + Something(u32), + SomethingElse(u64), +} + +impl EncodeDefault for Either { + fn is_default(&self) -> bool { + matches!(*self, Either::Something(3)) + } +} + +impl DecodeDefault for Either { + fn default() -> Self { + Either::Something(3) + } +} +``` + +We just specified the following: +- `is_default`: whenever we have variant `Either::Something` _and_ the value contained is 3, encode as empty buffer; +- `default`: whenever we try to decode an empty buffer, we yield `Either::Something(3)`. + +The same here, `::is_default(::default())` should be true. No other constraints over what the default value should be, of which variant. `Either::SomethingElse(0)` could also have been chosen to be the default. + +--- + +### delegators + +This page describes the structure of the `delegators` index (Elasticsearch), and also depicts a few examples of how to query it. + + +## _id + +The `_id` field of this index is composed in this way: `blake2bHash(delegatorAddress+stakingProviderAddress)`, in a base64 encoding (example: `YZNG+r3ZwFtOj0c057MnpVnXAfmSqLai15lusLWg+KM=`). + + +## Fields + + +| Field | Description | +|----------------|--------------------------------------------------------------------------------------------------------------------------------------| +| address | The address field holds the address in bech32 encoding of the delegator. | +| contract | This field holds the bech32 encoded address of the staking provider contract to whom it was delegated to. | +| activeStake | The activeStake field holds the EGLD amount of the active stake (not undelegated nor unbondable). | +| activeStakeNum | The activeStake field holds the EGLD amount of the active stake (not undelegated nor unbondable), in a numeric format. Example: 1.5. | +| unDelegateInfo | The unDelegateInfo contains a list with data about the unDelegated values. | +| timestamp | The timestamp field represents the last moment of an interaction with the delegation contract. | + +The `unDelegateInfo` field is populated with the fields below: + + +| unDelegateInfo fields | Description | +|-----------------------|-------------------------------------------------------------------------------------------------| +| value | The value field holds the EGLD amount that was undelegated. | +| valueNum | The value field holds the EGLD amount that was undelegated, in a numeric format (example: 1.5). | +| timestamp | The timestamp field represents the timestamp when the unDelegation operation was done. | + + +## Query examples + + +### Fetch all delegations of an address + +``` +curl --request GET \ + --url ${ES_URL}/delegators/_search \ + --header 'Content-Type: application/json' \ + --data '{ + "query": { + "match": { + "address":"erd.." + } + } +}' +``` + + +### Fetch all delegators to a staking provider + +``` +curl --request GET \ + --url ${ES_URL}/delegators/_search \ + --header 'Content-Type: application/json' \ + --data '{ + "query": { + "match": { + "contract":"erd.." + } + } +}' +``` + +--- + +### Deploy a SC in 5 minutes - SpaceCraft interactors + +This short guide demonstrates how to deploy and interact with a smart contract (SC) on MultiversX using the SpaceCraft interactors. We will cover essential topics such as the SC framework, integration tests, `sc-meta`, and interactors (devtools). + + +## Introduction + +Building smart contracts involves complex tasks. Beyond the syntax, a smart contract acts as a public server where users pay for actions. Enforcing rules to ensure safety (treating possible exploits) and efficiency (timewise - transaction speed, costwise - gas fees) for all users interacting with the contract is crucial. + +In order to make sure that the smart contract works as expected, there are at least three stages of testing that we recommend to be performed before live deployment: + +- unit testing ([SpaceCraft testing framework](/docs/developers/testing/rust/sc-test-overview.md#overview) - Rust unit tests, RustVM) +- scenarios ([mandos](/docs/developers/testing/scenario/concept.md#what-is-mandos) - json files, can be generated from Rust unit tests). Mandos can be used to test the logic on the [GoVM](/docs/learn/space-vm.md) as well, which is the actual VM running on the node +- integration testing ([SpaceCraft Rust interactors](/docs/developers/meta/interactor/interactors-overview.md#overview) - testing on the blockchain). Integration tests cover real life scenarios across the different MultiversX blockchain environments - devnet/testnet/mainnet + +In this tutorial we will focus on integration testing using the interactors made available by the SpaceCraft smart contract framework. + +::::important Prerequisites + +- `stable` Rust version `≥1.83.0` (install via [rustup](/docs/developers/toolchain-setup.md#installing-rust-and-sc-meta)): +- `multiversx-sc-meta` (cargo install [multiversx-sc-meta](/docs/developers/meta/sc-meta-cli.md)) +:::: + + +## Step 1: Start from a template + +Get a headstart by using `sc-meta` to generate one of our smart contract templates as a starting point for your smart contract. Let’s say we start from the `empty` template contract and name it `my-contract`. + +```bash +sc-meta new --template empty --name my-contract +code my-contract # opens the contract in VSCode (optional) +``` + +This command generates an empty contract called `MyContract`, with the minimum requirements for deployment. The main file *(my-contract/src/my_contract.rs)* includes only two empty endpoints: `init` and `upgrade`. + + +## Step 2: Customize the template + +However, we are not interested in deploying an empty contract, so we will quickly add some endpoints: + +```rust title=my_contract.rs +#![no_std] + +#[allow(unused_imports)] +use multiversx_sc::imports::*; + +/// An empty contract. To be used as a template when starting a new contract from scratch. +#[multiversx_sc::contract] +pub trait MyContract { + #[init] + fn init(&self) {} + + #[upgrade] + fn upgrade(&self) {} + + #[endpoint] + fn register_me(&self) { + let caller = self.blockchain().get_caller(); + require!( + self.already_registered(&caller).is_empty(), + "user already registered" + ); + self.already_registered(&caller).set(true) + } + + #[endpoint] + fn deregister_me(&self) { + let caller = self.blockchain().get_caller(); + require!( + !self.already_registered(&caller).is_empty(), + "user not registered" + ); + self.already_registered(&caller).clear() + } + + #[view] + #[storage_mapper] + fn already_registered(&self, user: &ManagedAddress) -> SingleValueMapper; +} +``` + +In this snippet, we have created a storage mapper named `already_registered` to track user registration status, and two endpoints to handle the registration process and enforce a clear path of action for the user: first register, then deregister. Any other path of action should result in an error. In order to make sure the rules are enforced on the actual blockchain, we will create some integration tests. + + +## Step 3: Build the contract + +Considering our syntax development is done, we should now be able to build the contract. We need to build the contract in order to generate necessary files for deployment and testing inside the `output` folder such as *my-contract.wasm* and *my-contract.mxsc.json*. + +```bash +cd my-contract +sc-meta all build +``` + + +## Step 4: Generate the interactor + +Based on the smart contract we have just created, we can generate interactors with just one command using `sc-meta` in the root folder of the contract. + +```bash +sc-meta all snippets +``` + +This command compiled the contract and generated a new folder called `interactor`. The interactor is by default a Rust CLI program that uses the smart contract proxy to send calls to the contract. + +Inside the source folder *(my-contract/interactor/src)*, we should find the newly generated proxy of the contract *(my_contract_proxy.rs)* and the `interactor_main.rs` file, which is the main file of the project. A *sc-config.toml* file has also been created (if not existent) in the contract root containing the path of the proxy file. + +If we navigate to *interactor/src/interact.rs*, inside the `my_contract_cli()` function, we can find all the CLI command available to us: + +```rust title=interact.rs +pub async fn my_contract_cli() { + env_logger::init(); + + let mut args = std::env::args(); + let _ = args.next(); + let cmd = args.next().expect("at least one argument required"); + let config = Config::new(); + let mut interact = ContractInteract::new(config).await; + match cmd.as_str() { + "deploy" => interact.deploy().await, + "upgrade" => interact.upgrade().await, + "register_me" => interact.register_me().await, + "deregister_me" => interact.deregister_me().await, + "already_registered" => interact.already_registered().await, + _ => panic!("unknown command: {}", &cmd), + } +} +``` + +As you can see, `sc-meta` automatically generated all the logic behind calling the smart contract endpoints. The interactor uses asynchronous Rust, so all the functions are marked as `async` and need to be awaited to get a result. + +In order to compile the project, we need to include it in the project tree. In this case, we have to add the interactor project to the smart contract’s workspaces, in the *my-contract/Cargo.toml* file: + +```toml title=Cargo.toml +[workspace] +members = [ + ".", + "meta", + "interactor" +] +``` + + +## Step 5: Create scenarios & run + +Now the setup is complete, it’s time to create some scenarios to test. For our use-case, the perfect scenario is: **deploy the contract**, **register from a user** and **deregister from the same user**. Anything else should result in an error. + +In order to test the perfect scenario first, we will first deploy the contract: + +```bash +cd interactor +cargo run deploy +``` + +After deploying the contract, a new file named *state.toml* will be created in the `interactor` directory, which contains the newly deployed SC address. For each deploy, a new address will be printed into the file. + +```toml title=state.toml +contract_address = "erd1qqqqqqqqqqqqqpgqpsev0x4nufh240l44gf2t6qzkh9xvutqd8ssrnydzr" +``` + +By default, the testing environment is `devnet`, specified by the `my-contract/interactor/config.toml`: + +```toml title=config.toml +chain_type = 'real' +gateway_uri = 'https://devnet-gateway.multiversx.com' +``` + +Changing the value of the `gateway_uri` will change the testing environment for a quick setup. Other options are: + +- Testnet: `https://testnet-gateway.multiversx.com` +- Mainnet: `https://gateway.multiversx.com` + +Each command has some waiting time and returns the result inside a variable in the function, but also prints it in the console for easy tracking. + +In this case, the console shows: + +```bash +interactor % cargo run deploy + Compiling rust-interact v0.0.0 (/Users/you/Documents/my-contract/interact-rs) + Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.96s + Running `/Users/you/Documents/my-contract/target/debug/rust-interact deploy` +sender's recalled nonce: 1717 +-- tx nonce: 1717 +sc deploy tx hash: 623c7b853b1fbb36762d433c6a5e27d34f48198e68bbba1216d1c676ab0ba3be +deploy address: erd1qqqqqqqqqqqqqpgqpsev0x4nufh240l44gf2t6qzkh9xvutqd8ssrnydzr +new address: erd1qqqqqqqqqqqqqpgqpsev0x4nufh240l44gf2t6qzkh9xvutqd8ssrnydzr +``` + +Then, we can continue testing the scenario: + +```bash +cargo run register_me +cargo run deregister_me +``` + +These commands will send two transactions to the newly deployed contract from the `test_wallets::alice()` wallet, each of them calling one endpoint of the contract in the specified order. + +```bash title=register_me +interactor % cargo run register_me + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.35s + Running `/Users/you/Documents/my-contract/target/debug/rust-interact register_me` +sender's recalled nonce: 1718 +-- tx nonce: 1718 +sc call tx hash: 97bea2b18ca0d1305200dc4ea0d1b2b32a666430f8d24ab042f59c324bf47eec +Result: () +``` + +```bash title=deregister_me +interactor % cargo run deregister_me + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.11s + Running `/Users/you/Documents/my-contract/target/debug/rust-interact deregister_me +sender's recalled nonce: 1719 +-- tx nonce: 1719 +sc call tx hash: 20540f1548508e198359d3e897c1e380796e89142801d485c76c700b7dab2a8b +Result: () +``` + + +## Step 6 (optional): Create integration tests + +Using the functions generated by `sc-meta`, we can extend the interactor to cover a series of integration tests. Organized tests help with maintenance and long-term testing. + +We can create a quick integration test as such: + +Inside the `tests` directory of the `interactor`, you can add integration tests that run either on the **chain simulator** or on the **gateway** configured in `config.toml`. + +For this example, we can create an integration test that runs on `devnet`, starting with the template provided in the file `my-contract/interactor/interact_tests.rs`: + +```rust title=interact_tests.rs +#[tokio::test] +async fn integration_test() { + let mut interactor = ContractInteract::new(Config::new()).await; + + interactor.deploy().await; + interactor.register_me().await; + interactor.deregister_me().await; +} +``` + +Alternatively, we can create an integration test that runs only on the [chain simulator](/docs/developers/tutorials/chain-simulator-adder.md): + +```rust +#[tokio::test] +#[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] +async fn chain_simulator_integration_test() { + let mut interactor = ContractInteract::new(Config::chain_simulator_config()).await; + + interactor.deploy().await; + interactor.register_me().await; + interactor.deregister_me().await; +} +``` + +Running `integration_test()` will perform the previous CLI actions in the same order, on the real blockchain. The console will show all the intermediate actions at the end of the test, as such: + +```bash +running 1 test +test integration_test ... ok + +successes: + +---- integration_test stdout ---- +sender's recalled nonce: 1720 +-- tx nonce: 1720 +sc deploy tx hash: ca6e69c18acd73b20bfd21142b45be1b530ecbec89d1eb9c374b93f7681dbc38 +deploy address: erd1qqqqqqqqqqqqqpgq0lkg29q0ep09llg0mva5lle3s0334wqtd8ss40lmkn +new address: erd1qqqqqqqqqqqqqpgq0lkg29q0ep09llg0mva5lle3s0334wqtd8ss40lmkn +sender's recalled nonce: 1721 +-- tx nonce: 1721 +sc call tx hash: f8692bd508c1d1aa10324a79d497f1c6995e053e3f89fca48e8f4185808f067a +Result: () +sender's recalled nonce: 1722 +-- tx nonce: 1722 +sc call tx hash: 369429790c8d77d85dd52f73c9a6b6427709f327b7e004ad99de3bc04f3ee767 +Result: () + + +successes: + integration_test + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 14.38s +``` + + +## Conclusion + +The interactors are a versatile tool that greatly simplifies various processes around a smart contract, including deployment, upgrades, interaction, and testing. These tools not only save time but also offer a robust starting codebase for Rust developers. They also provide a valuable learning opportunity for non-Rust developers to explore asynchronous Rust and other advanced features. + +We highly recommend experimenting with these interactors, as they are efficient time-savers and are likely to be expanded with even more features in the future. By incorporating these practices, you can ensure that your smart contract functions as intended, providing a reliable and efficient service to users on MultiversX. + +--- + +### Devcontainers + +In **Visual Studio Code**, you use a **container** as a [full-featured **development environment**](https://code.visualstudio.com/docs/devcontainers/containers). As of September 2023, one MultiversX devcontainer is available: + - [**MultiversX: Smart Contracts Development (Rust)**](https://containers.dev/templates). This one includes **mxpy**, **Rust**, **sc-meta**, a few Visual Studio Code extensions, and more. + +:::note +The devcontainers for MultiversX are maintained in [**mx-template-devcontainers**](https://github.com/multiversx/mx-template-devcontainers). +::: + +Before setting up a MultiversX devcontainer, make sure to follow these tutorials: + - [Beginner's Series to Dev Containers](https://youtube.com/playlist?list=PLj6YeMhvp2S5G_X6ZyMc8gfXPMFPg3O31) + - [Dev Container How To](https://youtube.com/playlist?list=PLj6YeMhvp2S6GjVyDHTPp8tLOR0xLGLYb) + +Once you're accustomed to the concept, follow the steps below to set up a MultiversX devcontainer: + - In Visual Studio Code, open the _Command Palette_ (e.g. `Ctrl + Shift + P`) and select `Dev Containers: New Dev Container`; + - When prompted, select `MultiversX: Smart Contracts Development (Rust)`, or any other devcontainer you want to use; + - When prompted, select `Trust @multiversx`; + - Enter a name for your devcontainer. + +Once the development environment is ready, do a quick exploration exercise. For example, run the following commands in the terminal (within Visual Studio Code): + +``` +mxpy --version +sc-meta --version +``` + + +## Using the Docker images without VSCode + +If you'd like to use the development Docker image(s) without VSCode's devcontainers feature, this is entirely possible. For example, let's try the following (in any terminal): + +``` +docker run --rm -it multiversx/devcontainer-smart-contracts-rust:latest mxpy --version +docker run --rm -it multiversx/devcontainer-smart-contracts-rust:latest sc-meta --version +``` + +For a set of real-world examples, please follow [this](https://github.com/multiversx/mx-template-devcontainers#using-the-docker-images-without-vscode). + +--- + +### Developers - Overview + +This page serves as the landing destination for builders seeking to construct on the Multiversx platform. + +If anything is missing, or you want to get more support, please refer to Discord or Telegram developers chats: + +- [Discord: MultiversX Builders](https://discord.gg/multiversxbuilders) +- [Telegram: MultiversX Developers](https://t.me/MultiversXDevelopers) + + +## Developer documentation + +Get started with MultiversX by learning to write your first Smart Contract, build your first dApp or learn how to use our +API. + +:::important +For interacting with MultiversX Blockchain via SDKs or Rest API, please refer to [SDKs & Tools](/sdk-and-tools/overview). +::: + + +## Table of contents + +A list with everything that you can explore as a developer on MultiversX. + + +### Tutorials + +Below is a list of tutorials for building on MultiversX: + +| Name | Description | +| ----------------------------------------------------------------------------------- | ---------------------------------------------------------------- | +| [Build your first dApp in 15 minutes](/developers/tutorials/your-first-dapp) | Video + written tutorial on how to create your first dApp. | +| [Cryptozombies Tutorials](https://cryptozombies.io/en/multiversx) | Interactive way of learning how to write MultiversX Smart Contracts. | +| [Build a microservice for your dApp](/developers/tutorials/your-first-microservice) | Video + written tutorial on how to create your microservice. | +| [Crowdfunding Tutorial - Part 1: Setup & Basics](/docs/developers/tutorials/crowdfunding/crowdfunding-p1.md) | Write, build and test your first smart contract. | +| [Crowdfunding Tutorial - Part 2: Core Logic](/docs/developers/tutorials/crowdfunding/crowdfunding-p2.md) | Add endpoints, payments, validation and comprehensive testing.| +| [Crowdfunding Tutorial - Part 3: Extend to Any Token](/docs/developers/tutorials/crowdfunding/crowdfunding-p3.md) | Generalize the contract to accept any fungible token.| +| [Crowdfunding Tutorial - Final Code](/docs/developers/tutorials/crowdfunding/final-code.md) | Complete reference implementation with full source code.| +| [Staking contract Tutorial](/developers/tutorials/staking-contract) | Step by step tutorial on how to create a Staking Smart Contract. | +| [Energy DAO Tutorial](/developers/tutorials/energy-dao) | In depth analysis of the Energy DAO SC template. | +| [DEX Walkthrough](/developers/tutorials/dex-walkthrough) | In depth walkthrough of all the main DEX contracts. | +| [WalletConnect 2.0 Migration](/developers/tutorials/wallet-connect-v2-migration) | WalletConnect 2.0 Migration Guide | +| [Ethereum to MultiversX migration guide](/developers/tutorials/eth-to-mvx) | Guide for Ethereum developers to start building on MultiversX. | +| [Chain Simulator in Adder - SpaceCraft interactors](/developers/tutorials/chain-simulator-adder)| Guide on how to interact with Chain Simulator in one of the simplest SCs.| + + +### SDKs and Tools + +One can (programmatically) interact with the MultiversX Network by leveraging a set of **SDKs (TypeScript, Go, Python, C++ etc.), tools and APIs**. For more details, please follow: + +| Name | Description | +| ---------------------------------------------------- | ------------------------------------------ | +| [SDKs and Tools - Overview](/sdk-and-tools/overview) | The complete list of SDKs, tools and APIs. | + + +### Signing transactions + +The following content explains the structure of a transaction, how to sign or send them: + +| Name | Description | +| ------------------------------------------------------------------------------------- | ------------------------------------------------------- | +| [Signing transactions](/developers/signing-transactions) | How to serialize and sign transactions. | +| [Tools for signing](/developers/signing-transactions/tools-for-signing) | What to use in order to generate and sign transactions. | +| [Signing programmatically](/developers/signing-transactions/signing-programmatically) | How to sign transactions by using one of our SDKs. | + + +### Gas and fees + +Learn about transaction's gas and how a fee is calculated: + +| Name | Description | +| ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | +| [Overview](/developers/gas-and-fees/overview) | General concepts and overview about gas cost, limit, price and fee. | +| [For move-balance transfers](/developers/gas-and-fees/egld-transfers) | How fees are computed for move-balance transfers (EGLD transfers). | +| [For System Smart Contracts](/developers/gas-and-fees/system-smart-contracts) | How fees are computed when interacting with system smart contracts. | +| [For User defined Smart Contracts](/developers/gas-and-fees/user-defined-smart-contracts) | How fees are computed when interacting with user defined smart contracts. | + + +### Smart Contract Developer reference + +| Name | Description | +|--------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------| +| [How to format the data field for Smart Contract calls](/developers/sc-calls-format) | Learn how a Smart Contract call looks like and how arguments should be encoded. | +| [MultiversX serialization format](/developers/data/serialization-overview) | How MultiversX smart contracts serialize arguments, results, and storage. | +| [MultiversX SC annotations](/developers/developer-reference/sc-annotations) | How to use annotations in your contracts to make use of many built-in features from the framework. | +| [MultiversX SC modules](/developers/developer-reference/sc-modules) | Learn how to divide a Smart Contract into multiples smaller components by using modules. | +| [MultiversX SC contract calls](/developers/transactions/tx-legacy-calls) | Learn how to call a Smart Contract from another Smart Contract. | +| [Code metadata](/developers/data/code-metadata) | Choose the properties / eligible actions of your Smart Contract. | +| [Upgrading smart contracts](/developers/developer-reference/upgrading-smart-contracts) | The implications of upgrading a Smart Contract. | +| [MultiversX SC api functions](/developers/developer-reference/sc-api-functions) | Make use of the MultiversX VM API functions to query relevant data from the blockchain. | +| [Storage mappers](/developers/developer-reference/storage-mappers) | Decide from multiple ways of storing data in your SC, by considering performance. | +| [Rust smart contract build reference](/developers/meta/sc-build-reference) | How to build and organize your Smart Contract. | +| [Random numbers in smart contracts](/developers/developer-reference/sc-random-numbers) | How to generate random number in Smart Contracts. | + + +### Smart Contract Developers Best Practices + +| Name | Description | +| ------------------------------------------------------------------------------------------- | -------------------------------------------------- | +| [Best practices basics](/developers/best-practices/best-practices-basics) | How to better structure your Smart Contract code. | +| [Biguint operations](/developers/best-practices/biguint-operations) | Handle Biguint operations in a more efficient way. | +| [The dynamic allocation problem](/developers/best-practices/the-dynamic-allocation-problem) | Description of the dynamic allocation problem. | +| [Multi values](/developers/data/multi-values) | Take advantage of the variadic input and output. | + + +### Smart Contract Testing + +| Name | Description | +|----------------------------------------------------------------------|----------------------------------------------------------| +| [Overview](/developers/testing/rust/sc-test-overview) | Introduction to all the testing methods available in Rust| +| [Blackbox tests](/developers/testing/rust/sc-blackbox-calls) | The best way to write integration tests, by simulating transactions| +| [Whitebox framework](/developers/testing/rust/whitebox-legacy) | Older testing framework, but still in use in some projects.| +| [Whitebox framework functions reference](/developers/testing/rust/whitebox-legacy-functions-reference) | A list of available functions to be used when using the whitebox framework. | +| [Debugging](/developers/testing/sc-debugging) | How to debug your smart contract tests. | + + +### Scenarios Reference + +| Name | Description | +|----------------------------------------------------------------------|----------------------------------------------------------| +| [Scenario Overview](/developers/testing/scenario/concept) | Test your Smart Contracts by using Scenario JSON tests. | +| [Scenario Structure](/developers/testing/scenario/structure-json) | How to structure a scenario. | +| [Scenario Simple Values](/developers/testing/scenario/values-simple) | Handle simple values in scenario tests. | +| [Scenario Complex Values](/developers/testing/scenario/values-complex) | Handle complex values in scenario tests. | +| [Embedding Scenario code in GO](/developers/testing/testing-in-go) | How to embed scenario code in Go. | + + +### Event logs + +Event logs are special events generated by smart contracts, built-in functions, or ESDT operations. They provide a way to record important information about +the execution of smart contract, information about ESDT transfers or built-in function calls. + + +#### Event structure + +| Field | Type | Description | +| ---------- | -------- | ------------------------------------------------ | +| identifier | string | The identifier for the event. | +| address | string | The associated address. | +| topics | []string | An array containing information about the event. | +| data | string | Additional data related to the event. | + + +#### Event logs can be categorized into the following types + +| Name | Description | +| ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [ESDT Operations Events](/developers/event-logs/esdt-events) | ESDT operations, which encompass token creation, transfers, and other critical actions, generate log events. These events record sender and receiver addresses, token amounts, and operation types. | +| [Execution Events](/developers/event-logs/execution-events) | Execution events are dedicated to recording the status of transaction execution. They indicate whether a transaction was successfully executed or encountered an error. | +| [Smart Contract Call Events](/developers/event-logs/contract-call-events) | Smart contract calls often emit log events to report their execution status and results. These events typically include information such as the caller's address, the called function, and any other relevant data. | +| [Smart Contract Deploy Events](/developers/event-logs/contract-deploy-events) | Smart contract deployment and upgrade events are used to record when a smart contract is initially deployed or when it undergoes an upgrade. | +| [System Delegation Events](/developers/event-logs/system-delegation-events) | System delegation events are generated in response to interactions with the system delegation contract. | + + +### Others + +| Name | Description | +| ------------------------------------------------------------------------ | -------------------------------------------------------------------------------------- | +| [Constants](/developers/constants) | A list of useful constants that governs the MultiversX Mainnet. | +| [Built in functions](/developers/built-in-functions) | Built-in functions - protocol-side functions. | +| [Account storage](/developers/account-storage) | How the data is stored under an account + how to query and change it. | +| [Relayed/meta transactions](/developers/relayed-transactions) | How to prepare transactions whose fee is not paid by the user, but by a relayer. | +| [Setup local testnet](/developers/setup-local-testnet) | How to set up a localnet (local testnet) - basic solution | +| [Setup local testnet advanced](/developers/setup-local-testnet-advanced) | How to set up a localnet (local testnet) - advanced solution | +| [Creating wallets](/developers/creating-wallets) | Examples on creating wallets. | +| [Reproducible builds](/developers/reproducible-contract-builds) | How to perform reproducible contract builds. | +| [Contract API limits](/developers/contract-api-limits) | Limits that a smart contract must abide when calling external (node-related) functions | + +--- + +### DEX Walkthrough + +## Introduction + +If you are building a project that involves decentralized exchange functionality, integrating DEX contracts can be a crucial step in achieving your goals. These contracts provide the underlying infrastructure necessary to facilitate the exchange of assets on a decentralized platform. However, implementing these contracts can be a complex process, and understanding the various public endpoints and functions can be challenging. In this in-depth walkthrough, we will guide you through the process of integrating DEX contracts into your __MultiversX__ project. We will cover all of the main contracts involved in a typical DEX implementation, and provide detailed explanations of the most commonly used public endpoints. By the end of this tutorial, you should have a solid understanding of how to implement DEX functionality in your own project, and be able to make informed decisions about how to customize and extend the functionality to meet your specific needs. + + +## Prerequisites + +The DEX contracts are a bit more advanced than the standard SCs, so basic knowledge about Rust SC development is required. If you are a beginner, an easier starting point (like the Crowdfunding SC or the Staking SC tutorials) is strongly advised. Also, to better grasp the DEX contracts implementation, it is important that you first understand the xExchange economic model. + +:::important +You can find the xExchange whitepaper here: +https://xexchange.com/x-exchange-economics.pdf +::: + + +## **DEX repo structure** & recommendations + +The main DEX contracts are as follow: + +- __Pair SC__ +- __Router SC__ +- __Farm SC__ +- __Proxy DEX SC__ +- __Farm Staking SC__ +- __Farm Staking Proxy SC__ +- __Simple Lock SC__ +- __Energy Factory SC__ +- __Token Unstake SC__ +- __Fees Collector SC__ +- __Locked Token Wrapper SC__ + +:::important +You can find the repository containing all the DEX contracts here: +https://github.com/multiversx/mx-exchange-sc +::: + +This walkthrough was made based on a synchronous, intrashard contract calls flow as the suggested implementation. While you can still use async calls, that approach would complicate the implementation of any new projects building on top of the DEX contracts to some extent, with more complex gas handling requirements and callbacks logic. In order to have synchronous integration with the DEX contracts, the newly developed SCs need to be deployed on the same shard as the xExchange contracts. + +Later on, with the launch of the AsyncV2 functionality, these kinds of contracts will be able to be deployed in other shards as well, as the protocol will support multiple asyncCalls. + +:::important +You can find an in-depth overview of SC interactions here: +https://docs.multiversx.com/developers/developer-reference/sc-contract-calls +::: + + +## Pair SC + +This contract allows users to provide liquidity and to swap tokens. Users are incentivized to add liquidity by earning rewards from fees and by being able to enter farms, thus earning even more rewards. This contract is usually deployed by the router smart contract and it (usually) has no dependency, as it is used as a DeFi primitive. + + +### Add liquidity + +```rust + pub type AddLiquidityResultType = + MultiValue3, EsdtTokenPayment, EsdtTokenPayment>; + + #[payable("*")] + #[endpoint(addLiquidity)] + fn add_liquidity( + &self, + first_token_amount_min: BigUint, + second_token_amount_min: BigUint, + ) -> AddLiquidityResultType +``` + +The process of adding liquidity to a pool is a straightforward one and does not affect the ratio between the two tokens. Let's assume that the reserves of the first and second tokens are denoted by __rA__ and __rB__ respectively, while the desired amounts of those tokens to be added as liquidity are denoted by __aA__ and __aB__. In order to maintain the ratio of the tokens in the liquidity pool, the following formula must hold true: `rA / rB = aA / aB`. Calculating the appropriate values is easy since one of the desired values, __aA__ or __aB__, can be fixed, and the other one can be derived from the aforementioned formula. + +For newly deployed pairs, the first liquidity addition sets the ratio and price of the tokens since there are no tokens in the pool yet, and thus no formula to be followed. + +When the add liquidity function is called, it takes an array of two payments that correspond to the amounts the user wants to add to the liquidity pool. The order of the payments is important, as they must correspond to the order of the tokens in the contract. The function also takes in two arguments, __first_token_amount_min__ and __second_token_amount_min__, which represent the desired slippage, or the minimum amount of tokens that must be returned by the contract. + +After all necessary checks and computations are done, the endpoint returns a vector of 3 payments to the user in the following order: __liquidity added__, the difference between the __first token__ amount sent by the user and the amount that was used, and the difference between the __second token__ amount sent by the user and the amount that was used. A __MultiValue__ of these 3 __EsdtTokenPayment__ is returned as the final result. + + +### Remove liquidity + +```rust + pub type RemoveLiquidityResultType = + MultiValue2, EsdtTokenPayment>; + + #[payable("*")] + #[endpoint(removeLiquidity)] + fn remove_liquidity( + &self, + first_token_amount_min: BigUint, + second_token_amount_min: BigUint, + ) -> RemoveLiquidityResultType +``` + +The removal of liquidity from a pool is a process that can be thought of as the reverse of adding liquidity. It involves a liquidity provider sending their LP tokens back to the __Pair SC__ and providing the same parameters that were presented in the `addLiquidity` endpoint, namely the __first_token_amount_min__ and __second_token_amount_min__. In exchange, the provider receives back both types of tokens that he initially provided. Typically, for a pool that is relatively stable, the amounts received when removing liquidity will be greater than the amounts provided initially during the addition process, as they will include the accumulated swap fees. + +After all the checks and computations are done, the endpoint constructs and sends back to the user a vector of 2 payments with the following amounts: __first_token_amount_removed__ and __second_token_amount_removed__. In the end, a __MultiValue__ of 2 __EsdtTokenPayment__ is returned. + + +### Swap tokens fixed input + +```rust + pub type SwapTokensFixedInputResultType = EsdtTokenPayment; + + #[payable("*")] + #[endpoint(swapTokensFixedInput)] + fn swap_tokens_fixed_input( + &self, + token_out: TokenIdentifier, + amount_out_min: BigUint, + ) -> SwapTokensFixedInputResultType +``` + +This smart contract acts as an AMM based on the constant product formula `x * y = k`. +This means that swapping, when ignoring fees, would happen based on the following logic: + +Let's assume that: + +- __rI__ is the reserve of the input token (the one that the user paid) +- __rO__ is the reserve of the output token (the one that the user desires in exchange of the paid one) +- __aI__ is the amount of the input token +- __aO__ is the amount of the output token + +```math +rI * rO = k +(rI + aI) * (rO - aO) = k +``` + +From the two equations, we can safely state that + +```math +rI * rO = (rI + aI) * (rO - aO) +``` + +Where __aI__ would be known, and __aO__ would need to be calculated. + +Considering __f__ being the percent of total fee, the formula including fees is the following: + +```math +rI * rO = (rI + (1 - f) * aI) * (rO - aO) +``` + +The workflow of the endpoint is as follows: the users sends a payment with the tokens he wants to swap to the contract, along with 2 parameters (__token_out__ and __amount_out_min__). Based on the __token_out__ parameter, the swapping order is deducted, the variables are checked and then the contract performs the swap operation as described above. + +In the end, the user receives back his requested tokens, with one important mention. If one of the pair tokens is an underlying version of a locked token, then the output token is locked before it is sent to the user. Finally, the endpoints returns the __EsdtTokenPayment__, containing the output token payment. + + +### Swap tokens fixed output + +```rust + pub type SwapTokensFixedOutputResultType = + MultiValue2, EsdtTokenPayment>; + + #[payable("*")] + #[endpoint(swapTokensFixedOutput)] + fn swap_tokens_fixed_output( + &self, + token_out: TokenIdentifier, + amount_out: BigUint, + ) -> SwapTokensFixedOutputResultType +``` + +The flow is approximately the same as with the SwapFixedInput function, with the main difference that __aO__ is fixed and __aI__ is calculated using the same formulas. One other difference is that besides the desired tokens, the contract also sends back the __leftover__ tokens, in case there are any. The __leftover__ amount in this case is the difference between the __amount_in_max__ and the actual amount that was used to swap in order to get to the desired __amount_out__. +In the end, the endpoint returns a __MultiValue__ of 2 __EsdtTokenPayment__. + + +## Router SC + +The __Router SC__ serves as a convenient tool for efficiently managing and monitoring Pair contracts in a decentralized environment. It enables the deployer to easily keep track of the existing Pair contracts and offers a wide array of settings functions, that makes the management of the liquidity pools much more easier. + +Taking into consideration that this tutorial is intended for developers who wish to import more easily the DEX contracts into their own projects, we will concentrate on the only public endpoint that can be particularly beneficial for external projects, the `multiPairSwap` endpoint. + + +### Multi pair swap + +```rust + type SwapOperationType = + MultiValue4, ManagedBuffer, TokenIdentifier, BigUint>; + + #[payable("*")] + #[endpoint(multiPairSwap)] + fn multi_pair_swap(&self, swap_operations: MultiValueEncoded>) +``` + +The `multiPairSwap` endpoint allows users to swap two different tokens, that don't have a direct pool, in one transaction. It receives an array (of type __MultiValueEncoded__) of __SwapOperationType__ (which are basically a __MultiValue__ of 4 different parameters). The 4 parameters are (in this exact order): __pair_address__, __function__, __token_wanted__, __amount_wanted__. So, for each __SwapOperationType__, the flow is as follows: +- The endpoint checks if the __pair_address__ is indeed a pair contract, in order to be able to call the swap function. +- It then calls the specified __function__ (which can be of type __swap_tokens_fixed_input__ or __swap_tokens_fixed_output__), by sending the tokens received as a payment in the endpoint, in return of the __token_wanted__, with the specified __amount_wanted__. +- A __PaymentsVec__ is then created, consisting in the desired token (the last token from the __swap_operations__ list), along with all the remaining tokens that were not used during the swap operations. +- In case the entire flow works as intended, the __PaymentsVec__ is then sent back to the user. + +## Farm SC + +This base farm contract has the role of generating and distributing __MEX__ tokens to liquidity providers that choose to lock their LP tokens, thus increasing the ecosystem stability. On the xExchange, a variation of the __Farm__ contract is deployed, namely the __Farm with locked rewards__ contract, which heavily relies on the standard __Farm__ contract, the difference being that the generated rewards are locked (which also involves an additional energy computation step, according to the new xExchange economics model). + +Throughout the __Farm SC__ we will come across an optional variable, namely __opt_orig_caller__. When building an external SC on top of the xExchange farm contract, this argument must always be sent as __None__ (it is used by the other whitelisted xExchange contracts to claim rewards and compute energy for another user, other that the caller - in our case the external xExchange contract). With the new update of the __MEX__ economics model (where SCs are allowed to have energy), the account that now has and uses the Energy can be the external SC itself, which later computes any existing rewards for its users or applies any other custom logic (e.g. like the __Energy DAO SC__) to further distribute those rewards. + + +### Enter farm + +```rust + pub type EnterFarmResultType = DoubleMultiPayment; + + #[payable("*")] + #[endpoint(enterFarm)] + fn enter_farm_endpoint( + &self, + opt_orig_caller: OptionalValue, + ) -> EnterFarmResultType +``` + +This endpoint receives at least one payment: + +- The first payment has to be of type __farming_token_id__, and represents the actual token that is meant to be locked inside the Farm contract. +- The additional payments, if any, will be current farm positions and will be merged with the newly created tokens, in order to consolidate all previous positions with the current one. + +This endpoint will give back to the caller a Farm position as a result. This is a __MetaESDT__ that contains, in its attributes, information about the user input tokens and the current state of the contract when the user did enter. This information will be later used when trying to claim rewards or exit farm. + +### Claim rewards + +```rust + pub type ClaimRewardsResultType = DoubleMultiPayment; + + #[payable("*")] + #[endpoint(claimRewards)] + fn claim_rewards_endpoint( + &self, + opt_orig_caller: OptionalValue, + ) -> ClaimRewardsResultType +``` + +When a user makes a call to this endpoint, they must provide their current farm position (or positions). The endpoint will then use this position to compute the rewards that the user has earned. The rewards that are calculated will depend on each specific farm. Some farms may offer both base rewards and boosted rewards, with the latter being calculated only once every 7 epochs. Other farms may offer only base rewards. +In the end, the function will return two pieces of information: the updated farm position (which will now include the latest RPS information) and the amount of rewards that the user has earned. + + +### Exit farm + +```rust + pub type ExitFarmWithPartialPosResultType = + MultiValue3, EsdtTokenPayment, EsdtTokenPayment>; + + #[payable("*")] + #[endpoint(exitFarm)] + fn exit_farm_endpoint( + &self, + exit_amount: BigUint, + opt_orig_caller: OptionalValue, + ) -> ExitFarmWithPartialPosResultType +``` + +The `exitFarm` endpoint allows users to exit their position in the farm. It receives the entire farm position as a payment, and an __exit_amount__ as a parameter, which tells the function with how much tokens the user wants to exit the farm. This logic was implemented in order to be able to compute the complete farm position's boosted rewards, without losing any tokens. + +The flows is as follows: +- The user calls the endpoint with his entire farm position as a single payment and specifies which is the exit amount +- The endpoint first computes the boosted rewards, if any, and then it exits the farm with the specified amount +- The energy of the user is updated accordingly (or deleted if the user exited the farm with the entire position) +- Lastly, the user receives back the initial farming position (usually the LP tokens), the rewards, if any, and the remaining farm position, in case he did not exit the farm with the entire position + + +### Merge farm tokens + +```rust + #[payable("*")] + #[endpoint(mergeFarmTokens)] + fn merge_farm_tokens_endpoint( + &self, + opt_orig_caller: OptionalValue, + ) -> EsdtTokenPayment +``` + +The `mergeFarmTokens` endpoint allows users to send multiple farm positions and combine them into one aggregated position. One important aspect here is that in order to be able to merge the farm tokens, the user must have the energy claim progress up-to-date. + + +### Boosted rewards formula + +It's worth noting that while not a specific function, the boosted rewards formula is still an important concept to understand when participating in certain farms. This formula is used to maximize the potential boosted rewards that an account can receive. + +The formula takes into account several variables, including the amount of tokens that the user has staked in the farm (__user_farm_amount__), the total amount of tokens staked in the farm (__total_farm_amount__), the amount of energy that the user has (__user_energy_amount__), and the total amount of energy contributed to the farm (__total_energy__). It is important to mention that the weekly values are used. Additionally, certain boost factors are applied to further fine-tune the calculation of rewards. For example, some factors may overemphasize the importance of the user's energy contribution in the rewards calculation. + +By understanding this formula, an account holder can determine how much energy they need to have in order to maximize the rewards for their current farm position. Alternatively, they can determine how much energy they need to obtain in order to to achieve a certain level of boosted rewards. This knowledge can be especially valuable for those projects seeking to optimize their yield farming strategies. + +```rust + // computed user rewards = total_boosted_rewards * + // (energy_const * user_energy / total_energy + farm_const * user_farm / total_farm) / + // (energy_const + farm_const) + let boosted_rewards_by_energy = + &weekly_reward.amount * &factors.user_rewards_energy_const * energy_amount + / total_energy; + let boosted_rewards_by_tokens = + &weekly_reward.amount * &factors.user_rewards_farm_const * &self.user_farm_amount + / &farm_supply_for_week; + let constants_base = &factors.user_rewards_energy_const + &factors.user_rewards_farm_const; + let boosted_reward_amount = + (boosted_rewards_by_energy + boosted_rewards_by_tokens) / constants_base; + + // min between base rewards per week and computed rewards + let user_reward = cmp::min(max_rewards, boosted_reward_amount); + if user_reward > 0 { + sc.remaining_boosted_rewards_to_distribute(week) + .update(|amount| *amount -= &user_reward); + + user_rewards.push(EsdtTokenPayment::new( + weekly_reward.token_identifier, + 0, + user_reward, + )); + } +``` + + +## Proxy DEX SC + +This smart contract offers users with locked __MEX__ the possibility of interacting with the DEX contracts, for operations like adding liquidity or entering farms, as if they had __MEX__. + + +### Add liquidity proxy + +```rust + #[payable("*")] + #[endpoint(addLiquidityProxy)] + fn add_liquidity_proxy( + &self, + pair_address: ManagedAddress, + first_token_amount_min: BigUint, + second_token_amount_min: BigUint, + ) -> MultiValueEncoded +``` + +The `addLiquidityProxy` intermediates liquidity adding in a __Pair SC__ as follows: +- The user must send the tokens in the same order as they are in the Pair contract +- The user must configure the slippage as he would in the Pair contract + +The output payments of this endpoint consists not in the original LP token, but instead in a Wrapped LP token, along with any leftover tokens. The reason for wrapping the LP tokens is that if the user receives them directly, he would have had the possibility of removing the liquidity and thus unlocking his locked __MEX__. + + +### Remove liquidity proxy + + ```rust + #[payable("*")] + #[endpoint(removeLiquidityProxy)] + fn remove_liquidity_proxy( + &self, + pair_address: ManagedAddress, + first_token_amount_min: BigUint, + second_token_amount_min: BigUint, + ) -> MultiValueEncoded +``` + +The `removeLiquidityProxy` endpoint intermediates removing liquidity from a Pair contract as follows: the user sends Wrapped LP tokens and receives the first token and the locked __MEX__ tokens. The address and slippage are configurable as they would be for the __Pair SC__. + + +### Merge wrapped LP tokens + +```rust + #[payable("*")] + #[endpoint(mergeWrappedLpTokens)] + fn merge_wrapped_lp_tokens_endpoint(&self) -> EsdtTokenPayment +``` + +This function merges two or more positions of Wrapped LP tokens (LP positions obtained using locked __MEX__ instead of __MEX__ and this intermediary contract). The same logic as for __mergeWrappedFarmTokens__ is applied. + + +### Enter farm proxy + +```rust + #[payable("*")] + #[endpoint(enterFarmProxy)] + fn enter_farm_proxy_endpoint(&self, farm_address: ManagedAddress) -> EsdtTokenPayment +``` + +The process of entering a Farm contract is facilitated by the Enter Farm Proxy function. This involves the user sending Wrapped LP tokens and receiving Wrapped Farm tokens. The rationale behind using Wrapped Farm tokens is similar to that of Wrapped LP tokens. + +It should be noted that the original LP tokens and Farm tokens are kept in the smart contract, which creates Wrapped Tokens. These original tokens will be used by the smart contract to carry out actions on behalf of the user. + + +### Exit farm proxy + +```rust + pub type ExitFarmProxyResultType = + MultiValue3, EsdtTokenPayment, EsdtTokenPayment>; + + #[payable("*")] + #[endpoint(exitFarmProxy)] + fn exit_farm_proxy( + &self, + farm_address: ManagedAddress, + exit_amount: BigUint, + ) -> ExitFarmProxyResultType +``` + +The `exitFarmProxy` works exactly like its base contract counterpart, except it takes Wrapped Farm tokens as input. This includes the new __exit_amount__ logic, where the user sends his entire position and specifies the actual exit amount as an argument. The output of this endpoint consists of a __MultiValue__ of 3 __EsdtTokenPayment__, namely the __initial_proxy_farming_tokens__, the __reward_tokens__ and the __remaining_wrapped_tokens__, like with the base `exitFarm` endpoint. + + +### Claim rewards proxy + +```rust + pub type ClaimRewardsFarmProxyResultType = MultiValue2, EsdtTokenPayment>; + + #[payable("*")] + #[endpoint(claimRewardsProxy)] + fn claim_rewards_proxy( + &self, + farm_address: ManagedAddress, + ) -> ClaimRewardsFarmProxyResultType +``` + +As with the __exitFarm__ function, the `claimRewardsProxy` endpoint works in the exact same way as the base farm __claimRewards__ function, but instead it receives a payment of Wrapped Farm tokens. The output of this endpoint consists of a __MultiValue__ of 2 __EsdtTokenPayment__, namely the __new_wrapped_token__ and the __reward_tokens__. + + +### Merge wrapped farm tokens + +```rust + #[payable("*")] + #[endpoint(mergeWrappedFarmTokens)] + fn merge_wrapped_farm_tokens_endpoint(&self, farm_address: ManagedAddress) -> EsdtTokenPayment +``` + +This function merges two or more positions of Wrapped Farm (farm positions obtained using locked __MEX__ instead of __MEX__ and this intermediary contract). In order to merge two positions of this type, the contract uses merge endpoints for the underlying tokens like Farm tokens, locked __MEX__ tokens, Wrapped LP tokens and so on, and after that, the newly created Wrapped Farm token will just reference the newly created and merged underlying tokens. + +## Farm Staking SC + +This contract allows users to stake their tokens and/or LP tokens and earn rewards. It works in conjunction with the Farm Staking Proxy contract and offers the complete array of utility functions, from entering and exit the contract, to rewards handling and tokens merging. + +It is important to note that the following functions are related to the current implementation of the __Farm Staking SC__, that does not take into account the user's energy. In the future, a new energy-integrated __Farm Staking SC__ will be used. + + +### Stake farm + +```rust + #[payable("*")] + #[endpoint(stakeFarm)] + fn stake_farm_endpoint(&self) -> EsdtTokenPayment +``` + +Endpoint that allows an user to stake his tokens (different for each contract) in order to enter the staking farm. It receives the farming_token as a payment and it sends the farm_token back to the caller. + + + +### Farm staking claim rewards + +```rust + pub type ClaimRewardsResultType = MultiValue2, EsdtTokenPayment>; + + #[payable("*")] + #[endpoint(claimRewards)] + fn claim_rewards(&self) -> ClaimRewardsResultType +``` + +Endpoint that allows the caller to send his farm staking tokens and to receive the corresponding rewards. The sent farm staking tokens are burnt and new tokens are minted, in order to reset that user's position. The output result of this endpoint consists of a __MultiValue__ of 2 __EsdtTokenPayment__, namely the __new_token__ and the __reward_tokens__. + + +### Farm staking compound rewards + +```rust + #[payable("*")] + #[endpoint(compoundRewards)] + fn compound_rewards(&self) -> EsdtTokenPayment +``` + +Payable endpoint that allows the caller to harvest the rewards generated by the staking farm and reinvest them seamlessly, within a single endpoint. It burns the current farm tokens and computes the actual position with the rewards included. It returns an __EsdtTokenPayment__ with the new farm staking tokens. + + +### Unstake farm staking + +```rust + pub type ExitFarmResultType = MultiValue2, EsdtTokenPayment>; + + #[payable("*")] + #[endpoint(unstakeFarm)] + fn unstake_farm(&self) -> ExitFarmResultType +``` + +Endpoint that allows the user to unstake his farm staking tokens. It receives the __farm_token__ as a payment and it sends the __unbond_farming_token__ back to the caller. The __farm_tokens__ are burnt and the __unbond_farming_tokens__ are then minted through the `nft_create_tokens` function, which encodes the `UnbondSftAttributes` in the newly created tokens. Also, the calculated rewards, if any, are sent to the caller. The output result of this endpoint consists of a __MultiValue__ of 2 __EsdtTokenPayment__, namely the __new_token__ and the __reward_tokens__. + + +### Unbond farm staking + +```rust + #[payable("*")] + #[endpoint(unbondFarm)] + fn unbond_farm(&self) -> EsdtTokenPayment +``` + +Endpoint that allows the user to unbond his farming tokens. As previously stated, the `unstakeFarm` endpoint gives the user __unbond_farming_tokens__, that have the unbonding period encoded. The unbond function receives the __unbond_farming_tokens__ as a payment and decodes the unbonding period in order to check if the tokens can be unbonded. If the unbonding period has passed, the __unbond_farming_tokens__ are burnt and then the __farming_tokens__ are sent back to the caller. + + +### Merge farm staking tokens + +```rust + #[payable("*")] + #[endpoint(mergeFarmTokens)] + fn merge_farm_tokens_endpoint(&self) -> EsdtTokenPayment +``` + +A payable endpoint that allows the user to merge his __farm_staking_tokens__. It is also the method that is called by the proxy farm staking contract in order to give the user his combined position. In this case, a __MultiESDTTransfer__ is being send by the proxy contract with the __lp_farm_token__ and the __proxy_dual_yield_token__. The new __lp_farm_tokens__ are being minted and sent back to the proxy contract, which will then send the new __dual_yield_tokens__ to the user. + + +## Farm Staking Proxy SC + +This SC works in conjunction with the Farm Staking contract and offers the configuration means for the dual yield token, that takes care of the staking logic of the farm staking process. As a high level overview, we can underline the following steps: +- The user follows the usual steps to enter a simple farm: add liquidity + enter farm with the LP tokens +- He then sends the farming tokens to the farm staking proxy contract +- The proxy contract calculates the user's position and simulates a transfer on his behalf to the staking contract. By being whitelisted as a trustworthy address, the staking contract accepts the data as a simulated transfer +- The staking contract calculates the farming token (by quoting the LP contract) and sends the farm staking position to the proxy contract +- The proxy contract keeps the farming token and sends the dual yield token instead to the user +- The user can then use the dual yield token to claim his rewards or unstake his position + +For this walkthrough we will take a look at the main functions that you will use when implementing the __Farm Staking Proxy SC__. Again, as with the __Farm Staking SC__, this walkthrough uses the current implementation of the __Farm Staking Proxy SC__, that does not take into account the user's energy. When implementing these 2 DEX contracts be sure to check which is the latest version of the contracts. + + +### Stake farm proxy + +```rust + pub type StakeResult = EsdtTokenPayment; + + #[payable("*")] + #[endpoint(stakeFarmTokens)] + fn stake_farm_tokens(&self) -> StakeResult +``` + +The first endpoint in the farm staking workflow. It receives the __farming_token__ as a single or as a multiple payment. The endpoint calculates the position for each payment and burns the current __dual_yield_token__ for the corresponding nonce, if there is any. The workflow continues by quoting the LP contract of the correct token amount and then simulates a token transfer with that amount towards the farm staking contract. It will then receive the corresponding __farm_staking_token__ amount (amount that will remain inside the contract) and will send the user the corresponding __dual_yield_token__. +It is important to mention that only the proxy contract can simulate the token transfer, by being whitelisted inside the farm staking contract to do so. This means that any outside attempts to replicate this process will fail in the staking contract. +Another aspect that is worth mentioning is that the endpoint will try to merge the user's position. For that, it calls the merging function of the farm staking contract in order to give the user a combined position. + + +### Claim farm staking proxy rewards + +```rust + pub type ClaimDualYieldResult = MultiValueEncoded>; + + #[payable("*")] + #[endpoint(claimDualYield)] + fn claim_dual_yield(&self) -> ClaimDualYieldResult +``` + +For claiming rewards from the farm staking contract, the user has to send his __dual_yield_tokens__ to the proxy contract as a payment. Based on this payment, the proxy contract identifies the corresponding position for the user and burns those dual yield tokens. It then uses the staking farm tokens to claim the corresponding rewards. In the end, the proxy contract sends those claimed rewards to the user, along with a new, reset position for the __dual_yield_tokens__. +One thing to note here is that between claiming rewards in the farming contract and the staking contract, the balance of the LP token may vary. Because of that, the proxy contract first harvest the rewards from the farming contract with the initial known value and then requotes the LP contract to get the new LP ratio (that may or may not vary). It then harvest rewards with the new value. + + +### Unstake farm staking proxy + +```rust + pub type UnstakeResult = MultiValueEncoded>; + + #[payable("*")] + #[endpoint(unstakeFarmTokens)] + fn unstake_farm_tokens( + &self, + pair_first_token_min_amount: BigUint, + pair_second_token_min_amount: BigUint, + exit_amount: BigUint, + ) -> UnstakeResult +``` + +To unstake his current position, a user must send the desired amount of __dual_yield_tokens__ to the proxy contract. At this moment, the proxy contract knows, based on the sent __dual_yield_token__, both the __farm_token__ position and __staking_token__ position. The first step is for the proxy contract to withdraw the LP tokens from the farms and the liquidity from the pair contract. After that all the harvested rewards, the resulting __farming_tokens__ from removing the LP token and the unstake position of the staking token are all sent to the user. The unstaking process is ended with the burning of the __dual_yield_tokens__. +It is important to note that because of the user’s unstaked position, an unbonding period is not needed. + + +## Simple Lock SC + +The __Simple Lock SC__ facilitates the locking of tokens, useful for example when launching a new token/product, while also offering the means to unlock or to use them in other xExchange contracts (like the __Pair SC__ or the __Farm SC__). + + +### Lock tokens + +```rust + #[payable("*")] + #[endpoint(lockTokens)] + fn lock_tokens_endpoint( + &self, + unlock_epoch: u64, + opt_destination: OptionalValue, + ) -> EgldOrEsdtTokenPayment +``` + +This endpoint receives any type of token as a payment (including __EGLD__) and locks them until `unlock_epoch`, by minting MetaESDT LOCKED tokens on a 1:1 ratio. If unlock epoch has already passed, the original tokens are sent instead. In the end, it sends the LOCKED tokens (or original payment if current_epoch >= unlock_epoch) + +Arguments: +- unlock epoch - the epoch from which the LOCKED token holder may call the unlock endpoint +- opt_destination - OPTIONAL: destination address for the LOCKED tokens. Default is caller. + + +### Unlock tokens + +```rust + #[payable("*")] + #[endpoint(unlockTokens)] + fn unlock_tokens_endpoint( + &self, + opt_destination: OptionalValue, + ) -> EgldOrEsdtTokenPayment +``` + +Endpoint that unlocks tokens, previously locked with the `lockTokens` endpoint, so it receives the LOCKED tokens as the payment. If the unlocking period has passed, the function sends & returns the originally locked tokens. + +Arguments: +- opt_destination - OPTIONAL: destination address for the unlocked tokens + + +### Add liquidity locked tokens + +```rust + pub type AddLiquidityThroughProxyResultType = + MultiValue3, EsdtTokenPayment, EsdtTokenPayment>; + + #[payable("*")] + #[endpoint(addLiquidityLockedToken)] + fn add_liquidity_locked_token( + &self, + first_token_amount_min: BigUint, + second_token_amount_min: BigUint, + ) -> AddLiquidityThroughProxyResultType +``` + +As it name suggests, this endpoint allow users to use their locked tokens in order to provide liquidity as if they had the unlocked token. It will fail if a liquidity pool is not configured for the token pair. The endpoint can receive any type of payments pair from the following: `(LOCKED token, LOCKED token)` / `(LOCKED token, any token)` / `(any token, LOCKED token)`. + +Arguments: +- first_token_amount_min: forwarded to the LP pool, may not be zero. +- second_token_amount_min: forwarded to the LP pool, may not be zero. + +After the endpoint successfully provides liquidity in the __Pair SC__, it returns a __MultiValue__ of 3 __EsdtTokenPayment__: the refunded tokens from the first payment and from the second payment, as well as the LP_PROXY tokens, which can later be used to further interact with the LP pool through this SC. + + +### Remove liquidity locked tokens + +```rust + pub type RemoveLiquidityThroughProxyResultType = + MultiValue2, EsdtTokenPayment>; + + #[payable("*")] + #[endpoint(removeLiquidityLockedToken)] + fn remove_liquidity_locked_token( + &self, + first_token_amount_min: BigUint, + second_token_amount_min: BigUint, + ) -> RemoveLiquidityThroughProxyResultType +``` + +The counterpart of the add liquidity function, it removes liquidity previously added through this SC. One important aspect here is what kind of tokens will the user receive back. If the unlock_epoch has not passed for the original LOCKED tokens, he caller will receive locked tokens. Otherwise, they will receive the unlocked version. It receives a payment of LP_PROXY tokens. + +Arguments: +- first_token_amount_min: forwarded to the LP pool, may not be zero. +- second_token_amount_min: forwarded to the LP pool, may not be zero. + +In the end, the endpoint sends to the user and returns a __MultiValue__ of 2 __EsdtTokenPayment__, consisting of the __first_token__ original liquidity and the __second_token__ original liquidity, along with any accumulated rewards for each token (included in the sent amount). + + +### Enter farm locked tokens + +```rust + pub enum FarmType { + SimpleFarm, + FarmWithLockedRewards, + } + + pub type EnterFarmThroughProxyResultType = MultiValue2, EsdtTokenPayment>; + + #[payable("*")] + #[endpoint(enterFarmLockedToken)] + fn enter_farm_locked_token( + &self, + farm_type: FarmType, + ) -> EnterFarmThroughProxyResultType +``` + +Like with the __Pair SC__ interactions, this endpoint facilitates entering a farm with LOCKED tokens. User will choose if they want to enter a farm with normal rewards, or locked rewards. At this moment, in the case of the xExchange contracts, only farms with locked rewards are applicable. As with the normal farm contract, the user should provide not only the farming token (in our case the LP_PROXY tokens), but also any additional farm positions that he may have, in order to receive any accumulated reward. + +Arguments: +- farm_type - The farm type the user wishes to enter. + +The final output is a __MultiValue__ of 2 __EsdtTokenPayment__, consisting of the FARM_PROXY token, which can later be used to further interact with the specific farm, and any accumulated reward, if any. + + +### Exit farm locked tokens + +```rust + pub type ExitFarmThroughProxyResultType = MultiValue2, EsdtTokenPayment>; + + #[payable("*")] + #[endpoint(exitFarmLockedToken)] + fn exit_farm_locked_token( + &self, + exit_amount: BigUint, + ) -> ExitFarmThroughProxyResultType +``` + +Endpoint used to exit a farm previously entered through `enterFarmLockedToken`, so it receives a single payment consisting of __FARM_PROXY__ tokens. This payment should be the full farm position of the user, in order to also receive all the accumulated rewards. + +Arguments: +- exit_amount - The amount that the user intends to withdraw from the farm + +In the end, the function sends and returns a __MultiValue__ of 2 __EsdtTokenPayment__, consisting of the original farming tokens and the farm reward tokens, if any. + + +### Claim locked tokens farm rewards + +```rust + pub type FarmClaimRewardsThroughProxyResultType = + MultiValue2, EsdtTokenPayment>; + + #[payable("*")] + #[endpoint(farmClaimRewardsLockedToken)] + fn farm_claim_rewards_locked_token(&self) -> FarmClaimRewardsThroughProxyResultType +``` + +Another important public endpoint is `farmClaimRewardsLockedToken`, which claims the rewards from a previously entered farm. The __FARM_PROXY__ tokens received as a single payment are burned, and new ones are created. This is needed because every farm action changes the farm token nonce, to properly store the new token RPS. Finally, the output payments consist of a new __FARM_PROXY__ token, and the computed farm reward tokens. + + +## Energy Factory SC + +Before delving into the __Energy Factory SC__, it is important to have a clear understanding of the Energy concept on the xExchange. The introduction of the __MultiversX__ DEX brought a new utility token, __XMEX__, which is the new locked version of the __MEX__ token. __XMEX__ offers users the ability to control the locking period through a lock/unlock mechanism. By locking __MEX__ tokens for a certain period (1, 2, or 4 years), the account accumulates Energy, with the amount of Energy increasing the longer the tokens are locked. + +With the accumulated Energy, the account is eligible for various benefits, including collecting fees from swaps and __XMEX__ unlocks, Metabonding rewards, and most significantly, enhanced rewards for Farms and Metastaking. It is this Energy that is integral to the __Energy Factory SC__ and its functionality. + + +### Energy structure + +```rust + pub struct Energy { + amount: BigInt, + last_update_epoch: Epoch, + total_locked_tokens: BigUint, +} +``` + +The Energy refers to a struct that contains necessary data about the locked tokens held by an account, including the last update epoch and the actual computed amount of Energy. This struct is utilized in all the contracts that implement the Energy mechanism, including the __Farm SC__. + +In the farm contract, the rewards __claim_progress__ of the user is saved as a struct that comprises an Energy structure and the last claim week. This enables the contract to keep track of the amount of Energy each user has in each week and the duration of time that has elapsed since their last claim. + + +### Energy factory lock tokens + +```rust + #[payable("*")] + #[endpoint(lockTokens)] + fn lock_tokens_endpoint( + &self, + lock_epochs: Epoch, + opt_destination: OptionalValue, + ) -> EsdtTokenPayment +``` + +The `lockTokens` endpoint, as it name implies, locks a whitelisted token until a specified `unlock_epoch` and receive MetaESDT LOCKED tokens on a 1:1 ratio. Accepted input tokens: +- base asset token +- old factory token -> extends all periods to the provided option +- previously locked token -> extends period to the provided option + +As for the arguments, the only one that needs to be sent is the __lock_epochs__ variable, which refers to the number of epochs for which the tokens are locked. The caller may only choose an option from the available ones, options that can be seen by querying `getLockOptions`. The second argument is the optional __opt_destination__, which represents the destination address for the LOCKED tokens. In case of a __None__ argument value, the default is the caller, and this will probably be the case for most SC that will be built on top of the DEX contract (in order to have their logic inside the contract). But of course the external contract can still specify another destination. + + +### Energy factory unlock tokens + +```rust + #[payable("*")] + #[endpoint(unlockTokens)] + fn unlock_tokens_endpoint(&self) -> EsdtTokenPayment +``` + +The `unlockTokens` endpoint, as it name suggests, unlocks tokens, previously locked with the `lockTokens` endpoint. This function works only with unlockable tokens, and it also updates the energy of the user as well. In case the tokens locking period has not passed, they can be unlocked through the `unlockEarly` endpoint. + + +### Unlock early + +```rust + #[payable("*")] + #[endpoint(unlockEarly)] + fn unlock_early(&self) +``` + +Unlocks a locked token, with an unbonding period. This incures a penalty. The longer the remaining locking time, the bigger the penalty. Tokens can be unlocked through another SC after the unbond period has passed. This endpoint updates the user's energy as well. + + +### Reduce lock period + +```rust + #[payable("*")] + #[endpoint(reduceLockPeriod)] + fn reduce_lock_period(&self, new_lock_period: Epoch) -> EsdtTokenPayment +``` + +Reduce the locking period of a locked token. This incures a penalty. The longer the reduction, the bigger the penalty. The __new_lock_period__ parameter must be one of the available lock options and is used as the new lock duration of the tokens. The endpoint returns the __new_locked_tokens__ payment, containing the new unlock epoch. + + +### Migrate old locked tokens + +```rust + #[payable("*")] + #[endpoint(migrateOldTokens)] + fn migrate_old_tokens(&self) -> MultiValueEncoded +``` + +With the new __MEX__ economic model, a new locked __MEX__ token was introduced, that benefits from all these new improved features, like the ability to extend the locking period, in order to gain more energy, or the __unlockEarly__ functionality, that allows the user to get the unlock tokens any time he wants, by paying a fee. This endpoint does just that, it receives a __PaymentsVec__ of legacy locked tokens, and returns back the new locked tokens. An important aspect here is that the new locking period is not maintained at a 1:1 parity, but instead a 4x longer locking period is used for the new token. This was introduced in order to avoid users gaming the system, by migrating and immediately unlocking the new locked __MEX__. + + +### Energy factory merge locked tokens + +```rust + #[payable("*")] + #[endpoint(mergeTokens)] + fn merge_tokens_endpoint( + &self, + opt_original_caller: OptionalValue, + ) -> EsdtTokenPayment +``` + +This endpoint receives a __PaymentsVec__ of locked tokens, which then merges into one locked token payment, and update the user's energy accordingly. As always, the __original_caller__ optional argument should be ignored (as it only accepts values from whitelisted addresses). + + +## Token Unstake SC + +The __Energy Factory SC__ is one of the most used SC on the __MultiversX Network__. In order to keep it as simple as possible, the entire unlock/unbond __XMEX__ logic was designed as a different contract, namely the __Token Unstake SC__. Besides unbonding tokens, this contract offers the possibility to cancel the unbond process at any time, through a dedicated endpoint. + + +### Claim unlocked tokens + +```rust + #[endpoint(claimUnlockedTokens)] + fn claim_unlocked_tokens(&self) -> MultiValueEncoded +``` + +This endpoint enables the user to initiate the unbonding process and receive their unlocked tokens. In the background, a record of the tokens that are eligible for unlocking (including the unbonding epoch) is maintained. When the user requests to claim their tokens, the system checks the list and only sends the tokens that are unlockable. However, before the tokens are sent, a penalty fee is applied (fee which is computed based on the lock duration of those newly unlocked tokens). + + +### Cancel unbond + +```rust + #[endpoint(cancelUnbond)] + fn cancel_unbond(&self) -> MultiValueEncoded +``` + +The purpose of this endpoint is to enable users to cancel the unbonding process without incurring any fees. It is important to note that it is not possible to specify a particular token for which the unbonding process is to be reverted. For example, if a user wants to unbond two locked tokens and the unbonding period has expired only for one of them, they can either claim the unlocked token using the `claimUnlockedTokens` endpoint before cancelling the unbonding process to receive the second token, or they can use the `cancelUnbond` endpoint to receive both tokens directly without any penalty. + +When a user cancels the unbonding process of a token, the __Energy Factory SC__ calculates the user's energy using the newly regained tokens and updates it accordingly. + + +## Fees Collector SC + +The __Fees Collector SC__ plays a critical role in the new Energy mechanism and serves as a central contract that collects and distributes fees. These fees are collected in various tokens, including locked tokens, and they come from both trading and energy removal taxes. As the fee tokens accumulate, they are grouped by weeks to ensure that they are later collected by users once every seven epochs. This was necessary to avoid distributing amounts that are too small. + +The collected fees are distributed to users who have locked their __MEX__ tokens and have accumulated energy as a result. The users are entitled to a multitude of benefits, including boosted rewards for Farms & Metastaking and collecting fees gathered from swaps and __XMEX__ unlocks. By gathering and distributing these fees, the __Fees Collector SC__ plays a crucial role in maintaining the new Energy mechanism relevant and ensuring that users are rewarded for their participation. + + +### Fees collector claim rewards + +```rust + #[endpoint(claimRewards)] + fn claim_rewards(&self) -> PaymentsVec +``` + +The `claimRewards` endpoint in the __Fees Collector SC__ allows users to claim their weekly rewards for the total energy that they've accumulated. The function calculates the user's rewards using a __ClaimProgress__ struct and a global weekly energy amount, and returns a PaymentsVec that includes all rewarded tokens, including locked __MEX__ tokens. It is important to note that the contract keeps rewards for as long as 4 weeks, so the tokens should be claimed at least once during each interval. + + +## Locked Token Wrapper SC + +The __Locked Token Wrapper SC__ is used to distribute locked tokens through a wrapping mechanism, allowing anyone to wrap a token, but only user accounts to unwrap it. However, certain limitations are in place to ensure the security and integrity of the system. Users must first be granted a transfer role to send wrapped tokens, which is restricted to whitelisted addresses to prevent unauthorized users or projects from compromising the ecosystem. The contract is designed to provide a community-facilitating mechanism, particularly for projects building on top of the xExchange. Wrapping and unwrapping the locked token also updates the account's Energy, and this should be taken into account by projects using the contract. + +Key aspects: +- Anyone (user or SC) can wrap a token +- Only user accounts can unwrap the token (SCs are limited only to wrapping the token) +- The wrapped token can be sent only by whitelisted addresses +- Wrapping & unwrapping the locked token also updates the account's Energy + + +### Wrap locked token + +```rust + #[payable("*")] + #[endpoint(wrapLockedToken)] + fn wrap_locked_token_endpoint(&self) -> EsdtTokenPayment +``` + +This function is designed to facilitate the wrapping of locked tokens. When called, the function receives a single payment of locked tokens, which are then deducted from the account's energy. The function then mints a new wrapped locked token and sends it to the caller. + + +### Unwrap locked token + +```rust + #[payable("*")] + #[endpoint(unwrapLockedToken)] + fn unwrap_locked_token_endpoint(&self) -> EsdtTokenPayment +``` + +This endpoint is responsible for unwrapping locked tokens that were previously minted using the `wrapLockedToken` endpoint. Upon calling this endpoint, the contract will burn the wrapped tokens, mint the locked tokens, and add back the correct amount of energy to the user's account. It's worth noting that only user accounts are authorized to call this endpoint, as smart contracts are prohibited from unwrapping tokens for security reasons explained previously. + + +## **Closing thoughts and next steps** + +As you've learned from this walkthrough, integrating DEX contracts can be a complex but essential step in building a decentralized exchange platform. By understanding the key contracts and public endpoints, you can create a platform that is secure, reliable, and user-friendly. As with any development project, it's important to thoroughly test your code and perform due diligence to ensure that your platform is secure and reliable. Additionally, as the blockchain ecosystem is constantly evolving, it's important to stay up-to-date on the latest changes and developments of the DEX contracts in order to ensure that your platform remains competitive and relevant. With the knowledge and skills you've gained from this walkthrough, you are well on your way to building a successful and innovative Web3 application. Good luck with your project! + +--- + +### EGLD transfers (move balance transactions) + +## Formula + +For EGLD transfers, the **actual gas cost** of processing is easy to determine precisely, since it only contains the **value movement and data handling** component. The **gas limit** should be set to the **actual gas cost**, according to the previously depicted formula: + +``` +tx.gasLimit = + networkConfig.erd_min_gas_limit + + networkConfig.erd_gas_per_data_byte * lengthOf(tx.data) +``` + + +## Examples + +Given: + +``` +networkConfig.erd_min_gas_limit is 50000 +networkConfig.erd_gas_per_data_byte is 1500 +networkConfig.erd_min_gas_price is 1000000000 + +tx1.data = "" +tx1.gasPrice = networkConfig.erd_min_gas_price + +tx2.data = "Hello world!" +tx2.gasPrice = networkConfig.erd_min_gas_price +``` + +Then: + +``` +tx1.gasLimit = 50000 + +tx2.gasLimit + = 50000 + 1500 * len("Hello world!") + = 68000 +``` + +Furthermore, the fee would be as follows: + +``` +fee(tx1) + = tx1.gasLimit * tx1.gasPrice + = 50000 * 1000000000 + = 50000000000000 atoms of EGLD + = 0.00005 EGLD + +fee(tx2) + = tx2.gasLimit * tx2.gasPrice + = 68000 * 1000000000 + = 68000000000000 atoms of EGLD + = 0.000068 EGLD +``` + +--- + +### Elasticindexer service + +## Overview + +:::tip +This feature will work starting from `rc/v1.6.0` version of the node +::: + +A MultiversX observer node can send messages over `WebSocket` to an elastic indexer service, which will process and index the data in an Elasticsearch database. + +The GitHub repository for the `elasticindexer` service can be found [here](https://github.com/multiversx/mx-chain-es-indexer-go/tree/rc/v1.6.0). + + +## Architectural Overview + +The observer node in the network will be connected to `elasticindexer` service. + +:::important +Set up one observer for each shard in order to handle all the data in the chain. +::: + +![img](/technology/indexer.png) + +In the figure above: +- The observer nodes will send WebSocket messages to the `elasticindexer` service. +- The `elasticindexer` service will receive and process the WebSocket messages and index data in an Elasticsearch cluster. + + +## Set up observer and elasticindexer + + +### Observer Client + +On the observer side, there is a WebSocket host that will send messages to the elasticindexer service. + +In the observer node's configuration directory, `external.toml` config file can be configured +to enable host driver config. The config file can be found +[here](https://github.com/multiversx/mx-chain-go/blob/rc/v1.6.0/cmd/node/config/external.toml). + +The corresponding config section for enabling the driver: + +```toml +[[HostDriversConfig]] + # This flag shall only be used for observer nodes + Enabled = true + # This flag will start the WebSocket connector as server or client (can be "client" or "server") + Mode = "client" + # URL for the WebSocket client/server connection + # This value represents the IP address and port number that the WebSocket client or server will use to establish a connection. + URL = "127.0.0.1:22111" + # After a message will be sent it will wait for an ack message if this flag is enabled + WithAcknowledge = true + # The duration in seconds to wait for an acknowledgment message, after this time passes an error will be returned + AcknowledgeTimeoutInSec = 60 + # Possible values: json, gogo protobuf. Should be compatible with mx-chain-es-indexer-go config + MarshallerType = "json" + # The number of seconds when the client will try again to send the data + RetryDurationInSec = 5 + # Sets if, in case of data payload processing error, we should block or not the advancement to the next processing event. Set this to true if you wish the node to stop processing blocks if the client/server encounters errors while processing requests. + BlockingAckOnError = true + # Set to true to drop messages if there is no active WebSocket connection to send to. + DropMessagesIfNoConnection = false +``` + + +### Elasticindexer service + +In the `elasticindexer` configuration directory (`cmd/elasticindexer/config`), there is the `prefs.toml` +file that can be used to configure the service. + +The `config.web-socket` section has to be aligned with the one from observer node: + +```toml +[config] + disabled-indices = [] + [config.web-socket] + # URL for the WebSocket client/server connection + # This value represents the IP address and port number that the WebSocket client or server will use to establish a connection. + url = "localhost:22111" + # This flag describes the mode to start the WebSocket connector. Can be "client" or "server" + mode = "server" + # Possible values: json, gogo protobuf. Should be compatible with mx-chain-node outport driver config + data-marshaller-type = "json" + # Retry duration (receive/send ack signal) in seconds + retry-duration-in-seconds = 5 + # Signals if in case of data payload processing error, we should send the ack signal or not + blocking-ack-on-error = true + # After a message will be sent it will wait for an ack message if this flag is enabled + with-acknowledge = true + # The duration in seconds to wait for an acknowledgment message, after this time passes an error will be returned + acknowledge-timeout-in-seconds = 50 +``` + +The corresponding config section for the Elasticsearch section: + +```toml +[config.elastic-cluster] + use-kibana = false + url = "http://localhost:9200" + username = "" + password = "" + bulk-request-max-size-in-bytes = 4194304 # 4MB +``` + +For more details on `elasticindexer` service setup, please follow the **Install** and **Launching** +sections from [README](https://github.com/multiversx/mx-chain-es-indexer-go) in the repository. + +--- + +### Elasticsearch + +## Overview + +A MultiversX node can enable the indexing within an Elasticsearch instance. Indexed data will serve as historical data source +that can be used as it is for searching purposes or to serve a front-end application. + +:::tip +Due to the possible high data volume, it's not recommended to use validators as nodes to index in Elasticsearch from. +Our implementation uses a concept of a queue and makes sure that everything is being processed. Consensus and synchronization mechanisms can have delays because of the indexing. +::: + + +## Public instance + +We generally recommend setting up **your own indexing nodes (observers) and Elasticsearch instance**. However, if you would like to test the Elasticsearch integration or clone a cluster (see below), you can use the public MultiversX Elasticsearch instances. Please note that they are subject to rate limiting: + +- **Mainnet:** https://index.multiversx.com - 5 requests / IP / second +- **Devnet:** https://devnet-index.multiversx.com - 5 requests / IP / second +- **Testnet:** https://testnet-index.multiversx.com - 5 requests / IP / second + + +## Setup + + +### Option 1 + +Set up four observers, each corresponding to a shard, with the `WebSocketHost` enabled, and create an instance of the `elasticindexer` service. +Follow the instructions provided on [this](/sdk-and-tools/indexer) page to accomplish this. + + +### Option 2 + +:::warning +We plan to remove this option in the `rc/v1.7.0` release. +::: + +In order to set up an observer that indexes in Elasticsearch, one has to update the `external.toml` file from the node's +configuration directory. A minimum configuration would have `Enabled` set to `true` and the rest of the fields updated +accordingly (`URL`, `Username`, `Password`). + +An example of a configuration is: + +``` +# ElasticSearchConnector defines settings related to ElasticSearch such as login information or URL +[ElasticSearchConnector] + ## We do not recommend to activate this indexer on a validator node since + #the node might loose rating (even facing penalties) due to the fact that + #the indexer is called synchronously and might block due to external causes. + #Strongly suggested to activate this on a regular observer node. + Enabled = true + IndexerCacheSize = 0 + BulkRequestMaxSizeInBytes = 4194304 # 4MB + URL = "http://localhost:9200" + UseKibana = false + Username = "elastic-username" + Password = "elastic-password" + # EnabledIndexes represents a slice of indexes that will be enabled for indexing. Full list is: + # ["rating", "transactions", "blocks", "validators", "miniblocks", "rounds", "accounts", "accountshistory", "receipts", "scresults", "accountsesdt", "accountsesdthistory", "epochinfo", "scdeploys", "tokens", "tags", "logs", "delegators", "operations"] + EnabledIndexes = ["rating", "transactions", "blocks", "validators", "miniblocks", "rounds", "accounts", "accountshistory", "receipts", "scresults", "accountsesdt", "accountsesdthistory", "epochinfo", "scdeploys", "tokens", "tags", "logs", "delegators", "operations"] +``` + +`Kibana` can be used for visualizing Elastic Data. Kikana's path must be `_plugin/kibana/api` (as seen in AWS managed instances). + +`EnabledIndexes` array specifies the indices that will be populated. + + +### Proxy support + +There are some endpoints in elrond-proxy that rely on an Elasticsearch instance. They can be found [here](/sdk-and-tools/proxy#dependency-on-elasticsearch). + + +## Multi-shards + +In order to have the history of the entire network, one has to enable elastic indexing for a node in each shard (0, 1, 2 and metachain). +Some features that ensure data validity rely on the fact that a node of each shard indexes in the database. For example, the status +of a cross-shard transaction is decided on the destination shard. + + +## Elasticsearch cluster system requirements + +The Elasticsearch cluster can be installed on multiple machines (we recommend a setup with more nodes in a cluster) or on a single one. + +In case of a single machine, our recommendation is as follows: + +- 12 x CPU +- 32 GB RAM +- Disk space that can grow up to 3 TB +- 100 Mbit/s always-on Internet connection + + +## Clone an Elasticsearch cluster + +In order to have all the information about the MultiversX chain in an Elasticsearch cluster (from genesis to current time) one has to copy all the data with a specific tool from an Elasticsearch cluster to another. +To get more information how to do this use the documentation from this [repository](https://github.com/multiversx/mx-chain-tools-go/tree/main/elasticreindexer). + + +## Elasticsearch indices + +An observing-squad with the elastic indexer enabled will save data in different indices. This data is used for multiple use cases. An example is to fetch all the +transactions that belong to an address or to display all the address sorted based on the EGLD balances. + +Each entry in an Elasticsearch index will have a format similar to this: + +``` +{ + "_id": "..." + "_source": { + ... + } +} +``` + +| Name | Description | +|----------------------------------------------------------------------------|-------------------------------------------------------------------------------| +| [transactions](/sdk-and-tools/indices/es-index-transactions) | Contains all transactions. | +| [blocks](/sdk-and-tools/indices/es-index-blocks) | Contains all executed blocks. | +| [validators](/sdk-and-tools/indices/es-index-validators) | Contains the public keys of the validators grouped by epoch and shard. | +| [rating](/sdk-and-tools/indices/es-index-rating) | Contains the validators' rating for every epoch. | +| [miniblocks](/sdk-and-tools/indices/es-index-miniblocks) | Contains all executed minblocks. | +| [rounds](/sdk-and-tools/indices/es-index-rounds) | Contains details of each round that has passed. | +| [accounts](/sdk-and-tools/indices/es-index-accounts) | Contains the addresses' balances and the timestamp when they were modified. | +| [accountshistory](/sdk-and-tools/indices/es-index-accountshistory) | Contains historical information about the address balances. | +| [receipts](/sdk-and-tools/indices/es-index-receipts) | Contains all generated receipts. | +| [scresults](/sdk-and-tools/indices/es-index-scresults) | Contains all generated smart contract results. | +| [accountsesdt](/sdk-and-tools/indices/es-index-accountsesdt) | Contains the addresses' ESDT balances. | +| [accountsesdthistory](/sdk-and-tools/indices/es-index-accountsesdthistory) | Contains historical information about the address ESDT balances. | +| [epochinfo](/sdk-and-tools/indices/es-index-epochinfo) | Contains the accumulated fees and the developer fees grouped by epochs. | +| [scdeploys](/sdk-and-tools/indices/es-index-scdeploys) | Contains details about all the deployed smart contracts. | +| [tokens](/sdk-and-tools/indices/es-index-tokens) | Contains all created ESDT tokens. | +| [tags](/sdk-and-tools/indices/es-index-tags) | Contains the NFTs' tags. | +| [logs](/sdk-and-tools/indices/es-index-logs) | Contains all the logs generated by transactions and smart contract results. | +| [events](/sdk-and-tools/indices/es-index-events) | Contains all the events generated by transactions and smart contract results. | +| [delegators](/sdk-and-tools/indices/es-index-delegators) | Contains details about all the delegators. | +| [operations](/sdk-and-tools/indices/es-index-operations) | Contains all transactions and smart contract results. | + + +## Troubleshooting + +- [fix an index with a wrong mapping](/sdk-and-tools/es-index-wrong-mapping) + +--- + +### Energy DAO SC tutorial + +## **Introduction** + +This tutorial will provide an in-depth analysis of the Energy DAO SC template, diving deeper into the concept of Energy and how a smart contract can use it to provide utility for users. Furthermore, while going through the various features of the contract, we will underline different aspects about how you can modify the template, in order to best suit your requirements. + +While being in a way a variation of the auto-farm SC, it was designed as a completely independent contract in the mx-exchange-tools repo. It can be cloned directly, without the need to import any other contract. The only external dependency is the xExchange suite of contracts, that are referenced through a Github commit hash from the latest version of that repo. + +:::important +The Energy DAO template SC can be found at the following address: +https://github.com/multiversx/mx-exchange-tools-sc +::: + + +## **Prerequisites** + +This tutorial requires some basic knowledge regarding SC development on MultiversX, so in case this is your first tutorial, it is recommended that you follow some more basic SC tutorials first, like the Crowdfunding SC or the Staking SC. + + +## **Design** + +So what exactly is an Energy DAO? + +Let's first take a look at the concept of Energy. With the launch of the new MultiversX DEX, a new utility token was introduced. XMEX is the locked counterpart of the MEX token, and it allows users to control the locking period, through a lock / unlock mechanism. The MEX token can be locked for a predefined period of 1, 2 or 4 years, and the more time it is locked, the more Energy that account has. With that Energy, the account is then entitled to a multitude of benefits, including collecting fees gathered from swaps & XMEX unlocks, Metabonding rewards and most importantly, boosted rewards for Farms & Metastaking. + +And this is where the Energy DAO contract comes in. With the newly introduced feature that allows Energy for contracts, we can now deploy a SC that allows users to use their assets to farm & stake tokens on xExchange, while also enjoying the boosted rewards, without having any Energy. It does it by allowing users to deposit their tokens in the Energy DAO contract, for staking purposes, while the contract receives and locks MEX tokens in order to gather energy that benefits all the users. + +:::important +In order to also benefit the ecosystem, the entire process of having Energy for contracts comes with a few mentions. First of all, XMEX cannot be transferred unless that account is whitelisted by the __Energy Factory__ contract. This means the Energy of a SC must come from new MEX tokens that are locked by the contract itself. Also, in order to send the rewards generated in XMEX to the users, those tokens need to be wrapped (which means they lose their Energy property). By being wrapped, the tokens can then be transferred, but they can be unwrapped (in order to benefit again from the Energy mechanism) only by user accounts, and not other SCs. +::: + + +## **Contract structure** + +The contract acts like a wrapper over the xExchange contracts, with different approaches for each of them. + +**Key aspects** + +- The Energy DAO integrates multiple DEX contracts, including __Farms__, __Metastaking__ (Farm Staking), __Fees Collector__, __Energy Factory__, as well as other smaller utility contracts. +- The SC always keeps one aggregated position for each feature (__Farms__ and __Metastaking__), and computes rewards using a rewards-per-share algorithm. +- Each user position is represented by specific tokens issued by the Energy DAO SC. There are tokens for both Farm & Metastaking current positions, as well as tokens for unbonding positions. +- The tokens are storing different metadata according to the user position, including the position's __rps__. +- The __Farms__ integration covers all 3 interaction points of the farm contract, including __enter_farm__, __exit_farm__ (with a base farm dependent unbonding period) and __claim_rewards__ as well, which aggregates all rewards and distributes them using an internal __rps__ computation. +- The __Metastaking__ integration resembles pretty much with the __Farms__ integration, with a few differences, including a double __rps__ computation, for each reward token, as well as a different unbonding implementation, in line with the __Metastaking__ SC logic. +- This SC template keeps the rewards from the __Fees Collector__ contract as rewards for providind Energy. Also, while entering the SC and claiming rewards are penalty free, a fee of __x%__ is imposed on every user exit action (the fee percentage is subject to change for each project individually). + +:::important +During the entire SC implementation, every time a DEX contract is called and the respective endpoins require the opt_original_caller argument, the value OptionalValue::None is passed, as we want all the benefits of the integration to be sent to Energy DAO contract. Later, the contract can manage how these rewards are computed and further distributed. +::: + + +## **Technical implementation** + +In the following section we will go through the main interest points of the template implementation. + + +## Init and Cargo.toml + +The `init` method of the Energy DAO smart contract is quite simple, as it only sets up different configs of the contract. + +```rust + #[init] + fn init( + &self, + energy_factory_address: ManagedAddress, + fees_collector_sc_address: ManagedAddress, + locked_token_wrapper_sc_address: ManagedAddress, + exit_penalty_percent: u64, + ) { + self.require_sc_address(&energy_factory_address); + self.require_sc_address(&fees_collector_sc_address); + self.require_sc_address(&locked_token_wrapper_sc_address); + + self.energy_factory_address() + .set_if_empty(energy_factory_address); + self.fees_collector_sc_address() + .set_if_empty(fees_collector_sc_address); + self.locked_token_wrapper_sc_address() + .set_if_empty(locked_token_wrapper_sc_address); + self.set_exit_penalty_percent(exit_penalty_percent); + + let caller = self.blockchain().get_caller(); + self.add_permissions(caller, Permissions::OWNER); + } +``` + +Now let's take a look at the general Cargo.toml file of the rust SC. Below you will see only a part of the entire Cargo.toml, for demonstration purposes. Other that the usual lines found in other SCs, you can see that xExchange dependencies are also declared. The particularity here is that they reference a particular Github commit hash of the DEX SCs, which you must be sure that it is always up-to-date with the last version of the DEX, deployed on the mainnet. + +```rust +[package] +name = "energy-dao" +version = "0.0.0" +authors = ["you"] +edition = "2021" +publish = false + +[lib] +path = "src/lib.rs" +[dependencies.multiversx-sc] +version = "0.39.4" +features = ["esdt-token-payment-legacy-decode"] + +[dependencies.multiversx-sc-modules] +version = "0.39.4" + +[dependencies.farm] +git = "https://github.com/multiversx/mx-exchange-sc" +rev = "8812ab8" + +[dependencies.farm-with-locked-rewards] +git = "https://github.com/multiversx/mx-exchange-sc" +rev = "8812ab8" + +[dependencies.farm-staking] +git = "https://github.com/multiversx/mx-exchange-sc" +rev = "8812ab8" + +[dependencies.farm-staking-proxy] +git = "https://github.com/multiversx/mx-exchange-sc" +rev = "8812ab8" + +[dependencies.pair] +git = "https://github.com/multiversx/mx-exchange-sc" +rev = "8812ab8" + +[dependencies.energy-factory] +git = "https://github.com/multiversx/mx-exchange-sc" +rev = "8812ab8" + +[dependencies.fees-collector] +git = "https://github.com/multiversx/mx-exchange-sc" +rev = "8812ab8" + +[dependencies.locked-token-wrapper] +git = "https://github.com/multiversx/mx-exchange-sc" +rev = "8812ab8" + +[dependencies.simple-lock] +git = "https://github.com/multiversx/mx-exchange-sc" +rev = "8812ab8" + +[dependencies.common_structs] +git = "https://github.com/multiversx/mx-exchange-sc" +rev = "8812ab8" + +[dependencies.mergeable] +git = "https://github.com/multiversx/mx-exchange-sc" +rev = "8812ab8" + +[dev-dependencies] +num-bigint = "0.4.2" + +[dev-dependencies.multiversx-sc-scenario] +version = "0.39.4" +``` + +:::important +For testing purposes, we recommend that you update the references in the Cargo.toml file to a local source, like the one below. This way, you can still do step-by-step debugging. +```rust +[dependencies.pair] +path = "../../mx-exchange-sc/dex/pair" +``` +::: + + +## Energy DAO config + +In the __EnergyDAOConfigModule__ you can find almost all the configs and general utilities of this SC. + +**Token issuance** + +There are 4 tokens issued by this Energy DAO SC template, 2 for wrapping the user positions inside the contract (for farms & metastaking), and 2 for unbonding. Let's look at the how we issue one of these tokens. The mx-sdk framework has a storage mapper that is specialized in non fungible token management, namely the __NonFungibleTokenMapper__. A payment of __0.05 EGLD__ is needed to issue a token on the MultiversX Network. + +```rust + #[only_owner] + #[payable("EGLD")] + #[endpoint(registerWrappedFarmToken)] + fn register_wrapped_farm_token( + &self, + token_display_name: ManagedBuffer, + token_ticker: ManagedBuffer, + num_decimals: usize, + ) { + let payment_amount = self.call_value().egld_value(); + self.wrapped_farm_token().issue_and_set_all_roles( + EsdtTokenType::Meta, + payment_amount, + token_display_name, + token_ticker, + num_decimals, + None, + ); + } +``` +:::important +You can find more details about the __NonFungibleTokenMapper__ here: +https://docs.multiversx.com/developers/developer-reference/storage-mappers/#nonfungibletokenmapper +::: + +**Farms & Metastaking addresses management** + +The template SC stores the data for each farm or farm staking in a __SingleValueMapper__, having the address of that contract as the key for the storage mapper. For each __Farm__ or __Metastaking__ address, we save a __FarmState__ or a __MetastakingState__ respectively, each with its own specific variables. Please observe the fact that these management endpoints do not have the `#[only_owner]` adnotation, but instead restricts the possibility of being called in a custom way, so that it allows both the owner and some designated admins to manage these settings. For this template SC, we only allow the owner to add new farms, to underline the importance of having trustworthy admins (but this can be changed by updating only one line of code). + +```rust + #[derive(TypeAbi, TopEncode, TopDecode, Debug)] + pub struct FarmState { + pub farm_staked_value: BigUint, + pub farm_token_nonce: Nonce, + pub reward_token_nonce: Nonce, + pub farm_unstaked_value: BigUint, + pub reward_reserve: BigUint, + pub farm_rps: BigUint, + } + + #[endpoint(addFarms)] + fn add_farms(&self, farms: MultiValueEncoded) { + self.require_caller_has_owner_permissions(); + for farm_addr in farms { + let farm_state_mapper = self.farm_state(&farm_addr); + require!(farm_state_mapper.is_empty(), ERROR_FARM_ALREADY_DEFINED); + self.require_sc_address(&farm_addr); + + let farm_state = FarmState { + farm_staked_value: BigUint::zero(), + farm_token_nonce: 0u64, + reward_token_nonce: 0u64, + farm_unstaked_value: BigUint::zero(), + reward_reserve: BigUint::zero(), + farm_rps: BigUint::zero(), + }; + farm_state_mapper.set(farm_state); + } + } + + #[storage_mapper("farmState")] + fn farm_state(&self, farm_address: &ManagedAddress) -> SingleValueMapper>; +``` + + +## Farm integration + +The Energy DAO __Farm__ integration refers to the following workflow: User A provides a farming position (LP token) and the DAO SC enters the DEX farm contract. Then a second user B does the same thing, at which moment the DAO contract enters with both the current position and user B's position, always maintaining an aggregated farm position. The users positions are kept using a new __WrappedFarmToken__, issued by the Energy DAO SC. As new rewards are accumulated, they are stored in the contract and a __reward_per_share__ computation is saved as the rewards pool increases. The __WrappedFarmToken__ contains data about the __rps__ computed at the moment when the user entered the SC, and with that token, the user can claim his corresponding rewards. Because the rewards are given in XMEX, they are always merged as they are accumulated, and when they are sent to the users, they are first wrapped, in order to be transferable (Wrapped XMEX can only be unwrapped by user accounts). In the end, the user can choose to exit the Energy DAO SC, and after an unbonding period that must pass, a fee is applied on the farming position, before the user receives his tokens. + +This template contract splits the __Farm__ integration in 2 different files, for better readability. One with the actual user interactions (the endpoints), where all the custom computation are done, and another one with the more generic actions regarding the DEX farm contract integration and any other general functions needed on this part. + + +### Farm actions + +Here we have a code snippet, that does the actual interaction with the farm contract. We use a __farm_proxy__ imported from the DEX reference declared in the Cargo.toml file, proxy that receives the address of the corresponding farm. We then call the desired endpoint on the farm contract (in our case __enter_farm_endpoint()__) using a multi_token_transfer of a __PaymentsVec__ received as an argument. Under the hood, this is a __ManagedVec__ of __ESDTTokenPayments__ usually consisting in two payments, the first one the being the position with which the user wants to enter the contract, and the second one the rest of the contract's aggregated position. Finally, the function returns an __EnterFarmResultType__ (type imported from the farm contract), which represents a __MultiValue2__ of __ESDTTokenPayments__, the first payment representing the new aggregated farm position, and the second one the boosted_rewards, if any. + +```rust + fn call_enter_farm( + &self, + farm_address: ManagedAddress, + farming_tokens: PaymentsVec, + ) -> EnterFarmResultType { + self.farm_proxy(farm_address) + .enter_farm_endpoint(OptionalValue::::None) + .with_multi_token_transfer(farming_tokens) + .execute_on_dest_context() + } + + #[proxy] + fn farm_proxy(&self, sc_address: ManagedAddress) -> farm_with_locked_rewards::Proxy; +``` + +:::important +A very important aspect here is that, with the current protocol design, in order to work as intended, the EnergyDAO SC must be deployed on the same shard as the DEX, in order to use intrashard contract calls and have synchronous, realtime SC results from the xExchange contracts. +Later on, with the launch of the AsyncV2 functionality, these kinds of contracts will be able to be deployed in other shards as well, as the protocol will support multiple asyncCalls. +::: + +The other farm_proxy calls (__exit_farm__ & __claim_rewards__) follow the same logic as the one presented above, using the proper parameters for each case individually. + +Going further, we can look at the `update_farm_after_claim` function (along with some descriptive comments), which updates the farm state, and which is called every time the `total_farm_supply` or `rewards_reserve` variables are updated (basically each time a proxy farm endpoint is called). + +```rust + fn update_farm_after_claim( + &self, + initial_farm_state: &FarmState, + farm_state_mapper: &mut SingleValueMapper>, + new_farm_token: &EsdtTokenPayment, + farm_rewards: EsdtTokenPayment, + division_safety_constant: &BigUint, + ) { + // We instantiate the most current farm state + let mut farm_state = farm_state_mapper.get(); + + // The total stake value and the nonce of the new aggregated farm position are always saved + // We then either return if there are no new rewards, or continue to further update the farm state + farm_state.farm_staked_value = new_farm_token.amount.clone(); + farm_state.farm_token_nonce = new_farm_token.token_nonce; + + if farm_rewards.amount == 0 { + farm_state_mapper.set(farm_state); + return; + } + + // The total farm rps is updated, using the newly aggregated farm position and the new received rewards + let rps_increase = self.compute_farm_rps_increase( + &farm_rewards.amount, + &new_farm_token.amount, + division_safety_constant, + ); + + // In most cases there will always be some remaining rewards so each time more rewards are accumulated, + // we merge the new rewards with the existing ones, in order to always keep one position + let new_rewards = if initial_farm_state.reward_reserve > 0 { + let mut reward_payments = ManagedVec::new(); + let current_rewards = EsdtTokenPayment::new( + farm_rewards.token_identifier.clone(), + initial_farm_state.reward_token_nonce, + initial_farm_state.reward_reserve.clone(), + ); + reward_payments.push(farm_rewards); + reward_payments.push(current_rewards); + self.merge_locked_tokens(reward_payments) + } else { + farm_rewards + }; + + // Finally, we update all these variables in the farm state and save the updated state in the storage + farm_state.reward_token_nonce = new_rewards.token_nonce; + farm_state.reward_reserve = new_rewards.amount; + farm_state.farm_rps += rps_increase; + + farm_state_mapper.set(farm_state); + } +``` + +Another part that is worth mentioning is the function that computes the user rewards. It is very important that this function is called after the `update_farm_after_claim` function, otherwise the rewards computation will be inconsistent. The user rewards are computed based on the __token_rps__ variable that is saved in the __WrappedFarmToken__ attributes. In the end, a new TokenPayment is returned, containing the computed amount and the current rewards token_nonce (this is why we always merge the locked rewards and keep only one position). + +```rust + fn compute_user_rewards_payment( + &self, + farm_state_mapper: &mut SingleValueMapper>, + payment: &EsdtTokenPayment, + division_safety_constant: &BigUint, + ) -> EsdtTokenPayment { + let farm_state = farm_state_mapper.get(); + let token_attributes: WrappedFarmTokenAttributes = + self.get_token_attributes(&payment.token_identifier, payment.token_nonce); + let token_rps = token_attributes.token_rps; + let reward = if farm_state.farm_rps > token_rps { + let rps_diff = &farm_state.farm_rps - &token_rps; + &payment.amount * &rps_diff / division_safety_constant + } else { + BigUint::zero() + }; + let locked_token_id = self.get_locked_token_id(); + EsdtTokenPayment::new(locked_token_id, farm_state.reward_token_nonce, reward) + } +``` + + +### Farm interactions + +The __FarmInteractionsModule__ is the place where you can find the most of the __Farm__ integration logic. + +Let's take a look at the `enter_farm` endpoint, with some descriptive comments. + +```rust + // The farm_address argument specifies which farm the user wants to enter. + #[payable("*")] + #[endpoint(enterFarm)] + fn enter_farm_endpoint(&self, farm_address: ManagedAddress) -> EsdtTokenPayment { + let payment = self.call_value().single_esdt(); + + // We load the farm_state_mapper into a variable, to avoid reading it multiple times from the storage + // We then do a set of checks, to avoid SC errors from the beginning, in case some variables are not correct + let mut farm_state_mapper = self.farm_state(&farm_address); + require!(!farm_state_mapper.is_empty(), ERROR_FARM_DOES_NOT_EXIST); + let farming_token_id = self.get_farming_token(&farm_address); + require!( + farming_token_id == payment.token_identifier, + ERROR_BAD_PAYMENT_TOKENS + ); + + let farm_state = farm_state_mapper.get(); + let farm_token_id = self.get_farm_token(&farm_address); + let division_safety_constant = self.get_division_safety_constant(&farm_address); + let mut enter_farm_payments = ManagedVec::from_single_item(payment); + + // We create a new payment with the current aggregated farm position from the farm state + // We then add it as an additional payment for the DEX enter_farm endpoint, in case the amount is greater than 0 + let current_farm_position = EsdtTokenPayment::new( + farm_token_id, + farm_state.farm_token_nonce, + farm_state.farm_staked_value.clone(), + ); + let initial_total_farm_amount = current_farm_position.amount.clone(); + if initial_total_farm_amount > 0 { + enter_farm_payments.push(current_farm_position); + } + + // DEX enter farm is called with a PaymentsVec, consisting of the user payment and the current SC total farm position + let enter_farm_result = self.call_enter_farm(farm_address.clone(), enter_farm_payments); + + // We receive a result containing the new aggregated position as well as any boosted rewards, if any + // The new total position should be bigger than the initial position that we saved prior to entering the farm + let (new_farm_token, farm_rewards) = enter_farm_result.into_tuple(); + + require!( + new_farm_token.amount > initial_total_farm_amount, + ERROR_EXTERNAL_CONTRACT_OUTPUT + ); + + let user_farm_amount = &new_farm_token.amount - &initial_total_farm_amount; + + // The contract then updates the farm state, updating variables like total farm supply, total rewards or reward_per_share + // More detailed info was presented in the Farm Actions section + self.update_farm_after_claim( + &farm_state, + &mut farm_state_mapper, + &new_farm_token, + farm_rewards, + &division_safety_constant, + ); + + // Finally, a new WrappedFarmToken is minted (containing the current farm_rps) and sent to the user + let caller = self.blockchain().get_caller(); + let new_farm_state = farm_state_mapper.get(); + let user_token_attributes = WrappedFarmTokenAttributes { + farm_address, + token_rps: new_farm_state.farm_rps, + }; + self.wrapped_farm_token().nft_create_and_send( + &caller, + user_farm_amount, + &user_token_attributes, + ) + } +``` + +Going further to the `claim_user_rewards` endpoint, we can observe the same logical layout as with the `enter_farm_endpoint`, with a few particularities. We start once again reading the user's payment and checking that all variables are in the correct state, before calling the `claim_and_compute_user_rewards` function, which does a few operations (calls the claim_rewards endpoint from the DEX farm, updates the farm state, burns the current __WrappedFarmToken__ and then computes and returns the current accumulated rewards of the user). After that, a new __WrappedFarmToken__ is minted, containing the up-to-date __farm_rps__. Finally, the new position alongside the computed rewards are then sent to the user, but not before wrapping the reward tokens, in order to be able to make the transfer. + +```rust + #[payable("*")] + #[endpoint(claimUserRewards)] + fn claim_user_rewards(&self) -> PaymentsVec { + let payment = self.call_value().single_esdt(); + require!( + payment.token_identifier == self.wrapped_farm_token().get_token_id(), + ERROR_BAD_PAYMENT_TOKENS + ); + let token_attributes: WrappedFarmTokenAttributes = + self.get_token_attributes(&payment.token_identifier, payment.token_nonce); + let farm_address = token_attributes.farm_address; + let mut farm_state_mapper = self.farm_state(&farm_address); + require!(!farm_state_mapper.is_empty(), ERROR_FARM_DOES_NOT_EXIST); + + let (_, user_rewards) = self + .claim_and_compute_user_rewards(&payment, &farm_address, &mut farm_state_mapper) + .into_tuple(); + + let new_farm_state = farm_state_mapper.get(); + let new_attributes = WrappedFarmTokenAttributes { + farm_address, + token_rps: new_farm_state.farm_rps, + }; + let new_farm_token = self + .wrapped_farm_token() + .nft_create(payment.amount, &new_attributes); + let mut user_payments = ManagedVec::from_single_item(new_farm_token); + if user_rewards.amount > 0 { + let wrapper_user_rewards = self.wrap_locked_token(user_rewards); + user_payments.push(wrapper_user_rewards); + } + let caller = self.blockchain().get_caller(); + self.send().direct_multi(&caller, &user_payments); + + user_payments + } +``` + +**Unstake and unbond** + +There are a few things that are important to keep in mind when exiting from a farm. In order to avoid having users that enter & benefit from the boosted rewards, to then just exit after the boosted rewards are computed, the farm contract has a 7 day penalty period policy, in which if the user exits the farm after entering, he will receive a certain penalty fee. That is why, there is a __farm_unbond_period__ in the DAO contract (in our case, the period is read directly from the __Farm SC__) that the user needs to wait before exiting the farm. + +For that, the Energy DAO SC has an unstake & unbond mechanism, that is actually imposed only at the DAO SC level, and not by the actual farm contract. But how exactly can the user wait a predefined period of time, if the contract always keeps one general aggregated position? +When the users calls the `unstake_farm` endpoint, the farm proxy `claim_rewards` endpoint is called with the full position, which then gives the user his last rewards before unstaking his position. Then, while the newly created total farm position nonce is saved in both the farm state and the __UnstakeFarmToken__ attributes, the __farm_staked_value__ value is updated to reflect the user exit, by subtracting the payment amount. And from this point forward, in future user interactions, the new amount and that token nonce will be used to do any kind of farm interaction, which will then lead to creating a new aggregated farm position (which in turn will have a different nonce). + +```rust + #[payable("*")] + #[endpoint(unstakeFarm)] + fn unstake_farm(&self) -> PaymentsVec { + let payment = self.call_value().single_esdt(); + require!( + payment.token_identifier == self.wrapped_farm_token().get_token_id(), + ERROR_BAD_PAYMENT_TOKENS + ); + let token_attributes: WrappedFarmTokenAttributes = + self.get_token_attributes(&payment.token_identifier, payment.token_nonce); + let farm_address = token_attributes.farm_address; + let mut farm_state_mapper = self.farm_state(&farm_address); + require!(!farm_state_mapper.is_empty(), ERROR_FARM_DOES_NOT_EXIST); + + let (new_farm_token, user_rewards) = self + .claim_and_compute_user_rewards(&payment, &farm_address, &mut farm_state_mapper) + .into_tuple(); + + farm_state_mapper.update(|config| { + config.farm_staked_value -= &payment.amount; + config.farm_unstaked_value += &payment.amount; + }); + let current_epoch = self.blockchain().get_block_epoch(); + let unstake_attributes = UnstakeFarmAttributes { + farm_address, + unstake_epoch: current_epoch, + token_nonce: new_farm_token.token_nonce, + }; + let unstake_token_payment = self + .unstake_farm_token() + .nft_create(payment.amount, &unstake_attributes); + + let mut user_payments = ManagedVec::from_single_item(unstake_token_payment); + if user_rewards.amount > 0 { + let wrapper_user_rewards = self.wrap_locked_token(user_rewards); + user_payments.push(wrapper_user_rewards); + } + let caller = self.blockchain().get_caller(); + self.send().direct_multi(&caller, &user_payments); + + user_payments + } +``` + +Finally, when the unbonding period has passed, the Energy DAO contract exits the farm with the amount of __UnstakeFarmToken__ that the user sends when calling this endpoint, and the token nonce that was saved in the attributes. After that, the __UnstakeFarmToken__ is burned, and the user gets his assets back, but not before the exit fee (discussed in the beginning) is applied. + +```rust + #[payable("*")] + #[endpoint(unbondFarm)] + fn unbond_farm(&self) -> PaymentsVec { + let payment = self.call_value().single_esdt(); + require!( + payment.token_identifier == self.unstake_farm_token().get_token_id(), + ERROR_BAD_PAYMENT_TOKENS + ); + let token_attributes: UnstakeFarmAttributes = + self.get_token_attributes(&payment.token_identifier, payment.token_nonce); + let farm_address = token_attributes.farm_address; + let farm_state_mapper = self.farm_state(&farm_address); + require!(!farm_state_mapper.is_empty(), ERROR_FARM_DOES_NOT_EXIST); + + let current_epoch = self.blockchain().get_block_epoch(); + let unbond_period = self.farm_unbond_period().get(); + let unbond_epoch = token_attributes.unstake_epoch + unbond_period; + require!(current_epoch >= unbond_epoch, ERROR_UNBOND_TOO_SOON); + + let farm_token_id = self.get_farm_token(&farm_address); + let unstake_payment = EsdtTokenPayment::new( + farm_token_id, + token_attributes.token_nonce, + payment.amount.clone(), + ); + let exit_farm_result = self.call_exit_farm(farm_address, unstake_payment); + let (mut farming_tokens, locked_rewards_payment, _) = exit_farm_result.into_tuple(); + + farm_state_mapper.update(|config| { + config.farm_unstaked_value -= &payment.amount; + }); + + self.send().esdt_local_burn( + &payment.token_identifier, + payment.token_nonce, + &payment.amount, + ); + self.apply_fee(&mut farming_tokens); + let mut user_payments = ManagedVec::from_single_item(farming_tokens); + if locked_rewards_payment.amount > 0 { + user_payments.push(locked_rewards_payment); + } + let caller = self.blockchain().get_caller(); + self.send().direct_multi(&caller, &user_payments); + + user_payments + } +``` + + +## Metastaking integration + +The __Metastaking__ integration is quite similar to the __Farms__ integration, so following this integration should be pretty straightforward by now. There are still a few different nuances, especially regarding rewards computation (there are now 2 different reward tokens and for that we have 2 different rps amounts, one for each token) and the unstake & unbond mechanism (as this differs from the farm logic, by being imposed by the DEX contract), but none should provide any difficulties at this point. + +:::note +There is an important aspect that needs mentioning, and that is the fact the both __Farm__ and __Metastaking__ implementations cannot coexist for the same underlying tokens, while having maximum rewards efficiency. Combining these two approaches results in a loss of rewards for one of the farms, as boosted rewards are given per account, and cannot be claimed with one aggregated position consisting of both farm and metastaking tokens. To address this issue, the __Energy DAO SC__ template does not allow to use both options at the same time. In other words, you cannot enter a __Farm__ contract, if for that __Farm__ you have defined the __Metastaking__ contract as well. Of course, each project can define its own custom logic regarding the rewards distribution, which may allow both implementations to work simultaneously. +::: + + +## Locked token integration + +This Energy DAO contract template was designed with the following workflow regarding the accumulation of Energy +- The owner buys MEX tokens and sends them to the Energy DAO SC +- The contract then locks & energizes the account +- For providing the tokens that are now locked, the owner is entitled to rewards from the __Fees Collector__, as well as the exit fees (the percentage can be changed by each project) from users that use the Energy DAO contract. + +The workflow can be updated, for example allowing admin wallets to also deposit MEX tokens in order to further energize the Energy DAO SC, but that may require some extra data handling. For this documentation though, we'll stick to the workflow presented above. + +The `lock_energy_tokens` endpoint is quite simple, it just verifies that the token sent is the correct one, calls the lock endpoint, and then stores a list of locked tokens, that will make future locked tokens management more easier (feature is not implemented at this time). It receives a __lock_epoch__ argument, allowing the owner to choose for what period he wants to lock his tokens. In case the argument is not correct, the contract call will fail during the `lock_tokens_endpoint` execution in the __Energy Factory__ contract. + +```rust + #[only_owner] + #[payable("*")] + #[endpoint(lockEnergyTokens)] + fn lock_energy_tokens(&self, lock_epoch: u64) { + let payment = self.call_value().single_esdt(); + let base_token_id = self.get_base_token_id(); + require!( + payment.token_identifier == base_token_id, + ERROR_BAD_PAYMENT_TOKENS + ); + + let new_locked_tokens = self.lock_tokens(payment, lock_epoch); + self.internal_locked_tokens() + .update(|locked_tokens| locked_tokens.push(new_locked_tokens)); + } + + #[storage_mapper("internalLockedTokens")] + fn internal_locked_tokens(&self) -> SingleValueMapper>; +``` + +Diving deeper into the `lock_tokens` endpoint, the external interaction for locking the deposited tokens is done calling the __Energy Factory__ contract (__energy_factory_address__ was saved in the init function), using the desired epoch which is sent as an argument. + +```rust + fn lock_tokens(&self, payment: EsdtTokenPayment, epoch: Epoch) -> EsdtTokenPayment { + let energy_factory_address = self.energy_factory_address().get(); + self.energy_factory_proxy(energy_factory_address) + .lock_tokens_endpoint(epoch, OptionalValue::::None) + .with_egld_or_single_esdt_transfer(payment) + .execute_on_dest_context() + } +``` + +As stated in the beginning, Energy is computed using the amount of MEX tokens that an account has and the number of epochs that the tokens are locked. Naturally, as time passes, the energy decreases. That's why, an XMEX holder can choose to extend the lock period of his tokens, to gain back the Energy that he lost, in order to further maximize the generated rewards. The same option is available for the __Energy DAO SC__, through the `extend_lock_period()` endpoint, which allows the owner to extend the lock period of his deposited MEX tokens. Furthermore, as the XMEX tokens are not merged but rather kept as a list of ESDTTokenPayments, the endpoint allows an optional nonce parameter, in case the owner wants to extend the lock period of only one particular XMEX token. + +```rust + #[only_owner] + #[endpoint(extendLockPeriod)] + fn extend_lock_period(&self, lock_epoch: u64, opt_nonce_to_update: OptionalValue) { + let locked_tokens_mapper = self.internal_locked_tokens(); + require!( + !locked_tokens_mapper.is_empty(), + ERROR_LOCKED_TOKENS_NOT_FOUND + ); + let initial_locked_tokens = locked_tokens_mapper.get(); + let nonce_to_update = match opt_nonce_to_update { + OptionalValue::Some(nonce_to_update) => nonce_to_update, + OptionalValue::None => 0u64, + }; + + let mut new_locked_tokens = ManagedVec::new(); + for locked_token in initial_locked_tokens.iter() { + if locked_token.token_nonce == nonce_to_update || nonce_to_update == 0u64 { + let new_token = self.lock_tokens(locked_token, lock_epoch); + new_locked_tokens.push(new_token); + } else { + new_locked_tokens.push(locked_token); + } + } + + locked_tokens_mapper.set(new_locked_tokens); + } +``` + +:::important +__Food for thought.__ In this version of the contract, the XMEX solely purpose is to provide energy for the account. In a future iteration, the Energy DAO could be extended to accept WEGLD as a one side payment, in order to enter the MEX-EGLD farm. But a completely new logic needs to be defined on this part, in order to make it attractive for the external users to provide the WEGLD, while also remaining profitable for the contract as well. +::: + + +## Collecting fees and the RewardsWrapper + +In this version of the Energy DAO template, for providing MEX for the Energy, the owner of the contract is entitled to the rewards from the __Fees Collector__. Besides these rewards, the contract also takes an exit fee from users using it. For better handling these fees (they are kept in separate storages, for clear distinction), the Energy DAO SC implements a __RewardsWrapper__ which has some features that simplifies the rewards handling inside the contract. + +Let's first look at the __Fees Collector__ integration. + +The base idea of this endpoint is that it calls the __fees_collector_proxy__, receives a __PaymentsVec__ of rewards (based on the Energy of the account), and then saves them through the __RewardsWrapper__ in the __collected_fees__ storage mapper. + +```rust + #[endpoint(claimFeesCollectorRewards)] + fn claim_fees_collector_rewards(&self) { + let mut rewards = self.call_fees_collector_claim(); + let rewards_len = rewards.len(); + if rewards_len == 0 { + return; + } + + // tokens from the fees collector are kept by the contract + let collected_fees_mapper = self.collected_fees(); + let mut new_collected_fees = if collected_fees_mapper.is_empty() { + let locked_token_id = self.get_locked_token_id(); + RewardsWrapper::new(locked_token_id) + } else { + collected_fees_mapper.get() + }; + + // locked token rewards, if any, are always in the last position + let last_payment = rewards.get(rewards_len - 1); + if &last_payment.token_identifier == new_collected_fees.get_locked_token_id() { + let mut fees_payments = new_collected_fees.locked_tokens.into_payments(); + fees_payments.push(last_payment); + let new_locked_fee = self.merge_locked_tokens(fees_payments); + new_collected_fees.locked_tokens = UniquePayments::new(); + new_collected_fees.add_tokens(new_locked_fee); + rewards.remove(rewards_len - 1); + } + + for rew in &rewards { + new_collected_fees.add_tokens(rew); + } + collected_fees_mapper.set(new_collected_fees); + } + + fn call_fees_collector_claim(&self) -> PaymentsVec { + let sc_address = self.fees_collector_sc_address().get(); + self.fees_collector_proxy(sc_address) + .claim_rewards(OptionalValue::::None) + .execute_on_dest_context() + } + + #[proxy] + fn fees_collector_proxy(&self, sc_address: ManagedAddress) -> fees_collector::Proxy; + + #[storage_mapper("feesCollectorScAddress")] + fn fees_collector_sc_address(&self) -> SingleValueMapper; + + #[storage_mapper("collectedFees")] + fn collected_fees(&self) -> SingleValueMapper>; +``` + +**RewardsWrapper deep dive** + +The __RewardsWrapper__ is basically a small struct with a few custom implementations. It first stores the __locked_token_id__, which is needed in order to check the TokenIdentifier of the payment, to see if it is a fungible reward payment, or a XMEX payment. It may seem a bit strange to save this information at this level, but the alternative is to check each time for the token_id during the endpoints execution, by reading the __locked_token_id__ from the storage. That's why, saving it in this manner, actually increases the efficiency of the contract. + +```rust + #[derive(TypeAbi, TopEncode, TopDecode, NestedEncode, NestedDecode, PartialEq, Debug)] + pub struct RewardsWrapper { + locked_token_id: TokenIdentifier, + pub locked_tokens: UniquePayments, + pub other_tokens: UniquePayments, + } + + impl RewardsWrapper { + pub fn new(locked_token_id: TokenIdentifier) -> Self { + Self { + locked_token_id, + locked_tokens: UniquePayments::default(), + other_tokens: UniquePayments::default(), + } + } + + pub fn add_tokens(&mut self, payment: EsdtTokenPayment) { + if payment.token_identifier == self.locked_token_id { + self.locked_tokens.add_payment(payment); + } else { + self.other_tokens.add_payment(payment); + } + } + + #[inline] + pub fn get_locked_token_id(&self) -> &TokenIdentifier { + &self.locked_token_id + } + } +``` + +But then, what about the __UniquePayments__ struct? Well, the __UniquePayments__ is a single field struct, containing a simple __PaymentsVec__, but with a few implementations of its own. It implements the more generic __default()__ and __new()__ functions, and also a few other simple utility functions, like __new_from_payments()__ and __into_payments()__, which are self explanatory. +Now, the magic under the hood, so to speak, is that it also implements the __Mergeable__ trait from the DEX modules, which allows it to check if a new payment can be merged, and also handles the entire merging process, comparing both the token_id and the token_nonce of the payment. This merging algorithm is then used inside the custom __add_payment()__ function of the __UniquePayments__ struct, which simply receives the new payment that needs to be either added or merged, depending if another similar ESDTTokenPayment already exists or not, always keeping only one instance of a token id/nonce pair (hence the name __UniquePayments__). +This all helps throughout the contract, including in the __claim_fees_collector_rewards__ presented above, where we simply call the __add_tokens__ function of the __PaymentsWrapper__, and all the checks and merging computation is done by the wrapper. + +```rust + #[derive(TypeAbi, TopEncode, TopDecode, NestedEncode, NestedDecode, Clone, PartialEq, Debug)] + pub struct UniquePayments { + payments: PaymentsVec, + } + + impl Default for UniquePayments { + #[inline] + fn default() -> Self { + Self { + payments: PaymentsVec::new(), + } + } + } + + impl UniquePayments { + #[inline] + pub fn new() -> Self { + Self::default() + } + + #[inline] + pub fn new_from_unique_payments(payments: PaymentsVec) -> Self { + UniquePayments { payments } + } + + pub fn new_from_payments(payments: PaymentsVec) -> Self { + let mut merged_payments = Self::new(); + for p in &payments { + merged_payments.add_payment(p); + } + + merged_payments + } + + pub fn add_payment(&mut self, new_payment: EsdtTokenPayment) { + if new_payment.amount == 0 { + return; + } + + let len = self.payments.len(); + for i in 0..len { + let mut current_payment = self.payments.get(i); + if current_payment.can_merge_with(&new_payment) { + current_payment.amount += new_payment.amount; + let _ = self.payments.set(i, ¤t_payment); + + return; + } + } + + self.payments.push(new_payment); + } + + #[inline] + pub fn into_payments(self) -> PaymentsVec { + self.payments + } + } + + impl Mergeable for UniquePayments { + #[inline] + fn can_merge_with(&self, _other: &Self) -> bool { + true + } + + fn merge_with(&mut self, mut other: Self) { + self.error_if_not_mergeable(&other); + + if self.payments.is_empty() { + self.payments = other.payments; + return; + } + if other.payments.is_empty() { + return; + } + + let first_len = self.payments.len(); + let mut second_len = other.payments.len(); + for i in 0..first_len { + let mut current_payment = self.payments.get(i); + for j in 0..second_len { + let other_payment = other.payments.get(j); + if !current_payment.can_merge_with(&other_payment) { + continue; + } + + current_payment.amount += other_payment.amount; + let _ = self.payments.set(i, ¤t_payment); + + other.payments.remove(j); + second_len -= 1; + + break; + } + } + + self.payments.append_vec(other.payments); + } + } +``` + + +## **Testing** + +The Energy DAO SC was tested through various unit tests, that were conducted on top of a complete setup of the xExchange suite of contracts. Specifically, all the involved DEX contracts (like pair, farm, farm-staking, farm-staking-proxy, energy factory & so on) were set up from scratch, so the testing scenario could follow a complete flow where the owner locks his tokens through the SC in order to get Energy for the contract, and users provide liquidity in the pair contract, to later enter farm or metastaking, claim rewards and exit the Energy DAO contract. + +:::note +In order to be able to have a complete step-by-step debugging layout, all the Github references from the main `Cargo.toml` file need to be updated to a local DEX repo path, as shown below. +```rust +[dependencies.pair] +path = "../../mx-exchange-sc/dex/pair" +``` +::: + + +## **Next steps** + +As stated in the beginning, this SC template is just but one possible variation of how an Energy DAO on the MultiversX Network could look like. There are quite a few more features that could be added, and the current implementation can be changed as needed. But the entire xExchange contracts integration flow is there, and the Energy DAO SC template can certainly be the backbone of any such DAO project. + +--- + +### Environments + +## Overview + +When discussing the unified transaction syntax, we identify four primary environments crucial for developing and deploying secure smart contracts on MultiversX. This design aims to cover all essential development stages, ensuring a seamless workflow. Notably, it streamlines the process for Rust developers by empowering them to solely concentrate on mastering Rust. As a result, they can effortlessly write, test, deploy, and interact with smart contracts without the burden of learning additional complementary technologies. + +The environments: +- Smart contract - Rust framework, the syntax for writing smart contracts (`TxScEnv`) +- Integration test - Rust testing framework, used for writing Rust tests against the Rust VM and Go VM (`ScenarioTxEnv`, `ScenarioEnvExec`, `ScenarioTxEnvData`, `ScenarioEnvQuery`) +- Parametric test - symbolic execution, safety of the smart contract code (coming soon) +- Interactor - Rust program, used for system testing and interaction with the smart contract on the real blockchain (`InteractorExecEnv`, `InteractorQueryEnv`) + +:::info +The Rust unified syntax is able to interact with all of these environments, meaning that the syntax remains the same for all purposes mentioned above. The only element that changes the route of the transaction (whether it's going to be run inside a `smart contract`, `test`, or `interactor`) is the environment `TxEnv`. + +Creating a transaction with a specific environment gives the developer access to the specific functions used in said environment only. +::: + +Switching between environments is accessible for the developer and enforced by the Rust type system. Each environment is represented by a different type, and switching between environments can be easily done by specializing the `TxEnv` generic of the transaction with a concrete value from the pool of available environments. + +Each transaction starts from the environment, then multiple smaller bits of information are added in a modular way so that the generics of the `Tx` object are instantiated one by one and the route of the transaction is defined. The developer is not forced to complete the transaction in any way, but the route of the transaction is narrowed down by the types used at every step. + +For example, let's suppose we are in the smart contract environment of `SomeContract`: + +```rust title=lib.rs +#[multiversx_sc::contract] +pub trait SomeContract { + + #[endpoint] + fn deploy_another_contract( + &self, + source: ManagedAddress, + deploy_arg: Option + ) { + self.tx() // TxScEnv - smart contract environment + .raw_deploy() // TxScEnv + DeployCall - deploy tx in a sc environment + .from_source(source) // deploy detail, further defines DeployCall + .argument(&deploy_arg) // deploy argument + .sync_call() // type of call + } +} +``` + +In this example, the endpoint `deploy_another_contract` performs a smart contract deploy action with specific parameters. First, we create a transaction with `self.tx()`, which automatically translates to a transaction with the `TxScEnv` environment when called from inside the smart contract. Then, we add bits of information to the transaction, such as the type of action we are looking for: `.raw_deploy()`. This narrows down our environment to `TxScEnv` and also specializes our `TxData` generic field as a `DeployCall`, which now creates a route for our transaction (deploy) and gives us access to other functions that can help the deploy action only. + +:::info +Once the environment is selected, developers have four methods to narrow down the route of the transaction: +- `.raw_call("function_name")` - creates a raw call to a smart contract (function name can also be empty) +- `.raw_deploy()` - creates a deploy call, can not have a recipient +- `.raw_upgrade()` - creates an upgrade call +- `.typed(proxy::ContractProxy)` - creates a typed call through a contract proxy, knows the expected return type of the endpoint +::: + +:::note +Apart from the environment, developers are not obliged to include any specific details within a transaction. They have the freedom to construct the transaction in any manner they see fit, provided that the transaction ultimately contains sufficient information to execute the intended action accurately. +::: + + + +## Diagram + +Environments are the first field that gets initialized, as soon as the transaction object is created. + +```mermaid +graph LR + subgraph Environment + sc-code["SC Code"] -->|"self.tx()"| sc-env[TxScEnv] + test-code["Test Code"] -->|"world.tx()"| ScenarioEnvExec + test-code["Test Code"] -->|"world.query()"| ScenarioEnvQuery + intr-code["Interactor Code"] -->|"interactor.tx()"| InteractorEnvExec + intr-code["Interactor Code"] -->|"interactor.query()"| InteractorEnvQuery + end +``` + + +## Smart contract + +A transaction with the smart contract environment can be created from inside the smart contract (annotated with `#[multiversx_sc::contract]`), by simply calling `self.tx()`. The returned transaction env is `TxScEnv` by default, which helps us build transactions in this environment. + +```rust title=lib.rs +#[multiversx_sc::contract] +pub trait SomeContract { + #[endpoint] + fn send_async_call( + &self, + to: ManagedAddress, + function_name: ManagedBuffer, + args: ManagedArgBuffer, + ) { + self.tx() // tx with sc environment + .to(to) // recipient + .raw_call(function_name) // function call + .arguments_raw(args) // arguments + .async_call_and_exit() // type of call - async + } +} +``` + + + +## Integration test + +When building integration tests, we have to deal with the test environments such as `ScenarioEnvExec` for sending transactions and `ScenarioEnvQuery` for querying results from smart contracts. The easiest way to create transactions with the test environments is to create an instance of the `ScenarioWorld` struct. + +```rust title=blackbox_test.rs +use multiversx_sc_scenario::imports::*; + +mod proxy; + +#[test] +fn blackbox_test() { + let mut world = ScenarioWorld::new(); // ScenarioWorld struct + + let _query_env = world.query(); // tx with ScenarioEnvQuery + let _tx_env = world.tx(); // tx with ScenarioEnvExec +} +``` + +After defining the environment, we can further build transactions, as such: + +```rust title=blackbox_test.rs +use multiversx_sc_scenario::imports::*; + +mod proxy; + +const OWNER_ADDRESS: TestAddress = TestAddress::new("owner"); +const CODE_PATH: MxscPath = MxscPath::new("output/some-contract.mxsc.json"); + +#[test] +fn blackbox_test() { + let mut world = ScenarioWorld::new(); // ScenarioWorld struct + + let new_address = world + .tx() // tx with test exec environment + .from(OWNER_ADDRESS) // caller + .typed(proxy::SomeContractProxy) // typed call + .init() // calling init function => deploy + .code(CODE_PATH) // deploy detail + .returns(ReturnsNewAddress) // result handler - expected return type + .run(); // runs the step + + let _result = world + .query() // tx with test query environment + .to(new_address) // newly deployed sc address + .typed(proxy::SomeContractProxy) // typed call + .sum() // smart contract view + .returns(ReturnsResult) // result handler - expected return type + .run(); // runs the step +} +``` + + + +## Parametric tests + +Not yet available, coming soon. + +## Interactor + +The interactors are Rust programs, used for interacting with the smart contract on the real blockchain (deploy/upgrade, query, call) and system testing. Interactor environments such as `InteractorExecEnv` and `InteractorQueryEnv` are essential for transactions meant to be ran on the real blockchain, from a different environment than the smart contract. The easiest way to create transactions using the interactor environments is to create an instance of the `Interactor` struct. + +```rust title=interact.rs +const GATEWAY: &str = "https://devnet-gateway.multiversx.com"; + +async fn example_scenario() { + // No chain simulator flag, as we are using the real blockchain for this test. + let mut interactor = Interactor::new(GATEWAY).await; // Interactor struct + + let _query_env = interactor.query(); // tx with InteractorQueryEnv + let _tx_env = interactor.tx(); // tx with InteractorExecEnv +} +``` + +After creating the transaction with the interactor environment, we can start building transactions that will run on the real blockchain, as such: + +```rust title=interact.rs +use multiversx_sc_snippets::imports::*; + +mod proxy; + +const GATEWAY: &str = "https://devnet-gateway.multiversx.com"; + +async fn example_scenario() -> num_bigint::BigUint { + let mut interactor = Interactor::new(GATEWAY).await; // create Interactor struct + let wallet_address = interactor.register_wallet(test_wallets::mike()); // register a test wallet from the framework + let sc_address = Bech32Address::from_bech32_string( + "erd1qqqqqqqqqqqqqpgqtsw8s3evhhyqqa2j2tfn9yvufqskdv236n9s2a06h9".to_string(), + ); // actual smart contract address on devnet + + interactor + .tx() // tx with interactor exec environment + .from(&wallet_address) // caller is test wallet + .to(&sc_address) // recipient + .typed(proxy::SomeContractProxy) // typed call + .add(5u64) // contract endpoint + .prepare_async() // prepares tx data, async Rust (different from async blockchain tx) + .run() // runs the transaction + .await; // awaits response + + interactor + .query() // tx with interactor query environment + .to(&sc_address) // recipient + .typed(proxy::SomeContractProxy) // typed call + .sum() // contract view, returns value + .returns(ReturnsResultUnmanaged) // result handler - expected return type, converted for ease of use + .prepare_async() // prepares tx data, async Rust (different from async blockchain tx) + .run() // runs the transaction + .await // awaits and returns response +} +``` +For the moment, every interactor transaction needs to go through `prepare_async` in order to convert tx data to other needed types. The function does `not` create an async blockchain call, rather it refers to async Rust. + + +As seen above, the transaction syntax remains consistent over the various environments. The developer can further improve the interactor by including the scenario function into a CLI or writing system tests. + +For example, a system test can look like this: +```rust title=interact.rs +#[cfg(test)] +pub mod system_test { + use crate::example_scenario; + use multiversx_sc_snippets::tokio; + + #[tokio::test] + async fn test_full_farm_scenario() { + let result = example_scenario().await; + println!("result {:#?}", result); + } +} +``` + +Running this test will call the `add` endpoint of the contract, query the `sum` view to get the result value (on the devnet, according to the gateway and test wallet), convert it into a similar type that doesn't require an API (in this case from `multiversx_sc::types::Biguint` to `num_bigint::BigUint`) and print it in the console. + +:::info +For ease of use, a small interactor CLI built around a smart contract's endpoints can now be `generated automatically` using [sc-meta all snippets](/developers/meta/sc-meta-cli#calling-snippets). +::: + +--- + +### epochinfo + +This page describes the structure of the `epoch-info` index (Elasticsearch), and also depicts a few examples of how to query it. + + +## _id + +The `_id` field of this index is represented by epoch. + + +## Fields + + +| Field | Description | +|------------------|--------------------------------------------------------------------------------------------| +| accumulatedFees | The accumulatedFees field represents the accumulated fees that were paid in the epoch. | +| developerFees | The developerFees field represents the developer fees that were accumulated in the epoch. | + + +## Query examples + + +### Fetch accumulatedFees and developerFees for a specific epoch + +``` +curl --request GET \ + --url ${ES_URL}/epochinfo/_search \ + --header 'Content-Type: application/json' \ + --data '{ + "query": { + "match": { + "_id":"600" + } + } +}' +``` + +--- + +### ESDT Operations Events + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + + +ESDT Operations Events are generated in response to interactions with an ESDT token. +These events help keep track of actions like creating new tokens, transferring tokens between addresses, burning tokens, etc. + + +### Fungible token transfer + +The `ESDTTransfer` event is emitted when a fungible token undergoes a transfer operation via the `ESDTTransfer` built-in function. + + + +| Field | Value | +|------------|------------------------------------------------------------------------------------------------------------------------| +| identifier | ESDTTransfer | +| address | the sender of the token | +| topics | `topics[0]` - token identifier base64 encoded
`topics[1]` - empty
`topics[2]` - value bytes base64 encoded
`topics[3]` - receiver address address bytes base64 encoded | +| data | empty | + +
+ + +```json +{ + "identifier": "ESDTTransfer", + "address": "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u", + "topics": [ + "VVRLLWI5NzQ4MA==", + "", + "AzsuPJ/QgDzoAAAA", + "0+diI4/UjUcgbmchGtML1QwheMY68dnsww9vhRPJUWg=" + ], + "data": null +} +``` + + +
+ + +### Semi-fungible, non-fungible or meta-esdt token creation + +The `ESDTNFTCreate` event is generated when a new token is created using the built-in function `ESDTNFTCreate`. + + + + +| Field | Value | +|------------|------------------------------------------------------------------------------------------------------------------------------| +| identifier | ESDTNFTCreate | +| address | the creator of the token | +| topics | `topics[0]` - token identifier base64 encoded
`topics[1]` - token nonce bytes base64 encoded
`topics[2]` - value bytes base64 encoded
`topics[3]` - ESDigitalToken structure marshalled with gogo proto serializer | +| data | empty | + +
+ + +```json +{ + "identifier": "ESDTNFTCreate", + "address": "erd1qqqqqqqqqqqqqpgq0tajepcazernwt74820t8ef7t28vjfgukp2sw239f3", + "topics": [ + "TFlaLTBmM2MxMQ==", + "AQ==", + "AQ==", + "CAESAgABIvwBCAESBWx5cmFaGiAJ7NX5x1oDr20FnI7bUkyNRhecngUl6Jlvjjzlcu0BuSD0Ayo7YmFma3JlaWNqbXVwMndna21qaXR0a2dpbzZydXlpb3h6cHpraHZ6b3Jvand5eHU0Y2RiNnQycHRhaGkyQmlwZnM6Ly9iYWZrcmVpY2ptdXAyd2drbWppdHRrZ2lvNnJ1eWlveHpwemtodnpvcm9qd3l4dTRjZGI2dDJwdGFoaTpLbWV0YWRhdGE6YmFma3JlaWNqbXVwMndna21qaXR0a2dpbzZydXlpb3h6cHpraHZ6b3Jvand5eHU0Y2RiNnQycHRhaGkvbHlyYVp9" + ], + "data": null +} +``` + + +
+ +### Semi-fungible, non-fungible or meta-esdt token transfer + +The `ESDTNFTTransfer` event is generated when a token, which can be semi-fungible, non-fungible, or meta-esdt, +is moved through the `ESDTNFTTransfer` built-in function. + + + + +| Field | Value | +|------------|------------------------------------------------------------------------------------------------------------------------------| +| identifier | ESDTNFTTransfer | +| address | the sender of the token | +| topics | `topics[0]` - token identifier base64 encoded
`topics[1]` - token nonce bytes base64 encoded
`topics[2]` - value bytes base64 encoded
`topics[3]` - receiver address bytes base64 encoded | +| data | empty | + +
+ + +```json +{ + "identifier": "ESDTNFTTransfer", + "address": "erd1qqqqqqqqqqqqqpgq0tajepcazernwt74820t8ef7t28vjfgukp2sw239f3", + "topics": [ + "WE1FWC1mZGEzNTU=", + "Aw==", + "LUrGxb4/2VjTAA==", + "0+diI4/UjUcgbmchGtML1QwheMY68dnsww9vhRPJUWg=" + ], + "data": null +} +``` + + +
+ + +### Multi token transfer + +The `MultiESDTNFTTransfer` event is generated when one or multiple tokens are transferred using the built-in +function `MultiESDTNFTTransfer`. + + + + + +| Field | Value | +|------------|---------------------------------------------------------------------------------------------------------------------------------| +| identifier | MultiESDTNFTTransfer | +| address | the sender of the token | +| topics | `topics[0]` - token identifier
`topics[1]` - token nonce base64 encoded (can be empty in case of fungible token)
`topics[2]` - value bytes base64 encoded
`topics[3]` - receiver address bytes base64 encoded | +| data | empty | + +
+ + +```json +{ + "identifier": "MultiESDTNFTTransfer", + "address": "erd1qqqqqqqqqqqqqpgq0tajepcazernwt74820t8ef7t28vjfgukp2sw239f3", + "topics": [ + "WE1FWC1mZGEzNTU=", + "Aw==", + "LUrGxb4/2VjTAA==", + "0+diI4/UjUcgbmchGtML1QwheMY68dnsww9vhRPJUWg=" + ], + "data": null +} +``` + + +
+ +:::note +For a multi-token transfer operation, multiple `MultiESDTNFTTransfer` events will be generated, one for each token being transferred. +::: + +:::important Important +Starting from release `rc/v1.6.0`, the `MultiESDTNFTTransfer` will be changed when the flag `ScToScLogEventEnableEpoch` will be enabled. + +Instead of generating multiple events with the same identifier, only one event will be generated for the entire multi-token transfer operation. +The event will follow the new format outlined below: + + + + +| Field | Value | +|------------|---------------------------------------------------------------------------------------| +| identifier | MultiESDTNFTTransfer | +| address | the sender of the token | +| topics | `PAIRS OF`
<
`topics[i]` - token identifier
`topics[i+1]` - token nonce base64 encoded (can be empty in case of fungible token)
`topics[i+2]` - value bytes base64
>
`topics[n-1]` - receiver address | +| data | empty | + +
+ + +```json +{ + "identifier": "MultiESDTNFTTransfer", + "address": "erd1j467tvyyr2dtxdz3lsgqaeez5umjsdn8fajexqlu7eum5wx2u0aqkca23a", + "topics": [ + "MzlINk8tNDc5NmI4", + "Aw==", + "AQ==", + "UFFTMFItZDlmOTlk", + "", + "AQ==", + "StPRLp5kwnctf4If2CUhKr6Vux8WN3k3i5chL/y93UQ=" + ], + "data": null +} +``` + + +
+ + +::: + +--- + +### Ethereum to MultiversX migration guide + +## Introduction + +In the last period smart contracts suffered a rapid growth as many blockchains brought on the table better and better ways to develop one. In this article we will make a short comparison between writing a smart contract in Ethereum and one in MultiversX, and bring you another perspective from the Ethereum background to what could be an amazing experience of developing in Rust with SpaceCraftSDK - MultiversX’s framework. + + +## SC in Ethereum vs MultiversX + +The Ethereum execution client relies on Solidity as its programming language, even though it provides several implementations and support for multiple other languages such as Javascript, Rust, Python, Java, C++, C# and Go. + +On the other hand, the MultiversX virtual machine (VM) executes WebAssembly. This means that it can execute smart contracts written in any programming language that can be compiled to WASM bytecode. However, MultiversX strongly encourages developers to adopt Rust for the smart contracts by providing a strong Rust framework which allows for unusually clean and efficient code in smart contracts, a rarity in the blockchain field. A declarative testing framework is bundled as well. + +The MultiversX VM was built to be a fast and secure execution engine, stateless and to allow asynchronous calls between contracts in the most transparent way possible to the user. + +Let’s take a look to a simple adder contract in Ethereum’s Solidity + +```typescript +contract Adder { + uint private sum; + + constructor(uint memory initialValue) public { + sum = initialValue; + } + + function add(uint value) public { + sum += value; + } + + function getSum() public constant returns (uint) { + return sum; + } +} +``` + +This would translate in MultiversX’s Rust into: + +```rust +#[multiversx_sc::contract] +pub trait Adder { + #[init] + fn init(&self, initial_value: BigUint) { + self.sum().set(initial_value); + } + + #[endpoint] + fn add(&self, value: BigUint) { + self.sum().update(|sum| *sum += value); + } + + #[view(getSum)] + #[storage_mapper("sum")] + fn sum(&self) -> SingleValueMapper; +} +``` +The element that defines SpaceCraftSDK is the fact that by a series of implementations the whole structure of smart contracts is drastically simplified. SpaceCraftSDK offers a series of annotations that transforms basic rust elements such as functions, traits into smart contract specific elements such as endpoints, storage mappers, modules etc.. In this way, for example, a method marked with `#[endpoint]` makes it public to be called as an endpoint of the contract. + +## Handling the storage + +Contracts in MultiversX are important to be as small as possible, the reason why the whole storage is not handled by the contract, but rather the VM, the contract only holding a handle of a memory location inside the VM where the data is actually stored. A very popular concept that you will see here is the one of `storage mappers`, which are some structures designed to handle the serialization inside the storage, depending on the data types. + +In the contract example the annotation above the declaration of `sum` is a storage mapper holding a single value of type `BigUint`. This mapper allows accessing and changing the value of our variable stored in the assigned memory location inside the VM. For multiple particular uses we implemented a series of mappers of which you can find out more about in our [documentation](https://docs.multiversx.com/developers/developer-reference/storage-mappers/). + +Despite the fact that Ethereum also advances towards a similar concept of storage, the possibilities on MultiversX are more vast due to an optimisation made at the level of memory allocation which leads to saving up gas and ultimately speeding up execution. + +## The dynamic allocation problem + +One of the main issues of some Rust types such as `String` or `Vec` when it comes to smart contract development is that they are dynamically allocated on heap, meaning that the smart contract asks for more memory than it actually needs from the runtime environment (the VM). For a small collection this is insignificant, but for a bigger collection, this can become slow and the VM might even stop the contract and mark the execution as failed. Not to mention that more memory used leads to longer execution times, which ultimately leads to also increased gas costs. + +One of the most important outcomes of this issue is the fact that MultiversX smart contracts don’t use the `std` library. You will always see them marked with `#[no_std]`. + +The solution to this problem, that the developers of MultiversX came with, is called `managed types`. These managed types are the key elements behind the concept with the VM handling the smart contract memory that we mentioned before. Inside the contract these managed types only store a `handle`, which is a `u32` index representing the location within the VM memory where the data is stored. + +With that being said, here’s a list of managed counterpart data types to use in MultiversX smart contracts: + +| Unmanaged (safe to use) | Unmanaged (allocates on the heap) | Managed | +| :---------------------: | :-------------------------------: | :------------------------------------------: | +| - | - | `BigUint` | +| `&[u8]` | - | `&ManagedBuffer` | +| - | `BoxedBytes` | `ManagedBuffer` | +| `ArrayVec`[^1] | `Vec` | `ManagedBuffer` | +| - | `String` | `ManagedBuffer` | +| - | - | `TokenIdentifier` | +| - | `MultiValueVec` | `MultiValueEncoded` / `MultiValueManagedVec` | +| `ArrayVec`[^1] | `Vec` | `ManagedVec` | +| `[T; N]`[^2] | `Box<[T; N]>` | `ManagedByteArray` | +| - | `Address` | `ManagedAddress` | +| - | `H256` | `ManagedByteArray<32>` | +| - | - | `EsdtTokenData` | +| - | - | `EsdtTokenPayment` | + +## Data serialization + +All data that interacts with a smart contract is represented as byte arrays that need to be interpreted according to a consistent specification defined in MultiversX’s framework, named SpaceCraftSDK. This format is aimed to be somewhat readable and to interact with the rest of the blockchain ecosystem as easily as possible, the reason why all numeric types follow the `big endian representation`. + +We mentioned before that dynamic sized variables have their size calculated at compile time, the reason why we know the size of the byte arrays entering the contract. All arguments have a known size in bytes, and we normally learn the length of storage values before loading the value itself into the contract. This gives us some additional data straight away that allows us to encode less. We can say here that our data is represented in its top-level form, reading from the storage is done by zero size deserializing. + +We implemented a concept called `ManagedType` which is a trait that many of our data types possess, allowing a more efficient way to hold them internally. + +Lets take for example an endpoint having an optional parameter. Option as you know has 2 possible elements: an empty value representing `none` or a parameter encrusted in something representing `some`. Putting a small restriction for this parameter to be one of a kind in an endpoint we can assume that if by the time we reached it, if there are no bytes left to cover, we have a `none`, or if we have straight up a parameter of the type we specified, than we have the `some` option. Imagine now that we also extended this principle to a multivalue type attribute having the same logic behind. + +Because of these optimisations MultiversX’s provides one of the most efficient and cheap ways to interact with a smart contract on the blockchain. + +## Fees in MultiversX + +An usual term met on blockchains when it comes about fees is gas, which translates to the unit measuring the computational effort to execute specific operations on the blockchain. + +Usually these gas costs for each blockchain are paid in the blockchain’s native currency. + +For Ethereum for instance, there is something called `gwei`, an unit equal to one-billionth of an ETH, or 10-9ETH. These fees have a tendency to grow depending on how crowded the network is. You can think of the gas cost as a result of a formula something like: `units of gas used * (base fee + priority fee)`, where the base fee differs per block, and will increase by a maximum of 12.5% per block if the target block size is exceeded. + +The priority fee (tip) incentivizes validators to include a transaction in the block. Without tips, validators would find it economically viable to mine empty blocks, as they would receive the same block reward. + +For MultiversX, the gas used within the contract is terminologically separated from the unconsumed remaining gas. Here the processing time breaks the consumed gas into two components: +- gas used by `value movement and data handling` +- gas used by `contract execution` (for executing System or User-Defined Smart Contract) + +The `value movement and data handling` component is defined by the formula: + +``` +tx.gasLimit = + networkConfig.erd_min_gas_limit + networkConfig.erd_gas_per_data_byte * lengthOf(tx.data) +``` + +whereas the contract execution despite being easily computable for System Smart Contracts, it is harder to determine a priori for user-defined Smart Contracts. This is where simulations and estimations are employed. + +Finally, the processing fee used by the MultiversX blockchain looks something like this: + +``` +processing_fee = + value_movement_and_data_handling_cost * value_movement_and_data_handling_price_per_unit + + contract_execution_cost * contract_execution_price_per_unit +``` + +Let's look at some actual numbers! + +Based on https://etherscan.io/txs and https://beaconcha.in/charts/validators Ethereum at the moment of writing this tutorial had 1,135,937 transactions in the last 24h with an average transaction fee of 2.47 USD with 1,031,453 active validators. + +On the other side, MultiversX from their source (https://explorer.multiversx.com/) had 217,263 transactions in the last 24h with an average fee of 0.02 USD with 3,323 validators, which makes 100 times cheaper than a transaction on Ethereum. + +![img](/img/mvx-avg-tx.png) + +As you can see in the histogram above, the costs of the transactions on the MultiversX blockchain kept low. Note that the prices you see are in MultiversX’s native coin EGLD. + +![img](/img/mvx-max-fee.png) + +While sometimes when the Ethereum Network gets crowded the fee of a single transaction could easily jump over 47$, the fee of a single transaction on MultiversX in the last year barely reached 34.75$. Similar to the first histogram, the prices you see in the one with maximum transaction fee each month are in MultiversX’s native coin EGLD. + +## Token standards + +When it comes to tokens, Ethereum comes with a couple standards that ensure the smart contracts composability. Most of them are well known: + +- `ERC-20` - a standard for fungible & interchangeable tokens such as voting tokens, staking tokens or virtual currencies +- `ERC-721` - a standard for non-fungible tokens such as deeds for artwork or songs +- `ERC1155` - a standard for fungible tokens, non-fungible tokens or other configurations such as semi-fungible tokens used for more efficient trades and bundling of transactions + +On the other hand MultiversX comes with a standard called `ESDT` (eStandard Digital Token) used to manage fungible, non-fungible and semi-fungible tokens at protocol level. The important aspect about MultiversX tokens is that they don’t require a dedicated smart contract for issuing. The direct implication of this element is that custom tokens are as fast and as scalable as the native EGLD token itself. Furthermore, the protocol employs the same handling mechanism for ESDT transactions across shards as the mechanism used for the EGLD token. + +Technically, the balances of ESDT tokens held by an account are stored directly under the data trie of that account. It also implies that an account can hold balances of any number of custom tokens, in addition to the native EGLD balance. The protocol guarantees that no account can modify the storage of ESDT tokens, neither its own nor of other accounts. + +## Transactions in MultiversX + +We decided to model all transactions using a single object, but with exactly 7 generic arguments, one for each of the transaction fields. + +| Field | Description | +| :-------------: | :-------------------------------------------------------------------------------------------------------: | +| Environment | Some representation of the environment where the transaction runs. | +| From | The transaction sender. Implicit for SC calls (the contract is the sender), but mandatory anywhere else. | +| To | The receiver. Needs to be specified for any transaction except for deploys. | +| Payment | Optional, can be EGLD, single or multi-ESDT. We also have some payment types that get decided at runtime. | +| Gas | Some transactions need explicit gas, others don't. | +| Data | Proxies (ideally) or raw | +| Result Handlers | Anything that deals with results, from callbacks to decoding logic. | + +Each one of these 7 fields has a trait that governs what types are allowed to occupy the respective position. + +All of these positions (except the environment Env) can be empty, uninitialized. This is signaled at compile time by the unit type, (). In fact, a transaction always starts out with all fields empty, except for the environment. + +For instance, if we are in a contract and write `self.tx()`, the universal start of a transaction, the resulting type will be `Tx, (), (), (), (), (), ()>`, where `TxScEnv` is simply the smart contract call environment. Of course, the transaction at this stage is unusable, it is up to the developer to add the required fields and send it. + +In its most basic form, a transaction might be constructed as follows: + +```rust +self.tx() + .from(from) + .to(to) + .payment(payment) + .gas(gas) + .raw_call("function") + .with_result(result_handler) +``` + +Ultimately, the purpose of a transaction is to be executed. Simply constructing a transaction has no effect in itself. So we must finalize each transaction construct with a call that sends it to the blockchain. + +In SpaceCraftSDK the transaction syntax is consistent, the only element differing being the execution. More specifically: + +In a contract, the options are `.transfer()`, `.transfer_execute()`, `.async_call_and_exit()`, `.sync_call()`, etc. +In unit tests written in Rust just `.run()` is universal. +In interactors, we call `.run().await`. It's the same method name, but this time it's asynchronous Rust, so it can only be called in an `async` function, and `.await` is necessary. + +Let's dive into an example! + + +In Ethereum a transfer requires a couple of implementations of some functions of a ERC721 standard. + +```typescript +import "./erc721.sol"; + + function _transfer(address _from, address _to, uint256 _tokenId) private { + balance[_to]++; + balance[_from]--; + tokenOwner[_tokenId] = _to; + emit Transfer(_from, _to, _tokenId); + } + + function transferFrom(address _from, address _to, uint256 _tokenId) external payable { + _transfer(_from, _to, _tokenId); + } +``` + +In MultiversX making a token transfer is done simply without any other requirements as follows: + +```rust +#[payable("*")] +#[endpoint] +fn transfer(&self, to_address: ManagedAddress) { + let payment= self.call_value().single_esdt(); + self.tx() + .to(to_address) + .single_esdt(&payment.token_identifier, payment.token_nonce, &payment.amount) + .transfer(); +} +``` +An essential element of the MultiversX smart contract is that every smart contract is also an account, just like a normal user, meaning that it also has an address and a balance and it can perform transactions. From this perspective we can say that transfers in MultiversX are always done from the address of the contract, and in that case, before the transaction the tokens have to be owned by the contract. + +## Smart contract differences + +# Upgradable contracts + +Just as in Ethereum a contract has an initiator, in MultiversX as an init function, and if needed also an upgrade, allowing the builder to define behavior when the contract gets upgraded. The upgrade function is similar to the init function, but named accordingly and preceded by the `#[upgrade]` annotation. + +# Mapping + +As storage mappers were previously mentioned, let's talk about mapping. +In a Ethereum contract as many times you would like to simply map an address on its balance, element simply done by `mapping(address => uint) public balances`. + +For a MultiversX contract would translate to a storage mapper such as: + +```rust +#[storage_mapper("balance")] +fn balance(&self, address:ManagedAddress) -> SingleValueMapper; +``` +In this syntax we mapped to an address a storage of a single value type containing a BigUint representing the balance. The greatest part of this is that if at one point we want this balance to be accessible public for viewing the storage mapper can just be preceded by an annotation such as `#[view(getBalance)]` allowing anyone that queries this mapper to see the balance of a given address. + +Furthermore the balance can be easily set in any endpoint by calling `self.balance(my_address).set(my_value);` + +## Building an EVM project on MultiversX + +Lets take a simple voting contract in Solidity: + +```typescript +pragma solidity ^0.6.4; + +contract Voting { + + mapping (bytes32 => uint256) public votesReceived; + bytes32[] public candidateList; + + constructor(bytes32[] memory candidateNames) public { + candidateList = candidateNames; + } + + function voteForCandidate(bytes32 candidate) public { + require(validCandidate(candidate)); + votesReceived[candidate] += 1; + } + + function totalVotesFor(bytes32 candidate) view public returns (uint256) { + require(validCandidate(candidate)); + return votesReceived[candidate]; + } + + function validCandidate(bytes32 candidate) view public returns (bool) { + for(uint i = 0; i < candidateList.length; i++) { + if (candidateList[i] == candidate) { + return true; + } + } + return false; + } +} +``` + +With this model in mind, the MultiversX implementation would look like: + +```rust +#![no_std] + +#[allow(unused_imports)] +use multiversx_sc::imports::*; + +#[multiversx_sc::contract] +pub trait Voting { + #[init] + fn init(&self, candidates: ManagedVec) { + for candidate in &candidates { + self.candidates().insert(candidate); + } + } + + #[endpoint] + fn vote_for_candidate(&self, candidate: ManagedBuffer) { + require!( + self.candidates().contains(&candidate), + "Name not in candidate list" + ); + self.votes_received(&candidate).update(|votes| *votes += 1); + } + + #[view(getCandidates)] + #[storage_mapper("candidates")] + fn candidates(&self) -> UnorderedSetMapper; + + #[view(getvotesReceived)] + #[storage_mapper("votesReceived")] + fn votes_received(&self, candidate: &ManagedBuffer) -> SingleValueMapper; +} +``` + +Notice that the candidates from the Solidity contract in MultiversX’s contract translate into a storage mapper of type `UnorderedSetMapper`. This mapper was specially designed to allow easy and cheap access to a list of elements stored within at the cost of its ordering. Checking whether a candidate exists within the list or not becomes simple by just calling `self.candidates().contains(&candidate)`. + +Next we have the increase of the number of votes which is done by `self.votes_received(&candidate).update(|votes| *votes += 1);`. Here update receives a lambda allowing a total control of the contents of the storage. + +The implementation of totalVotesFor becomes redundant in MultiversX thanks to the `#[view(getvotesReceived)]` annotation which gives our votes_received storage view access on blockchain. + +## Interactors + +While Ethereum implemented its own [tool](https://remix.ethereum.org/) to facilitate deployment and interaction with smart contracts, the developers at MultiversX went on an approach facilitating these elements from the same IDE, within the same framework, and by writing mostly the same Rust syntax as in the smart contract itself. + +For MultiversX, the Interactors have multiple purposes, besides just helping you deploy a smart contract on the blockchain. These interactors work side by side with another tool developed by MultiversX developers called [sc-meta](https://docs.multiversx.com/developers/meta/sc-meta/) which facilitates smart contract building and proxies and interactor code auto-generation. + +By using sc-meta one can easily be straight up ready to interact with his smart contract by generating his interactor with `sc-meta all snippets`. + +Let’s take the Adder contract presented at the beginning of this tutorial. By generating its interactor we would obtain something like this: + +```rust +#[tokio::main] +async fn main() { + env_logger::init(); + + let mut args = std::env::args(); + let _ = args.next(); + let cmd = args.next().expect("at least one argument required"); + let mut interact = ContractInteract::new().await; + match cmd.as_str() { + "deploy" => interact.deploy().await, + "getSum" => interact.sum().await, + "add" => interact.add().await, + _ => panic!("unknown command: {}", &cmd), + } +} +``` + +This interactor code now allows us to simply interact with the contract straight up from the command line. +Making a deploy is easily done by simply calling `interactor % cargo run deploy`. By running this deploy command a new `state.toml` file will be generated containing the newly deployed address. + +As you might see in the interactor code, all the expoints of the smart contract are easy to call in the same manner as the deploy. + +## BackTransfer + +One great Feature, the developers of MultiversX came with was due an element regarding its old VM. Back then if a parent SC called a child SC and the child SC transferred back tokens to the parent, it must have called a “deposit” function and the parent SC had to register that deposit into a storage. After the child SC execution was finished, the execution of parent SC was resumed and where we had to read from the storage, what kind of transfers the child did towards itself. + +Another problem with this, is if the parent SC was not payable by SC and the child SC did not call deposit, only transfers, the execution would have failed. This was even more complicated in asynchronous calls. It was discovered that if a child SC is not well written, a malicious parent SC can become problematic for the child SC. One example was the liquid staking contracts, where unbonded funds could get lost because a malicious parent SC was not payable by SC. + +So MultiversX decided to change its paradigm: When a parent SC calls a child SC and the child SC does a set of transfers to the parent, the payable check is skipped for the parent. The check is skipped even if the parent SC called the childSC with asyncCall and even if the child SC is in another shard compared to the parent SC. +Technically speaking: All transfers without execution from the child SC to the parent SC are accumulated in a new structure called `backTransfers`. In the end the parent SC can do the following ` = ExecuteOnDest(child)`. This makes DeFi lego blocks easier to create. + +The accumulated backTransfers can be read by the parent SC by calling the `managedGetBackTransfers VM API`. + +--- + +### events + +This page describes the structure of the `events` index (Elasticsearch), and also depicts a few examples of how to query it. + + +## _id + +The `_id` field for this index is composed of hex-encoded hash of the transaction or the smart contract result that generated the log plus shard ID and order of event + +example: `{hash}-{shardID}-{order}` (example: `abcd-2-1`). + + +## Fields + +| Field | Description | +|----------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| logAddress | The address field holds the address in bech32 encoding. It can be the address of the smart contract that generated the log or the address of the receiver of the transaction. | +| address | The address field holds the address in bech32 encoding. It can be the address of the smart contract that generated the event or the address of the receiver of the transaction. | +| txHash | The txHash field field for this index is composed of hex-encoded hash of the transaction or the smart contract result that generated the log. | +| originalTxHash | The originalTxHash field holds the hex-encoded hash of the initial transaction. When this field is not empty the log is generated by a smart contract result and this field represents the hash of the initial transaction. | +| timestamp | The timestamp field represents the timestamp of the block in which the log was generated. | +| identifier | This field represents the identifier of the event. | +| topics | The topics field holds a list with extra information, hex-encoded. They don't have a specific order because the smart contract is free to log anything that could be helpful. | +| data | The data field can contain information added by the smart contract that generated the event, hex-encoded. | +| order | The order field represents the index of the event indicating the execution order. | +| txOrder | The txOrder field represents the execution order of transaction/smart contract who generated this event. | +| timestamp | The timestamp field represents the timestamp of the block in which the event was generated. | +| shardID | The shardID field represents the shard this events belongs to. | + + +## Query examples + +### Fetch all the events generated by a transaction + +``` +curl --request GET \ + --url ${ES_URL}/events/_search \ + --header 'Content-Type: application/json' \ + --data '{ + "query": { + "match": { + "txHash":"d6.." + } + } +}' +``` + +### Fetch all the events generated by a transaction and the smart contract results triggered by it + +``` +curl --request GET \ + --url ${ES_URL}/events/_search \ + --header 'Content-Type: application/json' \ + --data '{ + "query": { + "bool": { + "should": [ + { + "match": { + "_id":"d6.." + } + }, + { + "match": { + "originalTxHash": "d6.." + } + } + ] + } + } +}' +``` + +--- + +### Events notifier + +## Overview + +A MultiversX observer node can push block events to a notifier service, which will process +and forward the events to subscribers (via RabbitMQ or WebSocket Client). This way, one can subscribe to a RabbitMQ +queue (or WebSocket connection) and receive block events, whenever a block is committed to the chain, instead of +polling an API frequently. + +The GitHub repository for the notifier service can be found [here](https://github.com/multiversx/mx-chain-notifier-go). + + +## Architectural Overview + +The observer node in the network will be connected to notifier service. +The observer will send block events to notifier. The notifier service will +receive and filter events, it will apply deduplication if enabled, and it will push the events +to RabbitMQ instance, or to WebSocket subscribers. + +:::important +Set up at least one observer for each shard in order to handle all the events in the chain. +::: + +![img](/technology/notifier-overview.png) + +In the figure above: +- The observer nodes will push block events to Notifier instance, via WebSocket or HTTP requests. There are several endpoints/topics for this: + - `block events` -> it will handle all events for each round + - `revert events` -> if there is a reverted block, the event will be + pushed on this route + - `finalized events` -> when the block has been finalized, the events + will be pushed on this route +- Notifier checks locker service (via Redis) and applies deduplication +- Notifier will push events to RabbitMQ if enabled, or via Websocket. If Websocket will be enabled an additional endpoint will be available: + - `/hub/ws` (GET) - this route can be used to manage the websocket connection subscription + +:::info +Please make the distinction between `observer node` -> `notifier` communication (which can be via HTTP or WebSocket) and +`notifier` -> `subscriber` (which can be via RabbitMQ or WebSocket). +::: + + +## Set up observer client + +On the observer side, there is a WebSocket client that will push block events to notifier service. +There is also the HTTP Integration, which will be deprecated in the future. + +In the observer node's configuration directory, the `external.toml` config file can be configured +to enable events notifier connector via WebSocket or via HTTP integrations. The config file can be found +[here](https://github.com/multiversx/mx-chain-go/blob/master/cmd/node/config/external.toml). + +:::tip +For setting up an observer client, make sure to also check the +[README](https://github.com/multiversx/mx-chain-notifier-go?tab=readme-ov-file#prerequisites) +instructions within the events notifier source code repository. +::: + + +### WebSocket Integration + +The WebSocket integration is a generic one, and can be used for multiple outport driver integrations. +In case Elasticsearch integration is already being used with WebSocket connector, a separate config +section `HostDriversConfig` has to be set for event notifier. + +The corresponding config section for enabling the WebSocket driver on observer node: + +```toml +[[HostDriversConfig]] + # This flag shall only be used for observer nodes + Enabled = true + + # This flag will start the WebSocket connector as server or client (can be "client" or "server") + Mode = "client" + + # URL for the WebSocket client/server connection + # This value represents the IP address and port number that the WebSocket client or server will use to establish a connection. + URL = "127.0.0.1:22111" + + # After a message will be sent it will wait for an ack message if this flag is enabled + WithAcknowledge = true + + # The duration in seconds to wait for an acknowledgment message, after this time passes an error will be returned + AcknowledgeTimeoutInSec = 60 + + # This flag defines the marshaller type. Currently supported: "json", "gogo protobuf" + MarshallerType = "gogo protobuf" + + # The number of seconds when the client will try again to send the data + RetryDurationInSec = 5 + + # Sets if, in case of data payload processing error, we should block or not the advancement to the next processing event. Set this to true if you wish the node to stop processing blocks if the client/server encounters errors while processing requests. + BlockingAckOnError = true + + # Set to true to drop messages if there is no active WebSocket connection to send to. + DropMessagesIfNoConnection = false + + # Defines the payload version. Version will be changed when there are breaking + # changes on payload data. The receiver/consumer will have to know how to handle different + # versions. The version will be sent as metadata in the websocket message. + Version = 1 +``` + +In this case, observer node will act as client and events notifier service will act as a server. +`Mode` option should be set to `client`. It is important to have `WithAcknowledge` set to `true` +since observer node should continue only if there is an acknowledge that the event was processed +successfully. `MarshallerType` field has to be aligned with `DataMarshallerType` on events notifier +configuration file. + + +### HTTP Integration + +For http integration, the supported config variables are as follows: + +- `Enabled`: signals whether a driver should be attached when launching the node. +- `UseAuthorization`: signals whether to use authorization. For testing purposes it can be set to `false`. +- `ProxyUrl`: host and port on which the `eventNotifier` will push the parsed event data. +- `Username`: the username used for authorization, if enabled. +- `Password`: the password used for authorization, if enabled. + +The corresponding config section for enabling the driver on observer node: + +```toml +[EventNotifierConnector] + # Enabled will turn on or off the event notifier connector + Enabled = true + + # UseAuthorization signals the proxy to use authorization + # Never run a production setup without authorization + UseAuthorization = false + + # ProxyUrl is used to communicate with the subscriptions hub + # The indexer instance will broadcast data using ProxyUrl + ProxyUrl = "http://localhost:5000" + + # Username and Password need to be specified if UseAuthorization is set to true + Username = "" + + # Password is used to authorize an observer to push event data + Password = "" + + # RequestTimeoutSec defines the timeout in seconds for the http client + RequestTimeoutSec = 60 + + # MarshallerType is used to define the marshaller type to be used for inner + # marshalled structures in block events data + MarshallerType = "json" +``` + +:::info +HTTP Integration will be deprecated in the future. +::: + +:::tip +Due to the possible high data volume, it's not recommended to use validators as nodes +to push events to Notifier Service. +Similar to Elasticsearch indexing, our implementation uses a concept of a queue and makes +sure that everything is being processed. Consensus and synchronization mechanisms can have +delays due to outport driver. +::: + + +## Set up notifier service + +In the notifier configuration directory (`cmd/notifier/config`), there is the `config.toml` +file that can be used to configure the service. + +:::tip +For setting up an events notifier service, make sure to also check the +[README](https://github.com/multiversx/mx-chain-notifier-go?tab=readme-ov-file#prerequisites) +instructions within the events notifier source code repository. +::: + +There are some general configuration options, which should be fine with their default values: +```toml +[General] + # CheckDuplicates signals if the events received from observers have been already pushed to clients + # Requires a redis instance/cluster and should be used when multiple observers push from the same shard + CheckDuplicates = true + + # ExternalMarshaller is used for handling incoming/outcoming api requests + [General.ExternalMarshaller] + Type = "json" + # InternalMarshaller is used for handling internal structs + # This has to be mapped with the internal marshalling used for notifier outport driver + [General.InternalMarshaller] + Type = "json" + + # Address pubkey converter config options + [General.AddressConverter] + Type = "bech32" + Prefix = "erd" + Length = 32 +``` + +:::info +Starting with release `v1.2.0`, `CheckDuplicates` field has been moved from `ConnectorApi` section to +the newly added `General` section. Please make sure to put `CheckDuplicates` field before other inner +struct fields in `General` section. +::: + +There are 2 ways to connect observer node with events notifier service: +- via WebSocket integration +- via HTTP integration (which will be deprecated in the future) + + +### WebSocket Integration {#notifier-websocket-integration} + +There is a separate config section `WebSocketConnector` that has to be aligned with +`HostDriversConfig` from observer node. + +```toml +[WebSocketConnector] + # Enabled will determine if websocket connector will be enabled or not + Enabled = false + + # URL for the WebSocket client/server connection + # This value represents the IP address and port number that the WebSocket client or server will use to establish a connection. + URL = "localhost:22111" + + # This flag describes the mode to start the WebSocket connector. Can be "client" or "server" + Mode = "server" + + # Possible values: json, gogo protobuf. Should be compatible with mx-chain-node outport driver config + DataMarshallerType = "gogo protobuf" + + # Retry duration (receive/send ack signal) in seconds + RetryDurationInSec = 5 + + # Signals if in case of data payload processing error, we should send the ack signal or not + BlockingAckOnError = false + + # Set to true to drop messages if there is no active WebSocket connection to send to. + DropMessagesIfNoConnection = false + + # After a message will be sent it will wait for an ack message if this flag is enabled + WithAcknowledge = true + + # The duration in seconds to wait for an acknowledgment message, after this time passes an error will be returned + AcknowledgeTimeoutInSec = 60 +``` + + +### HTTP Integration {#websocket-http-integration} + +The supported config variables are: +- `Host`: the address and/or port on which the http server listens on. Should be the same + port in the `ProxyUrl` described above, for observer node. +- `Username`: the username used to authorize an observer. Can be left empty for `UseAuthorization = false`. +- `Password`: the password used to authorize an observer. Can be left empty for `UseAuthorization = false`. +- `CheckDuplicates`: if true, it will check (based on a locker service using redis) if the event have been already pushed to clients + +The `ConnectorApi` section has to be aligned with the one from observer node: +```toml +[ConnectorApi] + # Enabled will determine if http connector will be enabled or not + Enabled = true + + # The address on which the events notifier listens for subscriptions + # It can be specified as "localhost:5000" or only as "5000" + Host = "5000" + + # Username and Password needed to authorize the connector + # BasicAuth is enabled only for the endpoints with "Auth" flag enabled + # in api.toml config file + Username = "" + Password = "" +``` + +:::info +Starting with release `v1.2.0`, an additional field `Enabled = true` has been added. +::: + + +### Deduplication + +If `CheckDuplicates` is set to `true` in events notifier main config file, +the notifier service will try to connect to a **redis** instance. +In this context, redis will be used as a locker service mechanism for deduplication. +This is useful in scenarios when multiple observer nodes from same shard are used to send +events to the same notifier instance. + +The `Redis` section includes the following parameters as described below: + +```toml +[Redis] + # The url used to connect to a pubsub server + Url = "redis://localhost:6379/0" + + # The master name for failover client + MasterName = "mymaster" + + # The sentinel url for failover client + SentinelUrl = "localhost:26379" + + # The redis connection type. Options: | instance | sentinel | + # instance - it will try to connect to a single redis instance + # sentinel - it will try to connect to redis setup with master, slave and sentinel instances + ConnectionType = "sentinel" + + # Time to live (in minutes) for redis lock entry + TTL = 30 +``` + +The `redis` service has to be configured separately. +For more details on notifier service redis setup, please follow the **Install** and **Launching** +sections from [README](https://github.com/multiversx/mx-chain-notifier-go) in the repository. +There is also an [example](https://github.com/multiversx/mx-chain-notifier-go/blob/main/docker-compose.yml) +on how to run a setup with redis locally (for development) with docker-compose. + + +## Subscribers + +Currently there are 2 supported subscribing solutions: +* RabbitMQ +* WebSocket + +The subscribing solution is selected based on a CLI parameter, please check +[README](https://github.com/multiversx/mx-chain-notifier-go) from +github repository for more info on the CLI parameters. + + +### RabbitMQ + +In the notifier configuration directory (`cmd/notifier/config`), in `config.toml` there is +a separate section `RabbitMQ`, which can be used to set up rabbitMQ connection url and +exchanges. The exchanges will be created automatically (if they are not already created) on +notifier service start. + +```toml +[RabbitMQ] + # The url used to connect to a rabbitMQ server + Url = "amqp://guest:guest@localhost:5672" + + # The exchange which holds all logs and events + [RabbitMQ.EventsExchange] + Name = "all_events" + Type = "fanout" + + # The exchange which holds revert events + [RabbitMQ.RevertEventsExchange] + Name = "revert_events" + Type = "fanout" + + ... +``` + +:::tip +It is recommended to use the setup with RabbitMQ, if it is very important to avoid losing any event. +::: + + +### WebSocket + +If WebSocket subscribing solution is selected via CLI parameter, an additional HTTP +endpoint `/hub/ws/` will be available for sending subscriptions. + +There are more notes +on how to send subscriptions and how to consume events +[here](https://github.com/multiversx/mx-chain-notifier-go?tab=readme-ov-file#websockets). + +Please check also events section [below](#events) on how a WS event is constructed. + +:::info +Please make the distinction between `observer node` -> `notifier` communication which can be done via `WebSocket` and the +`WebSocket` subscribing solution which is a different setup from the one presented [above](#notifier-websocket-integration) +::: + + +## Events + +There are multiple event types: +- `Push Block event`: when the block is committed, it contains logs and events +- `Revert Block event`: when the block is reverted +- `Finalized Block event`: when the block is finalized + +In RabbitMQ there is a separate exchange for each event type. +In Websocket setup, there is a event type field in each message. + +The WS event is defined as follows: + +| Field | Description | +|------------|--------------------------------------------------------------------------------| +| Type | The type field defines the event type, it can be one of the following: `all_events`, `revert_events`, `finalized_events`. `all_events` refers to all logs and events. | +| Data | Serialized data corresponding to the event type. | + + +### Push Block Event + +Each time a block is committed on the chain, an event will be triggered with basic information +on the block together with logs and events. The structure data fields are as following: + +Push Block Event + +| Field | Description | +|----------------|-----------------------------------------------------------| +| hash | The hash field represents the hash of the committed block. | +| events | The events field holds a list of events. | + +Event structure + +| Field | Description | +|-------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| identifier | This field represents the identifier of the event. | +| address | The address field holds the address in bech32 encoding. It can be the address of the smart contract that generated the event or the address of the receiver address of the transaction. | +| topics | The topics field holds a list with extra information. They don't have a specific order because the smart contract is free to log anything that could be helpful. | +| data | The data field can contain information added by the smart contract that generated the event. | +| order | The order field represents the index of the event indicating the execution order. | + + +### Revert Block Event + +When there is a revert for a particular block on the chain, a revert event will be triggered, +containing basic info on the block. + +| Field | Description | +|------------|--------------------------------------------------------------------------------| +| hash | The hash field represents the hash of the committed block. | +| nonce | The nonce field represents the sequence number of the block. | +| round | The round field represents the round when the block was proposed and executed. | +| epoch | The epoch field represents the epoch when the block was proposed and executed. | + + +### Finalized Block Event + +When a block is completely finalized, including intra-shard transactions, a finalized event will +be triggered containing the hash of the block. + +| Field | Description | +|------------|--------------------------------------------------------------------------------| +| hash | The hash field represents the hash of the committed block. | + +--- + +### Execution Events + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +The execution events are generated in case of transaction with smart contract call or ESDT transaction or built-in function calls. + + +### Complete transaction event + +The `completedTxEvent` event is generated when the execution of a transaction is completed, +indicating that the generated smart contract results have also been executed. + + + + +| Field | Value | +|------------|------------------------------------------------------------------------------------------------------------------------| +| identifier | completedTxEvent | +| address | the address that emitted the event | +| topics | empty | +| data | empty | + + + + +```json +{ + "identifier": "completedTxEvent", + "address": "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u", + "topics": [], + "data": null +} +``` + + + + + +### Informative event + +An informative event is generated after the execution of a smart contract call transaction, +which includes data about the execution. + + + + + +| Field | Value | +|------------|------------------------------------------------------------------------------------------------------------------------| +| identifier | writeLog | +| address | the address that emitted the event | +| topics | empty | +| data | returned code after execution base64 encoded | + + + + +```json +{ + "identifier": "writeLog", + "address": "erd1272et87h3sa7hlg5keuswh50guz2ngmd6lhmjxkwwu0ah6gdds5qhka964", + "topics": [], + "data": "QDZmNmI=" +} +``` + + + + + + +### Signal error event + +The `signalError` event is generated when a transaction involving a smart contract call has +been executed but encountered an error in the process. + + + + + +| Field | Value | +|------------|------------------------------------------------------------------------------------------------------------------------| +| identifier | signalError | +| address | the address that emitted the event | +| topics | `topics[0]` - caller address bytes base64 encoded
`topics[1]` - the execution error message base64 encoded | +| data | returned code after execution base64 encoded | + +
+ + +```json +{ + "identifier": "signalError", + "address": "erd1qqqqqqqqqqqqqpgqhdj69t0f687rl85s5gd4x6r2f4vu6qwcs70qevwuqx", + "topics": [ + "+s3dM1epYprNjMgbgJ2P80NvWk+jc3zddU+93t8oOow=", + "RW5kcG9pbnQgY2FuIG9ubHkgYmUgY2FsbGVkIGJ5IG93bmVy" + ], + "data": "QDc1NzM2NTcyMjA2NTcyNzI2Zjcy" +} +``` + + +
+ +### Internal errors event + +The `internalVMErrors` event is generated when a transaction involving multiple smart contract calls has +been executed but encountered an error in the process. + + + + + +| Field | Value | +|------------|------------------------------------------------------------------------------------------------------------------------| +| identifier | internalVMErrors | +| address | the address that emitted the event | +| topics | `topics[0]` - caller address bytes base64 encoded
`topics[1]` - the execution error message base64 encoded | +| data | a message with more details about the encountered error base64 encoded | + +
+ + +```json +{ + "identifier": "internalVMErrors", + "address": "erd1ltxa6v6h493f4nvveqdcp8v07dpk7kj05deheht4f77aaheg82xq680mlx", + "topics": [ + "AAAAAAAAAAAFALtloq3p0fw/npCiG1Noak1ZzQHYh54=", + "YnV5Q2FyZHM=" + ], + "data": "CglydW50aW1lLmdvOjExNzIgW2Vycm9yIHNpZ25hbGxlZCBieSBzbWFydGNvbnRyYWN0XSBbYnV5Q2FyZHNdCglydW50aW1lLmdvOjExNzIgW2Vycm9yIHNpZ25hbGxlZCBieSBzbWFydGNvbnRyYWN0XSBbYnV5Q2FyZHNdCglydW50aW1lLmdvOjExNjkgW0VuZHBvaW50IGNhbiBvbmx5IGJlIGNhbGxlZCBieSBvd25lcl0=" +} +``` + + +
+ +### Transfer value only event + +The `transferValueOnly` event is generated when a transaction involving multiple smart contract calls has been executed, +and during the execution, some EGLD tokens were transferred from one smart contract to another. + + + + + +| Field | Value | +|------------|------------------------------------------------------------------------------------------------------------------------| +| identifier | transferValueOnly | +| address | the destination address | +| topics | `topics[0]` - sender address bytes base64 encoded
`topics[1]` - destination address bytes base64 encoded
`topics[2]` - transferred value bytes base64 encoded | +| data | empty | + +
+ + +```json +{ + "identifier": "transferValueOnly", + "address": "erd1qqqqqqqqqqqqqpgqd77fnev2sthnczp2lnfx0y5jdycynjfhzzgq6p3rax", + "topics": [ + "AAAAAAAAAAAFADz3VNyYTcz4h9Y/rLRPPr0jIp5kL5w=", + "AAAAAAAAAAAFAG+8meWKgu88CCr80meSkmkwSck3EJA=", + "9DzAfFqMRw==" + ] + "data": "", +} +``` + + +
+ + +:::important Important +From the `rc/v1.6.0` release onward, the `transferValueOnly` will undergo a modification once the `ScToScLogEventEnableEpoch` flag is activated as follows: + + + + + +| Field | Value | +|------------|---------------------------------------------------------------------------------------| +| identifier | transferValueOnly | +| address | the sender address | +| topics | `topics[0]` - transferred value bytes base64 encoded
`topics[1]` - destination address bytes base64 encoded | +| data | empty | + +
+ + +```json +{ + "identifier": "transferValueOnly", + "address": "erd1qqqqqqqqqqqqqpgqd77fnev2sthnczp2lnfx0y5jdycynjfhzzgq6p3rax", + "topics": [ + "AQ==", + "rwBuzoNHMQTqkff/VgXEwXQvchSh9GvimeMO4uhwcWk=" + ], + "data": null +} +``` + + +
+ +::: + +--- + +### Extend to Any Token + +Generalize the crowdfunding contract to accept any fungible token instead of just EGLD. + + +## Introduction + +In [Part 2](crowdfunding-p2.md), we built a complete crowdfunding contract that accepts EGLD. The `cf_token_id()` storage mapper was initialized to `TokenId::egld()` in the init function. In this chapter, we'll make it configurable so the contract can accept any fungible token (EGLD or ESDT), specified at deployment time. + +This demonstrates an important principle in smart contract design: making contracts configurable and reusable for different use cases. + + +## Making the Token Identifier Configurable + +The `cf_token_id()` storage mapper already exists from Part 2. We just need to update the `init` function to accept a token identifier as a parameter instead of hardcoding it: + +```rust +#[init] +fn init(&self, token_identifier: TokenId, target: BigUint, deadline: TimestampMillis) { + require!(token_identifier.is_valid(), "Invalid token provided"); + self.cf_token_id().set(token_identifier); + + require!(target > 0, "Target must be more than 0"); + self.target().set(target); + + require!( + deadline > self.get_current_time_millis(), + "Deadline can't be in the past" + ); + self.deadline().set(deadline); +} + +fn get_current_time_millis(&self) -> TimestampMillis { + self.blockchain().get_block_timestamp_millis() +} +``` + +The key changes: + +1. **New parameter**: `token_identifier: TokenId` is now the first parameter +2. **Validation**: We validate that the token identifier is valid before storing it +3. **Configuration**: Instead of `self.cf_token_id().set(TokenId::egld())`, we now use the provided parameter + + +## Updating the Fund Endpoint + +The `fund` endpoint from Part 2 already validates the token identifier against `cf_token_id()`, so it automatically works with any configured token. We only need to add one more check to ensure that only fungible tokens (not NFTs) are accepted: + +```rust +#[endpoint] +#[payable] +fn fund(&self) { + let payment = self.call_value().single(); + + require!( + payment.token_identifier == self.cf_token_id().get(), + "wrong token" + ); + require!(payment.is_fungible(), "only fungible tokens accepted"); + + require!( + self.status() == Status::FundingPeriod, + "cannot fund after deadline" + ); + + let caller = self.blockchain().get_caller(); + self.deposit(&caller) + .update(|deposit| *deposit += payment.amount.as_big_uint()); +} +``` + +The only new line is the fungible check. Everything else works automatically because `cf_token_id().get()` now returns the configured token instead of always returning EGLD. + + +## Other Methods + +The `get_current_funds()`, `status()`, and `claim()` methods from Part 2 work automatically with the configurable token because they all use `cf_token_id().get()`, which now returns the configured token instead of hardcoded EGLD. + + +## Testing with Different Tokens + +Now that our contract is token-agnostic, we can test it with both EGLD and ESDT tokens. The key difference is in the deployment - we pass different token identifiers: + +**For EGLD:** +```rust +.init(TokenId::native(), 2_000u32, CF_DEADLINE) +``` + +**For ESDT:** +```rust +.init(CF_TOKEN_ID, 2_000u32, CF_DEADLINE) +``` + +The complete test files demonstrate this: + +- [EGLD Test File](final-code.md#complete-blackbox-test-egld) +- [ESDT Test File](final-code.md#complete-blackbox-test-esdt) + +Key differences in the ESDT test: + +1. **Token setup**: Accounts are given ESDT balances instead of EGLD +2. **Deployment**: Contract is initialized with an ESDT token identifier +3. **Funding**: Uses `.payment(Payment::new(...))` instead of `.egld()` +4. **Balance checks**: Uses `.esdt_balance()` instead of `.balance()` + +Both test files verify the same functionality (successful funding, failed funding, wrong token rejection), proving the contract works identically regardless of the token type! + + +## Rebuild and Test + +Don't forget to rebuild and test: + +```bash +sc-meta all build +sc-meta all proxy +sc-meta test +``` + + +## What We Achieved + +By making these changes, we've: + +1. ✅ Made the contract **configurable** - it can work with any fungible token +2. ✅ Improved **type safety** with `TokenId` and `TimestampMillis` +3. ✅ Added **proper validation** for token types (fungible only) +4. ✅ Used modern **transaction syntax** for transfers +5. ✅ Maintained **backward compatibility** - EGLD still works perfectly + +The contract is now more flexible and can be deployed multiple times with different tokens, making it truly reusable! + + +## Next Steps + +- **View the complete code**: Check out the [final contract code](final-code.md) with all the improvements +- **Explore more examples**: Visit the [MultiversX contracts repository](https://github.com/multiversx/mx-contracts-rs) for more advanced patterns +- **Continue learning**: Try other [tutorials](/developers/tutorials/your-first-dapp) to expand your MultiversX development skills + +--- + +### Extending sdk-js + +This tutorial will guide you through the process of extending and tailoring certain modules from sdk-js. + +:::important +Documentation in this section is preliminary and subject to change. +::: + + +## Extending the Network Providers + +The default classes from `@multiversx/sdk-core/networkProviders` should **only be used 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). + + +### Extending a default Network Provider + +You can derive from the default network providers (`ApiNetworkProvider` and `ProxyNetworkProvider`) and overwrite / add additional methods, making use of `doGetGeneric()` and `doPostGeneric()`. For example: + +```js +export class MyTailoredNetworkProvider extends ApiNetworkProvider { + async getEconomics(): Promise<{totalSupply: number, marketCap: number}> { + let response = await this.doGetGeneric("economics"); + return { totalSupply: response.totalSupply, marketCap: response.marketCap } + } + + // ... other methods +} +``` + + +## Customizing the transaction awaiting + +If, for some reason, the default transaction completion detection algorithm provided by **sdk-js** does not satisfy your requirements, you may want to use a different strategy for transaction awaiting, such as: + +```js +await transactionWatcher.awaitAllEvents(txHash, ["mySpecialEventFoo", "mySpecialEventBar"]); +await transactionWatcher.awaitAnyEvents(txHash, ["mySpecialEventFoo", "mySpecialEventBar"]); +``` + +--- + +### Final Code + +Complete crowdfunding smart contract implementation with all features. + +This page provides the complete, final version of the crowdfunding smart contract developed throughout the tutorial. This implementation includes all the features covered in [Part 1](crowdfunding-p1.md), [Part 2](crowdfunding-p2.md), and [Part 3](crowdfunding-p3.md). + + +## Overview + +The final crowdfunding smart contract includes: + +- **Initialization**: Sets up the token identifier, target amount, and deadline +- **Fund endpoint**: Accepts token payments from donors during the funding period +- **Claim endpoint**: Allows the owner to claim funds if successful, or donors to get refunds if failed +- **Status view**: Returns the current campaign status (FundingPeriod, Successful, or Failed) +- **Storage**: Tracks target amount, deadline, deposits per donor, and token identifier + + +## Contract Features + +### Status Enum + +The contract uses a custom `Status` enum to represent the three possible states of a crowdfunding campaign: + +- **FundingPeriod**: The campaign is still accepting donations (before the deadline) +- **Successful**: The deadline has passed and the target amount was reached +- **Failed**: The deadline has passed but the target amount was not reached + +### Key Methods + +- **`init`**: Initializes the contract with a token identifier, target amount, and deadline. Includes validation to ensure the token is valid, the target is greater than zero, and the deadline is in the future. + +- **`fund`**: Allows users to contribute tokens to the campaign. Validates that the correct token is being sent, that only fungible tokens are accepted, and that the funding period is still active. + +- **`claim`**: Handles the claiming logic based on the campaign status: + - During the funding period: Returns an error + - If successful: Allows only the owner to claim all collected funds + - If failed: Allows donors to claim their individual refunds + +- **`status`**: A view function that returns the current status of the campaign based on the deadline and funds raised. + +- **`get_current_funds`**: Returns the total amount of tokens currently held by the contract. + + +## Complete Contract Code + +```rust title=crowdfunding.rs +#![no_std] + +use multiversx_sc::{derive_imports::*, imports::*}; +pub mod crowdfunding_proxy; + +#[type_abi] +#[derive(TopEncode, TopDecode, PartialEq, Eq, Clone, Copy, Debug)] +pub enum Status { + FundingPeriod, + Successful, + Failed, +} + +#[multiversx_sc::contract] +pub trait Crowdfunding { + #[init] + fn init(&self, token_identifier: TokenId, target: BigUint, deadline: TimestampMillis) { + require!(token_identifier.is_valid(), "Invalid token provided"); + self.cf_token_id().set(token_identifier); + + require!(target > 0, "Target must be more than 0"); + self.target().set(target); + + require!( + deadline > self.get_current_time_millis(), + "Deadline can't be in the past" + ); + self.deadline().set(deadline); + } + + #[endpoint] + #[payable] + fn fund(&self) { + let payment = self.call_value().single(); + + require!( + payment.token_identifier == self.cf_token_id().get(), + "wrong token" + ); + require!(payment.is_fungible(), "only fungible tokens accepted"); + require!( + self.status() == Status::FundingPeriod, + "cannot fund after deadline" + ); + + let caller = self.blockchain().get_caller(); + self.deposit(&caller) + .update(|deposit| *deposit += payment.amount.as_big_uint()); + } + + #[view] + fn status(&self) -> Status { + if self.get_current_time_millis() < self.deadline().get() { + Status::FundingPeriod + } else if self.get_current_funds() >= self.target().get() { + Status::Successful + } else { + Status::Failed + } + } + + #[view(getCurrentFunds)] + #[title("currentFunds")] + fn get_current_funds(&self) -> BigUint { + let token = self.cf_token_id().get(); + + self.blockchain().get_sc_balance(&token, 0) + } + + #[endpoint] + fn claim(&self) { + match self.status() { + Status::FundingPeriod => sc_panic!("cannot claim before deadline"), + Status::Successful => { + let caller = self.blockchain().get_caller(); + require!( + caller == self.blockchain().get_owner_address(), + "only owner can claim successful funding" + ); + + let token_identifier = self.cf_token_id().get(); + let sc_balance = self.get_current_funds(); + + if let Some(sc_balance_non_zero) = sc_balance.into_non_zero() { + self.tx() + .to(&caller) + .payment(Payment::new(token_identifier, 0, sc_balance_non_zero)) + .transfer(); + } + } + Status::Failed => { + let caller = self.blockchain().get_caller(); + let deposit = self.deposit(&caller).get(); + + if deposit > 0u32 { + let token_identifier = self.cf_token_id().get(); + + self.deposit(&caller).clear(); + + if let Some(deposit_non_zero) = deposit.into_non_zero() { + self.tx() + .to(&caller) + .payment(Payment::new(token_identifier, 0, deposit_non_zero)) + .transfer(); + } + } + } + } + } + + // private + + fn get_current_time_millis(&self) -> TimestampMillis { + self.blockchain().get_block_timestamp_millis() + } + + // storage + + #[view(getTarget)] + #[title("target")] + #[storage_mapper("target")] + fn target(&self) -> SingleValueMapper; + + #[view(getDeadline)] + #[title("deadline")] + #[storage_mapper("deadline")] + fn deadline(&self) -> SingleValueMapper; + + #[view(getDeposit)] + #[title("deposit")] + #[storage_mapper("deposit")] + fn deposit(&self, donor: &ManagedAddress) -> SingleValueMapper; + + #[view(getCrowdfundingTokenId)] + #[title("tokenIdentifier")] + #[storage_mapper("tokenIdentifier")] + fn cf_token_id(&self) -> SingleValueMapper; +} +``` + + +## Complete Blackbox Test (ESDT) + +```rust title=crowdfunding_esdt_blackbox_test.rs +use crowdfunding::crowdfunding_proxy; + +use multiversx_sc_scenario::imports::*; + +const CF_DEADLINE: TimestampMillis = TimestampMillis::new(7 * 24 * 60 * 60 * 1000); // 1 week in milliseconds + +const FIRST_USER_ADDRESS: TestAddress = TestAddress::new("first-user"); +const OWNER_ADDRESS: TestAddress = TestAddress::new("owner"); +const SECOND_USER_ADDRESS: TestAddress = TestAddress::new("second-user"); + +const CODE_PATH: MxscPath = MxscPath::new("output/crowdfunding.mxsc.json"); +const CROWDFUNDING_ADDRESS: TestSCAddress = TestSCAddress::new("crowdfunding-sc"); + +const CF_TOKEN_ID: TestTokenIdentifier = TestTokenIdentifier::new("CROWD-123456"); +const OTHER_TOKEN_ID: TestTokenIdentifier = TestTokenIdentifier::new("OTHER-123456"); + +fn world() -> ScenarioWorld { + let mut blockchain = ScenarioWorld::new(); + + blockchain.set_current_dir_from_workspace("contracts/examples/crowdfunding"); + blockchain.register_contract(CODE_PATH, crowdfunding::ContractBuilder); + blockchain +} + +struct CrowdfundingTestState { + world: ScenarioWorld, +} + +impl CrowdfundingTestState { + fn new() -> Self { + let mut world = world(); + + world.account(OWNER_ADDRESS).nonce(1); + + world + .account(FIRST_USER_ADDRESS) + .nonce(1) + .balance(1000) + .esdt_balance(CF_TOKEN_ID, 1000) + .esdt_balance(OTHER_TOKEN_ID, 1000); + + world + .account(SECOND_USER_ADDRESS) + .nonce(1) + .esdt_balance(CF_TOKEN_ID, 1000); + + Self { world } + } + + fn deploy(&mut self) { + self.world + .tx() + .from(OWNER_ADDRESS) + .typed(crowdfunding_proxy::CrowdfundingProxy) + .init(CF_TOKEN_ID, 2_000u32, CF_DEADLINE) + .code(CODE_PATH) + .new_address(CROWDFUNDING_ADDRESS) + .run(); + } + + fn fund(&mut self, address: TestAddress, amount: u64) { + self.world + .tx() + .from(address) + .to(CROWDFUNDING_ADDRESS) + .typed(crowdfunding_proxy::CrowdfundingProxy) + .fund() + .payment(Payment::new( + CF_TOKEN_ID.as_str().into(), + 0u64, + NonZeroBigUint::try_from(amount as u128).unwrap(), + )) + .run(); + } + + fn check_deposit(&mut self, donor: TestAddress, amount: u64) { + self.world + .query() + .to(CROWDFUNDING_ADDRESS) + .typed(crowdfunding_proxy::CrowdfundingProxy) + .deposit(donor) + .returns(ExpectValue(amount)) + .run(); + } + + fn check_status(&mut self, expected_value: crowdfunding_proxy::Status) { + self.world + .query() + .to(CROWDFUNDING_ADDRESS) + .typed(crowdfunding_proxy::CrowdfundingProxy) + .status() + .returns(ExpectValue(expected_value)) + .run(); + } + + fn claim(&mut self, address: TestAddress) { + self.world + .tx() + .from(address) + .to(CROWDFUNDING_ADDRESS) + .typed(crowdfunding_proxy::CrowdfundingProxy) + .claim() + .run(); + } + + fn check_esdt_balance(&mut self, address: TestAddress, balance: u64) { + self.world + .check_account(address) + .esdt_balance(CF_TOKEN_ID, balance); + } + + fn set_block_timestamp(&mut self, block_timestamp: TimestampMillis) { + self.world + .current_block() + .block_timestamp_millis(block_timestamp); + } +} + +#[test] +fn test_fund_esdt() { + let mut state = CrowdfundingTestState::new(); + state.deploy(); + + state.fund(FIRST_USER_ADDRESS, 1_000u64); + state.check_deposit(FIRST_USER_ADDRESS, 1_000u64); +} + +#[test] +fn test_status_esdt() { + let mut state = CrowdfundingTestState::new(); + state.deploy(); + + state.check_status(crowdfunding_proxy::Status::FundingPeriod); +} + +#[test] +fn test_sc_error_esdt() { + let mut state = CrowdfundingTestState::new(); + state.deploy(); + + state + .world + .tx() + .from(FIRST_USER_ADDRESS) + .to(CROWDFUNDING_ADDRESS) + .typed(crowdfunding_proxy::CrowdfundingProxy) + .fund() + .payment(Payment::new( + OTHER_TOKEN_ID.as_str().into(), + 0, + NonZeroBigUint::try_from(1000u128).unwrap(), + )) + .with_result(ExpectError(4, "wrong token")) + .run(); + + state.check_deposit(FIRST_USER_ADDRESS, 0); +} + +#[test] +fn test_successful_cf_esdt() { + let mut state = CrowdfundingTestState::new(); + state.deploy(); + + // first user fund + state.fund(FIRST_USER_ADDRESS, 1_000u64); + state.check_deposit(FIRST_USER_ADDRESS, 1_000); + + // second user fund + state.fund(SECOND_USER_ADDRESS, 1000); + state.check_deposit(SECOND_USER_ADDRESS, 1_000); + + // set block timestamp after deadline + state.set_block_timestamp(CF_DEADLINE + DurationMillis::new(1)); + + // check status successful + state.check_status(crowdfunding_proxy::Status::Successful); + + state + .world + .tx() + .from(FIRST_USER_ADDRESS) + .to(CROWDFUNDING_ADDRESS) + .typed(crowdfunding_proxy::CrowdfundingProxy) + .claim() + .with_result(ExpectError(4, "only owner can claim successful funding")) + .run(); + + // owner claim + state.claim(OWNER_ADDRESS); + + state.check_esdt_balance(OWNER_ADDRESS, 2000); + state.check_esdt_balance(FIRST_USER_ADDRESS, 0); + state.check_esdt_balance(SECOND_USER_ADDRESS, 0); +} + +#[test] +fn test_failed_cf_esdt() { + let mut state = CrowdfundingTestState::new(); + state.deploy(); + + // first user fund + state.fund(FIRST_USER_ADDRESS, 300); + state.check_deposit(FIRST_USER_ADDRESS, 300u64); + + // second user fund + state.fund(SECOND_USER_ADDRESS, 600); + state.check_deposit(SECOND_USER_ADDRESS, 600u64); + + // set block timestamp after deadline + state.set_block_timestamp(CF_DEADLINE + DurationMillis::new(1)); + + // check status failed + state.check_status(crowdfunding_proxy::Status::Failed); + + // first user claim + state.claim(FIRST_USER_ADDRESS); + + // second user claim + state.claim(SECOND_USER_ADDRESS); + + state.check_esdt_balance(OWNER_ADDRESS, 0); + state.check_esdt_balance(FIRST_USER_ADDRESS, 1000); + state.check_esdt_balance(SECOND_USER_ADDRESS, 1000); +} +``` + + +## Complete Blackbox Test (EGLD) + +If you are interested in specifically only testing for EGLD, we have a separate test file that uses native EGLD transfers. It is very similar, but there is some specific syntax to deal with the EGLD balances and transfers. + + +```rust title=crowdfunding_egld_blackbox_test.rs +use crowdfunding::crowdfunding_proxy; + +use multiversx_sc_scenario::imports::*; + +const CF_DEADLINE: TimestampMillis = TimestampMillis::new(7 * 24 * 60 * 60 * 1000); // 1 week in milliseconds + +const FIRST_USER_ADDRESS: TestAddress = TestAddress::new("first-user"); +const OWNER_ADDRESS: TestAddress = TestAddress::new("owner"); +const SECOND_USER_ADDRESS: TestAddress = TestAddress::new("second-user"); + +const CODE_PATH: MxscPath = MxscPath::new("output/crowdfunding.mxsc.json"); +const CROWDFUNDING_ADDRESS: TestSCAddress = TestSCAddress::new("crowdfunding-sc"); + +const OTHER_TOKEN_ID: TestTokenIdentifier = TestTokenIdentifier::new("OTHER-123456"); + +fn world() -> ScenarioWorld { + let mut blockchain = ScenarioWorld::new(); + + blockchain.set_current_dir_from_workspace("contracts/examples/crowdfunding"); + blockchain.register_contract(CODE_PATH, crowdfunding::ContractBuilder); + blockchain +} + +struct CrowdfundingTestState { + world: ScenarioWorld, +} + +impl CrowdfundingTestState { + fn new() -> Self { + let mut world = world(); + + world.account(OWNER_ADDRESS).nonce(1); + + world + .account(FIRST_USER_ADDRESS) + .nonce(1) + .balance(1000) + .esdt_balance(OTHER_TOKEN_ID, 1000); + + world.account(SECOND_USER_ADDRESS).nonce(1).balance(1000); + + Self { world } + } + + fn deploy(&mut self) { + self.world + .tx() + .from(OWNER_ADDRESS) + .typed(crowdfunding_proxy::CrowdfundingProxy) + .init(TokenId::native(), 2_000u32, CF_DEADLINE) + .code(CODE_PATH) + .new_address(CROWDFUNDING_ADDRESS) + .run(); + } + + fn fund(&mut self, address: TestAddress, amount: u64) { + self.world + .tx() + .from(address) + .to(CROWDFUNDING_ADDRESS) + .typed(crowdfunding_proxy::CrowdfundingProxy) + .fund() + .egld(amount) + .run(); + } + + fn check_deposit(&mut self, donor: TestAddress, amount: u64) { + self.world + .query() + .to(CROWDFUNDING_ADDRESS) + .typed(crowdfunding_proxy::CrowdfundingProxy) + .deposit(donor) + .returns(ExpectValue(amount)) + .run(); + } + + fn check_status(&mut self, expected_value: crowdfunding_proxy::Status) { + self.world + .query() + .to(CROWDFUNDING_ADDRESS) + .typed(crowdfunding_proxy::CrowdfundingProxy) + .status() + .returns(ExpectValue(expected_value)) + .run(); + } + + fn claim(&mut self, address: TestAddress) { + self.world + .tx() + .from(address) + .to(CROWDFUNDING_ADDRESS) + .typed(crowdfunding_proxy::CrowdfundingProxy) + .claim() + .run(); + } + + fn check_balance(&mut self, address: TestAddress, balance: u64) { + self.world.check_account(address).balance(balance); + } + + fn set_block_timestamp(&mut self, block_timestamp: TimestampMillis) { + self.world + .current_block() + .block_timestamp_millis(block_timestamp); + } +} + +#[test] +fn test_fund_egld() { + let mut state = CrowdfundingTestState::new(); + state.deploy(); + + state.fund(FIRST_USER_ADDRESS, 1_000u64); + state.check_deposit(FIRST_USER_ADDRESS, 1_000u64); +} + +#[test] +fn test_status_egld() { + let mut state = CrowdfundingTestState::new(); + state.deploy(); + + state.check_status(crowdfunding_proxy::Status::FundingPeriod); +} + +#[test] +fn test_sc_error_egld() { + let mut state = CrowdfundingTestState::new(); + + state.deploy(); + + state + .world + .tx() + .from(FIRST_USER_ADDRESS) + .to(CROWDFUNDING_ADDRESS) + .typed(crowdfunding_proxy::CrowdfundingProxy) + .fund() + .payment(Payment::new( + OTHER_TOKEN_ID.as_str().into(), + 0, + NonZeroBigUint::try_from(1000u128).unwrap(), + )) + .with_result(ExpectError(4, "wrong token")) + .run(); + + state.check_deposit(FIRST_USER_ADDRESS, 0); +} + +#[test] +fn test_successful_cf_egld() { + let mut state = CrowdfundingTestState::new(); + state.deploy(); + + // first user fund + state.fund(FIRST_USER_ADDRESS, 1_000u64); + state.check_deposit(FIRST_USER_ADDRESS, 1_000); + + // second user fund + state.fund(SECOND_USER_ADDRESS, 1000); + state.check_deposit(SECOND_USER_ADDRESS, 1_000); + + // set block timestamp after deadline + state.set_block_timestamp(CF_DEADLINE + DurationMillis::new(1)); + + // check status successful + state.check_status(crowdfunding_proxy::Status::Successful); + + state + .world + .tx() + .from(FIRST_USER_ADDRESS) + .to(CROWDFUNDING_ADDRESS) + .typed(crowdfunding_proxy::CrowdfundingProxy) + .claim() + .with_result(ExpectError(4, "only owner can claim successful funding")) + .run(); + + // owner claim + state.claim(OWNER_ADDRESS); + + state.check_balance(OWNER_ADDRESS, 2000); + state.check_balance(FIRST_USER_ADDRESS, 0); + state.check_balance(SECOND_USER_ADDRESS, 0); +} + +#[test] +fn test_failed_cf_egld() { + let mut state = CrowdfundingTestState::new(); + state.deploy(); + + // first user fund + state.fund(FIRST_USER_ADDRESS, 300); + state.check_deposit(FIRST_USER_ADDRESS, 300u64); + + // second user fund + state.fund(SECOND_USER_ADDRESS, 600); + state.check_deposit(SECOND_USER_ADDRESS, 600u64); + + // set block timestamp after deadline + state.set_block_timestamp(CF_DEADLINE + DurationMillis::new(1)); + + // check status failed + state.check_status(crowdfunding_proxy::Status::Failed); + + // first user claim + state.claim(FIRST_USER_ADDRESS); + + // second user claim + state.claim(SECOND_USER_ADDRESS); + + state.check_balance(OWNER_ADDRESS, 0); + state.check_balance(FIRST_USER_ADDRESS, 1000); + state.check_balance(SECOND_USER_ADDRESS, 1000); +} +``` + +The key differences in the EGLD test: +- Uses `TokenId::native()` for deployment +- Uses `.egld()` for funding transactions +- Uses `.balance()` for balance checks + +Both test files verify the same functionality (successful funding, failed funding, wrong token rejection), proving the contract works identically regardless of the token type! + + +## Storage Mappers + +The contract uses several storage mappers to persist data on the blockchain: + +- **`target`**: Stores the target amount of tokens to be raised (BigUint) +- **`deadline`**: Stores the campaign deadline as a timestamp in milliseconds (TimestampMillis) +- **`deposit`**: Maps each donor's address to their contribution amount (BigUint) +- **`cf_token_id`**: Stores the token identifier used for the crowdfunding campaign (TokenId) + +Each storage mapper is also exposed as a view function, allowing external queries to read these values. + + +## Next Steps + +Now that you have the complete crowdfunding contract: + +1. **Add more tests**: Try to write comprehensive tests covering all edge cases, especially for the `claim` function +2. **Extend the functionality**: Consider adding features like: + - Multiple funding rounds + - Partial withdrawals + - Campaign updates or extensions + - Reward tiers for different contribution levels +3. **Explore other contracts**: Check out more smart contract examples in the [MultiversX contracts repository](https://github.com/multiversx/mx-contracts-rs) + + +## Related Documentation + +- [Part 1: Crowdfunding Smart Contract Setup](crowdfunding-p1.md) +- [Part 2: Crowdfunding Logic](crowdfunding-p2.md) +- [Part 3: Supporting Any Fungible Token](crowdfunding-p3.md) +- [Smart Contract Developer Reference](/developers/developer-reference/sc-annotations) +- [Testing Smart Contracts](/developers/testing/testing-overview) + +--- + +### Fix IDEs configuration + +The issues tackled on this page are related to IDEs preferred by MultiversX builders, such as **VSCode** or **RustRover**. + + +## VSCode: fix configuration for Rust Analyzer + +If `rust-analyzer` is not working properly on VSCode, you might see (one of) the following error messages: + +``` + - rust-analyzer failed to load workspace: Failed to load the project. + - Failed to query rust toolchain version. + - error: rustup could not choose a version of cargo to run, because one wasn't specified explicitly, and no default is configured. +``` + +If so, **[make sure Rust is properly installed](/docs/developers/toolchain-setup.md#installing-rust-and-sc-meta)**. + +Then, restart VSCode. Now, `rust-analyzer` should work properly. If the problem persists, please [contact us](/developers/overview). + +--- + +### Fix Rust installation + +When encountering issues with your Rust installation, we recommend a cleanup (uninstall) first, especially if you have multiple installations (by accident or on purpose). + + +## Uninstall existing Rust + +If you've installed Rust using your OS package manager: + +```bash +# Ubuntu +sudo apt remove cargo +sudo apt remove rustc +sudo apt autoremove +``` + +If you've installed Rust using `rustup`: + +```bash +rustup self uninstall +``` + +If you've installed Rust using `brew`: + +```bash +brew uninstall rust +``` + +:::note +We never recommend installing Rust using `brew`, especially because it makes it non-trivial to switch between different Rust versions. +::: + +If you've installed Rust using `snap`: + +```bash +snap remove rustup +``` + +If you've installed Rust using `mxpy` with a version older than `v9`: + +```bash +rm -rf ~/multiversx-sdk/vendor-rust +``` + +If you've installed Rust using `mxpy v9` or later: + +```bash +rustup self uninstall +``` + +## Reinstall Toolchain Setup + +After successfully uninstalling your current toolchain, you can follow the [setup guide](/docs/developers/toolchain-setup.md) to reinstall the environment from scratch. + +--- + +### Fungible tokens + +```mdx-code-block +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; +``` + + +## **Introduction** + +**ESDT** stands for _eStandard Digital Token_. + +Custom tokens at native speed and scalability, without ERC20 + +The MultiversX network natively supports the issuance of custom tokens, without the need for contracts such as ERC20, but addressing the same use-cases. And due to the native in-protocol support, transactions with custom tokens do not require the VM at all. In effect, this means that custom tokens are **as fast and as scalable as the native EGLD token itself.** + +Users also do not need to worry about sharding when transacting custom tokens, because the protocol employs the same handling mechanisms for ESDT transactions across shards as the mechanisms used for the EGLD token. Sharding is therefore automatically handled and invisible to the user. + +Technically, the balances of ESDT tokens held by an Account are stored directly under the data trie of that Account. It also implies that an Account can hold balances of _any number of custom tokens_, in addition to the native EGLD balance. The protocol guarantees that no Account can modify the storage of ESDT tokens, neither its own nor of other Accounts. + +ESDT tokens can be issued, owned and held by any Account on the MultiversX network, which means that both users _and smart contracts_ have the same functionality available to them. Due to the design of ESDT tokens, smart contracts can manage tokens with ease, and they can even react to an ESDT transfer. + + +## **Issuance of fungible ESDT tokens** + +ESDT tokens are issued via a request to the Metachain, which is a transaction submitted by the Account which will manage the tokens. When issuing a token, one must provide a token name, a ticker, the initial supply, the number of decimals for display purpose and optionally additional properties. This transaction has the form: + +```rust +IssuanceTransaction { + Sender: + Receiver: erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u + Value: 50000000000000000 # (0.05 EGLD) + GasLimit: 60000000 + Data: "issue" + + "@" + + + "@" + + + "@" + + + "@" + +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +The issuance cost is set to 0.05 EGLD. + +Optionally, the properties can be set when issuing a token. Example: + +```rust +IssuanceTransaction { + Sender: + Receiver: erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u + Value: 50000000000000000 # (0.05 EGLD) + GasLimit: 60000000 + Data: "issue" + + "@" + + + "@" + + + "@" + + + "@" + + + "@" + <"canFreeze" hexadecimal encoded> + "@" + <"true" or "false" hexadecimal encoded> + + "@" + <"canWipe" hexadecimal encoded> + "@" + <"true" or "false" hexadecimal encoded> + + "@" + <"canPause" hexadecimal encoded> + "@" + <"true" or "false" hexadecimal encoded> + + "@" + <"canChangeOwner" hexadecimal encoded> + "@" + <"true" or "false" hexadecimal encoded> + + "@" + <"canUpgrade" hexadecimal encoded> + "@" + <"true" or "false" hexadecimal encoded> + + "@" + <"canAddSpecialRoles" hexadecimal encoded> + "@" + <"true" or "false" hexadecimal encoded> +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +The receiver address `erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u` is a built-in system smart contract (not a VM-executable contract), which only handles token issuance and other token management operations, and does not handle any transfers. +The contract will add a random string to the ticker thus creating the **token identifier**. The random string starts with “-” and has 6 more random characters (3 bytes - 6 characters hex encoded). For example, a token identifier could look like _ALC-6258d2_. + +:::note +Because of the 6 random characters sequence (3 bytes - 6 characters hex encoded), the token identifier cannot be precalculated, thus one has to check the Smart Contract Result of the issue transaction that indicates it. Alternatively, one can check its tokens via Explorer or API and search for the token that starts with the ticker that was chosen when issuing the token. +::: + + +### **Parameters format** + +Token Name: + +- length between 3 and 20 characters +- alphanumeric characters only + +Token Ticker: + +- length between 3 and 10 characters +- alphanumeric UPPERCASE only + +Number of decimals: + +- should be a numerical value between _0_ and _18_ +- hexadecimal encoded + +Numerical values, such as initial supply or number of decimals, should be the hexadecimal encoding of the decimal numbers representing them. Additionally, they should have an even number of characters. Examples: + +- **10** _decimal_ => **0a** _hex encoded_ +- **48** _decimal_ => **30** _hex encoded_ +- **1000000** _decimal_ => **0f4240** _hex encoded_ + + +### **Number of decimals usage** + +Front-end applications will use the number of decimals in order to display balances. +Therefore, you must adapt the supply according to the number of decimals parameter. + +For example, if you would like to create a token `ALC` with a total supply of 100 and number of decimals = 2, then you should set +the initial supply to `10000` ($100 * 10^2$). +Also, when transferring/burning/minting tokens, you should keep in mind there is also the denomination involved. + +Therefore, if you own some above-mentioned ALC tokens, and you want to transfer 7.5 ALC, then the value argument of the transaction should be `750` ($7.5 * 10^2$). The same rule applies to burning or minting. + +:::tip +This is only relevant when performing operations via manual transactions over ESDT tokens. The Web Wallet for example already has this feature in place, so you don't have to take care of the number of decimals. +::: + + +### **Issuance examples** + +For example, a user named Alice wants to issue 4091 tokens called "AliceTokens" with the ticker "ALC". Also, the number of decimals is 6. + +As stated above, if the user wants 4091 tokens with 6 decimals, then the initial supply has to be $4091 * 10^6$ tokens so a total of `4091000000`. + +```rust +IssuanceTransaction { + Sender: erd1sg4u62lzvgkeu4grnlwn7h2s92rqf8a64z48pl9c7us37ajv9u8qj9w8xg + Receiver: erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u + Value: 50000000000000000 # (0.05 EGLD) + GasLimit: 60000000 + Data: "issue" + + "@416c696365546f6b656e73" + // "AliceTokens" hex encoded + "@414c43" + // "ALC" hex encoded + "@f3d7b4c0" + // 4091000000 hex encoded + "@06" // 6 hex encoded +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +Once this transaction is processed by the Metachain, Alice becomes the designated **manager of AliceTokens**, and is granted a balance of `4091000000` AliceTokens with `6` decimals (resulting in `4091` tokens). She can increase the total supply of tokens at a later time if needed. For more operations available to ESDT token managers, see [Token management](/tokens/fungible-tokens#token-management). + +If the issue transaction is successful, a smart contract result will mint the requested token and supply in the account used for issuance, which is also the token manager. +In that smart contract result, the `data` field will contain a transfer syntax which is explained below. What is important to note is that the token identifier can be fetched from +here in order to use it for transfers. Alternatively, the token identifier can be fetched from the API (explained also below). + + +## **Transfers** + +Performing an ESDT transfer is done by sending a transaction directly to the desired receiver Account, but specifying some extra pieces of information in its `Data` field. An ESDT transfer transaction has the following form: + +```rust +TransferTransaction { + Sender: + Receiver: + Value: 0 + GasLimit: 500000 + Data: "ESDTTransfer" + + "@" + + + "@" + +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +:::important +The value of the transaction should be set to 0 EGLD, otherwise the transaction will fail. The (token) amount to be transferred is encoded into the data field. +::: + +While this transaction may superficially resemble a smart contract call, it is not. The differences are the following: + +- the receiver can be any account (which may or may not be a smart contract) +- the `GasLimit` must be set to the value required by the protocol for ESDT transfers, namely `500000` +- the Data field contains what appears to be a smart contract method invocation with arguments, but this invocation never reaches the VM: the string `ESDTTransfer` is reserved by the protocol and is handled as a built-in function, not as a smart contract call + +Following the example from earlier, assuming that the token identifier is `414c432d363235386432`, a transfer from Alice to another user, Bob, would look like this: + +```rust +TransferTransaction { + Sender: erd1sg4u62lzvgkeu4grnlwn7h2s92rqf8a64z48pl9c7us37ajv9u8qj9w8xg + Receiver: erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx + Value: 0 + GasLimit: 500000 + Data: "ESDTTransfer" + + "@414c432d363235386432" + + "@0c" +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +Using the transaction in the example above, Alice will transfer 12 AliceTokens to Bob. + + +### Transfers gas limit + +When computing the gas limit required for an ESDT transfer, our recommendation is to use 500_000 since it includes a buffer, +and the remaining gas will be refunded to the sender's account. + +However, if one wants to calculate the exact amount of gas needed, here's the formula: + +``` +ESDT_TRANSFER_GAS_LIMIT = MIN_GAS_LIMIT + data_length * GAS_PER_DATA_BYTE + ESDT_TRANSFER_FUNCTION_COST +``` + +Where the constants represent: + +- `MIN_GAS_LIMIT` - the minimum gas limit of a transaction. Each transaction will have at least this cost. Can be fetched from [here](/sdk-and-tools/rest-api/network/#get-network-configuration). +- `GAS_PER_DATA_BYTE` - the amount of gas to be used for each character of the data field. Can be fetched from [here](/sdk-and-tools/rest-api/network/#get-network-configuration). +- `ESDT_TRANSFER_FUNCTION_COST` - the cost of the `ESDTTransfer` function. Can be fetched from [here](https://gateway.multiversx.com/network/gas-configs). + +The current values of these constants are: + +``` +MIN_GAS_LIMIT = 50,000 +GAS_PER_DATA_BYTE = 1,500 +ESDT_TRANSFER_FUNCTION_COST = 200,000 +``` + +Therefore, knowing these values, the gas units needed for pre-calculating the ESDT transfers gas limit, the transaction's data length is the only variable. + +**Example** + +Let's take an example. If one wants to transfer `20,000 MEX`, the data field of the transaction would look like: + +- `ESDTTransfer@4d45582d343535633537@043c33c1937564800000` + Its length is 54 characters. + +Following the formula, we'll get: + +``` +ESDT_TRANSFER_GAS_LIMIT = MIN_GAS_LIMIT + data_length * GAS_PER_DATA_BYTE + ESDT_TRANSFER_FUNCTION_COST + = 50,000 + 54 * 1,500 + 200,000 + = 331,000 +``` + + +### Transfers fee + +When computing the fee, it isn't enough to multiply the obtained gas limit with the gas price, since there is a reduction +of fee in case of a Smart Contract call or a Built-In function call (which is the current case). + +Therefore, the formula for computing the fee is: + +``` +ESDT_TRANSFER_FEE = gas_price * gas_cost + = gas_price * [MIN_GAS_LIMIT + (data_length * GAS_PER_DATA_BYTE) + (ESDT_TRANSFER_FUNCTION_COST * GAS_PRICE_MODIFIER)] +``` + +Most of the constants can be found in the [Transfers gas limit section](/tokens/fungible-tokens#transfers-gas-limit), beside the `GAS_PRICE_MODIFIER` which can +be found [here](/sdk-and-tools/rest-api/network/#get-network-configuration). + +Currently, the constants values are: + +``` +MIN_GAS_LIMIT = 50,000 +GAS_PER_DATA_BYTE = 1,500 +ESDT_TRANSFER_FUNCTION_COST = 200,000 +GAS_PRICE_MODIFIER = 0.01 +``` + +**Example** + +Let's take an example. If one wants to transfer `20,000 MEX`, the data field of the transaction would look like: + +```rust +TransferTransaction { + ... + gasPrice: 1000000000 + data: ESDTTransfer@4d45582d343535633537@043c33c1937564800000 +} +``` + +We can see that the data length is 54, while the gas price is the network's minimum, that is, `1000000000`. + +Following the formula, we'll get: + +``` +ESDT_TRANSFER_FEE = gas_price * gas_cost + = 1000000000 * [ MIN_GAS_LIMIT + (data_length * GAS_PER_DATA_BYTE) + (ESDT_TRANSFER_FUNCTION_COST * GAS_PRICE_MODIFIER)] + = 1,000,000,000 * [ 50,000 + ( 54 * 1500 ) + ( 200,000 * 0.01 )] + = 133000000000000 + = 0.000133 EGLD +``` + + +## **Transfers to a smart contract** + +Smart contracts may hold ESDT tokens and perform any kind of transaction with them, just like any Account. However, there are a few extra ESDT features dedicated to smart contracts: + +**Payable versus non-payable contract**: upon deployment, a smart contract may be marked as _payable_, which means that it can receive either EGLD or ESDT tokens without calling any of its methods (i.e. a simple transfer). But by default, all contracts are _non-payable_, which means that simple transfers of EGLD or ESDT tokens will be rejected, unless they are method calls. + +**ESDT transfer with method invocation**: it is possible to send ESDT tokens to a contract _as part of a method call_, just like sending EGLD as part of a method call. A transaction that sends ESDT tokens to a contract while also calling one of its methods has the following form: + +```rust +TransferWithCallTransaction { + Sender: + Receiver: + Value: 0 + GasLimit: 500000 + + Data: "ESDTTransfer" + + "@" + + + "@" + + + "@" + + + "@" + + + "@" + + + <...> +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +Sending a transaction containing both an ESDT transfer _and a method call_ allows non-payable smart contracts to receive tokens as part of the call, as if it were EGLD. The smart contract may use dedicated API functions to inspect the name of the received ESDT tokens and their amount, and react accordingly. + + +## **Multiple tokens transfer** + +There is also the possibility to perform multiple tokens transfers in a single bulk. This way, one can send (to a single receiver) multiple +fungible, semi-fungible or non-fungible tokens via a single transaction. + +A multi-token transfer transaction has the following form: + +```rust +MultiTokensTransferTransaction { + Sender: + Receiver: + Value: 0 + GasLimit: 1_100_000 * num tokens + Data: "MultiESDTNFTTransfer" + + "@" + + + "@" + + + "@" + + + "@" + + + "@" + + + "@" + + + "@" + + + "@" + + + ... +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +:::tip +Each token requires the token identifier, the nonce and the quantity to transfer. + +For fungible tokens (regular ESDT) the nonce has to be 0 (`00` hex-encoded) +::: + +Example: + +```rust +MultiTokensTransferTransaction { + Sender: erd1sg4u62lzvgkeu4grnlwn7h2s92rqf8a64z48pl9c7us37ajv9u8qj9w8xg + Receiver: erd1sg4u62lzvgkeu4grnlwn7h2s92rqf8a64z48pl9c7us37ajv9u8qj9w8xg + Value: 0 + GasLimit: 2_200_000 + Data: "MultiESDTNFTTransfer" + + "@8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8" + // erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx + "@02" + // 2 tokens to transfer + "@414c432d363235386432" + // ALC-6258d2 + "@00" + // 0 -> the nonce is 0 for regular ESDT tokens + "@0c" + // 12 -> value to transfer + "@5346542d317134723869" + // SFT-1q4r8i + "@01" + // 1 -> the nonce of the SFT + "@03" // 3 -> the quantity to transfer +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +Using the transaction in the example above, the receiver should be credited `12 ALC-6258d2` tokens and `3 SFT-1q4r8i` tokens. + + +## **Multiple tokens transfer to a smart contract** + +Smart contracts may hold ESDT tokens and perform any kind of transaction with them, just like any Account. However, there are a few extra ESDT features dedicated to smart contracts: + +**Payable versus non-payable contract**: upon deployment, a smart contract may be marked as _payable_, which means that it can receive either EGLD or ESDT tokens without calling any of its methods (i.e. a simple transfer). But by default, all contracts are _non-payable_, which means that simple transfers of EGLD or ESDT tokens will be rejected, unless they are method calls. + +**ESDT transfer with method invocation**: it is possible to send ESDT tokens to a contract _as part of a method call_, just like sending EGLD as part of a method call. A transaction that sends ESDT tokens to a contract while also calling one of its methods has the following form: + +```rust +MultiTokensTransferWithCallTransaction { + Sender: + Receiver: + Value: 0 + GasLimit: 1_100_000 * num tokens + + Data: "MultiESDTNFTTransfer" + + "@" + + + "@" + + + "@" + + + "@" + + + "@" + + + "@" + + + "@" + + + "@" + + + "@" + + + "@" + + + "@" + + + ... +} +``` + +:::tip +Each token requires the token identifier, the nonce and the quantity to transfer. + +For fungible tokens (regular ESDT) the nonce has to be 0 (`00` hex-encoded) +::: + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +Example: + +We send tokens and call the 'transfer' method on the smart contract. We also define the receiver as the argument of the transfer method. + +```rust +MultiTokensTransferWithCallTransaction { + Sender: erd1sg4u62lzvgkeu4grnlwn7h2s92rqf8a64z48pl9c7us37ajv9u8qj9w8xg + Receiver: erd1sg4u62lzvgkeu4grnlwn7h2s92rqf8a64z48pl9c7us37ajv9u8qj9w8xg + Value: 0 + GasLimit: 8_200_000 + Data: "MultiESDTNFTTransfer" + + "@0000000000000000050032a3dc7511d6062c6a3b90ac02d8c3f474ef71c65008" + // smart contract address: erd1qqqqqqqqqqqqqpgqx23acag36crzc63mjzkq9kxr736w7uwx2qyqcmq7ar + "@02" + // 2 tokens to transfer + "@414c432d363235386432" + // ALC-6258d2 + "@00" + // 0 -> the nonce is 0 for regular ESDT tokens + "@0c" + // 12 -> value to transfer + "@5346542d317134723869" + // SFT-1q4r8i + "@01" + // 1 -> the nonce of the SFT + "@03" + // 3 -> the quantity to transfer + "@7472616e73666572" + // method name is 'transfer' + "@f69adc800dca9e3ba3328d17ded25f3a8f328aa1e0e8a347f34ce5ea3aac5008" // there is one argument, the address: 'erd176ddeqqde20rhgej35taa5jl828n9z4pur52x3lnfnj75w4v2qyqa230vx' +} +``` + +Sending a transaction containing both an ESDT transfer _and a method call_ allows non-payable smart contracts to receive tokens as part of the call, as if it were EGLD. The smart contract may use dedicated API functions to inspect the name of the received ESDT tokens and their amount, and react accordingly. + + +## **Transfers done programmatically** + +The [Rust framework](https://github.com/multiversx/mx-sdk-rs) exposes several ways in which you can transfer ESDT tokens. For example, in order to transfer _amount_ of _esdt_token_name_ to _address_, one would do the following: + +```rust +self.send().direct_esdt(&address, &esdt_token_name, token_nonce: u64, &amount); +``` + + +## **Token management** + +The Account which submitted the issuance request for a custom token automatically becomes the manager of the token (see [Issuance of ESDT tokens](/tokens/fungible-tokens#issuance-of-fungible-esdt-tokens)). The manager of a token has the ability to manage the properties, the total supply and the availability of a token. Because smart contracts are Accounts as well, a smart contract can also issue and own ESDT tokens and perform management operations by sending the appropriate transactions, as shown below. + + +## **Configuration properties of an ESDT token** + +Every ESDT token has a set of properties which control what operations are possible with it. See [Management operations](/tokens/fungible-tokens#management-operations) below for the operations controlled by them. The properties are: + +- `canPause` - the token manager may prevent all transactions of the token, apart from minting and burning +- `canFreeze` - the token manager may freeze the token balance in a specific account, preventing transfers to and from that account +- `canWipe` - the token manager may wipe out the tokens held by a frozen account, reducing the supply +- `canChangeOwner` - token management can be transferred to a different account +- `canUpgrade` - the token manager may change these properties +- `canAddSpecialRoles` - the token manager can assign a specific role(s) +- `canCreateMultiShard` - if true, then local mint/burn can be used so the token will be distributed among shards. + + +## **Management operations** + +The manager of an ESDT token has a number of operations at their disposal, which control how the token is used by other users. These operations can only be performed by the token manager - no other account may perform them. One special exception is the `ESDTBurn` operation, which is available to any Account which holds units of the token in cause. + + +### **Minting** + +:::tip +On Mainnet, starting with epoch 432, global mint is disabled so one has to use local mint instead. +::: + +An account with the `ESDTRoleLocalMint` role set can perform a local mint: + +```rust +LocalMintTransaction { + Sender:
+ Receiver: + Value: 0 + GasLimit: 300000 + Data: "ESDTLocalMint" + + "@" + + + "@" + +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + + +### **Burning** + +:::tip +On Mainnet, starting with epoch 432, global burn is disabled so one has to use local burn instead. +::: + +Anyone that holds an amount of ESDT tokens may burn it at their discretion, effectively losing them permanently. This operation reduces the total supply of tokens, and cannot be undone, unless the token manager mints more tokens. + +An account with the `ESDTRoleLocalBurn` role set can perform a local burn: + +```rust +LocalBurnTransaction { + Sender:
+ Receiver: + Value: 0 + GasLimit: 300000 + Data: "ESDTLocalBurn" + + "@" + + + "@" + +} +``` + +Following this transaction, the token holder loses from the balance the amount of tokens specified by the Data. + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +:::note +Tokens issued after [release v1.3.42](https://multiversx.com/releases/release-elrond-go---v1342) (_October 2022_), are burn-able by each holder, so setting the role for these tokens becomes redundant. +However, for older tokens, a transaction that will set the special role `ESDTRoleLocalBurn` is still necessary. Docs [here](#setting-and-unsetting-special-roles). +::: + + +### **Pausing and Unpausing** + +The manager of an ESDT token may choose to suspend all transactions of the token, except minting, freezing/unfreezing and wiping. The transaction form is as follows: + +```rust +PauseTransaction { + Sender: + Receiver: erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u + Value: 0 + GasLimit: 60000000 + Data: "pause" + + "@" + +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +The reverse operation, unpausing, will allow transactions of the token again: + +```rust +UnpauseTransaction { + Sender: + Receiver: erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u + Value: 0 + GasLimit: 60000000 + Data: "unPause" + + "@" + +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +These two operations require that the option `canPause` is set to `true`. + + +### **Freezing and Unfreezing** + +The manager of an ESDT token may freeze the tokens held by a specific Account. As a consequence, no tokens may be transferred to or from the frozen Account. Freezing and unfreezing the tokens of an Account are operations designed to help token managers to comply with regulations. The transaction that freezes the tokens of an Account has the form: + +```rust +FreezeTransaction { + Sender: + Receiver: erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u + Value: 0 + GasLimit: 60000000 + Data: "freeze" + + "@" + + + "@" + +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +The reverse operation, unfreezing, will allow further transfers to and from the Account: + +```rust +UnfreezeTransaction { + Sender: + Receiver: erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u + Value: 0 + GasLimit: 60000000 + Data: "unFreeze" + + "@" + + + "@" + +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +These two operations require that the option `canFreeze` is set to `true`. + + +### **Wiping** + +The manager of an ESDT token may wipe out all the tokens held by a frozen Account. This operation is similar to burning the tokens, but the Account must have been frozen beforehand, and it must be done by the token manager. Wiping the tokens of an Account is an operation designed to help token managers to comply with regulations. Such a transaction has the form: + +```rust +WipeTransaction { + Sender: + Receiver: erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u + Value: 0 + GasLimit: 60000000 + Data: "wipe" + + "@" + + + "@" + +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + + +### **Setting and unsetting special roles** + +The manager of an ESDT token can set and unset special roles for a given address. Only applicable if `canAddSpecialRoles` property is `true`. +The special roles available for basic ESDT tokens are: + +- **ESDTRoleLocalBurn**: an address with this role can burn tokens + +- **ESDTRoleLocalMint**: an address with this role can mint new tokens + +- **ESDTTransferRole**: this role restricts transferability of the token only to the addresses that have the role set, while these addresses can send to any address + +For NFTs, there are different roles that can be set. You can find them [here](/tokens/nft-tokens#assigning-roles). + + +#### **Set special role** + +One or more roles for an address can be set by the owner by performing a transaction like: + +```rust +RolesAssigningTransaction { + Sender:
+ Receiver: erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u + Value: 0 + GasLimit: 60000000 + Data: "setSpecialRole" + + "@" + + + "@" +
+ + "@" + + + "@" + + + ... +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + + +#### **Unset special role** + +One or more roles for an address can be unset by the owner by performing a transaction like: + +```rust +RolesAssigningTransaction { + Sender:
+ Receiver: erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u + Value: 0 + GasLimit: 60000000 + Data: "unSetSpecialRole" + + "@" + + + "@" +
+ + "@" + + + "@" + + + ... +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + + +### **Managing Token Transferability** + +The manager of an ESDT token can restrict transferability by setting **ESDTTransferRole** to specific addresses. + +If at least one address has the role, the free transfer is restricted between arbitrary addresses. However, any address without the role can still send tokens to an address with the role. + +When the role is removed from all addresses, the token can be transferable again with no restrictions. + +:::tip +Previously, on Mainnet, there was a limitation where the sender and recipient of ESDT tokens had to be in the same shard. +Starting from epoch 795, the implementation has been updated to remove this restriction by setting ESDTTransferRole. However, tokens that had transferability set before this epoch will require an additional transaction to follow the latest implementation: + +```rust +SendAllTransferRoleAddressesTransaction { + Sender:
+ Receiver: erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u + Value: 0 + GasLimit: 60000000 + Data: "sendAllTransferRoleAddresses" + + "@" + + +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +::: + + +### **Transferring token management rights** + +The manager of an ESDT token may transfer the management rights to another Account. This can be done with a transaction of the form: + +```rust +TransferOwnershipTransaction { + Sender: + Receiver: erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u + Value: 0 + GasLimit: 60000000 + Data: "transferOwnership" + + "@" + + + "@" + +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +After this transaction is processed by the Metachain, any subsequent management operations will only be permitted to the new Account, as specified by the Data field of the transaction. + +This operation requires that the option `canChangeOwner` is set to `true`. + + +### **Upgrading (changing properties)** + +:::tip +On Mainnet, starting with epoch 432, global mint and global burn are disabled so one has to use local mint/burn instead. +Therefore, properties canMint and canBurn aren't effective anymore after that epoch. For setting those properties, one has to set the `ESDTRoleLocalMint` and/or `ESDTRoleLocalBurn` instead. +::: + +The manager of an ESDT token may individually change any of the properties of the token, or multiple properties at once. Such an operation is performed by a transaction of the form: + +```rust +UpgradingTransaction { + Sender: + Receiver: erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u + Value: 0 + GasLimit: 60000000 + Data: "controlChanges" + + "@" + + + "@" + + + "@" + <"true" or "false" in hexadecimal encoding> + + "@" + + + "@" + <"true" or "false" in hexadecimal encoding> + + <...> +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +As an example, assume that the "AliceTokens" discussed in earlier sections has the property `canWipe` set to `true`, but Alice, the token manager, wants to change these properties to `false`. The transaction that would achieve this change is: + +```rust +UpgradingTransaction { + Sender: erd1sg4u62lzvgkeu4grnlwn7h2s92rqf8a64z48pl9c7us37ajv9u8qj9w8xg + Receiver: erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u + Value: 0 + GasLimit: 60000000 + Data: "controlChanges" + + "@414c432d363235386432" + # ALC-6258d2 + "@63616e57697065" + # canWipe + "@66616c7365" + # false +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + + +## **Branding** + +Anyone can create an ESDT token on MultiversX Network. There are also no limits in tokens names or tickers. For example, +one issues an `AliceToken` with the ticker `ALC`. Anyone else is free to create a new token with the same token name and +the same token ticker. The only difference will be the random sequence of the token identifier. So the "original" token +could have received the random sequence `1q2w3e` resulting in the `ALC-1q2w3e` identifier, while the second token could +have received the sequence `3e4r5t` resulting in `ALC-3e4r5t`. + +In order to differentiate between an original token and other tokens with the same name or ticker, we have introduced a +branding mechanism that allows tokens owners to provide a logo, a description, a website, as well as social link for their tokens. +An example of a branded token is MEX, the xExchange's token. MultiversX products such as Explorer, Wallet and so on +will display tokens in accordance to their branding, if any. + +A token owner can submit a branding request by opening a Pull Request on [https://github.com/multiversx/mx-assets/](https://github.com/multiversx/mx-assets/). + + +### **Submitting a branding request** + +Project owners can create a PR against [https://github.com/multiversx/mx-assets/](https://github.com/multiversx/mx-assets/) repository with the logo in `.png` and `.svg` format, as well as a `.json` file containing all the relevant information. + +Here’s a prefilled template for the .json file to get you started: + +```json +{ + "website": "https://www.multiversxtoken.com", + "description": "The MXT token is the utility token of MultiversX Token", + "social": { + "email": "mxt-token@multiversxtoken.com", + "blog": "https://www.multiversxtoken.com/MXT-token-blog", + "twitter": "https://twitter.com/MXT-token-twitter", + "whitepaper": "https://www.multiversxtoken.com/MXT-token-whitepaper.pdf", + "coinmarketcap": "https://coinmarketcap.com/currencies/MXT-token", + "coingecko": "https://www.coingecko.com/en/coins/MXT-token" + }, + "status": "active" +} +``` + +The ledgerSignature will be generated by MultiversX. It will give your token “whitelist” status on the Ledger app and enable a more data rich flow for users storing your token on their Ledger hardware wallets. If one wants to set a Ledger signature, request it when opening a PR. + + +## **REST API** + +There are a number of API endpoints that one can use to interact with ESDT data. These are: + + +### GET **Get all ESDT tokens for an address** {#get-all-esdt-tokens-for-an-address} + + + + +Returns an array of ESDT Tokens that the specified address has interacted with (issued, sent or received). + +```bash +https://gateway.multiversx.com/address/*bech32Address*/esdt +``` + +| Param | Required | Type | Description | +| ------------- | ----------------------------------------- | -------- | -------------------------------------- | +| bech32Address | REQUIRED | `string` | The Address to query in bech32 format. | + + + + +```json +{ + "data": { + "tokens": ["ABC-0d0060", "DEF-d00600"] + }, + "error": "", + "code": "successful" +} +``` + + + + + +### GET **Get balance for an address and an ESDT token** {#get-balance-for-an-address-and-an-esdt-token} + + + + +Returns the balance of an address for specific ESDT Tokens. + +```bash +https://gateway.multiversx.com/address/*bech32Address*/esdt/*tokenIdentifier* +``` + +| Param | Required | Type | Description | +| --------------- | ----------------------------------------- | -------- | -------------------------------------- | +| bech32Address | REQUIRED | `string` | The Address to query in bech32 format. | +| tokenIdentifier | REQUIRED | `string` | The token identifier. | + + + + +```json +{ + "data": { + "tokenData": { + "balance": "99502603", + "properties": "", + "tokenIdentifier": "GLD-0d0060" + } + }, + "error": "", + "code": "successful" +} +``` + + + + + +### GET **Get all roles for tokens of an address** {#get-all-roles-for-tokens-of-an-address} + +This involves a basic request that contains the address to fetch all tokens and roles for. +For example: + + + + +```bash +https://gateway.multiversx.com/address/*bech32Address*/esdts/roles +``` + + + + +```json +{ + "data": { + "roles": { + "TCK-0cv5hj": ["ESDTRoleNFTAddQuantity", "ESDTRoleNFTBurn"], + "TCK-ft90kn": ["ESDTRoleLocalBurn"] + } + }, + "error": "", + "code": "successful" +} +``` + + + + + +### GET **Get token's supply, burnt and minted values** {#get-tokens-supply-burnt-and-minted-values} + +This involves a basic request that contains the token name. It will gather data from all shards and compute the +initial minted value, burnt value, minted value and total supply value. + + + + +| Param | Required | Type | Description | +| --------------- | ----------------------------------------- | -------- | ---------------------------------------------- | +| tokenIdentifier | REQUIRED | `string` | The token identifier (example: `WEGLD-bd4d79)` | + +```bash +https://gateway.multiversx.com/network/esdt/supply/*token name* +``` + + + + +```json +{ + "data": { + "supply": "95000000000000000000", + "minted": "5000000000000000000", + "burned": "10000000000000000000", + "initialMinted": "100000000000000000000" + }, + "error": "", + "code": "successful" +} +``` + + + + + +### GET **Get all issued ESDT tokens** {#get-all-issued-esdt-tokens} + +1. All ESDT tokens + +For example: + + + + +```bash +https://gateway.multiversx.com/network/esdts +``` + + + + +```json +{ + "data": { + "tokens": [ + "token1", + "token2", + ... + ], + "error": "", + "code": "successful" +} +``` + + + + +--- + +2. Fungible tokens + + + + +```bash +https://gateway.multiversx.com/network/esdt/fungible-tokens +``` + + + + +```json +{ + "data": { + "tokens": [ + "token1", + "token2", + ... + ], + "error": "", + "code": "successful" +} +``` + + + + +--- + +3. Semi-fungible tokens + + + + +```bash +https://gateway.multiversx.com/network/esdt/semi-fungible-tokens +``` + + + + +```json +{ + "data": { + "tokens": [ + "token1", + "token2", + ... + ], + "error": "", + "code": "successful" +} +``` + + + + +--- + +4. Non-fungible tokens + + + + +```bash +https://gateway.multiversx.com/network/esdt/non-fungible-tokens +``` + + + + +```json +{ + "data": { + "tokens": [ + "token1", + "token2", + ... + ], + "error": "", + "code": "successful" +} +``` + + + + + +### GET **Parse fungible tokens transfer logs** {#parse-fungible-tokens-transfer-logs} + +Each **successful** ESDT transfer generates logs and events that can be used to parse all the details about a transfer +(token identifier, sent amount and receiver). +In order to get the logs and events generated by the transfer, one should know the transaction's hash. + + + + +| Param | Required | Type | Description | +| ------ | ----------------------------------------- | -------- | --------------------------- | +| txHash | REQUIRED | `string` | The hash of the transaction | + +```bash +https://gateway.multiversx.com/transaction/*txHash*?withResults=true +``` + + + + +```json +{ + "data": { + "transaction": { + ... + "logs": { + "address": "...", + "events": [ + { + "address": "...", + "identifier": "ESDTTransfer", + "topics": [ + "TUVYLTQ1NWM1Nw==", // MEX-455c57 + "", // N/A + "CKxyMEiegAAA", // 160000000000000000000 + "givNK+JiLZ5VA5/dP11QKoYEn7qoqnD8uPchH3ZMLw4=" // erd1sg4u62lzvgkeu4grnlwn7h2s92rqf8a64z48pl9c7us37ajv9u8qj9w8xg + ], + "data": null + }, + } + } + "error": "", + "code": "successful" +} +``` + +The event with the identifier `ESDTTransfer` will have the following topics: + +- 1st topic: token identifier (decoding: base64 to string) +- 2nd topic: token nonce (used for NFTs only, not applicable here) +- 3rd topic: the amount to be sent (decoding: base64 to hex string + hex string to big number) +- 4th topic: the recipient of the tokens (decoding: base64 to hex string + hex string to bech32 address) + +In this example, `erd1sg4u62lzvgkeu4grnlwn7h2s92rqf8a64z48pl9c7us37ajv9u8qj9w8xg` received `160 MEX-455c57` (MEX-455c57 has 18 decimals) + + + + + +### POST **Get ESDT token properties** {#get-esdt-token-properties} + +This involves a `vm query` request to the `ESDT` address. +For example: + + + + +```bash +https://gateway.multiversx.com/vm-values/query +``` + +```json +{ + "scAddress": "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u", + "funcName": "getTokenProperties", + "args": ["474c442d306430303630"] +} +``` + +The argument must be the token identifier, hexadecimal encoded. In the example, `474c442d306430303630` = `GLD-0d0060`. + + + + +```json +{ + "data": { + "data": { + "returnData": [ + "QWxpY2VUb2tlbnM=", + "RnVuZ2libGVFU0RU", + "2DSJxJNAmou8TU9f4WQo7rpyJ822eZVUQYwnabJM5hk=", + "MTAwMDAwMDAwMDA=", + "MA==", + "TnVtRGVjaW1hbHMtNg==", + "SXNQYXVzZWQtZmFsc2U=", + "Q2FuVXBncmFkZS10cnVl", + "Q2FuTWludC10cnVl", + "Q2FuQnVybi10cnVl", + "Q2FuQ2hhbmdlT3duZXItZmFsc2U=", + "Q2FuUGF1c2UtdHJ1ZQ==", + "Q2FuRnJlZXplLXRydWU=", + "Q2FuV2lwZS10cnVl", + "Q2FuQWRkU3BlY2lhbFJvbGVzLXRydWU=", + "Q2FuVHJhbnNmZXJORlRDcmVhdGVSb2xlLWZhbHNl", + "TkZUQ3JlYXRlU3RvcHBlZC1mYWxzZQ==", + "TnVtV2lwZWQtMA==" + ], + "returnCode": "ok", + "returnMessage": "", + "gasRemaining": 18446744073659551615, + "gasRefund": 0, + "outputAccounts": { + "000000000000000000010000000000000000000000000000000000000002ffff": { + "address": "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u", + "nonce": 0, + "balance": null, + "balanceDelta": 0, + "storageUpdates": {}, + "code": null, + "codeMetaData": null, + "outputTransfers": [], + "callType": 0 + } + }, + "deletedAccounts": null, + "touchedAccounts": null, + "logs": [] + } + }, + "error": "", + "code": "successful" +} +``` + +The `returnData` member will contain an array of the properties in a fixed order (base64 encoded). For the example response, the meaning is: + +```json +"returnData": [ + "QWxpY2VUb2tlbnM=", | token name | AliceTokens + "RnVuZ2libGVFU0RU", | token type | FungibleESDT + "2DSJxJNAmou8TU9f4WQo7rpyJ822eZVUQYwnabJM5hk=", | bytes of a bech32 address | erd1mq6gn3yngzdgh0zdfa07zepga6a8yf7dkeue24zp3snknvjvucvs37hmrq after decoding + "MTAwMDAwMDAwMDA=", | total supply | 10000000000 + "MA==", | burnt value | 0 + "TnVtRGVjaW1hbHMtNg==", | number of decimals | NumDecimals-6 + "SXNQYXVzZWQtZmFsc2U=", | is paused | IsPaused-false + "Q2FuVXBncmFkZS10cnVl", | can upgrade | CanUpgrade-true + "Q2FuTWludC10cnVl", | can mint | CanMint-true + "Q2FuQnVybi10cnVl", | can burn | CanBurn-true + "Q2FuQ2hhbmdlT3duZXItZmFsc2U=", | can change token management address | CanChangeOwner-true + "Q2FuUGF1c2UtdHJ1ZQ==", | can pause | CanPause-true + "Q2FuRnJlZXplLXRydWU=", | can freeze | CanFreeze-true + "Q2FuV2lwZS10cnVl", | can wipe | CanWipe-true + "Q2FuQWRkU3BlY2lhbFJvbGVzLXRydWU=", | can add special roles | CanAddSpecialRoles-true + "Q2FuVHJhbnNmZXJORlRDcmVhdGVSb2xlLWZhbHNl", | can transfer nft create role | CanTransferNFTCreateRole-false + "TkZUQ3JlYXRlU3RvcHBlZC1mYWxzZQ==", | nft creation stopped | NFTCreateStopped-false + "TnVtV2lwZWQtMA==" | number of wiped quantity | NumWiped-0 +], +``` + + + + + +### POST **Get special roles for a token** {#get-special-roles-for-a-token} + +This involves a `vm query` request to the `ESDT` address. It will return all addresses that have roles assigned for the token +with the provided identifier. +For example: + + + + +```bash +https://gateway.multiversx.com/vm-values/query +``` + +```json +{ + "scAddress": "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u", + "funcName": "getSpecialRoles", + "args": ["474c442d306430303630"] +} +``` + +The argument must be the token identifier, hexadecimal encoded. In the example, `474c442d306430303630` = `GLD-0d0060`. + + + + +```json +{ + "data": { + "data": { + "returnData": [ + "ZXJkMTM2cmw4NzhqMDltZXYyNGd6cHk3MGsyd2ZtM3htdmo1dWN3eGZmczl2NXQ1c2sza3NodHN6ejI1ejk6RVNEVFJvbGVMb2NhbEJ1cm4=", + "ZXJkMWt6enYydXc5N3E1azltdDQ1OHFrM3E5dTNjd2h3cXlrdnlrNTk4cTJmNnd3eDdndnJkOXM4a3N6eGs6RVNEVFJvbGVORlRBZGRRdWFudGl0eSxFU0RUUm9sZU5GVEJ1cm4=" + ], + "returnCode": "ok", + ........ +} +``` + +In this example, converting the 2 messages from base64 to string would result in: + +- `erd136rl878j09mev24gzpy70k2wfm3xmvj5ucwxffs9v5t5sk3kshtszz25z9:ESDTRoleLocalBurn` +- `erd1kzzv2uw97q5k9mt458qk3q9u3cwhwqykvyk598q2f6wwx7gvrd9s8kszxk:ESDTRoleNFTAddQuantity,ESDTRoleNFTBurn` + + + + +--- + +### Gas + +## Overview + +**`Gas`** is the fourth generic that originated from a transaction. It sets an explicit gas limit for the call. Some transactions require explicit gas, others do not necessitate it. + + +## Diagram + +The gas limit can be set using the `.gas(...)` method. Gas is normally represented as a `u64` value. + +```mermaid +graph LR + subgraph Gas + gas-unit["()"] + gas-unit -->|gas| gas-explicit["ExplicitGas(u64)"] + gas-unit -->|gas| GasLeft + end +``` + + + +## Default gas + +Transactions that do not contain the **`.gas()`** call have a default gas limit of **5,000,000**. This is relevant only in MandosGO because RustVM does not measure. + +Simple transfers, of which VM is aware, that have no payload do not need to explicitly set the gas limit. It extracts the gas left. The default leftover is **100,000** so in case there is no more gas left, the current calling transaction is set to finish. + +For interactors, there is a default amount of **5,000,000** set. If a different amount is needed than the default one, it can be changed, like in the example below. + +```rust title=interactor.rs +fn register(&mut self, name: &str, address: Address) { + self.interactor + .tx() + .from(&self.wallet_address) + .to(self.contract ) + .gas(NumExpr("30,000,000")) + .typed(proxy::Proxy) + .register(address, name) + .prepare_async() + .run() + .await; +} +``` + +### Optional gas + +- **`.async_call()`**: since v0.49.0, this is a legacy function. +- **`.sync_call()`**: synchronously deploys a contract from source. For transactions that do not contain a payload, it is not necessary to set explicit gas. By default, the gas limit is set to the gas left. Executes transactions performed in the same context as the name of the caller. Equivalent to `.execute_on_dest_context()` from versions lower than `v0.49.0`. + +## Explicit gas + +Any **interactor call** that needs a higher amount than the default one is necessary to set using `.gas()`. You can find an example in the *Default Section*. + +The same rule is applied to functions created in integration testing (the blackbox test). + +If the transfer is executed with a payload, then **the gas must be explicitly specified**. The example below is a snippet from a contract that sets explicit the gas in the transaction because the deploy call contains an argument_raw. +```rust title=lib.rs +#[endpoint] +fn deploy_from_source( + &self, + source_contract_address: ManagedAddress, + code_metadata: CodeMetadata, + args: MultiValueEncoded, +) -> ManagedAddress { + self.tx() + .raw_deploy() + .from_source(source_contract_address) + .code_metadata(code_metadata) + .arguments_raw(args.to_arg_buffer()) + .gas(self.blockchain().get_gas_left()) + .returns(ReturnsNewManagedAddress) + .sync_call() +} +``` + +--- + +### Gateway overview + +## Overview of MultiversX Gateway REST API + +The MultiversX's Gateway REST API can be used by any application - dApp, desktop or server application - to interact with the Blockchain. +The Gateway is backed by the [proxy](/sdk-and-tools/proxy) and the names Gateway or Proxy are often referred as the same thing. +The difference is that `gateway` is MultiversX's infrastructure backed by the [mx-chain-proxy-go](https://github.com/multiversx/mx-chain-proxy-go) repository. + + +## **Proxy API vs. Observer API** + +Both the **MultiversX Proxy** and the **Node (Observer)** are designed to expose (almost) the same HTTP endpoints, though **this doesn't always hold due to architectural constraints**. When describing each HTTP endpoint on the following pages, if there is any discrepancy or mismatch between the _Proxy endpoint_ and the _Observer endpoint_, this will be captured in a note as the one below: + +:::important +Proxy/Gateway endpoints are referred as `https://gateway.multiversx.com/....`, while node endpoints are referred as `http://localhost:8080/....`. +::: + + +## **Authentication** + +Currently, authentication is not needed to access the API. + + +## **HTTP Response format** + +Each request against the MultiversX API will resolve to a JSON response having the following structure: + +``` +{ + "data": { + ... + }, + "error": "...", + "code": "..." +} +``` + +That is, all responses will contain the fields `data`, `error` and `code`. + +In case of a **success**, the `data` field is populated, the `error` field is empty, while the `code` field is set to `**successful**`. For example: + +``` +{ + "data": { + "account": { + "address": "erd1...", + "nonce": 0, + "balance": "42", + ... + } + }, + "error": "", + "code": "successful" +} +``` + +In the case of an **error**, the `data` field is unset, the `error` field contains a _human-readable_ description of the issue, while the `code` field is set to a both _machine_ and _human-readable_ error code. For example: + +``` +{ + "data": null, + "error": "checksum failed. Expected 2rq9g5, got smsgld.", + "code": "internal_issue" +} +``` + +:::important +When describing each HTTP endpoint on the following pages, the basic structure of the response is **simplified for brevity,** and, in general, only the actual payload of the response is depicted. +::: + +--- + +### Generating scenarios + +There are currently several ways to generate scenarios. + +The combination of generating and running scenarios is very powerful, since it means tests written originally for one system can be run of different systems too. + +This diagram shows all the currently possible paths. + +```mermaid +graph TD + interact ----> blockchain["⚙️ Blockchain"] + interact[Interactor] -->|trace| json + manual[Manually implemented] --> json["JSON Scenario + *.scen.json"] + bb[BlackBox Test] -->|trace| json + wb[WhiteBox Test] -.->|trace| json + json --> test-go["Generated + *_scenario_go_test.rs"] --> vm-go["⚙️ Go VM"] + json --> test-rs["Generated + *_scenario_rs_test.rs"] --> vm-rust["⚙️ Rust VM (Debugger)"] + bb --> vm-rust + wb --> vm-rust +``` + +More details about this coming soon. + +--- + +### Go SDK + +## MultiversX SDK for Golang + +**sdk-go** consists of Go helpers and utilities for interacting with the Blockchain. + +The source code be found here: [mx-sdk-go](https://github.com/multiversx/mx-sdk-go). + +--- + +### Google BigQuery + +[comment]: # "mx-abstract" + +This page succinctly describes how to use Google BigQuery to analyze data from the MultiversX blockchain. + + + +## Overview + +[**BigQuery**](https://cloud.google.com/bigquery/docs/introduction) is Google's fully managed, serverless data warehouse that enables analysis of extremely large datasets using [SQL queries](https://cloud.google.com/bigquery/docs/introduction-sql) and / or visual tools (such as [Google Looker Studio](https://lookerstudio.google.com)); it also has built-in [machine learning capabilities](https://cloud.google.com/bigquery/docs/bqml-introduction). + +[**MultiversX Blockchain data**](https://console.cloud.google.com/marketplace/product/bigquery-public-data/blockchain-analytics-multiversx-mainnet-eu) is published to Google BigQuery, and available (for free) through the [**Google Cloud Marketplace**](https://console.cloud.google.com/marketplace/product/bigquery-public-data/blockchain-analytics-multiversx-mainnet-eu). The dataset, namely [**`bigquery-public-data.crypto_multiversx_mainnet_eu`**](https://console.cloud.google.com/bigquery?p=bigquery-public-data&d=crypto_multiversx_mainnet_eu&page=dataset), is one of many crypto datasets that are available within [**Google Cloud Public Datasets**](https://cloud.google.com/bigquery/public-data). One can query these datasets for free: up to 1TB / month of free processing, every month. + +The MultiversX BigQuery dataset closely resembles the set of indices of the [**MultiversX Elasticsearch instance**](/sdk-and-tools/elastic-search#elasticsearch-indices). Their schema and data are **approximately equivalent**, the data [being mirrored from the Elasticsearch instance to BigQuery](https://github.com/multiversx/multiversx-etl) at regular intervals (most tables are updated _hourly_, and a few are updated every _4 hours_). + +:::note +As of February 2024, the MultiversX BigQuery dataset **is not updated in real-time** (see above). For real-time data, [use the public APIs](/sdk-and-tools/rest-api). +::: + +:::note +If you experience any issue with the published dataset, please [let us know](https://github.com/multiversx/multiversx-etl/issues). +::: + + +## Query from BigQuery Studio + +[**Google BigQuery Studio**](https://cloud.google.com/bigquery/docs/query-overview#bigquery-studio) is a unified workspace for Google Cloud's data analytics suite which incorporates, among others, an SQL editor (optionally [assisted by AI](https://cloud.google.com/bigquery/docs/write-sql-duet-ai)) and Python notebooks. It is a great way to explore the MultiversX dataset, and to run queries. Below, we'll explore a few example queries. + +:::tip +Make sure to explore the dataset, the tables and their schema before running queries. Both the schema and a data preview are available in BigQuery Studio. +::: + + +#### How many transactions were processed on MultiversX, in the last couple of days? + +```sql +SELECT + DATE(`timestamp`) `day`, + COUNT(*) `transactions` +FROM `bigquery-public-data.crypto_multiversx_mainnet_eu.operations` +WHERE + type = 'normal' + AND DATE(`timestamp`) >= DATE_SUB(CURRENT_DATE(), INTERVAL 7 DAY) +GROUP BY `day` +ORDER BY `day` DESC +``` + + +#### Which were the top used Smart Contracts, in the last couple of days? + +```sql +SELECT + DATE(`timestamp`) `day`, + `receiver` `contract`, + COUNT(DISTINCT `sender`) `num_users`, +FROM `bigquery-public-data.crypto_multiversx_mainnet_eu.operations` +WHERE + type = 'normal' + AND `isScCall` = true +GROUP BY `day`, `contract` +HAVING `day` >= DATE_SUB(CURRENT_DATE(), INTERVAL 3 DAY) AND `num_users` > 1000 +ORDER BY `day` DESC, `num_users` DESC +``` + + +#### What ESDT tokens have the most holders? + +```sql +SELECT + `token`, + `type`, + COUNT(_id) `num_holders` +FROM `bigquery-public-data.crypto_multiversx_mainnet_eu.accountsesdt` +WHERE `type` = 'FungibleESDT' OR `type` = 'MetaESDT' +GROUP BY `token`, `type` +HAVING `num_holders` > 5000 +ORDER BY `num_holders` DESC +``` + + +#### What are the transactions with the largest transferred EGLD amounts, in the last couple of days? + +```sql +SELECT + `day`, + `hash`, + `sender`, + `receiver`, + `amount` +FROM ( + SELECT + DATE(`timestamp`) `day`, + `_id` `hash`, + `sender`, + `receiver`, + PARSE_BIGNUMERIC(`value`) `amount`, + ROW_NUMBER() OVER (PARTITION BY DATE(`timestamp`) + ORDER BY PARSE_BIGNUMERIC(`value`) DESC) AS `row_num` + FROM + `bigquery-public-data.crypto_multiversx_mainnet_eu.operations` + WHERE + type = 'normal' + AND `status` = 'success' + AND DATE(`timestamp`) >= DATE_SUB(CURRENT_DATE(), INTERVAL 7 DAY) ) +WHERE `row_num` = 1 +ORDER BY `day` DESC +LIMIT 7; +``` + + +#### What is the (global) network hitrate, per day, in the last month? + +```sql +SELECT + DATE(`timestamp`) `day`, + -- 14400 is the number of rounds per day, and 3 + 1 = 4 is the number of shards + ROUND(COUNT(*) / (14400 * 4), 4) `hit_rate` +FROM `bigquery-public-data.crypto_multiversx_mainnet_eu.blocks` +WHERE + DATE(`timestamp`) >= DATE_SUB(CURRENT_DATE(), INTERVAL 30 DAY) + AND DATE(`timestamp`) < CURRENT_DATE() +GROUP BY `day` +ORDER BY `day` DESC +``` + +:::note +Even if BigQuery includes a generous free tier, it is important to be mindful of the costs associated with running queries. For more information, see [BigQuery pricing](https://cloud.google.com/bigquery/pricing). + +If you believe that specific optimizations can be applied to the dataset (to improve query performance), please [let us know](https://github.com/multiversx/multiversx-etl/issues). +::: + + +## Analyze using Looker Studio + +[**Google Looker Studio**](https://lookerstudio.google.com) is a powerful tool for analyzing data and creating (shareable) reports. Out of the box, it connects to BigQuery (and many other data sources), thus it's a great way to explore the MultiversX dataset. + +Example of report created in Looker Studio (leveraging the MultiversX dataset in BigQuery): + +![img](/sdk-and-tools/looker_studio_1.png) + +:::tip +In the BigQuery Studio, you can save the results of a given query as your own BigQuery tables, then immediately import them in Looker Studio, to create visualizations and reports. +::: + + +## Programmatic access + +One can also query datasets programmatically, using the [BigQuery client libraries](https://cloud.google.com/bigquery/docs/reference/libraries). + +See how to [query a public dataset with the BigQuery client libraries](https://cloud.google.com/bigquery/docs/quickstarts/quickstart-client-libraries). + +--- + +### Guard accounts + +On this page, you will find comprehensive information on all aspects of guarded (co-signed) transactions. + + +## Introduction + +Phishing attacks that scam people have become a constant problem on blockchains in general, especially when newbies to the crypto world are granted easy access to this new economy. In light of these new challenges, it is important to continue educating people on specific ways to protect themselves while also considering new methods to add protection against such attacks. That's how we came to a method that we called Guardians, an optional security feature that any user can enable. + + +### Trusted Co-Signer Service (TCS) + +In the proposed setup, an off-chain handshaking process is necessary between the user and the guardian to ensure transaction security. This protocol requires integration at the wallet application level, such as the MultiversX web wallet, MultiversX wallet extension, or xPortal wallet app. The purpose of the guardian feature is to protect users whose accounts may have been compromised due to leaked secret keys. + +Let's consider a scenario where two friends, Alice and Bob, have MultiversX wallets with addresses addrAlice and addrBob, respectively. Alice asks Bob to become her guardian, and after Bob agrees, Alice sets addrBob as her guardian. Once the guardian signing is activated through the GuardAccount function (which can only be done 20 days after setting the guardian), every transaction from Alice requires not only her signature but also Bob's signature. + +For example, when Alice creates a guarded transaction to transfer 1 EGLD to Charlie, the transaction includes two new fields: GuardianAddr and GuardianSignature, in addition to the regular transaction details. Alice signs the transaction and sends it to Bob for co-signing. The wallet back-end can handle the transaction submission and temporarily store incomplete guarded transactions, displaying them in the guardian wallet (Bob's wallet) as "transactions waiting for guardian approval." Bob then verifies if these transactions are genuinely from Alice or from someone impersonating her. In this scenario, Bob can directly ask Alice if the pending transaction is hers. If confirmed, he will sign it; otherwise, he may choose to reject it. + +While this process appears feasible in theory, there may be practical challenges. Alice might not want to wait for Bob's approval for a DEX transaction, or Bob could be overwhelmed with transactions from both Alice and potentially Eve, who gained access to Alice's secret key. Thus, it is crucial to automate the entire process to balance security and user experience without imposing high costs. One proposed automation solution is to use a Trusted Co-Signer (TCS) service that acts as a guardian, based on 2FA validation. This solution requires support from all wallet implementations that offer guardian-setting flows. In the case of the xPortal wallet, the application on the device will act as the guardian, optimizing the communication and eliminating the need for the TCS service. + + +## For developers + +If you are a developer and you understand what a transaction is, what fields it contains and how a signing of a transaction happens you may be interested in the technical aspects of the implementation. On top of a regular transaction, 2 new fields must be filled in: +- [NEW] `guardian` - representing the address of the Guardian that has to co-sign the transaction +- [NEW] `guardianSignature` - representing the the signature computed by the guardian + +Also, some other fields have to be altered: +- `version` needs to be at least `2` +- `options` needs to have the second least significant bit set to `1`. For example: `0011` _bin_ = `3` _dec_ + +The full activation of a guardian requires two actions: +1. Setting a Guardian +2. Guard the account + +After guarding the account, _almost_ every action the user takes should be co-signed by the guardian. + +:::note +**Why not mandate that every transaction is co-signed by a guardian when an account is guarded?** + +This is done in order to not have the user locked out of his account (e.g user guards his account but the guardian also loses access to his account). For this reason, the user should not only be able to **_set up a new guardian_**, but also **_change the guardian_** if he wishes so (e.g when the guardian is compromised). +::: + + +### Setting & Changing the guardian + + +### Set a Guardian + +In order to register a guardian a user has to set a **guardian** address by sending a ```SetGuardian``` transaction (described in built-in functions [here](/developers/built-in-functions#setguardian)). The guardian address becomes active after **20 epochs**, a period longer than the unbond time (**10 epochs**). The guardian address should be set into the account’s key-value store. + + +### Guard account + +In order to activate guardian signing (co-signing) a ```GuardAccount``` builtin function transaction needs to be sent to MultiversX network. A guard account transaction can be issued by the account and processed by the protocol only if the account has an ```activeGuardian``` (see [guardian-data fields](/sdk-and-tools/rest-api/addresses#get-address-guardian-data)) already set (which implies that the **20 epochs** since sending the ```SetGuardian``` already passed). When the account is guarded, any transaction issued by the owner of the account would be ignored unless it also carries the signature of its set guardian. This transaction could be sent either directly from the user or also through a relayer, if the user does not have the egld required for the transaction fee. + +The field ```"guarded":true``` in [guardian-data fields](/sdk-and-tools/rest-api/addresses#get-address-guardian-data) specifies that the ```GuardAccount``` transaction was executed and was successful for the account. In this case all transactions from the user account, to be executed by the protocol, will require to be guarded transactions, and the guardian to be the activeGuardian of the account. The only exception is the SetGuardian transaction which can as well be sent through a regular transaction, in which case, the new guardian will become pending for 20 epochs, and at the end of these 20 epochs replace the current active guardian. + +In case ```"guarded":false``` even if there is a non-empty ```activeGuardian```, the protocol will still only execute regular (non-guarded) transactions. + +:::note +The exception is the ```setGuardian``` transaction which can either: + +- be executed immediately if it is co-signed by the active guardian and **cleans up any pending guardian activation**; +- not be confirmed by the guardian but in this case the guardian can only be set with an activation time in future (e.g the same 20 epochs from the initial setting of a guardian). + +::: + +:::note +The ```GuardAccount``` transaction should clear any pending guardian. +::: + + +### Sending guarded (co-signed) transactions + +Sending a guarded transaction will follow the same process of using the [Send Transaction](/sdk-and-tools/rest-api/transactions#send-transaction) endpoint. In order for the transaction to be accepted by the protocol (when account is guarded and more than 20 epochs passed after sending the ```SetGuardian``` transaction), the optional fields ```guardian``` and ```guardianSignature``` have to be completed along with the proper version and option. + +:::info +For a Guarded Transaction the **Version** must be set to **2** and **Options** needs to have the second least significant bit set to `1`. +::: + +A guarded transaction would look like: + +```rust +{ + "nonce": 2, + "value": "0", + "receiver": "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + "sender": "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + "guardian": "erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8", + "gasPrice": 1000000000, + "gasLimit": 1177500, + "data": "U2V0R3VhcmRpYW5AYjEzYTAxNzQyM2MzNjZjYWZmOGNlY2ZiNzdhMTI2MTBhMTMwZjQ4ODgxMzQxMjJjNzkzN2ZlYWUwZDZkN2QxN0A3NTc1Njk2NA==", #base64 representation of Setting a Guardian + "signature": "08e324107be096fa887d3a7679c1a612f138a7fb99936c60f767ac2ff98bd9cb1d161f738971da3934aac639de83870e03fdf0753f40b59b250334ffc881af03", + "guardianSignature": "595a2fe26259c68789450f479515d40455b54507caf3a2e9bf62aa5e67ba45d38ea15c9ed06abb43a9a3644315ea2e3efefb83ce4a0f08ab89a99ac878049f01", + "chainId": "local-testnet", + "version": 2, + "options": 2, +} +``` + +:::info +Both sender and guardian must sign the following serialized transaction: +```rust +{ + "nonce": 2, + "value": "0", + "receiver": "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + "sender": "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + "guardian": "erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8", + "gasPrice": 1000000000, + "gasLimit": 1177500, + "data": "U2V0R3VhcmRpYW5AYjEzYTAxNzQyM2MzNjZjYWZmOGNlY2ZiNzdhMTI2MTBhMTMwZjQ4ODgxMzQxMjJjNzkzN2ZlYWUwZDZkN2QxN0A3NTc1Njk2NA==", #base64 representation of Setting a Guardian + "signature": "", + "guardianSignature": "", + "chainId": "local-testnet", + "version": 2, + "options": 2, +} +``` +::: + + +### Deactivate guarding the account + +For unguarding the account, a guarded ```UnGuardAccount``` transaction has to be sent (see details in [built-in functions](/developers/built-in-functions#unguardaccount)). + + +### Change the Active Guardian + +Accounts can still be compromised by phishing attacks or other different ways. For unknown reasons, you may be confronted with the necessity to change the active guardian. The scope of account guarding is to protect users in case of a compromised account, but there are edge cases where it is uncertain whether a user or a scammer is changing the guardian, potentially leading to a scammer taking control of a user's funds. To address this issue, additional measures are necessary to allow users to protect their funds. One solution is the need for an activation time for setting a new guardian. If the transaction is not confirmed by an existing guardian, the activation time will give the user a chance to take action before the new guardian is set. In case the guardian confirms the transaction, the change will be made immediately. This solution will ensure that users have control over their accounts and can protect their funds even in the face of unexpected changes. + +Taking into account the aforementioned considerations for changing to a new guardian, it's worth noting that a user must send a new ```SetGuardian``` ([described here](/developers/built-in-functions#setguardian)) transaction to change guardians. This transaction may either be unguarded (meaning that the guardian does not have to co-sign it, so the transaction will not contain a guardian address on the ```guardian``` field, nor the ```guardianSignature```, with proper transaction version and option, see [transaction fields](/sdk-and-tools/rest-api/transactions#send-transaction)), or it may be a guarded transaction (in the event that the account was already guarded). In the case of an unguarded transaction, it will take 20 days to become active, whereas a guarded transaction will occur instantly. + +If the sent ```SetGuardian``` transaction is an unguarded one, the user will be able to see ```pendingGuardian``` set ([along with the activation epoch](/sdk-and-tools/rest-api/addresses#get-address-guardian-data)). On the other hand, if the transaction is a guarded one, the new guardian will become directly active. + + +## For users + + +### Wallet Extension + +The process of utilizing the wallet extension generally remains unchanged, with one additional step. Now, whenever you wish to initiate a guarded transaction, you will need to input the 2FA code for the TCS service. This step ensures that your transaction is properly signed by the guardian. Refer to [wallet-extension](/wallet/wallet-extension#guardian) page for more information. + + +### Web-Wallet + +Follow on details about web-wallet [here](/wallet/web-wallet.md#guardian). + + +### xPortal + +**xPortal** fully supports the guardians feature. For more details, see [**invisible guardians**](/wallet/xportal/#what-are-invisible-guardians). + + +### Ledger + +The **MultiversX App** for **Ledger** supports the guardians feature (since version **v2.2.2**, released in August 2023). To set up a guardian for a Ledger account, follow [these steps](/wallet/web-wallet.md#guardian). + + +## For integrators + +There should be no impact on integrators who utilize the [sdk-dapp](https://github.com/multiversx/mx-sdk-dapp). For everybody else, let's take the subject in the thread open on this [Discord](https://discord.com/channels/1045353153073258557/1110128760595959838) channel. + + +### Trusted Co-Signer Service for Guardians + +In the future we want to publish the codebase to MultiversX TCS so that third party TCS service providers will be allowed to be listed in the MultiversX wallet, this process of launching a TCS guardian service needs to be defined. + +--- + +### Guardians + +## Introduction + +The guardians feature adds an extra signing layer on top of transactions. This means that a guarded account (having the `isGuarded` flag set to true) will require a Guardian to sign the transaction before it can be executed. + + +### Specifications of a guarded transaction + +The differences between a guarded transaction and a simple transaction are the following: +- it contains the `guardianAddress` field +- `gasLimit` has an extra `50000` gas added to it +- `options` field needs to have the second least signinficant bit set to "1" (ex: options: `2`) +- `version` field needs to be set to `2` + +This is how a transaction can be formatted using `@multiversx/sdk-core`: + +```js +transaction.setGuardian(Address.fromBech32(activeGuardianAddress)); +transaction.setVersion(TransactionVersion.withTxOptions()); +transaction.setOptions(TransactionOptions.withOptions({ + guarded: true, + hashSign: true // if available +})); +``` + + +### Signing the transaction + +Once these fields are set, the transaction must be signed by both Guardian (adding the `guardianSignature` field) and UserSigner (adding the `signature` field). +All signing providers (except Ledger) take care internally of formatting the transaction, as described above (excluding adding the extra `gasLimit`). + + +#### Signing the transaction with Ledger + +After formatting the transaction and applying the signature provided by the Ledger device, the transaction must be signed by the Guardian. This is done by sending the transaction (or transactions array) to the web wallet 2FA hook. The web wallet will then prompt the user to sign the transaction with the Guardian account, and respond with the signed transaction. + +```js +import { CrossWindowProvider } from "@multiversx/sdk-web-wallet-cross-window-provider"; + +// instantiate wallet cross-window provider +await CrossWindowProvider.getInstance().init(); +const crossWindowProvider = CrossWindowProvider.getInstance(); +crossWindowProvider.setWalletUrl(WALLET_PROVIDER_URL); + +// set sender +const ledgerSenderBech32 = await this.hwProvider.getAddress(); +const sender = Address.newFromBech32(ledgerSenderBech32); // or "erd1...abc" witohut awaiting `getAddress()` +crossWindowProvider.setAddress(sender); + +// To complete the transaction, the user will sign using their Ledger device. This requires an additional step: a confirmation popup will appear, prompting the user to approve the action, after which a new tab will open in the browser. +crossWindowProvider.setShouldShowConsentPopup(true); + +const guardedTransactions = await crossWindowProvider.guardTransactions( + signedTransactions +); +``` + +For a working example see the code used in [signing-providers](https://github.com/multiversx/mx-sdk-js-examples/releases/tag/v0.9.0) + +--- + +### How to fix Elasticsearch mapping errors + +Starting with the February 2023 mainnet upgrade new constrains for Elasticsearch indices were added. Therefore, one can notice +that the observers that index data in the Elasticsearch will remain stuck with an error similar to: + +`dataDispatcher.doWork could not index item (will retry) error = { "index": "operations-000001", +"id": "", "statusCode": 400, "errorType": "mapper_parsing_exception", "reason": "failed to parse field [esdtValuesNum] of type [long] in document` + +If an observer with `elastic-indexer` enabled will throw a `log.WARN` that contains `"errorType": "mapper_parsing_exception"` +for an index, then one should follow the next steps: + +In the example below we will repair the `operations` index. + + +## Solution 1 + +1. Stop the observers nodes that index data in the Elasticsearch cluster. +2. Force a `rollover` for the index with problems in this case `operations` index and the rollover have to contain the correct + mappings. + ``` + curl --request POST \ + --url ${ES_CLUSTER_URL}/operations/_rollover \ + --header 'Content-Type: application/json' \ + --data '{ + "mappings": { + "properties": { + "esdtValuesNum": { + "type": "double" + }, + {ADD_THE_REST_OF_THE_MAPPINGS_OF_THE_INDEX}:{}, + } + }, + "settings": { + "index": { + "sort.field": [ + "timestamp", + "nonce" + ], + "sort.order": [ + "desc", + "desc" + ] + }, + "number_of_replicas": 0, + "number_of_shards": 5 + } + }' + ``` +3. Make alias `operations` to fetch data from both indices("operations-000001" and "operations-000002"). + + ``` + curl --request POST \ + --url ${ES_CLUSTER_URL}/_aliases \ + --header 'Content-Type: application/json' \ + --data '{ + "actions" : [ + { "add" : { "index" : "operations-000001", "alias" : "operations" } }, + { "add" : { "index" : "operations-000002", "alias" : "operations", "is_write_index" : true } } + ] + }' + ``` + +4. Start again the observers. + + +## Solution 2 +:::caution +This solution will take more time because all the documents from the index with problems +have to be reindexed from a public cluster. +::: + +1. Stop the observers nodes that index data in the Elasticsearch cluster. +2. Delete affected index: `operations` +3. Create the index again with the correct mappings: + - in order to do this, clone this [repository](https://github.com/multiversx/mx-chain-tools-go) + - `cd elasticreindexer/cmd/indices-creator` + - open `config/cluster.toml` file and update it with the information about your cluster and at the `enabled-indices` + section put `["operations"]` + - build the binary and run it in order to create the index with the correct mappings. + +4. After the index was created you can start again the observers. +5. Copy all the data from a public Elasticsearch cluster for the index with problems. + - in order to do this follow the steps from this [documentation](https://github.com/multiversx/mx-chain-tools-go/tree/mappings-for-all-fields/elasticreindexer#step-2) (only for the index/indices with problems) + +--- + +### Installing mxpy + +This page describes how to install **mxpy** (the CLI tool). The recommended way to install **mxpy** is by using **pipx**. If you want to learn more about **pipx** you can check out [this page](https://pipx.pypa.io/stable/#overview-what-is-pipx). + +:::note +If you'd like to use **mxpy** on Windows, we recommend installing it within the [Windows Subsystem for Linux (WSL)](https://learn.microsoft.com/en-us/windows/wsl/install). If you experience an issue, please follow the [troubleshooter](/sdk-and-tools/troubleshooting/multiplatform). +::: + + +## **Prerequisites** + +Before installing **mxpy**, please make sure you have a working **Python 3** environment. You'll need **Python 3.8** or later on Linux or MacOS. + + +## **Install using pipx** + +You'll need **pipx** installed on your machine. For more details on how to install **pipx** check out [**this page**](https://pipx.pypa.io/stable/#install-pipx). + +:::note +If you've previously installed **mxpy** using **mxpy-up**, we advise you to switch to **pipx**, but make sure to remove the old `mxpy` shortcut and virtual Python environment beforehand: + +```sh +rm ~/multiversx-sdk/mxpy +rm -rf ~/multiversx-sdk/mxpy-venv +``` + +::: + +In order to install **mxpy** using `pipx`, run the following command: + +```sh +pipx install multiversx-sdk-cli --force +``` + +This will simply install the latest version available. + +As of latest versions, we also publish the package under the `mxpy` name. Typing the following command should also work: +```sh +pipx install mxpy +``` + +In case you want to install a specific version you should also specify the version. + +```sh +pipx install multiversx-sdk-cli==11.0.0 +``` + +You can also install **mxpy** directly from a GitHub branch. Replace `branch_name` with your desired branch and run the following command: + +```sh +pipx install git+https://github.com/multiversx/mx-sdk-py-cli@branch_name +``` + +To check that **mxpy** installed successfully you can run the following command: + +```sh +mxpy --version +``` + +As of version `v9.7.0`, **mxpy** includes support for shell completion on both Linux and macOS. To get started, first ensure that `pip` is installed by running the following command: + +```sh +pip3 --version +``` + +If `pip` is not installed, we can install it on **Linux** with: + +```sh +sudo apt install python3-pip +``` + +For **macOS** users, install `pip` by executing: + +```sh +python3 -m ensurepip +``` + +Once `pip` is installed, proceed by installing the required package with: + +```sh +pip3 install argcomplete +``` + +Finally, activate shell completion with this command: + +```sh +activate-global-python-argcomplete +``` + + +## **Upgrade mxpy using pipx** + +To upgrade **mxpy** to a newer version, you can simply run the following command: + +```sh +pipx upgrade multiversx-sdk-cli +``` + +or, if you've installed the CLI using the `mxpy` name: + +```sh +pipx upgrade mxpy +``` + +--- + +### Interactors Example + +## Setup + +To gain a practical understanding of how interactors work, let's examine an example using the adder contract. We'll generate an interactor template and demonstrate how to utilize its features effectively. While some steps are optional and depend on developer preference, they are included for comprehensive understanding. + +In order to save time, we will also generate the `adder` contract, using the following command: + +```bash +sc-meta new --template adder +``` + +This command will create a new contract folder, containing a copy of the `adder` contract. + + +## Steps + + +### Generate interactor code + +Quickly generate interactor code by executing `sc-meta all snippets` in the root of the adder folder: + +```bash +adder % sc-meta all snippets +``` + +![img](/img/adder_snippets_gen.jpeg) + +After running `sc-meta all snippets` in the adder root folder, a new project named `interactor` is generated along with its own `Cargo.toml`. For the adder contract, the `sc-config.toml` file is already created, so the new proxy path is only inserted into the file. + + +### Import the new project + +To compile and run the code, import the new project into the existing file hierarchy. Include `interactor` in adder's `Cargo.toml` workspaces: + +```toml +[workspace] +members = [ + ".", + "meta", + "interactor" +] +``` + + +### Use the CLI (optional) + +At this stage, the interactor is operational, and various CLI commands are available. In the `interactor_main.rs` file, the `main` function outlines all available CLI commands. + +```rust title=interactor_main.rs +#[tokio::main] +async fn main() { + env_logger::init(); + + let mut args = std::env::args(); + let _ = args.next(); + let cmd = args.next().expect("at least one argument required"); + let mut interact = ContractInteract::new().await; + match cmd.as_str() { + "deploy" => interact.deploy().await, + "getSum" => interact.sum().await, + "add" => interact.add().await, + _ => panic!("unknown command: {}", &cmd), + } +} +``` + +As seen in the picture, three CLI commands are available, each corresponding to an endpoint of the contract (deploy, add, and getSum). To deploy the contract and generate the `state.toml` file containing the newly deployed address, execute `cargo run deploy` in the `interactor` root folder. + +```bash +interactor % cargo run deploy +``` + +![img](/img/state_toml_gen.jpeg) + +Following this command, the `state.toml` file becomes visible and contains the newly deployed address from the API. + + +### Attach tracer (optional) + +Optionally, let's attach a [tracer](/developers/meta/interactor/interactors-overview#traces) to the interactor. This records every action and writes it as a mandos step at a specified path. + +```rust title=interactor_main.rs +impl ContractInteract { + async fn new() -> Self { + let config = Config::new(); + let mut interactor = Interactor::new(config.gateway_uri()) + .await + .use_chain_simulator(config.use_chain_simulator()) + .with_tracer("trace1.scen.json") // file path + .await; + let wallet_address = interactor.register_wallet(test_wallets::alice()).await; + + let contract_code = BytesValue::interpret_from( + "mxsc:../output/adder.mxsc.json", + &InterpreterContext::default(), + ); + + ContractInteract { + interactor, + wallet_address, + contract_code, + state: State::load_state(), + } + } +} +``` + +Now, all the interactor actions from a scenario will be transformed into mandos steps and written into a new file called `trace1.scen.json`. + +```json title=trace1.scen.json +{ + "steps": [ + { + "step": "setState", + "newAddresses": [ + { + "creatorAddress": "0x0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1", + "creatorNonce": "1427", + "newAddress": "bech32:erd1qqqqqqqqqqqqqpgqmauhsqd6zr7kt8pg80qhph2tw0ejed3pd8sszl98x7" + } + ] + }, + { + "step": "scDeploy", + "id": "", + "tx": { + "from": "0x0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1", + "contractCode": "mxsc:../output/adder.mxsc.json", + "arguments": [ + "0x" + ], + "gasLimit": "5,000,000" + }, + "expect": { + "out": [], + "status": "0" + } + }, + { + "step": "scCall", + "id": "", + "tx": { + "from": "0x0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1", + "to": "bech32:erd1qqqqqqqqqqqqqpgqmauhsqd6zr7kt8pg80qhph2tw0ejed3pd8sszl98x7", + "function": "add", + "arguments": [ + "0x03" + ], + "gasLimit": "5,000,000" + }, + "expect": { + "out": [], + "status": "0" + } + }, + { + "step": "scQuery", + "id": "", + "tx": { + "to": "bech32:erd1qqqqqqqqqqqqqpgqmauhsqd6zr7kt8pg80qhph2tw0ejed3pd8sszl98x7", + "function": "getSum", + "arguments": [] + }, + "expect": { + "out": [ + "0x03" + ], + "status": "0" + } + } + ] +} +``` + + +### Create scenarios (optional) + +Further, let's create more complex scenarios using the functions provided by the snippets generator or the built-in CLI commands. For instance, let's introduce a new CLI command named `full_scenario`. + +First, we are going to add the CLI command: + +```rust title=interactor_main.rs +#[tokio::main] +async fn main() { + env_logger::init(); + + let mut args = std::env::args(); + let _ = args.next(); + let cmd = args.next().expect("at least one argument required"); + let mut interact = ContractInteract::new().await; + match cmd.as_str() { + "deploy" => interact.deploy().await, + "getSum" => interact.sum().await, + "add" => interact.add().await, + "full" => interact.full_scenario().await, + _ => panic!("unknown command: {}", &cmd), + } +} +``` + +Then, we should add the actual `full_scenario` function that will run the scenario. + +```rust interactor_main.rs +impl ContractInteract { + // ... + + async fn full_scenario(&mut self) { + self.deploy().await; // deploys adder contract + self.add().await; // calls add endpoint with hardcoded value + self.sum().await; // queries sum view + } + + // ... +} +``` + +This function is a mix of our previous endpoint calls. First, we deploy a new contract, then we call the `add` endpoint to add value to the storage and, in the end, we query the `sum` view in order to receive the final sum amount. We have also modified the `add` function so that it actually sends some value instead of 0 (default). + +```rust title=interactor_main.rs + async fn add(&mut self) { + let value = BigUint::::from(3u128); + + let response = self + .interactor + .tx() + .from(&self.wallet_address) + .to(self.state.current_address()) + .typed(proxy::AdderProxy) + .add(value) + .returns(ReturnsResultUnmanaged) + .prepare_async() + .run() + .await; + + println!("Result: {response:?}"); + } +``` + +Executing the above mentioned function will give us this result: + +```bash +interactor % cargo run full + Compiling rust-interact v0.0.0 (/Users/calin/Documents/work/MultiversX/adder/interactor) + Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.69s + Running `/Users/calin/Documents/work/MultiversX/adder/target/debug/rust-interact full` +sender's recalled nonce: 1427 +-- tx nonce: 1427 +sc deploy tx hash: bf5122ea02e48d02f1107d494ceb3e75097d8ffd12ed050a0f3074ec5293c573 +deploy address: erd1qqqqqqqqqqqqqpgqmauhsqd6zr7kt8pg80qhph2tw0ejed3pd8sszl98x7 +new address: erd1qqqqqqqqqqqqqpgqmauhsqd6zr7kt8pg80qhph2tw0ejed3pd8sszl98x7 +sender's recalled nonce: 1428 +-- tx nonce: 1428 +sc call tx hash: 7f5ca4106aa0101e0712d8a582609bcfd6db80b299dae26ac25ae5a77995afbe +Result: () +Result: 3 +``` + + +### Create system tests (optional) + +Another effective method for system testing with interactors bypasses the need for CLI usage entirely. By leveraging `tokio`'s test feature, you can write system tests within a tokio runtime environment, functioning like standard Rust tests. + +![img](/img/system_test.jpeg) + +We can also write the test using the previously defined `full_scenario` method: + +```rust title=interactor_main.rs +#[tokio::test] +async fn test_full_farm_scenario() { + let mut contract_interactor = ContractInteract::new().await; + contract_interactor.full_scenario().await; +} +``` + +As illustrated, these tests resemble typical Rust tests but apply scenarios directly on the actual blockchain, facilitated by the [interactor tx environment](/developers/transactions/tx-env#interactor). These tests can be executed with `cargo test` or `sc-meta test`, seamlessly integrating into your standard testing workflow. + +--- + +### Interactors Overview + +## Overview + +Interactors are Rust programs designed to facilitate interactions with smart contracts on the blockchain. They are essential for system testing and managing the setup and execution of smart contracts in a live blockchain environment. Making use of unified syntax, proxies, and the capability to be autogenerated using `sc-meta all snippets`, interactors streamline the interaction process, providing a quick and efficient solution for system testing, seamlessly integrating with the development workflow. + + +## Key Features + +- **System testing**: Perform comprehensive scenario-based testing on the actual blockchain to validate contract behavior under real-world conditions. +- **Contract setup and calling**: Interactors simplify the process of setting up (deploy/upgrade) and interacting with smart contracts (calling smart contract endpoints, query), making it easier for Rust developers to deploy and manage their contracts on the blockchain. + +::::info +Before using interactors, make sure you have the following setup: + +- `Rust` programming language installed on your system: + - get `rustup`: + ```bash + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh + ``` + - choose default toolchain + ```bash + rustup update + rustup default stable + rustup target add wasm32-unknown-unknown + ``` + - check installed toolchains + ```bash + rustc --version + rustup show + ``` +- MultiversX Rust framework version `0.50.0` or higher. +- [`multiversx-sc-meta`](/developers/meta/sc-meta) tool installed for generating an interactor template. +:::: + + +## Workflow + +When interacting with a contract from a Rust testing environment, the following steps are typically required: + +1. **Build the contract**: Compile and build the smart contract. +2. **Create the proxy file**: This can be autogenerated using `sc-meta all proxy`, which requires a properly configured `sc-config.toml` for custom proxy paths. Adding a proxy path into the `sc-config.toml` file ensures that a proxy will be created at that path. +3. **Create a new project and import the proxy**: Set up a new project and import the generated proxy. +4. **Write code using unified syntax**: Utilize the proxy and unified syntax to write the interaction code. +5. **Run the code and send transactions**: Execute the code to send transactions, either against the Rust VM or on the actual blockchain. + + +**However**, thanks to the snippets generator, the workflow for the Rust interactors is very straightforward. The `sc-meta` tool **automates all the previously mentioned steps**, generating the necessary boilerplate code for building an interactor for the current contract. + + +::::important +To generate an interactor, simply run `sc-meta all snippets` in the root folder of the contract. + +```bash +adder % sc-meta all snippets +``` +:::: + +This command performs the following actions: +- **Compiles the contract** (still needs to be built). +- **Creates a** `sc-config.toml` configuration file. If the file already exists, another proxy path is inserted (the path to the interactor) without changing the previous setup. +- **Generates a proxy** based on the configuration path from the file (`proxy.rs`). +- **Creates an async Rust program with a CLI** based on the contract endpoints found in the proxy. Returns typed results for further processing. The new project will be under the newly created `interactor` folder, inside the contract root folder. +- **Creates** `config.toml` and `config.rs` files used to setup and parse the chain simulator config. + +::::note +Make sure to include the newly generated interactor into the existing file hierarchy to be able to compile and run the code. +:::: + + +## Scenarios & CLI + +Given the customizable nature of real-life scenarios, the generated interactor provides only a minimal starting point for your Rust program. +The generated interactions include: +- Separate functions for calling each endpoint of the smart contract (based on the proxy). +- A CLI with distinct commands for each specific endpoint call. + +In order to run the code and make use of the CLI, simply call `cargo run ` in the root of the `interactor` folder. + +```bash +interactor % cargo run deploy +``` + +If, however, the CLI needs to cover more complex scenarios, the generated async functions can be used directly and composed with each other. If a testing approach is preferred instead of the CLI, then we can make use of the `tokio::test` feature, and we could write a system test as such: + +```rust title=interact.rs +#[tokio::test] +async fn test_full_adder_scenario() { + let mut contract_interactor = ContractInteract::new().await; + + contract_interactor.deploy().await; // deploy adder + contract_interactor.add(2u64).await; // add 2 + let sum = contract_interactor.sum().await; // fetch sum + + println!("sum is {:#?}", sum); +} +``` + +If ran, this test will run the scenario and will produce transactions on the actual blockchain, depending on the environment setup (devnet, testnet and mainnet). + +Running the `deploy` command for every contract will create a new `state.toml` file, where information about the contract state will be written for consistency. + + +## Traces + +To enhance testing support, any instance of the `Interactor` struct implements the `.with_tracer(...)` method. This method enables the interactor to use a `tracer` and output the result to the specified path. A tracer records every action executed through the interactor, mapping each action to a `mandos step`. + +```rust title=interact.rs + let mut interactor = Interactor::new(config.gateway_uri()) + .await + .use_chain_simulator(config.use_chain_simulator()) + .with_tracer("test_trace.scen.json") + .await; +``` + +This ensures the tracer is active, and a file containing all the mandos steps for the specific scenario will be created at the specified path. Having a quickly generated mandos test that encompasses the entire scenario is highly beneficial. After generating the mandos test, you can swiftly test the same scenario against both the Rust and GO VMs. + +--- + +### Intro to ESDT + +Fungible tokens, such as cryptocurrencies, are interchangeable and have the same value as other tokens of the same type. Non-fungible tokens (NFTs) are unique digital assets that are assigned unique identification codes and metadata, making them one-of-a-kind. Semi-fungible tokens are a combination of the two, offering both interchangeable and unique properties. For us they are ESDTs. + +With ESDTs, you can take advantage of the security, transparency, and versatility of MultiversX blockchain technology to manage and transfer your assets. + + +## ESDT + +ESDT stands for eStandard Digital Token. + +One important implication is that a token issued on MultiversX does not need a dedicated smart contract. Token transactions do not require the Virtual Machine at all. + +This greatly enhances the efficiency and cost of managing and transferring any kind of token. In effect, this means that custom tokens are as fast and as scalable as the native EGLD token itself. + +The ESDT standard is used to manage fungible, semi-fungible and non-fungible tokens at protocol level. + +Users also do not need to worry about sharding when transacting custom tokens, because the protocol employs the same handling mechanisms for ESDT transactions across shards as the mechanisms used for the EGLD token. Sharding is therefore automatically handled and invisible to the user. + +Technically, the balances of ESDT tokens held by an account are stored directly under the data trie of that Account. It also implies that an account can hold balances of any number of custom tokens, in addition to the native EGLD balance. The protocol guarantees that no account can modify the storage of ESDT tokens, neither its own nor of other accounts. + +ESDT tokens can be issued, owned and held by any account on the MultiversX network, which means that both users and smart contracts have the same functionality available to them. Due to the design of ESDT tokens, smart contracts can manage tokens with ease, and they can even react to an ESDT transfer. + + +## Table of contents + +| Name | Description | +|-------------------------------------------------------------------------|--------------------------------------------------------| +| [Specs](https://github.com/multiversx/mx-specs/blob/main/ESDT-specs.md) | Official specifications of MultiversX's token standard | +| [Fungible tokens](/tokens/fungible-tokens) | Fungible ESDT tokens | +| [Semi-fungible tokens](/tokens/nft-tokens) | Semi-Fungible ESDT tokens | +| [Non-fungible tokens](/tokens/nft-tokens) | Non-Fungible ESDT tokens | + +--- + +### Iterate keys + +## Overview + +Retrieving all storage keys for an account can be resource-intensive if the account has many entries. The `/address/iterate-keys` endpoint allows clients to efficiently iterate through all key-value pairs in an account's data trie, fetching them in batches and resuming from a checkpoint using an iterator state. This is especially useful for large accounts or when paginating through storage. + +If you need to ensure consistency across multiple requests (e.g., if the account might change), use the `?blockNonce={blockNonce}` query parameter to lock iteration to a specific trie root. + +:::warning +This endpoint will only work if the node's `config.toml` file has the following setting enabled: + +``` +[TrieLeavesRetrieverConfig] + Enabled = true +``` +::: + + +## Endpoint details + +- **Method:** POST +- **Path:** `/address/iterate-keys` +- **Optional:** `/address/iterate-keys?blockNonce={blockNonce}` + +### Request body + +| Field | Type | Description | +| -------------- | ------ | --------------------------------------------------------------------------------------------------| +| `address` | string | Address of the account whose storage keys you want to iterate. | +| `numKeys` | int | Number of keys to retrieve in this batch. If 0, retrieves as many as possible until timeout. | +| `iteratorState`| array | Set to empty array for the first request, or use the value from the previous response to resume. | + +#### Example - First request + +```json +{ + "address": "erd1...", + "numKeys": 100, + "iteratorState": [] +} +``` + +#### Example - Subsequent request + +```json +{ + "address": "erd1...", + "numKeys": 100, + "iteratorState": [ + "QTyP0ZbUPao3dJiNhdduVDc2GlJO5XNSljRJS2lpF00EBg==", + "F6Wc4zEhjoS2cpcmb4h4tH+8hNHwbez/mskIzpKr7ooF", + "qE7Onkq+OYx9bCx2OPRl5GUIE3iqqA0I+hC7E35+2EwG", + "eNu9LmbWHS5cjjaONCn3oc22+9H/hc2rvjHdJVLb9p8H", + "9ikE6F470N3x4UxfSnXpqM6ATHUpdAAk7TwNEziXD5QI", + "pymZnCzkTZ91cKFLTUlY0S5du5deg3CJXcR/jZR9gDUJ", + "dK7SeJcCggBlkhKoQpfLbbQ1RkwRgDENK8YhjUu71HcK", + "pYbrJttg/Cqzxko2IyqVWLeEiY1ScLYjPiVdqNX1PFcL", + "vTuXGEd5YBqLX/bwG9rOhb+Ect25N5IIEgHR8TMklL4M", + "MXbChMP5migm07zByj85+h3EZorzDwj4A0lRcNBIV1QO", + "wq+g7t7WX/6bEcxZhGvQlIfgJxzY/gK2BR/IDjBVYw8P" + ] +} +``` + + +### Response body + +| Field | Type | Description | +|-------------------------|---------------------|-----------------------------------------------------------------------------| +| `data.blockInfo` | object | Information about the block (nonce, hash, rootHash). | +| `data.newIteratorState` | array \| null | Array of strings if more data remains, or null if iteration is complete. | +| `data.pairs` | object | Key-value pairs (hex-encoded) retrieved in this batch. | +| `error` | string | Error message, if any. | +| `code` | string | Status code, e.g., "successful". | + +#### Example response (more data to fetch) + +```json +{ + "data": { + "blockInfo": { + "nonce": 141738, + "hash": "f7fd9ee6a3a24ab63c30f2a7c9df360d8e1d367aed52b43ec527bfd6aa8eae35", + "rootHash": "96bb085e08cec47a45df37ed07abd6ed2e22fdeeb5192a6a2bb624cb8c18b3e1" + }, + "newIteratorState": [ + "JZWO0TF7sEredrzrZQ3aSj40w29valylR4GHv65qiBoADw==", + "WrN0877/4OOj2muLRNNhXbSB7wBpSKVqHJRhiinqOuQB", + "4WECDX5h4NSKqdG4KOJgOdlbmbKMOYDc0GmUPH7ALoUC", + "j+j5EIMbiE6VXkRRarus5AMImhC4eo6HIb7SBNga9VMD", + "kR1nAJ1HXgc1OKnuJOu2U4qlGVx90zTiiFzvvyTBZ2AE", + "F6Wc4zEhjoS2cpcmb4h4tH+8hNHwbez/mskIzpKr7ooF", + "qE7Onkq+OYx9bCx2OPRl5GUIE3iqqA0I+hC7E35+2EwG", + "eNu9LmbWHS5cjjaONCn3oc22+9H/hc2rvjHdJVLb9p8H", + "9ikE6F470N3x4UxfSnXpqM6ATHUpdAAk7TwNEziXD5QI", + "pymZnCzkTZ91cKFLTUlY0S5du5deg3CJXcR/jZR9gDUJ", + "dK7SeJcCggBlkhKoQpfLbbQ1RkwRgDENK8YhjUu71HcK", + "pYbrJttg/Cqzxko2IyqVWLeEiY1ScLYjPiVdqNX1PFcL", + "vTuXGEd5YBqLX/bwG9rOhb+Ect25N5IIEgHR8TMklL4M", + "MXbChMP5migm07zByj85+h3EZorzDwj4A0lRcNBIV1QO", + "wq+g7t7WX/6bEcxZhGvQlIfgJxzY/gK2BR/IDjBVYw8P" + ], + "pairs": { + "454c524f4e4465736474415745534f4d452d313632623032": "0403", + "454c524f4e4465736474415745534f4d452d383831343636": "0403" + // ... more key-value pairs ... + } + }, + "error": "", + "code": "successful" +} +``` + +#### Example response (iteration complete) + +```json +{ + "data": { + "blockInfo": { + "nonce": 112594, + "hash": "f26e9d8c00d7a56c071989d14544e0c3431eb33956854ce780374d8c08b4aa9f", + "rootHash": "75c50f6913d4badb9635235f9dafc7eb14ce14406f8c59d05838ff25021c009f" + }, + "newIteratorState": null, + "pairs": { + "454c524f4e446573647446554e4749424c452d326562313837": "0401" + } + }, + "error": "", + "code": "successful" +} +``` + + +## Usage notes + +- The `pairs` object contains hex-encoded keys and values. Decode as needed. +- Always pass `newIteratorState` as-is in your next request's `iteratorState` field to continue iteration. +- When `newIteratorState` is null, all keys have been retrieved. +- If `iteratorState` is an empty array (`[]`) in the request, iteration starts from the beginning of the trie. +- If `numKeys` is 0, the server will return as many keys as possible until all are retrieved or until the `TrieOperationsDeadlineMilliseconds` timeout is reached. +- If retrieving `numKeys` takes more time than the configured timeout, the request will return the keys collected up to that point. +- Use `?blockNonce={blockNonce}` to ensure all requests iterate the same trie root, even if the account changes between requests. + + +## Example usage + +1. **Initial request:** + - Send a POST with `iteratorState` as an empty array and desired `numKeys` (or 0 for maximum batch). +2. **Subsequent requests:** + - Use the `newIteratorState` from the previous response to fetch the next batch. Pass it as-is. + - Optionally, provide `blockNonce` to ensure consistency if the account may change. +3. **Finish:** + - When `newIteratorState` is null, you have retrieved all keys. + +--- + +### Java SDK + +MultiversX SDK for Java + +**erdjava** consists of Java helpers and utilities for interacting with the Blockchain. The source code can be found here: [mx-sdk-erdjava](https://github.com/multiversx/mx-sdk-erdjava/). + +--- + +### JSON Structure + +Scenario JSON files are designed to be readable by humans too. This is why you will see + + +## **File extension** + +Scenario files should end in `.scen.json`, where "scen" comes from "scenario". The framework uses the double extension to identify tests to attempt running. Any other extension will be ignored. + +On a side note, there is also an older format that is now deprecated, where test file names end in `.test.json`, but you shouldn't worry about it. + + +## **Top level** + +A scenario test file is essentially a collection of steps to be performed on a mock blockchain. The simplest such file looks like this: + +```json +{ + "name": "example scenario file", + "comment": "comments are nice", + "steps": [ + ] +} +``` + +The top-level fields are as follows: + +- `name` (optional) - it is possible to name scenarios; this doesn’t have any effect on test execution +- `comment` (optional) - it is possible to have some comment; this doesn’t have any effect on test execution +- `steps` - the core of the scenario. Running a scenario means going through a number of different steps. There are several step types, we will go through each, one by one. Note that each item in this list will be a JSON map with a `step` field that discriminates the step type. + + +## Step type: `externalSteps` + +```json +{ + "steps": [ + { + "step": "externalSteps", + "path": "other.json" + } + ] +} +``` + +This step type is very useful for splitting, composing and reusing scenario steps. It is possible to have scenario bifurcation, to do so simply reuse the common part in 2 different tests. + +The only specific field here is `path`, which indicates the relative path to a JSON file containing scenario steps. The referenced file does not need to have the `.scen.json `extension, so does not need to be a valid scenario on its own. + +The imported steps will be run or re-run every time they are imported. There is no caching. + +Also beware that there is currently no protection against cyclic imports. + + +## Step type: `setState` + +```json +{ + "steps": [ + { + "step": "setState", + "comment": "not much to comment here, but we can", + "accounts": { + "address:user_account": { + "comment": "we can comment on individual account initializations", + "nonce": "0", + "balance": "123,000,000,000", + "esdt": { + "str:MYFUNGIBLE-0001": "400,000,000,000", + "str:MYSFT-123456": { + "instances": [ + { + "nonce": "24", + "balance": "1" + }, + { + "nonce": "25", + "balance": "1", + "creator": "address:other_creator_address", + "royalties": "5000", + "hash": "keccak256:str:other_nft_hash", + "uri": [ + "str:www.something.com/funny.jpeg" + ], + "attributes": "str:other_attributes" + } + ], + "lastNonce": "7", + "roles": [ + "ESDTRoleLocalMint", + "ESDTRoleLocalBurn", + "ESDTRoleNFTCreate", + "ESDTRoleNFTAddQuantity", + "ESDTRoleNFTBurn" + ], + "frozen": "false" + } + }, + "username": "str:myusername.x", + "storage": {}, + "code": "" + }, + "sc:smart_contract_address": { + "nonce": "0", + "balance": "23,000", + "esdt": { + "str:MYFUNGIBLE-0001": "100,000,000,000" + }, + "storage": { + "str:storage-key-1": "-5", + "str:storage-key-2|u32:4": ["u32:1", "u32:2", "u32:3"] + }, + "code": "file:smart-contract.wasm" + } + }, + "newAddresses": [ + { + "creatorAddress": "address:creator", + "creatorNonce": "1234", + "newAddress": "sc:future_sc" + } + ] + }, + { + "step": "setState", + "comment": "only set block info this time", + "previousBlockInfo": { + "blockNonce": "222", + "blockRound": "333", + "blockEpoch": "444" + }, + "currentBlockInfo": { + "blockTimestamp": "511", + "blockNonce": "522", + "blockRound": "533", + "blockEpoch": "544" + } + } + ] +} +``` + +This step type is used to initialize the blockchain mock, or reconfigure it during execution. + +At least one such step is required before any execution, because all transactions need existing accounts to work with. + +Not all of its sections are required each time. These sections are: + +- `comment` doesn't affect execution +- `accounts` any number of accounts can be specified, both user accounts and smart contracts. The account contains several fields, all of them optional: + - `comment` doesn't affect execution + - `nonce` account nonce at the beginning of the execution + - `balance` EGLD balance + - `esdt` a list of ESDT tokens owned by this account + - Owned ESDTs are represented as a map from token identifier to token data + - The parser does not validate token identifiers (the keys). + - There are 2 formats for expressing the ESDT value: + - Compact: for fungible tokens a single string containing the ESDT balance is enough. + - Full: as a map containing several fields: + - `instances` is a list containing the main token data. Each instance has a unique token nonce (although the parser does not enforce this uniqueness). Fungible tokens can only have 1 instance with the nonce 0. Semi-fungible tokens have non-zero nonces. Each instance has the following fields: + - `nonce` + - `balance` + - `creator` address of the account that created the NFT + - `royalties` a proportion out of 10000 that represents what percentage of an NFT sell price should be transferred to the creator. This is not enforced by the protocol or the parser in any way. + - `hash` NFT hash + - `uri` a list of URIs associated to the NFT/SFT + - `attributes` raw bytes where any data can be stored + - `lastNonce` the last created instance nonce for this token identifier. The next NFT/SFT will have nonce `lastNonce + 1` + - `roles` determine how the current account can interact with the ESDT token + - `frozen` ESDT tokens can be frozen by their creator if they are configured to be freezable + - `username` the "herotag", which is stored directly in the account trie + - `storage` initializes storage with given key-value map. Both keys and values can be of any length. + - `code` typically provided in the format `"code": "file:path/to/binary"` More on this [here](/developers/testing/scenario/values-simple#file-contents). Having a `code` makes the account a smart contract. +- `newAddresses` - mock contract address generation during deploy. We basically tell the blockchain mock what address name to generate when deploying new contracts. Not having this would give a generated address that is hard to predict when developing tests. It consists of a list of triples: + - `creatorAddress` + - `creatorNonce` + - `newAddress` - whenever an account with the given address and nonce deploys a contract, this will receive this address. This should make the test more readable, by having the new addresses explicitly stated, rather than being a magic new number popping up at some point in the scenario. +- `previousBlockInfo` and `currentBlockInfo` - set or change data that the blockchain mock is providing to smart contracts via hooks. The scenario test system does not model blocks, so this is how we simulate the passing of time in scenarios. Fields: + - `blockTimestamp` + - `blockNonce` + - `blockRound` + - `blockEpoch` + + +## Step type: `checkState` + +This step checks the state of the blockchain mock at a certain point. It can check the entire state or just part of it. + +Is allowed anywhere, not just as the end of tests, so progressive changes can be verified. + +```json +{ + "steps": [ + { + "step": "checkState", + "comment": "check that previous tx did the right thing", + "accounts": { + "address:user_account": { + "comment": "we can comment on individual account initializations", + "nonce": "0", + "balance": "*", + "esdt": { + "str:MYFUNGIBLE-0001": "*", + "str:MYSFT-123456": { + "instances": [ + { + "nonce": "24", + "balance": "*" + }, + { + "nonce": "25", + "balance": "1", + "creator": "address:other_creator_address", + "royalties": "5000", + "hash": "keccak256:str:other_nft_hash", + "uri": [ + "str:www.something.com/funny.jpeg" + ], + "attributes": "str:other_attributes" + } + ], + "lastNonce": "7", + "roles": [ + "ESDTRoleLocalMint", + "ESDTRoleLocalBurn", + "ESDTRoleNFTCreate", + "ESDTRoleNFTAddQuantity", + "ESDTRoleNFTBurn" + ], + "frozen": "false" + } + }, + "username": "str:myusername.x", + "storage": {}, + "code": "" + }, + "sc:smart_contract_address": { + "nonce": "0", + "balance": "23,000", + "esdt": { + "str:MYFUNGIBLE-0001": "100,000,000,000" + }, + "storage": { + "str:storage-key-1": "-5", + "str:storage-key-2|u32:4": "*", + "+": "" + }, + "code": "file:smart-contract.wasm" + } + } + } + ] +} +``` + +Fields: + +- `comment` (optional) - doesn't affect execution +- accounts - a map from account address to expected account state. It also accepts the optional entry `"+": ""`, which indicates that there can be other accounts in the blockchain mock that are not mentioned here. Without this field, unexpected account will cause an error. Each account state has the following fields: + - `nonce` - either expected nonce value, or `"*"` to skip check + - `balance` - either expected EGLD balance, or `"*" `to skip check + - `esdt` - either a list of token values, or `"*"` to skip check entirely. + - Note: by default no other tokens than the ones specified are allowed. To allow more tokens than the ones specified, add a `"+": ""` entry. + - `username` - either expected user name value, or `"*"` to skip check + - `storage` all key-value pairs must match, or `"*"` to skip check entirely. + - Note: by default no other entries than the ones specified are allowed. To allow more storage entries than the ones specified, add a `"+": ""` entry. + - `code` - expected smart contract code, or `"*"` to skip check + - `asyncCallData` - this field is set by asynchronous calls and when contracts send funds to an account + + +## Step type: `dumpState` + +Simply prints the entire state of the blockchain mock to the console. + +```json +{ + "step": "dumpState", + "comment": "print everything to console" +} +``` + + +## Step type: `scCall` + +```json +{ + "steps": [ + { + "step": "scCall", + "txId": "tx-name-or-id", + "comment": "just an example", + "tx": { + "from": "address:sender", + "to": "sc:contract", + "egldValue": "0", + "esdtValue": [ + { + "tokenIdentifier": "str:MYFUNGIBLE-000001", + "value": "250,000,000,000" + }, + { + "tokenIdentifier": "str:MYSFT-123456", + "nonce": "24", + "value": "1" + } + ], + "function": "contractEndpoint", + "arguments": [ + "str:argument-1", + "1234", + "", + "str:a message (as bytes)" + ], + "gasLimit": "5,000,000", + "gasPrice": "0" + }, + "expect": { + "out": [ + "5", + "*" + ], + "status": "", + "logs": [ + { + "address": "sc:contract", + "endpoint": "str:contractEndpoint", + "topics": [ + "str:topic-1", + "str:topic-2" + ], + "data": "str:log-value" + } + ], + "gas": "*", + "refund": "*" + } + } + ] +} +``` + +This step simulates a transaction to an existing smart contract. Fields: + +- `txId` (optional) - it shows up in the error messages, to help the developer find a transaction that produced a wrong result or that failed. It is also used to generate mock tx hashes. +- `comment` (optional) - developers can provide comments or a description of the transaction. Does not influence execution. +- `tx` - specifies the details of the transaction. + - `from` - account must exist in the blockchain mock + - `to` - account must exist in the blockchain mock and must be a smart contract + - `egldValue` - how much EGLD to transfer as part of the call. Only payable functions will accept this kind of payment. + - `esdtValue` - a list of ESDT tokens to transfer as part of the call. Cannot transfer both EGLD and ESDT at the same time. Each transferred item has the following fields: + - `tokenIdentifier` - the ESDT token unique identifier + - `nonce` - NFT/SFT token nonce. For fungible tokens the nonce is 0 and this field can be omitted. + - `value` - amount to transfer + - `function` - function name to call in the contract + - `arguments` - a list of the arguments to provide to the SC function + - `gasLimit` - maximum amount of gas allowed in SC execution + - `gasPrice` - how much each unit of gas costs in ERD. gasLimit x gasPrice will be subtracted from caller balance before the call. Normally, the unused gas (x gasPrice) is returned to the caller after executing the call. This does not happen in the scenario tests as it would make resulting balances a lot harder to manage. Hint: it is allowed for `gasPrice` to be zero, to avoid having to keep track of gas payments. +- `expect` (optional) - each transaction produces a receipt whose hash ends up on the blockchain. The contents of the receipt can be checked here. + - `out` - functions can return any number of results. This is an ordered list of these results. + - `status` - indicates whether execution completed successfully or not. Status 0 means success. All errors occurring in the contract will yield status 4 ("user error"). + - `message` (optional) - in case of error, the contract can also provide an error message. This is where this message can be checked, to make sure that the correct error occurred. It will be empty in case of success. + - `logs` - contracts can save logs off-chain, that can be later studied to determine what happened with the contract. In the contract they are referred to as "events". To skip checking logs, one can write `"logs": "*"`. + - `address` - smart contract address that produced the log. It can actually be different from the tx recipient if that contract in turn calls another contract. + - `identifier` - a contract can have multiple event types, each of them has an identifier. In the API the identifier is the first topic saved. In the Rust framework the event identifier is specified explicitly in the contract. + - `topics` - these are event arguments, provided by the contract. Off-chain they are indexed, so that users can search by these topics. All topics are currently 32 bytes in length, but this restriction might be lifted in the future. + - `data` - same as the topics, but this is not indexed, cannot perform searches on data. Can be of any length (or sometimes empty). + - `gas` - indicates the gas remaining for the transaction (`gasLimit` - gas consumed). To ignore this check, set to `"*"` + - `refund` - some operations, like freeing up storage actually gives EGLD back to the caller. To ignore this check, set to `"*"` + + +## Step type: `scQuery` + +```json +{ + "steps": [ + { + "step": "scQuery", + "txId": "query-id", + "comment": "query comment", + "tx": { + "to": "sc:contract", + "function": "contractView", + "arguments": [ + "str:argument-1", + "1234", + "", + "str:a message (as bytes)" + ], + }, + "expect": { + "out": [ + "5", + "*" + ], + "status": "" + } + } + ] +} +``` + +This step simulates a VM query from outside the blockchain. It is very similar to a `scCall`, with a few differences: +- There is no sender. Being an off-chain query, there is no actual transaction happening, so the idea of sender is immaterial. In practice, just like on the real blockchain, the sender is made equal to the contract address, so it's as if the contract is calling itself. +- Gas is irrelevant. No gas is consumed off-chain. +- None of the changes (if any) are persisted. Despite this, it is considered bad practice to query endpoints that are not readonly. +- Cannot transfer tokens in a query (neither EGLD nor ESDT). +- Querying does not increase any account nonce. + +Fields: + +- `txId` (optional) - it shows up in the error messages, to help the developer find a transaction that produced a wrong result or that failed. It is also used to generate mock tx hashes. +- `comment` (optional) - developers can provide comments or a description of the transaction. Does not influence execution. +- `tx` - specifies the details of the transaction. + - `to` - account must exist in the blockchain mock and must be a smart contract + - `function` - function name to call in the contract + - `arguments` - a list of the arguments to provide to the SC function +- `expect` (optional) - each transaction produces a receipt whose hash ends up on the blockchain. The contents of the receipt can be checked here. + - `out` - functions can return any number of results. This is an ordered list of these results. + - `status` - indicates whether execution completed successfully or not. Status 0 means success. All errors occurring in the contract will yield status 4 ("user error"). + - `message` (optional) - in case of error, the contract can also provide an error message. This is where this message can be checked, to make sure that the correct error occurred. It will be empty in case of success. + - `gas` - here the consumed gas can be checked. To ignore this check, set to `"*"` + - `refund` - some operations, like freeing up storage actually gives EGLD back to the caller. To ignore this check, set to `"*"` + + +## Step type: `scDeploy` + +It is very similar to `scCall`, but it is used specifically for simulating deployment of new smart contracts. + +```json +{ + "steps": [ + { + "step": "scDeploy", + "txId": "2", + "comment": "deploy example", + "tx": { + "from": "address:deployer", + "value": "123,000", + "contractCode": "str:new contract code here", + "arguments": [ + "str:init-arg-1", + "100", + "", + "str:a message (as bytes)" + ], + "gasLimit": "5,000,000", + "gasPrice": "0" + }, + "expect": { + "out": [], + "status": "", + "logs": [], + "gas": "*", + "refund": "0" + } + } + ] +} +``` + +The fields are: + +- `txId` (optional) - same as for `scCall` +- `comment` (optional) - same as for `scCall` +- `tx` - similar with `scCall`, but a few differences. First off, there is no `to` field, since the contract does not exist yet. Also function cannot be specified, on deploy the `init` function is always called. We have: + - `contractCode` - the code for the new contract. Typically, in the `"file:"` format. + - `from`, `value`, `arguments`, `gasLimit`, `gasPrice` - same as for `scCall` +- `expect` (optional) - same as for `scCall` + +Please note: cannot transfer ESDT during contract deploy. If you need to feed ESDTs to a contract when deploying, send them with a `scCall` immediately after deploy. + + +## Step type: `transfer` + +Lesser used step type. Simulates a simple transfer of EGLD between two accounts, without involving the VM. Note that simple transfers are also allowed toward smart contracts. They will increase the smart contract balance, without calling any function from it. + +```json +{ + "steps": [ + { + "step": "transfer", + "txId": "3", + "comment": "simple transfer, no VM", + "tx": { + "from": "address:sender", + "to": "address:receiver", + "egldValue": "0", + "esdtValue": [ + { + "tokenIdentifier": "str:MYFUNGIBLE-000001", + "value": "250,000,000,000" + }, + { + "tokenIdentifier": "str:MYSFT-123456", + "nonce": "24", + "value": "1" + } + ] + } + } + ] +} +``` + +The fields are: + +- `txId` (optional) - same as `scCall`/`scDeploy` +- `comment` (optional) - same as `scCall`/`scDeploy` +- `tx` + - `from` - same as `scCall`/`scDeploy` + - `to` - same as `scCall` + - `egldValue` - EGLD value + - `esdtValue` - same as `scCall` + + +## Step type: `validatorReward` + +Lesser used step type. Simulates a validator reward being sent by the protocol. This transaction has no sender, and beside increasing the recipient balance, it also increments the `ELRONDrewards` field in the smart contract storage. Useful when building delegation or other staking contracts. + +```json +{ + "steps": [ + { + "step": "validatorReward", + "txId": "4", + "comment": "system send out validator rewards", + "tx": { + "to": "sc:delegation_contract", + "value": "555,000,000" + } + } + ] +} +``` + +The fields are: + +- `txId` (optional) - same as `scCall`/`scDeploy` +- `comment` (optional) - same as `scCall`/`scDeploy` +- `tx` + - `to` + - `value` + +--- + +### Kotlin SDK + +MultiversX SDK for Kotlin + +**erdkotlin** consists of Kotlin helpers and utilities for interacting with the Blockchain. + +The source code can be found here: [mx-sdk-erdkotlin](https://github.com/multiversx/mx-sdk-erdkotlin). For more details, follow [the documentation](https://github.com/multiversx/mx-sdk-erdkotlin). + +--- + +### Legacy SC calls + +## Deprecated, kept for backwards compatibility + +This is the old contract call syntax, which was in use before the introduction of the unified transaction syntax. + +All of the objects described in this page are deprecated since version 0.49.0. + +:::caution +There are methods with the same names and arguments in the new syntax, added for backwards compatibility, but they don't construct the same object anymore. +::: + +:::caution +Unless you are still working on a project that predates 0.49.0, you should disregard this page. + +Even if you do work on an old project, you should strive to upgrade it to at least 0.50.0 and [migrate](tx-migration) the syntax. +::: + + +## Introduction + +As programmers, we are used to calling functions all the time. Smart contract calls, however, are slightly trickier. Beside code execution, there can also be transfers of EGLD and ESDT tokens involved, gas needs to be specified, and, finally, there are different flavors of calls. + +Any system for composing and sending these calls needs to be very versatile, to cater to all of the programmers' various needs. + +It is not so important _where_ that call comes from. Very often we need to call smart contracts from other smart contracts. However, testing systems and blockchain interactors also greatly benefit from such a system. + +The system we present below works just as well on- and off-chain. Think of it as a general Rust framework for specifying and sending calls in any context. + +:::note +The more primitive way to perform these calls is to just use the API methods directly, to serialize arguments in code and to specify endpoints as strings. But this does not give the compiler a chance to verify the correctness of the calls, and is very limited in how much we configure the call. It also normally leads to a lot of code duplication. For this reason we recommend always using contract call syntax when formatting transactions. +::: + +--- + + +## Contract calls: base + +Smart contract calls at the blockchain level have no notion of arity, or data types. That is, the blockchain itself does not validate the number of arguments (or results), and each of these only appear as raw binary data fields. + +It is the contract that keeps track of the number of arguments, and deserializes them. If a transaction has the wrong number of arguments, it is only the contract itself that will be able to complain. If the types are off, it is only during deserialization that the contract will know. + +The description of a smart contract's inputs is known as the [ABI](/developers/data/abi), and lives off-chain. In short, the ABI is a collection of endpoint names, with argument names and type descriptions. To be able to effectively call a smart contract, it is useful to know its ABI. + +The equivalent of the ABI in the Rust world is a a helper trait, called a __proxy__. All it does is that it provides a typed interface to any smart contract, it takes the typed arguments and it serializes them according to the [MultiversX serialization format](/developers/data/serialization-overview). + +Let's take this very simple example: + +```rust +adder_proxy.add(3u32) +``` + +Here, we have a proxy to the adder contract. The `add` method doesn't call the smart contract directly. Instead, it produces a contract call object that contains the data field `add@03`, which is something that the blockchain can make sense of. We will see later how this contract call can end up actually being executed. But until then, let's see how we can get hold of one of these proxies. + +This guide provides some examples on how to call a contract from another contract. More examples can be found in [the contract composability feature tests](https://github.com/multiversx/mx-sdk-rs/tree/master/contracts/feature-tests/composability). + +There are three ways of doing these calls: +- importing the callee contract's source code and using the auto-generated proxy (recommended) +- writing the proxy manually +- manually serializing the function name and arguments (not recommended) + + +### Proxies from contracts + +Whenever a smart contract is compiled, a proxy is also generated alongside it. This generated proxy is invisible, it comes from the procedural macros of the framework, specifically `#[multiversx_sc::contract]` and `#[multiversx_sc::module]`. + +This means that if you have access to the crate of the target contract, you can simply import it, and you get access to the generated proxy automatically. + +```toml +[dependencies.contract-crate-name] +path = "relative-path-to-contract-crate" +``` + +:::info important +Contract and module crates can be imported just like any other Rust crates either by: +- relative path; +- crate version, if it is released; +- git branch, tag or commit. + +Relative paths are the most common for contracts in the same workspace. +::: + +If the contract has modules with functionality that you may want to call, you will also need to import those. + +If the modules are in different crates than the target contract (and if the target contract doesn't somehow re-export them), you'll also have to add the module to the dependencies, the same way you added the target contract. + +These proxies are traits, just like the contracts themselves. The implementation is produced automatically, but nonetheless, this means that in order to call them, the proxy trait must be in scope. This is why you will see such imports everywhere these proxies are called: + +```rust +use module_namespace::ProxyTrait as _; +``` + +If you use the [rust-analyser VSCode extension](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer), it might complain that it can't find this, but if you actually build the contract, the compiler can find it just fine. + +Once you've imported the contract and any external modules it might use, you have to declare a proxy creator function in the contract: +```rust +#[proxy] +fn callee_contract_proxy(&self, callee_sc_address: ManagedAddress) -> contract_namespace::Proxy; +``` + +This function doesn't do much, it just tries to sort out the proxy trait imports, and neatly initializes the proxy for you. + +This function creates an object that contains all the endpoints of the callee contract, and it handles the serialization automatically. + +Let's say you have the following endpoint in the contract you wish to call: + +```rust +#[endpoint(caleeEndpoint)] +fn callee_endpoint(&self, arg: BigUint) -> BigUint { + // implementation +} +``` + +To call this endpoint, you would write something like: + +```rust +self.callee_contract_proxy(callee_sc_address) + .callee_endpoint(my_biguint_arg) + .async_call() + .call_and_exit(); +``` + +We'll talk about `async_call` and `call_and_exit` later on. + +:::caution +Importing a smart contract crate only works if both contracts use the exact framework version. Otherwise, the compiler will (rightfully) complain that the interfaces do not perfectly match. + +In case the target contract is not under our control, it is often wiser to just manually compose the proxy of interest. +::: + + +### Manually specified proxies + +If we don't want to have a dependency to the target contract crate, or there is no access to this crate altogether, it is always possible for us to create such a proxy manually. This might also be desirable if the framework versions of the two contracts are different, or not under our control. + +Below we have an example of such a proxy: + +```rust +mod callee_proxy { + multiversx_sc::imports!(); + + #[multiversx_sc::proxy] + pub trait CalleeContract { + #[payable] + #[endpoint(myPayableEndpoint)] + fn my_payable_endpoint(&self, arg: BigUint) -> BigUint; + } +} +``` + +The syntax is almost the same as for contracts, except the endpoints have no implementation. + +:::caution +Just like with smart contracts and modules, proxy declarations need to reside in their own module. This can either be a separate file, or an explicit declaration, like `mod callee_proxy {}` above. + +This is because there is quite a lot of code generated in the background that would interfere otherwise. +::: + +Manually declared proxies are no different from the auto-generated ones, so calling them looks the same. + +```rust +#[proxy] +fn callee_contract_proxy(&self, sc_address: ManagedAddress) -> callee_proxy::Proxy; +``` + + +### No proxy + +The point of the proxies is to help us build contract calls in a type-safe manner. But this is by no means compulsory. Sometimes we specifically want to build contract calls manually and serialize the arguments ourselves. + +We are looking to create a `ContractCallNoPayment` object. We'll discuss how to add the payments later. + +The `ContractCallNoPayment` has two type arguments: the API and the expected return type. If we are in a contract, the API will always be the same as for the entire contract. To avoid having to explicitly specify it, we can use the following syntax: + +```rust +let mut contract_call = self.send() + .contract_call::(to, endpoint_name); +contract_call.push_raw_argument(arg1_encoded); +contract_call.push_raw_argument(arg2_encoded); +``` + +If we are trying to create the same object outside of a smart contract, we do not have the `self.send()` API available, but we can always use the equivalent syntax: + +```rust +let mut contract_call = ContractCallNoPayment::::new(to, endpoint_name); +contract_call.push_raw_argument(arg1_encoded); +contract_call.push_raw_argument(arg2_encoded); +``` + + + + +### Diagram + +Up to here we have created a contract call without payment, so an object of type `ContractCallNoPayment`, in the following ways: + +```mermaid +graph LR + gen-proxy[Generated Proxy] --> ccnp[ContractCallNoPayment] + man-proxy[Manual Proxy] --> ccnp + ccnp-new["ContractCallNoPayment::new(to, function)"] --> ccnp +``` + +--- + + +## Contract calls: payments + +Now that we specified the recipient address, the function and the arguments, it is time to add more configurations: token transfers and gas. + +Let's assume we want to call a `#[payable]` endpoint, with this definition: + +```rust +#[payable] +#[endpoint(myPayableEndpoint)] +fn my_payable_endpoint(&self, arg: BigUint) -> BigUint { + let payment = self.call_value().any_payment(); + // ... +} +``` + +More on payable endpoints and simple transfers [here](/developers/developer-reference/sc-payments). This section refers to transfers during contract calls only. + + +### EGLD transfer + +To add EGLD transfer to a contract call, simply append `.with_egld_transfer` to the builder pattern. + +```rust +self.callee_contract_proxy(callee_sc_address) + .my_payable_endpoint(my_biguint_arg) + .with_egld_transfer(egld_amount) +``` + +Note that this method returns a new type of object, `ContractCallWithEgld`, instead of `ContractCallNoPayment`. Having multiple contract call types has multiple advantages: +- We can restrict at compile time what methods are available in the builder. For instance, it is possible to add ESDT transfers to `ContractCallNoPayment`, but not to `ContractCallWithEgld`. We thus no longer need to enforce at runtime the restriction that EGLD and ESDT cannot coexist. This restriction is also more immediately obvious to developers. +- The contracts end up being smaller, because the compiler knows which kinds of transfers occur in the contract, and which do not. For instance, if a contract only ever transfers EGLD, there is not need for the code that prepares ESDT transfers in the contract. If the check had been done only at runtime, this optimisation would not have been possible. + + +### ESDT transfers + +On the MultiversX blockchain, you can transfer multiple ESDT tokens at once. We create a single ESDT transfer type which works for both single- and multi-transfers. It is called `ContractCallWithMultiEsdt`. + +We can obtain such and object by starting with a `ContractCallNoPayment` and calling `with_esdt_transfer` once, or several times. The first such call will yield the `ContractCallWithMultiEsdt`, while subsequent calls simply add more ESDT transfers. + +:::info A note on arguments +There is more than one way to provide the arguments to `with_esdt_transfer`: +- as a tuple of the form `(token_identifier, nonce, amount)`; +- as a `EsdtTokenPayment` object. + +They contain the same data, but sometimes it is more convenient to use one, sometimes the other. +::: + +Example: + +```rust +let esdt_token_payment = self.call_value().single_esdt(); + +self.callee_contract_proxy(callee_sc_address) + .my_payable_endpoint(my_biguint_arg) + .with_esdt_transfer((token_identifier_1, 0, 100_000u32.into())) + .with_esdt_transfer((token_identifier_2, 1, 1u32.into())) + .with_esdt_transfer(esdt_token_payment) +``` + +In this example we passed the arguments both as a tuple and as an `EsdtTokenPayment` object. As we can see, when we already have an `EsdtTokenPayment` variable, it is easier to just pass it as it is. + +It is also possible to pass multiple ESDT transfers in one go, as follows: + +```rust +self.callee_contract_proxy(callee_sc_address) + .my_payable_endpoint(my_biguint_arg) + .with_multi_token_transfer(payments) +``` + +where `payments` is a `ManagedVec` of `EsdtTokenPayment`. + + +### Mixed transfers + +Sometimes we don't know at compile time what kind of transfers we are going to perform. For this reason, we also provide contract call types that work with both EGLD and ESDT tokens. + +First, we have the single transfer `ContractCallWithEgldOrSingleEsdt`, which can only handle a single ESDT transfer or EGLD. It used to be more popular before we introduced wrapped EGLD, but nowadays for most DeFi applications it is more convenient to just work with WEGLD and disallow EGLD transfers. + +```rust +let payment = self.call_value().egld_or_single_esdt(); +self.callee_contract_proxy(callee_sc_address) + .my_payable_endpoint(my_biguint_arg) + .with_egld_or_single_esdt_transfer(payment) +``` + +The most general such object is `ContractCallWithAnyPayment`, which can take any payment possible on the blockchain: either EGLD, or one or more ESDTs. + +```rust +let payments = self.call_value().any_payment(); +self.callee_contract_proxy(callee_sc_address) + .my_payable_endpoint(my_biguint_arg) + .with_any_payment(payments) +``` + + +### Diagram + +To recap, these are all the various ways in which we can specify value transfers for a contract call: + +```mermaid +graph LR + ccnp[ContractCallNoPayment] + ccnp -->|".with_egld_transfer"| cc-egld[ContractCallWithEgld] + ccnp -->|".with_esdt_transfer"| cc-multi[ContractCallWithMultiEsdt] + cc-multi -->|".with_esdt_transfer"| cc-multi + ccnp -->|".with_multi_token_transfer"| cc-multi + ccnp -->|".with_any_payment"| cc-any[ContractCallWithAnyPayment] + ccnp -->|".with_egld_or_single_esdt_transfer"| cc-egld-single[ContractCallWithEgldOrSingleEsdt] +``` + +--- + + +## Contract calls: gas + +Specifying gas is fairly straightforward. All contract call objects have a `with_gas_limit` method, so gas can be specified at any point. + +Not all contract calls require explicit specification of the gas limit, leaving it out is sometimes fine. Notably: +- async calls will halt execution and consume all the remaining gas, so specifying the gas limit is not necessary for them; +- synchronous calls will by default simply use all the available gas as the upper limit, since unspent gas is returned to the caller anyway. + +On the other hand, promises and transfer-execute calls do require gas to be explicitly specified. + +```rust +self.callee_contract_proxy(callee_sc_address) + .callee_endpoint(my_biguint_arg) + .with_gas_limit(gas_limit) +} +``` + +--- + + +## Contract calls: execution + +There are several ways in which contract calls are launched from another contract. Currently they are: +- asynchronous calls: + - single asynchronous calls: + - promises (multiple asynchronous calls), + - transfer-execute calls, +- synchronous calls: + - executed on destination context, + - executed on destination context, readonly, + - executed on same context. + +Out of these, the asynchronous calls and the promises need some additional configuration, whereas the other can be launched right away. + + +### Asynchronous calls + +To perform an asynchronous call, the contract call needs to be first converted to an `AsyncCall` object, which holds additional configuration. All contract call objects have an `async_call` method that does this. + +To finalize the call, you will need to call `.call_and_exit()`. At this point the current execution is terminated and the call launched. + +A minimal asynchronous call could look like this: + +```rust +#[endpoint] +fn caller_endpoint(&self) { + // other code here + + self.callee_contract_proxy(callee_sc_address) + .callee_endpoint(my_biguint_arg) + .async_call() + .call_and_exit(); +} +``` + + +### Callbacks + +But before sending the asynchronous call, we most likely will want to also configure a callback for it. This is the only way that our contract will be able to react to the outcome of its execution. + +Let's imagine that `callee_endpoint` returns a `BigUint`, and we want to do something if the result is even, and something else if the result is odd. We also want to do some cleanup in case of error. Our callback function would look something like this: + +```rust +#[callback] +fn callee_endpoint_callback( + &self, + #[call_result] result: ManagedAsyncCallResult +) { + match result { + ManagedAsyncCallResult::Ok(value) => { + if value % 2 == 0 { + // do something + } else { + // do something else + } + }, + ManagedAsyncCallResult::Err(err) => { + // log the error in storage + self.err_storage().set(&err.err_msg); + }, + } +} +``` + +The `#[call_result]` argument interprets the output of the called endpoint and must almost always be of type `ManagedAsyncCallResult`. This type decodes the error status from the VM, more about it [here](/developers/data/multi-values#standard-multi-values). Its type argument must match the return type of the called endpoint. + +To assign this callback to the aforementioned async call, we hook it after `async_call`, but before `call_and_exit`: + +```rust +#[endpoint] +fn caller_endpoint(&self) { + // previous code here + + self.callee_contract_proxy(callee_sc_address) + .callee_endpoint(my_biguint_arg) + .async_call() + .with_callback(self.callbacks().callee_endpoint_callback()) + .call_and_exit(); +} +``` + +:::caution +Callbacks should be prevented from failing, at all costs. Failed callbacks cannot be executed again, and can often lead smart contracts into a state of limbo, from where it is difficult to recover. + +For this reason we recommend keeping callback code as simple as possible. +::: + +Callbacks can also receive payments, both EGLD and ESDT. They are always payable, there is never any need to annotate them with ``#[payable]`. They will receive payments if the called contract sends back tokens to the caller. In this case, they can query the received payments, just like a regular payable endpoint would. + +```rust +#[callback] +fn callee_endpoint_callback(&self, #[call_result] result: ManagedAsyncCallResult) { + let payment = self.call_value().any_payment(); + + // ... +} +``` + +:::note Note on implementation +Even though, in theory, smart contract can only have ONE callback function, the Rust framework handles this for you by saving an ID for the callback function in storage when you fire the async call, and it knows how to retrieve the ID and call the correct function once the call returns. +::: + + +### The callback closure + +Assume there is some additional context that we want to pass from our contract directly to the callback. We cannot rely on the called contract to do the job for us, we want to be in full control of this context. + +This context forms the contents of our callback closure. + +More specifically, all callback arguments other than the `#[call_result]` will be passed to it before launching the call, they will be saved by the framework in the contract storage automatically, then given to the callback and deleted. + +Example: + +```rust +#[callback] +fn callee_endpoint_callback( + &self, + original_caller: ManagedAddress, + #[call_result] result: ManagedAsyncCallResult +) { + match result { + ManagedAsyncCallResult::Ok(value) => { + if value % 2 == 0 { + // do something + } else { + // do something else + } + }, + ManagedAsyncCallResult::Err(err) => { + // log the error in storage + self.err_storage().set(&err.err_msg); + }, + } +} +``` + +To assign this callback to the aforementioned async call, we hook it like this: +```rust +#[endpoint] +fn caller_endpoint(&self) { + // other code here + let caller = self.blockchain().get_caller(); + + self.callee_contract_proxy(callee_sc_address) + .callee_endpoint(my_biguint_arg) + .async_call() + .with_callback(self.callbacks().callee_endpoint_callback(caller)) + .call_and_exit(); +} +``` + +Notice how the callback now has an argument: + +```rust +self.callbacks().callee_endpoint_callback(caller) +``` + +You can then use `original_caller` in the callback like any other function argument. + + +### Promises + +Promises (or multi-async calls) are a new feature that will be introduced in mainnet release 1.6. They are very similar to the old asynchronous calls, with the following differences: +- launching them does not terminate current execution; +- there can be several launched from the same transaction. + +:::caution +Because this feature is currently not available on mainnet, contracts need to enable the "promises" feature flag in `Cargo.toml` to use the functionality: + +```toml +[dependencies.multiversx-sc] +version = "0.43.3" +features = ["promises"] +``` + +This is to protect developers from accidentally creating contracts that depend on unreleased features. +::: + +The syntax is also very similar. The same example from above looks like this with promises: + +```rust +#[endpoint] +fn caller_endpoint(&self) { + // other code here + let caller = self.blockchain().get_caller(); + + self.callee_contract_proxy(callee_sc_address) + .callee_endpoint(my_biguint_arg) + .with_gas_limit(gas_limit) + .async_call_promise() + .with_callback(self.callbacks().callee_endpoint_callback(caller)) + .with_extra_gas_for_callback(10_000_000) + .register_promise(); +} + +#[promises_callback] +fn callee_endpoint_callback( + &self, + original_caller: ManagedAddress, + #[call_result] result: ManagedAsyncCallResult +) { + match result { + ManagedAsyncCallResult::Ok(value) => { + if value % 2 == 0 { + // do something + } else { + // do something else + } + }, + ManagedAsyncCallResult::Err(err) => { + // log the error in storage + self.err_storage().set(&err.err_msg); + }, + } +} +``` + +The differences are: +- Calling `async_call_promise` instead of `async_call`. +- Calling `register_promise` instead of `call_and_exit`. This one does not terminate execution. +- Annotation `#[promises_callback]` instead of `#[callback]`. +- We need to specify the gas for the call, because the execution of our transaction will continue and it needs to know how much gas it can keep. +- We need to specify the amount of gas for the callback. This is the exact amount of gas reserved for the callback, irrespective of how much the target contract consumes. + + +### Transfer-execute + +Transfer-execute calls are similar to asynchronous calls, but they can have no callback, thus the caller cannot react in any way to what happens with the callee. + +Just like promises, there can be multiple such calls launched from a transaction, but unlike promises, these are already available on mainnet. + +Transfer-execute calls do not need any further configuration (other than an explicit gas limit), therefore there is no specific object type associated with them. They can be launched immediately: + +```rust +self.callee_contract_proxy(callee_sc_address) + .callee_endpoint(my_biguint_arg) + .with_gas_limit(gas_limit) + .transfer_execute(); +``` + + +### Synchronous calls + +Synchronous calls are executed inline: this means execution is interrupted while they are executed and resumed afterwards. We also get the result of the execution right away and we can use it immediately in the transaction. + +:::caution +Synchronous calls can only be sent to contracts in the __same shard__ as the caller. They will fail otherwise. +::: + +Synchronous calls also do not need any further configuration, the call is straightforward: + +```rust +let result: BigUint = self.callee_contract_proxy(callee_sc_address) + .callee_endpoint(my_biguint_arg) + .with_gas_limit(gas_limit) + .execute_on_dest_context(); +``` + +or + +```rust +let result = self.callee_contract_proxy(callee_sc_address) + .callee_endpoint(my_biguint_arg) + .with_gas_limit(gas_limit) + .execute_on_dest_context::(); +``` + +We always need to specify the type that we want for the result. The framework will type-check that the requested result is compatible with the original one, but will not impose it upon us. For example, an endpoint might return a `u32` result, but we might choose to deserialize it as `u64` or `BigUint`. This is fine, since the types have similar semantics and the same representation. On the other hand, casting it to a `ManagedBuffer` will not be allowed. + +The method `execute_on_dest_context` is by far the more common when performing synchronous calls. The other alternatives are: +- `execute_on_dest_context_readonly` - enforces that the target contract does not change state, at blockchain level; +- `execute_on_same_context` - useful for library-like contracts, all changes are saved in the caller instead of the called contract. + + +### Diagram + +To sum it all up, if we have a contract call object in a smart contract, these are the things that we can do to it: + +```mermaid +flowchart TB + cc[ContractCall] + cc --->|".async_call()"| async[AsyncCall] + async -->|".call_and_exit()"| exec-async["⚙️ Asynchronous call (legacy)"] + async -->|".with_callback"| async + cc --->|".async_call_promise()"| prom["AsyncCallPromises"] + prom -->|".with_callback"| prom + prom --> |".register_promise()"| exec-prom["⚙️ Asynchronous call (promise)"] + cc ---->|".transfer_execute()"| exec-te["⚙️ Transfer & execute"] + cc ---->|".execute_on_dest_context() + .execute_on_dest_context_readonly() + .execute_on_same_context()"| exec-dest["⚙️ Synchronous call"] + exec-dest --> result[Requested Result] +``` + +--- + + +## Contract calls: complete diagram + +To sum it all up, to properly set up a contract call from a contract, one needs to: +1. Get hold of a proxy. +2. Call it to get a basic contract call object. +3. Optionally, add EGLD or ESDT token transfers to it. +4. Optionally, also specify a gas limit for the call. +5. Launch it, either synchronously or asynchronously, in any one of a variety of flavors. + +Merging all these elements into one grand diagram, we get the following: + +```mermaid +flowchart TB + gen-proxy[Generated Proxy] --> cc + man-proxy[Manual Proxy] --> cc + ccnp-new["ContractCallNoPayment::new(to, function)"] --> cc + subgraph cc[ContractCall] + direction LR + ccnp[ContractCallNoPayment] + ccnp -->|".with_egld_transfer"| cc-egld[ContractCallWithEgld] + ccnp -->|".with_esdt_transfer"| cc-multi[ContractCallWithMultiEsdt] + cc-multi -->|".with_esdt_transfer"| cc-multi + ccnp -->|".with_multi_token_transfer"| cc-multi + ccnp -->|".with_any_payment"| cc-any[ContractCallWithAnyPayment] + ccnp -->|".with_egld_or_single_esdt_transfer"| cc-egld-single[ContractCallWithEgldOrSingleEsdt] + end + cc --->|".async_call()"| async[AsyncCall] + async -->|".call_and_exit()"| exec-async["⚙️ Asynchronous call (legacy)"] + async -->|".with_callback"| async + cc --->|".async_call_promise()"| prom["AsyncCallPromises"] + prom -->|".with_callback"| prom + prom --> |".register_promise()"| exec-prom["⚙️ Asynchronous call (promise)"] + cc ---->|".transfer_execute()"| exec-te["⚙️ Transfer & execute"] + cc ---->|".execute_on_dest_context() + .execute_on_dest_context_readonly() + .execute_on_same_context()"| exec-dest["⚙️ Synchronous call"] + exec-dest --> result[Requested Result] +``` + +--- + + +## Contract deploy: base + +A close relative of the contract call is the contract deploy call. It models the deployment or the upgrade of a smart contract. + +It shares a lot in common with the contract calls, with these notable differences: +- The endpoint name is always `init`. +- No ESDT transfers are allowed in `init`. +- They get executed slightly differently. + +```mermaid +flowchart TB + gen-proxy[Generated Proxy] --> cc + man-proxy[Manual Proxy] --> cc + ccnp-new["ContractDeploy::new"] --> cc + cc[ContractDeploy] + cc ---->|".deploy_contract() + .deploy_from_source()"| exec-deploy["⚙️ Deploy contract"] + exec-deploy --> result[Requested Result] + cc ---->|".deploy_contract() + .deploy_from_source()"| exec-upg["⚙️ Upgrade contract"] +``` + +The object encoding these calls is called `ContractDeploy`. Unlike the contract calls, there is a single such object. + +Creating this object is done in a similar fashion: either via proxies, or manually. Constructors in proxies naturally produce `ContractDeploy` objects: + +```rust +mod callee_proxy { + multiversx_sc::imports!(); + + #[multiversx_sc::proxy] + pub trait CalleeContract { + #[init] + fn init(&self, arg: BigUint) -> BigUint; + } +} +``` + +```rust +self.callee_contract_proxy() + .init(my_biguint_arg) +``` + +--- + + +## Contract deploy: configuration + +Just like with regular contract calls, we can specify the gas limit and perform EGLD transfers as part of the deploy as follows: + +```rust +self.callee_contract_proxy(callee_sc_address) + .init(my_biguint_arg) + +``` + +```rust +self.callee_contract_proxy() + .init(my_biguint_arg) + .with_egld_transfer(egld_amount) + .with_gas_limit(gas_limit) +``` + +--- + + +## Contract deploy: execution + + +### Deploy + +There are several ways to launch a contract deploy, different from a regular contract call. + +The simplest deploy operation we can perform is simply calling `deploy_contract`: + +```rust +let (new_address, result) = self.callee_contract_proxy() + .init(my_biguint_arg) + .deploy_contract::(code, code_metadata); +``` + +:::important +Contract deploys always happen in the same shard as the deployer. They are therefore always [synchronous calls](#synchronous-calls) and we get the result right away. Just like for `execute_on_dest_context` we need to either write a result with an explicit result type, or give the result type as type argument. + +Contract upgrades, on the other hand, can be sent to a different shard, and are therefore in essence asynchronous calls. +::: + +The methods for executing contract deploys are as follows: +- `.deploy_contract(code, code_metadata)` - deploys a new contract with the code given by the contract. +- `.deploy_from_source(source_address, code_metadata)` - deploys a new contract with the same code as the code of the contract at `source_address`. The advantage is that the contract doesn't need to handle the new contract code, which could be quite a large data blob. This saves gas. It requires that we have the code already deployed somewhere else. + + +### Upgrade + +To upgrade the contract we also need to specify the recipient address when setting up the `ContractDeploy` object, like so: + +```rust +self.callee_contract_proxy() + .contract(calee_contract_address) + .init(123, 456) + .with_egld_transfer(payment) + .upgrade_contract(code, code_metadata); +``` + +Note the `.contract(...)` method call. + +Just like deploy, upgrade also comes in two flavors: +- `.upgrade_contract(code, code_metadata)` - upgrades the target contract to the new code and sets the new code metadata. +- `.upgrade_from_source(source_address, code_metadata)` - updates the target contract with the same code as the code of the contract at `source_address`. + +--- + +### logs + +This page describes the structure of the `logs` index (Elasticsearch), and also depicts a few examples of how to query it. + + +## _id + +:::warning Important + +**The `logs` index will be deprecated and removed in the near future.** +We recommend using the [events](/sdk-and-tools/indices/es-index-events) index, which contains all the events included in a log. + +Please make the necessary updates to ensure a smooth transition. +If you need further assistance, feel free to reach out. +::: + +The `_id` field for this index is composed of hex-encoded hash of the transaction of the smart contract result that generated the log. + + +## Fields + + +| Field | Description | +|----------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| address | The address field holds the address in bech32 encoding. It can be the address of the smart contract that generated the log or the address of the receiver address of the transaction. | +| events | The events field holds a list of events. | +| originalTxHash | The originalTxHash field holds the hex-encoded hash of the initial transaction. When this field is not empty the log is generated by a smart contract result and this field represents the hash of the initial transaction. | +| timestamp | The timestamp field represents the timestamp of the block in which the log was generated. | + +Event structure + + +| Field | Description | +|-------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| identifier | This field represents the identifier of the event. | +| address | The address field holds the address in bech32 encoding. It can be the address of the smart contract that generated the event or the address of the receiver address of the transaction. | +| topics | The topics field holds a list with extra information. They don't have a specific order because the smart contract is free to log anything that could be helpful. | +| data | The data field can contain information added by the smart contract that generated the event. | +| order | The order field represents the index of the event indicating the execution order. | + + +## Query examples + + +### Fetch all the logs generated by a transaction + +``` +curl --request GET \ + --url ${ES_URL}/logs/_search \ + --header 'Content-Type: application/json' \ + --data '{ + "query": { + "match": { + "_id":"d6.." + } + } +}' +``` + + +### Fetch all the logs generated by a transaction and the smart contract results triggered by it + +``` +curl --request GET \ + --url ${ES_URL}/logs/_search \ + --header 'Content-Type: application/json' \ + --data '{ + "query": { + "bool": { + "should": [ + { + "match": { + "_id":"d6.." + } + }, + { + "match": { + "originalTxHash": "d6.." + } + } + ] + } + } +}' +``` + +--- + +### Managed Decimal + +## Managed Decimal Overview + +`ManagedDecimal` is a generic type representing a fixed-point decimal number managed by the API. +It is designed to handle decimal values with a specific number of decimals, providing operations such as addition, subtraction, multiplication, division, scaling, and conversion between different decimal precision and also more complex operations such as logarithm and nth root with a customizable degree of precision. + +Essentially, `ManagedDecimal` is simply a struct containing a wrapper over a `BigIntHandle` (`BigUint`) and the number of decimals associated with that value (precision). Having such a light design helps `ManagedDecimal` become the optimal solution for dealing with fixed-point decimal numbers in terms of efficiency and readability. + +```rust title=managed_decimal.rs +#[derive(Debug, Clone)] +pub struct ManagedDecimal { + data: BigUint, // value * scaling_factor + decimals: D, // number_of_decimals (precision) +} +``` + +The `scaling factor` of the decimal is `10^num_of_decimals` and is a cached value across multiple `ManagedDecimal` instances for increased efficiency. + +```rust title=example.rs +pub fn main() { + let decimal = ManagedDecimal::>::from(BigUint::from(1u64)); + let cached_decimal = ManagedDecimal::>::from(BigUint::from(5u64)); +} +``` + + +## Number of decimals + +`Decimals` is a trait representing the number of decimals associated with a decimal value and is only +implemented for `NumDecimals` and `ConstDecimals`. +`NumDecimals` is a type alias for `usize` and is used to specify the number of decimals dynamically, while `ConstDecimals` is a type that represents a constant number of decimals and is used to statically specify the number of decimals for ManagedDecimal instances. + +```rust title=example.rs +pub fn main() { + let decimal: ManagedDecimal = ManagedDecimal::from_raw_units(BigUint::from(100u64), 2usize); + let const_decimal = ManagedDecimal::>::const_decimals_from_raw(BigUint::from(500u64)) +} +``` + + +## Operations + +`ManagedDecimal` supports various types of simple and complex operations, as well as conversions and scaling. More code examples can be found at `mx-sdk-rs/framework/scenario/tests/managed_decimal_test.rs` + +### Available methods: +- Simple Operations: + - `add`, `mul`, `div`, `sub` are all implemented for `ConstDecimals` of the same scale. + ```rust title=example.rs + pub fn main() { + let fixed = ManagedDecimal::>::from(BigUint::from(1u64)); + let fixed_2 = ManagedDecimal::>::from(BigUint::from(5u64)); + + let addition = fixed + fixed_2; + assert_eq!( + addition, + ManagedDecimal::>::from(BigUint::from(6u64)) + ); + } + ``` + - `trunc(&self) -> BigUint` returns the `data` field without the `scaling_factor` applied, by dividing the value to the scaling factor and truncating. + ```rust title=example.rs + pub fn main() { + ... + assert_eq!(addition.trunc(), BigUint::from(6u64)); + } + ``` +- Complex Operations: + - `log(self, target_base: BigUint, precision: T) -> ManagedDecimal` returns the value of log in any base with customizable precision level. + ```rust title=example.rs + pub fn logarithm() { + let fixed = ManagedDecimal::::from_raw_units(BigUint::from(10u64), 1usize); + let fixed_const = ManagedDecimal::>::const_decimals_from_raw(BigUint::from(10u64)); + + let log2_fixed = fixed.log(BigUint::from(2u64), 10_000usize); + assert_eq!( + log2_fixed, + ManagedDecimal::::from_raw_units(BigUint::from(33219u64), 10_000usize) + ); + } + ``` +- Scaling: + - `scale(&self) -> usize` returns the number of decimals (the scale). + - `scaling_factor(&self) -> BigUint` returns the scaling factor value (`10^num_decimals`). + - `rescale(self, scale_to: T) -> ManagedDecimal` returns the correspondent of the decimal in the newly specified scale. It can also convert between `NumDecimal` and `ConstDecimals` instances. + + ```rust title=example.rs + pub fn main() { + ... + let fixed_8: ManagedDecimal = ManagedDecimal::from_raw_units(BigUint::from(5u64), 5usize); + let fixed_9 = fixed_8.rescale(ConstDecimals::<3>); + assert_eq!( + fixed_9, + ManagedDecimal::>::const_decimals_from_raw(BigUint::from(500u64)) + ); + } + ``` +- Conversions: + - `into_raw_units(&self) -> &BigUint` returns the `data` field value. + + ```rust title=example.rs + pub fn main() { + ... + assert_eq!(addition.into_raw_units(), &BigUint::from(600u64)); + } + ``` + - `from_raw_units(data: BigUint, decimals: D) -> Self` returns a `ManagedDecimal` from a data field value without applying scaling factor. + + ```rust title=example.rs + pub fn main() { + ... + let fixed_4: ManagedDecimal = ManagedDecimal::from_raw_units(BigUint::from(100u64), 2usize); + let fixed_5 = fixed_4.rescale(2usize); + assert_eq!( + fixed_5, + ManagedDecimal::from_raw_units(BigUint::from(100000000u64), 8usize) + ); + } + ``` + - `const_decimals_from_raw(data: BigUint) -> Self` returns a `ConstDecimals` type of `ManagedDecimal` from a data field value without applying scaling factor. + ```rust title=example.rs + pub fn main() { + ... + let fixed_const: ManagedDecimal> = ManagedDecimal::const_decimals_from_raw(BigUint::from(1u64)); + } + ``` + - `num_decimals(&self) -> NumDecimals` returns the number of decimals. + - `to_big_float(&self) -> BigFloat` returns the decimal as `BigFloat`. + - `to_big_int(self) -> BigInt` returns the decimal as `BigInt`. + - `from_big_int(big_int: BigInt, num_decimals: T) -> ManagedDecimal` constructs a `ManagedDecimal` from a `BigInt` with customizable `num_decimals`. + - `from_big_float(big_float: BigFloat, num_decimals: T) -> ManagedDecimal` constructs a `ManagedDecimal` from a `BigFloat` with customizable `num_decimals`. + ```rust title=example.rs + pub fn main() { + ... + let float_1 = BigFloat::::from_frac(3i64, 2i64); + let fixed_float_1 = ManagedDecimal::>::from_big_float(float_1.clone(),ConstDecimals::<1>); + let fixed_float_2 = ManagedDecimal::::from_big_float(float_1, 1usize); + + assert_eq!( + fixed_float_1, + ManagedDecimal::>::const_decimals_from_raw(BigUint::from(15u64)) + ); + assert_eq!( + fixed_float_2, + ManagedDecimal::::from_raw_units(BigUint::from(15u64), 1usize) + ); + } + ``` + +--- + +### Memory allocation + +MultiversX smart contracts are compiled to WebAssembly, which does not come with memory allocation out of the box. In general WebAssembly programs need special memory allocation functionality to work with the heap. + +Using traditional memory allocation is highly discouraged on MultiversX. There are several reasons: +- We have “managed types”, which are handled by the VM, and which offer a cheaper and more reliable alternative. +- “Memory grow” operations can be expensive and unreliable. For the stability of the blockchain we have chosen to limit them drastically. +- Memory allocators end up in smart contract code, bloating it with something that is not related in any way to its specifications. This contradicts our design philosophy. + +Even so, it is unreasonable to forbid the use of allocators altogether, whether to use them or not ultimately needs to be the developers' choice. Before framework version 0.41.0, the only allocator solution offered was `wee_alloc`. Unfortunately, it has not been maintained for a few years and has some known vulnerabilities. This was also causing Github’s Dependabot to produce critical warnings, not only to our framework, but to all contract projects, despite most of them not really using it. + +First of all, we made the allocator [configurable](/developers/meta/sc-config#single-contract-configuration) from multicontract.toml, currently the main source of contract build specifications. Developers currently have 4 allocators to choose from. + +Then, we added the following allocators to our framework: +- `FailAllocator` (the default) simply crashes whenever any memory allocation or deallocation is attempted. For the first time we have a tool that completely prevents accidental memory allocation. We already had an "alloc" feature in Cargo.toml, but it is only operating high-level and can easily (and sometimes accidentally) be circumvented. +- `StaticAllocator64k` pre-allocates a static 2-page buffer, where all memory is allocated. It can never call memory.grow . It never deallocates and crashes when the buffer is full. It can be suitable for small contracts with limited data being processed, who want to avoid the pitfalls of a memory.grow . +- `LeakingAllocator` uses memory.grow to get hold of memory pages. It also never deallocates. This is because contracts do not generally fill up so much memory and all memory is erased at the end of execution anyway. Suitable for contracts with a little more data. +- `wee_alloc` is still supported. It is, however, not included in the framework. Contracts need to import it explicitly. + +:::caution +While these allocators are functional, they should be avoided by all contracts. Only consider this functionality when all else fails, in extremely niche situations, or for dealing with very old code. +::: + +--- + +### Messages + +The SC framework supports message interpolation in a variety of situations. + +The mechanism makes full use of managed types, and does not require memory allocation on the heap. + +It resembles the standard message formatting in Rust, but it does not have all the features and is a completely separate implementation. + +To see these features in action, we have [an example contract just for that](https://github.com/multiversx/mx-sdk-rs/blob/master/contracts/feature-tests/formatted-message-features/src/formatted_message_features.rs). + + + +## Errors + +The most common place to see message interpolation is in the error messages. These messages will be seen in the explorer and tools if a transaction fails. + + +### sc_panic! + +Whenever encountered, it stops execution immediately, and returns the given error message. Also note that for all errors originating in the smart contract, the status code is "4". + +The macro simplifies the process of throwing exceptions with custom error messages. It works with formatted messages with arguments, and with static messages without arguments. + +```rust + sc_panic!("Formatted error message with arguments: {}", arg); // message with argument + sc_panic!("Static error message"); // static messages +``` + + +### require! + +`require!` is a macro used to enforce preconditions by checking whether an expression evaluates to *true*. If the expression evaluates to *false*, it will trigger a panic with a specific error message. It is very useful when validating pre-conditions. + +```rust +require!(expression, "formatted error message with argument: {}", arg); // error message with argument +require!(expression, "error message"); // static error messages +``` + + + +## Formatting string + +We might want to interpolate a string for other uses than throwing an error, such as returning it or saving it to storage. + + +### sc_format! + +The macro creates a formatted managed buffer in contracts. Just like all other format functionality, it supports both formatted messages with arguments and static messages without arguments. The returned value of this macro is a `ManagedBuffer` that can be used within smart contracts. + +```rust +let number_i32: i32 = 16; +let message_with_i32: ManagedBuffer = sc_format!("i32: {}", number_i32); + +let number_bigUint = BigUint::from(16u32); +let message_with_bigUint = sc_format!("BigUint: {}", number_bigUint); + +let buffer: ManagedBuffer = ManagedBuffer::new_from_bytes(b"message"); +let message_with_buffer = sc_format!("ManagedBuffer: {}", buffer); +let message_with_buffer_hex = sc_format!("ManagedBuffer hex: {:x}", buffer); +``` + + + +## Printing to console + +Smart contracts cannot print messages on-chain, because they do not have access to any console or standard output. They can, however, do so when run in tests. This printing feature is designed to come to complement the debugger when testing contracts. + + +### sc_print! + +This macro is the primary way to output messages to console, when running tests. + +Using it doesn't impact wasm builds in any way, so it is safe to use in any situation. To avoid any overhead, not even the formatting code will end up compiled to WebAssembly. + +Despite this, we recommend removing the `sc_print!` commands, once you are done with debugging. + +To produce logs on-chain, consider using [event logs](sc-annotations#events) instead. + + + +## Interpolation format + +Values can be interpolated into messages in several ways. +- `{}` - The human-readable representation. + - For **number types**, it is the base 10 representation. + ```rust + // "Printing u64: 372036854775807" + sc_print!("Printing u64: {}", 372036854775807u64;); + + // "Printing u32: 800000008" + sc_print!("Printing u32: {}", 800000008u32); + + // "Printing usize: 1800000000" + sc_print!("Printing usize: {}", 1800000000usize); + + // "Printing u16: 60123" + sc_print!("Printing u16: {}", 60123u16); + + // "Printing u8: 233" + sc_print!("Printing u8: {}", 233u8); + + // "Printing i64: -372036854775807" + sc_print!("Printing i64: {}", -372036854775807i64); + + // "Printing i32: -800000008" + sc_print!("Printing i32: {}", -800000008i32); + + // "Printing isize: -1800000000" + sc_print!("Printing isize: {}", -1800000000isize); + + // "Printing i16: -30123" + sc_print!("Printing i16: {}", -30123i16); + + // "Printing i8: -126" + sc_print!("Printing i8: {}", -126i8); + + let x_bigint: BigInt = BigInt::from(-3272036854775807i64); + // "Printing x_bigint: -3272036854775807" + sc_print!("Printing x_bigint: {}", x_bigint); + + let x_biguint: BigUint = BigUint::from(3272036854775807u64); + // "Printing x_biguint: 3272036854775807" + sc_print!("Printing x_biguint: {}", x_biguint); + ``` + - For `ManagedBuffer`, it is the contained text. + ```rust + let managed_buffer: ManagedBuffer = ManagedBuffer::new_from_bytes(b"Welcome to MultiversX!"); + // "Printing managed_buffer: Welcome to MultiversX!" + sc_print!("Printing managed_buffer: {}", managed_buffer); + ``` + - For **bool**, it indicates whether the condition is true or false. + ```rust + let x_bool: bool = true; + // "Printing x_bool: true" + sc_print!("Printing x_bool: {}", x_bool); + ``` + - For **byte values**, it is the string character. + ```rust + let x_bytes: &[u8] = b"MVX"; + // "Printing x_bytes: MVX" + sc_print!("Printing x_bytes: {}", x_bytes); + ``` + - For `CodeMetadata`, it is the flag represented as a text. + ```rust + let code_metadata: CodeMetadata = CodeMetadata::UPGRADEABLE; + // "Printing code_metadata: Upgradeable" + sc_print!("Printing code_metadata: {}", code_metadata); + ``` + - For `TokenIdentifier`, it is the token ticker. + ```rust + let token_identifier = TokenIdentifier::from(&b"TESTTOK-2345"[..]); + // "Printing token_identifier: TESTTOK-2345" + sc_print!("Printing token_identifier: {}", token_identifier); + ``` + - for `EgldOrEsdtTokenIdentifier`, it is the token ticker. + ```rust + let egld_or_esdt_token_identifier: EgldOrEsdtTokenIdentifier = EgldOrEsdtTokenIdentifier::egld(); + // "Printing egld_or_esdt_token_identifier: EGLD" + sc_print!("Printing egld_or_esdt_token_identifier: {}", egld_or_esdt_token_identifier); + ``` + +- `{:x}` - Lowercase hexadecimal encoding. + - For **number types**, it is the base 16 representation. + ```rust + // "Printing u64: 1525d94927fff" + sc_print!("Printing u64: {:x}", 372036854775807u64); + + // "Printing u32: 2faf0808" + sc_print!("Printing u32: {:x}", 800000008u32); + + // "Printing usize: 6b49d200" + sc_print!("Printing usize: {:x}", 1800000000usize); + + // "Printing u16: eadb" + sc_print!("Printing u16: {:x}", 60123u16); + + // "Printing u8: e9" + sc_print!("Printing u8: {:x}", 233u8); + + // "Printing i64: fffeada26b6d8001" + sc_print!("Printing i64: {:x}", -372036854775807i64); + + // "Printing i32: d050f7f8" + sc_print!("Printing i32: {:x}", -800000008i32); + + // "Printing isize: 94b62e00" + sc_print!("Printing isize: {:x}", -1800000000isize); + + // "Printing i16: 8a55" + sc_print!("Printing i16: {:x}", -30123i16); + + // "Printing i8: 82" + sc_print!("Printing i8: {:x}", -126i8); + ``` + - For `ManagedBuffer`, it is the contained text as hexadecimal. + ```rust + let managed_buffer: ManagedBuffer = ManagedBuffer::new_from_bytes(b"MultiversX!"); + // "Printing managed_buffer: 4d756c7469766572735821" + sc_print!("Printing managed_buffer: {:x}", managed_buffer); + ``` + - For `ManagedAddress`, it is the contained address as hexadecimal. + ```rust + let address: [u8; 32] = hex!("fefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefe"); + let managed_address: ManagedAddress = ManagedAddress::from(&address); + // "Printing managed_address: fefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefe" + sc_print!("Printing managed_address: {:x}", managed_address); + ``` + - For `ManagedByteArray`, it is the hexadecimal representation for the ASCII characters. + ```rust + let managed_byte_array: ManagedByteArray = ManagedByteArray::new_from_bytes(b"MVX"); + // "Printing managed_byte_array: 4d5658" + sc_print!("Printing managed_byte_array: {:x}", managed_byte_array); + ``` + - For `CodeMetadata`, it is the metadata stored in the flag as hexadecimal. + ```rust + let code_metadata: CodeMetadata = CodeMetadata::UPGRADEABLE; + // "Printing code_metadata: 0100" + sc_print!("Printing code_metadata: {}", code_metadata); + ``` + - For `TokenIdentifier`, it is the token ticker as hexadecimal. + ```rust + let token_identifier = TokenIdentifier::from(&b"TESTTOK-2345"[..]); + // "Printing token_identifier: 54455354544f4b2d32333435" + sc_print!("Printing token_identifier: {:x}", token_identifier); + ``` + - for `EgldOrEsdtTokenIdentifier`, it is the token ticker as hexadecimal. + ```rust + let egld_or_esdt_token_identifier: EgldOrEsdtTokenIdentifier = EgldOrEsdtTokenIdentifier::egld(); + // "Printing egld_or_esdt_token_identifier: 45474C44" + sc_print!("Printing egld_or_esdt_token_identifier: {:x}", egld_or_esdt_token_identifier); + ``` + +- `{:b}` - Binary encoding ("0" and "1"). + - For **number types**, it is the base 2 representation. + ```rust + // "Printing u64: 1010100100101110110010100100100100111111111111111" + sc_print!("Printing u64: {:b}", 372036854775807u64); + + // "Printing u32: 101111101011110000100000001000" + sc_print!("Printing u32: {:b}", 800000008u32); + + // "Printing usize: 1101011010010011101001000000000" + sc_print!("Printing usize: {:b}", 1800000000usize); + + // "Printing u16: 1110101011011011" + sc_print!("Printing u16: {:b}", 60123u16); + + // "Printing u8: 11101001" + sc_print!("Printing u8: {:b}", 233u8); + ``` + - For `CodeMetadata`, we get the individual bits. + ```rust + let code_metadata: CodeMetadata = CodeMetadata::UPGRADEABLE; + // "Printing code_metadata: 0000000100000000" + sc_print!("Printing code_metadata: {:b}", code_metadata); + ``` + - For `ManagedBuffer`, it is the contained text as bits. + ```rust + let managed_buffer: ManagedBuffer = ManagedBuffer::new_from_bytes(b"MVX"); + // "Printing managed_buffer: 010011010101011001011000" + sc_print!("Printing managed_buffer: {}", managed_buffer); + ``` + +- `{:c}` - Encoded the same as the [codec representation](/developers/data/serialization-overview). Helpful when trying to visualize the encoding of an object. It is also the one available for most types, so it is a good fallback when other types of formatting are not available, e.g. for custom structs. + - For all types, it is the [top-encoded](/developers/data/serialization-overview#the-concept-of-top-level-vs-nested-objects) representation. + ```rust + // "Printing u64: 01525d94927fff" + sc_print!("Printing u64: {:c}", 372036854775807u64); + ``` + +--- + +### Migration + +There is an older syntax for producing contract calls, detailed [here](./tx-legacy-calls.md). It is already in use in many projects. + +Upgrading to framework version 0.49.0 is almost completely backwards compatible, but the new syntax is nicer and more reliable, so we encourage everyone to migrate. + +This will be a migration guide, as well as some frequently encountered pitfalls. + +:::caution +Even though the syntax is backwards compatible, the implementation of the [old syntax](./tx-legacy-calls.md) has been replaced. + +To the best of our knowledge, all code should continue to behave the same. However, if you upgrade beyond 0.49.0, please make sure to **test your smart contract fully** once again, even if you do not change a single line of code in your code base. + +Do not be fooled by the identical legacy syntax, the implementation for **all** contract calls is new. +::: + + + +## Imports + +This is not strictly related to the unified syntax, but the imports were recently cleaned up. A single line should be enough for each of the contexts, as follows: +- In contracts: `use multiversx_sc::imports::*;` +- In tests: `use multiversx_sc_scenario::imports::*;` +- In interactors: `use multiversx_sc_snippets::imports::*;` + +We also have `use multiversx_sc::derive_imports::*;`, which gives you derives like `TypeAbi` and the codec derives. + +The proxies have `use multiversx_sc::proxy_imports::*;`, but that gets generated automatically, so develoeprs shouldn't worry about it. + + + + +## Old `Proxy` type caveat + +We must start with the only instance of backwards incompatibility that we have after 0.49.0. + +It is very uncommon to encounter this problem, we don't expect developers to encounter it, but it did pop up in the DEX when migrating. + +The old `Proxy` type has been split in two: `Proxy` no longer includes the field for the recipient, whereas there is a new `ProxyTo` type that does include it. + +Whenever you encounter code of this sort, everything should remain unchanged: + +```rust + #[proxy] + fn vault_proxy(&self) -> vault::Proxy; +``` + +If, however, your proxy getter looks like this, the return type has changed, the return type is now `ProxyTo`: + +```rust + #[proxy] + fn vault_proxy(&self, sc_address: ManagedAddress) -> vault::Proxy; +``` + +To preserve backwards compatibility in this case as well, we placed a hack in the pre-processor stage: the `Proxy` return type is silently replaced by `ProxyTo` in the background, unbeknownst to the developer. For all practical purposes, the code should still be functioning the same way. + +If, however, the developer does not use the legacy proxy object directly, i.e. it returns it, or passes it on to another function, the framework cannot do the replacement there, and you might get a compilation error. + +The solution in this case is simple: replace `Proxy` with `ProxyTo` in code. + + + + +## Replace `#[derive(TypeAbi)]` with `#[type_abi]` + +To use the new proxies, one must first [generate](./tx-proxies.md#how-to-generate) them. The proxy is designed to be self-contained, so unless configured otherwise, it will also output a copy of the contract types involved in the ABI. + +No methods of these types are copied, but the annotations are important most of the time, so they need to be copied too. These types will need the encode/decode annotations, as well as `Clone`, `Eq`, etc. + +In order to do this, the proxy generator (via `TypeAbi`) needs to know what type annotations were originally declared. It turns out, derive annotations in Rust don't have access to the other derives that were declared on the same line. For instance, `B` does not see `A` in `#[derive(A, B)]`. + +The solution is to have another annotation, called `#[type_abi]` **before** the derives. + +Currently, `#[type_abi]` takes no arguments and works the same way as `#[derive(TypeAbi)]`, but it might be extended in the future. + + +## Generate the new proxies + +Just like for a new project, you will need to [generate](./tx-proxies.md#how-to-generate) the new proxies and embed them in your project. + + + +## Replace the old proxies in calls + +You might have this kind of syntax in your contract. You can easily find it by searching in your project for `#[proxy]` or `.contract(`. + +```rust title="Variant A" +#[proxy] +fn vault_proxy(&self) -> vault::Proxy; + +#[endpoint] +fn do_call(&self, to: ManagedAddress, args: MultiValueEncoded) { + self.vault_proxy() + .contract(to) + .echo_arguments(args) + .async_call() + .with_callback(self.callbacks().echo_args_callback()) + .call_and_exit(); +} +``` + +```rust title="Variant B" +#[proxy] +fn vault_proxy(&self, sc_address: ManagedAddress) -> vault::Proxy; + +#[endpoint] +fn do_call(&self, to: ManagedAddress, args: MultiValueEncoded) { + self.vault_proxy(to) + .echo_arguments(args) + .async_call() + .with_callback(self.callbacks().echo_args_callback()) + .call_and_exit(); +} +``` + +```rust title="Replace by" +#[endpoint] +fn do_call(&self, to: ManagedAddress, args: MultiValueEncoded) { + self.tx() + .to(&to) + .typed(vault_proxy::VaultProxy) + .echo_arguments(args) + .async_call() + .with_callback(self.callbacks().echo_args_callback()) + .call_and_exit(); +} +``` + +Both variants above should be replaced by this pattern. + +:::info +The new proxies no longer have a recipient field in them. The [recipient (to) field](./tx-to.md) is completely independent. It is set the same way for transactions with or without proxies. + +This feature has proven not worth the complication, we are happy to see it go. +::: + +Once you've done this, you can safely delete all the old proxy getters (all methods annotated with `#[proxy]`). + +You can also delete all proxy trait imports of the form `my_module::ProxyTrait as _`. The new proxies require no trait imports. + + +:::info +The explicit proxies (traits annotated with `#[multiversx_sc::proxy]`) do not yet have an equivalent in the new syntax. + +It's ok to not migrate them yet. + +Of course, it is possible rewrite them in the style of the new proxies, but in the absence of code generation, this might be tedious. + +A solution is planned for the near future. +::: + + +In case you want to migrate to the unified syntax and you cannot, or do not want to get rid of the old proxies, this is an alternative transitional syntax: + +```rust title="Transitional variant" +#[proxy] +fn vault_proxy(&self, sc_address: ManagedAddress) -> vault::Proxy; + +#[endpoint] +fn do_call(&self, to: ManagedAddress, args: MultiValueEncoded) { + self.tx() + .legacy_proxy_call(self.vault_proxy(to).echo_arguments(args)) + .async_call() + .with_callback(self.callbacks().echo_args_callback()) + .call_and_exit(); +} +``` + + +## (Optional) Remove contract dependencies + +The new proxies can be copy-pasted between contract crates. This means that a caller's contract no longer needs to depend on its callees, in order to obtain the proxy. Once the new proxies are set up, you might be able to get rid of the dependency. + +This also means contracts no longer need to be on the same framework version. The same proxy, once generated, should work with any framework version the caller uses, irrespective of the callee. + +:::info +Dependencies might still be needed for shared structures and modules. +::: + + + + +## Replace method names + +To achieve backwards compatibility, all the old methods were kept (even though their implementations changed). + +We did not yet deprecate the old ones, but we encourage everyone to switch to the new ones. They have shorter names and tend to be more expressive. + +| Old method | New method | Comments | +| -------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ | +| `.with_egld_transfer(amount)` | `.egld(amount)` | | +| `.with_esdt_transfer((token, nonce, amount))` | `.esdt((token, nonce, amount))`
or
`.single_esdt(&token, nonce, &amount)` | `single_esdt` can deal with references, instead of taking owned objects. | +| `.with_multi_token_transfer(p)` | `.payment(p)` | `payment` is universal. | +| `.with_egld_or_single_esdt_transfer(p)` | `.payment(p)` | Method `payment` is universal. | +| `.with_gas_limi(gas)` | `.gas(gas)` | | +| `.with_extra_gas_for_callback(gas)` | `.gas_for_callback(gas)` | Method `payment` is universal. | +| `.async_call()` | - | Does nothing, can be removed with no consequences. | +| `.async_call_promise()` | - | Does nothing, can be removed with no consequences. | +| `.with_callback(cb)` | `.callback(cb)` | | +| `.deploy_contract(code, code_metadata)` | `.code(code)`
`.code_metadata(code_metadata)`
`.sync_call()` | Also add result handlers for decoding the result. | +| `.deploy_from_source(address, code_metadata)` | `.from_source(code)`
`.code_metadata(code_metadata) .sync_call()` | Also add result handlers for decoding the result. | +| `.upgrade_contract(code, code_metadata)` | `.code(code) .code_metadata(code_metadata) .upgrade_async_call_and_exit()` | Upgrades are async calls. | +| `.upgrade_from_source(address, code_metadata)` | `.from_source(code)`
`.code_metadata(code_metadata)`
`.upgrade_async_call_and_exit()` | Upgrades are async calls. | +| `.execute_on_dest_context()` | `.sync_call()` | Also add result handlers for decoding the result. | +| `.execute_on_dest_context`
`_with_back_transfers()` | `.returns(ReturnsBackTransfers)`
`.sync_call()` | Add additional result handlers for decoding the result. | + + + + +## Black-box tests + +We have not extensively advertised the scenario-based black-box tests, knowing they would eventually be superseded by the unified transaction syntax. + +If, however, you got to develop on that syntax, here is how to migrate to the new one. + +```rust title=before_migration.rs +use multiversx_sc_scenario::{scenario_model::*, *}; + +const ADDER_PATH_EXPR: &str = "mxsc:output/adder.mxsc.json"; + +fn world() -> ScenarioWorld { + let mut blockchain = ScenarioWorld::new(); + blockchain.register_contract(ADDER_PATH_EXPR, adder::ContractBuilder); + blockchain +} + +#[test] +fn adder_blackbox_raw() { + let mut world = world(); + let adder_code = world.code_expression(ADDER_PATH_EXPR); + + world + .set_state_step( + SetStateStep::new() + .put_account("address:owner", Account::new().nonce(1)) + .new_address("address:owner", 1, "sc:adder"), + ) + .sc_deploy( + ScDeployStep::new() + .from("address:owner") + .code(adder_code) + .argument("5") + .expect(TxExpect::ok().no_result()), + ) + .sc_query( + ScQueryStep::new() + .to("sc:adder") + .function("getSum") + .expect(TxExpect::ok().result("5")), + ) + .sc_call( + ScCallStep::new() + .from("address:owner") + .to("sc:adder") + .function("add") + .argument("3") + .expect(TxExpect::ok().no_result()), + ) + .check_state_step( + CheckStateStep::new() + .put_account("address:owner", CheckAccount::new()) + .put_account( + "sc:adder", + CheckAccount::new().check_storage("str:sum", "8"), + ), + ); +} +``` + +This was the old blackbox code from the `adder` contract, present in framework version `0.48.0`. + +Migrating this test to unified syntax means, in a nutshell, separating each action into different steps, replacing verbose `CheckStateStep` and `SetStateStep` with their specific state builders, and replacing the interactions with actual transactions (deploy, query, call). + +This is how the migrated test looks like: +```rust title=after_migration.rs +use multiversx_sc_scenario::imports::*; + +use adder::*; + +// new types +const OWNER_ADDRESS: TestAddress = TestAddress::new("owner"); +const ADDER_ADDRESS: TestSCAddress = TestSCAddress::new("adder"); +const CODE_PATH: MxscPath = MxscPath::new("output/adder.mxsc.json"); + +fn world() -> ScenarioWorld { + let mut blockchain = ScenarioWorld::new(); + + blockchain.register_contract(CODE_PATH, adder::ContractBuilder); + blockchain +} + +#[test] +fn adder_blackbox() { + let mut world = world(); // ScenarioWorld + + // starting mandos trace + world.start_trace(); + + // set state for owner + world.account(OWNER_ADDRESS).nonce(1); + + // deploy the contract + let new_address = world + .tx() // tx with test environment + .from(OWNER_ADDRESS) + .typed(adder_proxy::AdderProxy) // typed call - proxy + .init(5u32) // deploy call + .code(CODE_PATH) + .new_address(ADDER_ADDRESS) // custom deploy address for tests + .returns(ReturnsNewAddress) // returns new address after deploy + .run(); // send transaction + + assert_eq!(new_address, ADDER_ADDRESS.to_address()); + + // query the contract, `sum` view + world + .query() // tx with test query environment + .to(ADDER_ADDRESS) + .typed(adder_proxy::AdderProxy) // typed call - proxy + .sum() + .returns(ExpectValue(5u32)) // asserts returned value == 5u32 + .run(); // send transaction + + // contract call, `add` endpoint + world + .tx() // tx with test environment + .from(OWNER_ADDRESS) + .to(ADDER_ADDRESS) + .typed(adder_proxy::AdderProxy) // typed call - proxy + .add(1u32) + .run(); // send transaction + + // query the contract, `sum view` + world + .query() // tx with test query environment + .to(ADDER_ADDRESS) + .typed(adder_proxy::AdderProxy) // typed call - proxy + .sum() + .returns(ExpectValue(6u32)) // asserts returned value == 6u32 + .run(); // send transaction + + // check state for owner + world.check_account(OWNER_ADDRESS); + + // check state for adder + world + .check_account(ADDER_ADDRESS) + .check_storage("str:sum", "6"); + + // write mandos trace to file + world.write_scenario_trace("trace1.scen.json"); +} +``` + +--- + +### miniblocks + +This page describes the structure of the `miniblocks` index (Elasticsearch), and also depicts a few examples of how to query it. + + +## _id + +The _id field of this index is represented by the miniblock hash, in a hexadecimal encoding. + + +## Fields + + +| Field | Description | +|-------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| senderShard | The senderShard field represents the shard ID of the source block. | +| receiverShard | The receiverShard field represents the shard ID of the destination block. | +| senderBlockHash | The senderBlockHash field represents the hash (hex encoded) of the source block in which the miniblock was included. | +| receiverBlockHash | The receiverBlockHash field represents the hash (hex encoded) of the destination block in which the miniblock was included. | +| type | The type field represents the type of the miniblock. It can be `TxBlock` (if it contains transactions) or `SmartContractResultBlock` (if it contains smart contracts results). | +| procTypeS | The procTypeS field represents the processing type at the source shard. It can be `Normal` or `Scheduled`. | +| procTypeD | The procTypeD field represents the processing type at the destination shard. It can be `Normal` or `Scheduled`. | +| timestamp | The timestamp field represents the timestamp of the block in which the miniblock was executed. | +| reserved | The reserved field ensures the possibility to extend the mini block. | + + +## Query examples + + +### Fetch all the miniblocks of a block + +``` +curl --request GET \ + --url ${ES_URL}/miniblocks/_search \ + --header 'Content-Type: application/json' \ + --data '{ + "query": { + "bool": { + "should": [ + { + "match": { + "senderBlockHash": "ddc..." + } + }, + { + "match": { + "receiverBlockHash": "ddc..." + } + } + ] + } + } +}' +``` + +--- + +### Multi-Values + +## Single values vs. multi-values + +To recap, we have discussed about data being represented either in a: +- nested encoding, as part of the byte representation of a larger object; +- top encoding, the full byte representation of an object. + +But even the top encoding only refers to a _single_ object, being represented as a _single_ array of bytes. This encoding, no matter how simple or complex, is the representation for a _single_ argument, result, log topic, log event, NFT attribute, etc. + +However, we sometimes want to work with _multiple_, variadic arguments, or an arbitrary number of results. An elegant solution is modelling them as special collections of top-encodable objects that each represent an individual item. For instance, we could have a list of separate arguments, of arbitrary length. + +Multi-values work similarly to varargs in other languages, such as C, where you can write `void f(int arg, ...) { ... }`. In the smart contract framework they do not need specialized syntax, we use the type system to define their behavior. + +:::info Note +In the framework, single values are treated as a special case of multi-value, one that consumes exactly one argument, or returns exactly one value. + +In effect, all serializable types implement the multi-value traits. +::: + + +## Parsing and limitations + +It is important to understand that arguments get read one by one from left to right, so there are some limitations as to how var-args can be positioned. Argument types also define how the arguments are consumed, so, for instance, if a type specifies that all remaining arguments will be consumed, it doesn't really make sense to have any other argument after that. + +For instance, let's consider the behavior of `MultiValueEncoded`, which consumes all subsequent arguments. Hence, it's advisable to place it as the last argument in the function, like so: + +```rust +#[endpoint(myEndpoint)] +fn my_endpoint(&self, first_arg: ManagedBuffer, second_arg: TokenIdentifier, last_arg: MultiValueEncoded) +``` +Placing any argument after `MultiValueEncoded` will not initialize that argument, because `MultiValueEncoded` will consume all arguments following it. An important rule to remember is that an endpoint can have only one `MultiValueEncoded` argument, and it should always occupy the last position in order to achieve the desired outcome. + +Another scenario to consider involves the use of multiple `Option` arguments. Take, for instance, the following endpoint: + +```rust +#[endpoint(myOptionalEndpoint)] +fn my_optional_endpoint(&self, first_arg: OptionalValue, second_arg: OptionalValue) +``` +In this context, both arguments (or none) should be provided at the same time in order to get the desired effect. Since arguments are processed sequentially from left to right, supplying a single value will automatically assign it to the first argument, making it impossible to determine which argument should receive that value. + +The same rule applies when any regular argument is placed after a var-arg, thus, a strong restriction regarding arguments' order has been enforced. Regular arguments `must not` be placed after var-args. + +To further enhance clarity and minimize potential errors related to var-args, starting from framework version `v0.44.0`, it is no longer allowed by default to have multiple var-args. This restriction can be lifted by using the #[allow_multiple_var_args] annotation. + +:::info Note +`#[allow_multiple_var_args]` is required when using more than one var-arg in an endpoint and is placed at the endpoint level, alongside the `#[endpoint]` annotation. Utilizing `#[allow_multiple_var_args]` in any other manner will not work. + +Considering this, our optional endpoint from the example before becomes: +```rust +#[allow_multiple_var_args] +#[endpoint(myOptionalEndpoint)] +fn my_optional_endpoint(&self, first_arg: OptionalValue, second_arg: OptionalValue) +``` +::: + +The absence of #[allow_multiple_var_args] as an endpoint attribute, along with the use of multiple var-args and/or the placement of regular arguments after var-args, leads to build failure, as the parsing validations now consider the count and positions of var-args. + +However, when `#[allow_multiple_var_args]` is used, there is no other parsing validation (except the ones from above) to enforce the var-args rules mentioned before. In simpler terms, using the annotation implies that the developer is assuming responsibility for handling multiple var-args and anticipating the outcomes, effectively placing trust in their ability to manage the situation. + + +## Standard multi-values + +These are the common multi-values provided by the framework: +- Straightforward var-args, an arbitrary number of arguments of the same type. + - Can be defined in code as: + - `MultiValueVec` - the unmanaged version. To be used outside of contracts. + - `MultiValueManagedVec` - the equivalent managed version. The only limitation is that `T` must implement `ManagedVecItem`, because we are working with an underlying `ManagedVec`. Values are deserialized eagerly, the endpoint receives them already prepared. + - `MultiValueEncoded` - the lazy version of the above. Arguments are only deserialized when iterating over this structure. This means that `T` does not need to implement `ManagedVecItem`, since it never gets stored in a `ManagedVec`. + - In all these 3 cases, `T` can be any serializable type, either single or multi-value. + - Such a var-arg will always consume all the remaining arguments, so it doesn't make sense to place any other arguments after it, single or multi-value. The framework doesn't forbid it, but single values will crash at runtime, since they always need a value, and multi-values will always be empty. +- Multi-value tuples. + - Defined as `MultiValueN`, where N is a number between 2 and 16, and `T1`, `T2`, ..., `TN` can be any serializable types, either single or multi-value; e.g. `MultiValue3`. + - It doesn't make much sense to use them as arguments on their own (it is easier and equivalent to just have separate named arguments), but they do have the following uses: + - They can be embedded in a regular var-arg to obtain groups of arguments. For example `MultiValueVec>` defines pairs of numbers. There is no more need to check in code that an even number of arguments was passed, the deserializer resolves this on its own. + - Rust does not allow returning more than one result, but by returning a multi-value tuple we can have an endpoint return several values, of different types. +- Optional arguments. + - Defined as `OptionalValue`, where `T` can be any serializable type, either single or multi-value. + - At most one argument will be consumed. For this reason it sometimes makes sense to have several optional arguments one after the other, or optional arguments followed by var-args. + - Do note, however, that an optional argument cannot be missing if there is anything else coming after it. For example if an endpoint has arguments `a: OptionalValue, b: OptionalValue`, `b` can be missing, or both can be missing, but there is no way to have `a` missing and `b` to be there, because passing any argument will automatically get it assigned to `a`. +- Counted var-args. + - Suppose we actually do want two sets of var-args in an endpoint. One solution would be to explicitly state how many arguments each of them contain (or at least the first one). Counted var-args make this simple. + - Defined as `MultiValueManagedVecCounted`, where `T` can be any serializable type, either single or multi-value. + - Always takes a number argument first, which represents how many arguments follow. Then it consumes exactly that many arguments. + - Can be followed by other arguments, single or multi-value. +- Async call result. + - Asynchronous call callbacks also need to know whether or not the call failed, so they have a special format for transmitting arguments. The first argument is always the error code. If the error code is `0`, then the call result follows. Otherwise, we get one additional argument, which is the error message. To easily deserialize this, we use a special type. + - Defined as `ManagedAsyncCallResult`. + - There is also an unmanaged version, `AsyncCallResult`, but it is no longer used nowadays. + - They are both enums, the managed part only refers to the error message. +- Ignored arguments. + - Sometimes, for backwards compatibility or other reasons it can happen to have (optional) arguments that are never used and not of interest. To avoid any useless deserialization, it is possible to define an argument of type `IgnoreValue` at the end. + - By doing so, any number of arguments are allowed at the end, all of which will be completely ignored. + +So, to recap: + +| Managed Version | Unmanaged version | What it represents | Similar single value | +| ---------------------------------- | -------------------- | ------------------------------- | ------------------------------ | +| `MultiValueN` | | A fixed number of arguments | Tuple `(T1, T2, ..., TN)` | +| `OptionalValue` | | An optional argument | `Option` | +| `MultiValueEncoded` | `MultiValueVec` | Variable number of arguments | `Vec` | +| `MultiValueManagedVec` | `MultiValueVec` | Variable number of arguments | `Vec` | +| `MultiValueManagedVecCounted` | | Counted number of arguments | `(usize, Vec)` | +| `ManagedAsyncCallResult` | `AsyncCallResult` | Async call result in callback | `Result` | +| `IgnoreValue` | | Any number of ignored arguments | Unit `()` | + + +## Storage mapper contents as multi-values + +The storage mapper declaration is a method that can normally also be made into a public view endpoint. If so, when calling them, the entire contents of the mapper will be read from storage and serialized as multi-value. Only recommended when there is little data, or in tests. + +These storage mappers are, in no particular order: +- BiDiMapper +- LinkedListMapper +- MapMapper +- QueueMapper +- SetMapper +- SingleValueMapper +- UniqueIdMapper +- UnorderedSetMapper +- UserMapper +- VecMapper +- FungibleTokenMapper +- NonFungibleTokenMapper + + +## Multi-values in action + +To clarify the way multi-values work in real life, let's provide some examples of how one would go avout calling an endpoint with variadic arguments. + + +### Option vs. OptionalValue + +Assume we want to have an endpoint that takes a token identifier, and, optionally, a token nonce. There are two ways of doing this: + +```rust +#[endpoint(myOptArgEndpoint1)] +fn my_opt_arg_endpoint_1(&self, token_id: TokenIdentifier, opt_nonce: Option) {} +``` + +```rust +#[endpoint(myOptArgEndpoint2)] +fn my_opt_arg_endpoint_2(&self, token_id: TokenIdentifier, opt_nonce: OptionalValue) {} +``` + +We want to call these endpoints with arguments: `TOKEN-123456` (`0x544f4b454e2d313233343536`) and `5`. To contrast for the two endpoints: +- Endpoint 1: `myOptArgEndpoint1@544f4b454e2d313233343536@010000000000000005` +- Endpoint 2: `myOptArgEndpoint2@544f4b454e2d313233343536@05` + +:::info Note +In the first case, we are dealing with an [Option](/developers/data/composite-values#options), whose first encoded byte needs to be `0x01`, to signal `Some`. In the second case there is no need for `Option`, `Some` is signalled simply by the fact that the argument was provided. + +Also note that the nonce itself is nested-encoded in the first case (being _nested_ in an `Option`), whereas in the second case it can be top-encoded directly. +::: + +Now let's do the same exercise for the case where we want to omit the nonce altogether: +- Endpoint 1: + - `myOptArgEndpoint1@544f4b454e2d313233343536@`, or + - `myOptArgEndpoint1@544f4b454e2d313233343536@00` - also accepted +- Endpoint 2: + - `myOptArgEndpoint2@544f4b454e2d313233343536` + +:::info Note +The difference is less striking in this case. + +In the first case, we encoded `None` as an empty byte array (encoding it as `0x00` is also accepted). In any case, we do need to pass it as an explicit argument. + +In the second case, the last argument is omitted altogether. +::: + +We also want to point out that the multi-value implementation is more efficient in terms of gas. It is more easier for the smart contract to count the number of arguments and top-decode, than parse a composite type, like `Option`. + + +### ManagedVec vs. MultiValueEncoded + +In this example, let's assume we want to receive any number of triples of the form (token ID, nonce, amount). This can be implemented in two ways: + +```rust +#[endpoint(myVarArgsEndpoint1)] +fn my_var_args_endpoint_1(&self, args: ManagedVec<(TokenIdentifier, u64, BigUint)>) {} +``` + +```rust +#[endpoint(myVarArgsEndpoint2)] +fn my_var_args_endpoint_2(&self, args: MultiValueManagedVec) {} +``` + +The first approach seems a little simpler from the perspective of the smart contract implementation, since we only have a `ManagedVec` of tuples. But when we try to encode this argument, to call the endpoint, we are struck with a format that is quite devastating, both for performance and usability. + +Let's call these endpoints with triples: `(TOKEN-123456, 5, 100)` and `(TOKEN-123456, 10, 500)`. The call data would have to look like this: +`myVarArgsEndpoint1@0000000c_544f4b454e2d313233343536_0000000000000005_00000001_64_0000000c_544f4b454e2d313233343536_000000000000000a_00000002_01f4`. + +:::info Note +Above, we've separated the parts with `_` for readability purposes only. On the real blockchain, there would be no underscores, everything would be concatenated. + +Every single value in this call data needs to be nested-encoded. We need to lay out the length or each token identifier, nonces are spelled out in full 8 bytes, and we also need the length of each `BigUint` value. +::: + +As you can see, that endpoint is very hard to work with. All arguments are concatenated into one big chunk, and every single value needs to be nested-encoded. This is why we need to lay out the length for each `TokenIdentifier` (e.g. the 0000000c in front, which is length 12) as well as for each `BigUint` (e.g. the `00000001` before `64`). The nonces are spelled out in their full 8 bytes. + +The second endpoint is a lot easier to use. For the same arguments, the call data looks like this: +`myVarArgsEndpoint2@544f4b454e2d313233343536@05@64@544f4b454e2d313233343536@0a@01f4`. + +It is a lot more readable, for several reasons: +- We have 6 arguments instead of 1; +- The argument separator makes it much easier for both us and the smart contract to distinguish where each value ends and where the next one begins; +- All values are top-encoded, so there is no more need for lengths; the nonces can be expressed in a more compact form. + +Once again, the multi-value implementation is more efficient in terms of gas. All the contract needs to do is to make sure that the number of arguments is a multiple of 3, and then top-decode each value. Conversely, in the first example, a lot more memory needs to be moved around when splitting the large argument into pieces. + + +## Implementation details + +All serializable types will implement traits `TopEncodeMulti` and `TopDecodeMulti`. + +The components that do argument parsing, returning results, or handling of event logs all work with these two traits. + +All serializable types (the ones that implement `TopEncode`) are explicitly declared to also be multi-value in this declaration: + +```rust +/// All single top encode types also work as multi-value encode types. +impl TopEncodeMulti for T +where + T: TopEncode, +{ + fn multi_encode_or_handle_err(&self, output: &mut O, h: H) -> Result<(), H::HandledErr> + where + O: TopEncodeMultiOutput, + H: EncodeErrorHandler, + { + output.push_single_value(self, h) + } +} +``` + +To create a custom multi-value type, one needs to manually implement these two traits for the type. Unlike for single values, there is no [equivalent derive syntax](/developers/data/custom-types). + +--- + +### MultiversX API + +## About MultiversX API + +`api.multiversx.com` is the public instance of MultiversX API and is a wrapper over `gateway.multiversx.com` that brings a robust caching mechanism, alongside Elasticsearch +historical queries support, tokens media support, delegation & staking data, and many others. + + +## Public URLs + +Mainnet: [https://api.multiversx.com](https://api.multiversx.com). + +Testnet: [https://testnet-api.multiversx.com](https://testnet-api.multiversx.com). + +Devnet: [https://devnet-api.multiversx.com](https://devnet-api.multiversx.com). + + +## External Providers + +**Blastapi** + +Mainnet: [https://multiversx-api.public.blastapi.io](https://multiversx-api.public.blastapi.io). + +Devnet: [https://multiversx-api-devnet.public.blastapi.io](https://multiversx-api-devnet.public.blastapi.io). + +Checkout information about [pricing](https://blastapi.io/pricing) and API [limitations per plan](https://docs.blastapi.io/blast-documentation/apis-documentation/core-api/multiversx). + +More details on how to get your private endpoint can be found [here](https://docs.blastapi.io/blast-documentation/tutorials-and-guides/using-blast-to-get-a-blockchain-endpoint-1). + +**Kepler** + +High-performance infrastructure layer purpose-built for the MultiversX ecosystem. + +Affordable plans, high limits for API, Gateway, Elastic Search, Event Notifier, and many more! + +[https://kepler.projectx.mx](https://kepler.projectx.mx) + + +## Dependencies + + +### Core dependencies + +For its basic functionality (without including caching or storage improvements), api.multiversx.com depends on the following external systems: + +- `gateway`: also referred as Proxy, provides access to node information, such as network settings, account balance, sending transactions, etc + docs: [Proxy](/sdk-and-tools/proxy). +- `index`: a database that indexes data that can be queries, such as transactions, blocks, nfts, etc. + docs: [Elasticsearch](/sdk-and-tools/elastic-search). +- `delegation`: a microservice used to fetch providers list from the delegation API. Not currently open for public access. + + +### Other dependencies + +It uses on the following internal systems: + +- redis: used to cache various data, for performance purposes +- rabbitmq: pub/sub for sending mainly NFT process information from the transaction processor instance to the queue worker instance + +It depends on the following optional external systems: + +- events notifier rabbitmq: queue that pushes logs & events which are handled internally e.g. to trigger NFT media fetch +- data: provides EGLD price information for transactions +- xexchange: provides price information regarding various tokens listed on xExchange +- ipfs: ipfs gateway for fetching mainly NFT metadata & media files +- media: ipfs gateway which will be used as prefix for NFT media & metadata returned in the NFT details +- media internal: caching layer for ipfs data to fetch from a centralized system such as S3 for performance reasons +- AWS S3: used to process newly minted NFTs & uploads their thumbnails +- github: used to fetch provider profile & node information from github + +It uses the following optional internal systems: + +- mysql database: used to store mainly NFT media & metadata information +- mongo database: used to store mainly NFT media & metadata information + + +## Ways to start MultiversX API + +An API instance can be started with the following behavior: + +- public API: provides REST API for the consumers +- private API: used to report prometheus metrics & health checks +- transaction processor & completed: fetches real-time transactions & logs from the blockchain; takes action based on various events, such as clearing cache values and publishing events on a queue +- cache warmer: used to proactively fetch data & pushes it to cache, to improve performance & scalability +- elastic updater: used to attach various extra information to items in the elasticsearch, for not having to fetch associated data from other external systems when performing listing requests +- events notifier: perform various decisions based on incoming logs & events +- subscription: used to manage subscriptions, fetch and broadcast data to subscribers + +## Rate limiting + +Public MultiversX APIs have a rate limit mechanism that brings the following limitations: + +- api.multiversx.com (_mainnet_): 2 requests / IP / second +- devnet-api.multiversx.com (_devnet_): 5 requests / IP / second + + +## Rest API documentation + +Rest API documentation of `api.multiversx.com` can be found on the [Swagger docs](https://api.multiversx.com). + + +## WebSocket Subscription Documentation + +Real-time blockchain streaming is supported through the MultiversX WebSocket Subscription API. +A dedicated guide is available here [WebSocket Subscription Guide](./ws-subscriptions.md) + + +## References + +- Github repository: [https://github.com/multiversx/mx-api-service](https://github.com/multiversx/mx-api-service) +- Swagger docs: [https://api.multiversx.com](https://api.multiversx.com) +- Raw JSON Swagger OpenAPI definitions: [https://api.multiversx.com/-json](https://api.multiversx.com/-json) +- MultiversX blog: [https://multiversx.com/blog/elrond-api-internet-scale-defi](https://multiversx.com/blog/elrond-api-internet-scale-defi) + +--- + +### MultiversX API WebSocket + +## MultiversX WebSocket Subscription API + +Starting with the release [v1.17.0](https://github.com/multiversx/mx-api-service/releases/tag/v1.17.0) we introduced WebSocket Subscription functionality. + +It is useful for subscribing to new events in real-time, rather than performing polling (requesting latest events with a given refresh period). + +## Update Frequency and Stream Modes + +The WebSocket API supports two primary modes of data consumption: **Pulse Stream** and **Filtered Stream**. + +### 1. Pulse Stream (Snapshot & Loop) +Subscribers receive the most recent events for a specific timeframe at regular intervals defined by the API. +* **Behavior:** You receive an update every round (or configured interval). +* **Content:** Each update contains the latest events for the requested buffer (e.g., latest 25 blocks). +* **Duplicates:** Because of the repeating interval, **duplicate events may appear across batches**. It is the user’s responsibility to filter these duplicates. +* **Available Streams:** Transactions, Blocks, Pool, Events, Stats. + +### 2. Filtered Stream (Custom Real-time Streams) +Subscribers receive events strictly as they occur on the blockchain, filtered by specific criteria. +* **Behavior:** You are notified immediately when a new event matches your filter. +* **Content:** Data flows in real-time from the moment of subscription. +* **Duplicates:** **No duplicate events are sent.** You receive each item exactly once. +* **Available Streams:** `Transactions`, `Events`, and `Transfers` are supported in this mode. + +## Rest API models compatibility +The MultiversX WebSocket Subscription API provides real-time blockchain data identical in structure to REST API responses: + + +[https://api.multiversx.com](https://api.multiversx.com) +[https://devnet-api.multiversx.com](https://devnet-api.multiversx.com) +[https://testnet-api.multiversx.com](https://testnet-api.multiversx.com) + + +All updates mirror REST responses and include a `Count` field representing **the total number of existing items at the moment the update was delivered**. + +## Selecting the WebSocket Endpoint + +Before connecting, fetch the WebSocket cluster: + +### Mainnet +```text +https://api.multiversx.com/websocket/config +``` + +### Testnet +```text +https://testnet-api.multiversx.com/websocket/config +``` + +### Devnet +```text +https://devnet-api.multiversx.com/websocket/config +``` + +### Response example +```json +{ + "url": "socket-api-xxxx.multiversx.com" +} +``` + +### WebSocket endpoint +```text +https:///ws/subscription +``` + +--- + +## Subscription Events Overview + +| Stream Type | Stream Name | Subscribe Event | Update Event | Description | +|---|---|---|---|---| +| **Pulse** | Transactions | `subscribeTransactions` | `transactionUpdate` | Recurring latest buffer | +| **Pulse** | Blocks | `subscribeBlocks` | `blocksUpdate` | Recurring latest buffer | +| **Pulse** | Pool | `subscribePool` | `poolUpdate` | Recurring mempool dump | +| **Pulse** | Events | `subscribeEvents` | `eventsUpdate` | Recurring latest events | +| **Pulse** | Stats | `subscribeStats` | `statsUpdate` | Recurring chain stats | +| **Filtered** | Custom Txs | `subscribeCustomTransactions` | `customTransactionUpdate` | Real-time filtered Txs | +| **Filtered** | Custom Transfers | `subscribeCustomTransfers` | `customTransferUpdate` | Real-time filtered Transfers | +| **Filtered** | Custom Events| `subscribeCustomEvents` | `customEventUpdate` | Real-time filtered Events | + +--- + +## Pulse Stream Subscriptions + +**Note:** This mode pushes the latest buffer of data repeatedly. **Duplicate events may appear across batches**, and it is the user’s responsibility to filter or handle those duplicates on their side. + +### Transactions (Pulse) + +#### Payload (DTO) + +| Field | Type | Required | +|-------------------------|----------------------------------------------------|----------| +| from | number | YES | +| size | number (1–50) | YES | +| status | `"success" \| "pending" \| "invalid" \| "fail"` | NO | +| order | `"asc" \| "desc"` | NO | +| isRelayed | boolean | NO | +| isScCall | boolean | NO | +| withScResults | boolean | NO | +| withRelayedScresults | boolean | NO | +| withOperations | boolean | NO | +| withLogs | boolean | NO | +| withScamInfo | boolean | NO | +| withUsername | boolean | NO | +| withBlockInfo | boolean | NO | +| withActionTransferValue | boolean | NO | +| fields | string[] | NO | + +#### Example usage + +```js +import { io } from "socket.io-client"; + +async function main() { + const { url } = await fetch("https://api.multiversx.com/websocket/config") + .then((r) => r.json()); + + const socket = io(`https://${url}`, { path: "/ws/subscription" }); + + const payload = { from: 0, size: 25 }; + + socket.emit("subscribeTransactions", payload); + + socket.on("transactionUpdate", (data) => { + console.log("Transactions update:", data); + }); +} + +main().catch(console.error); +``` + +#### Update Example + +```json +{ + "transactions": [ + { + "txHash": "7f172e468e61210805815f33af8500d827aff36df6196cc96783c6d592a5fc76", + "sender": "erd1srdxd75cg7nkaxxy3llz4hmwqqkmcej0jelv8ults8m86g29aj3sxjkc45", + "receiver": "erd19waq9tlhj32ane9duhkv6jusm58ca5ylnthhg9h8fcumtp8srh4qrl3hjj", + "nonce": 211883, + "status": "pending", + "timestamp": 1763718888 + } + ], + "transactionsCount": 1234567 +} +``` + +--- + +### Blocks (Pulse) + +#### Payload (DTO) + +| Field | Type | Required | +|-----------------------|---------------------|----------| +| from | number | YES | +| size | number (1–50) | YES | +| shard | number | NO | +| order | `"asc" \| "desc"` | NO | +| withProposerIdentity | boolean | NO | + +#### Example usage + +```js +import { io } from "socket.io-client"; + +async function main() { + const { url } = await fetch("https://api.multiversx.com/websocket/config") + .then((r) => r.json()); + + const socket = io(`https://${url}`, { path: "/ws/subscription" }); + + const payload = { from: 0, size: 25 }; + + socket.emit("subscribeBlocks", payload); + + socket.on("blocksUpdate", (data) => { + console.log("Blocks update:", data); + }); +} + +main().catch(console.error); +``` + +#### Update Example + +```json +{ + "blocks": [ + { + "hash": "8576bb346bc95680f1ab0eb1fb8c43bbd03ef6e6ac8fd24a3c6e85d4c81be16b", + "epoch": 1939, + "nonce": 27918028, + "round": 27933551, + "shard": 0, + "timestamp": 1763718906 + } + ], + "blocksCount": 111636242 +} +``` + +--- + +### Pool (Pulse) + +#### Payload (DTO) + +| Field | Type | Required | +|--------|-----------------------------------------------------|----------| +| from | number | YES | +| size | number (1–50) | YES | +| type | `"Transaction" \| "SmartContractResult" \| "Reward"`| NO | + +#### Example usage + +```js +import { io } from "socket.io-client"; + +async function main() { + const { url } = await fetch("https://api.multiversx.com/websocket/config") + .then((r) => r.json()); + + const socket = io(`https://${url}`, { path: "/ws/subscription" }); + + const payload = { from: 0, size: 25, type: "Transaction" }; + + socket.emit("subscribePool", payload); + + socket.on("poolUpdate", (data) => { + console.log("Pool update:", data); + }); +} + +main().catch(console.error); +``` + +#### Update Example + +```json +{ + "pool": [ + { + "txHash": "0b0cd3932689c6853e50ccc0f49feeb9c5f2a68858cbd213fd0825dd4bc0632b", + "sender": "erd1jfwjg6tl87rhe73zmd5ygm8xmc9u3ys80mjvakdc7ca3kknr2kjq7s98h3", + "receiver": "erd1qqqqqqqqqqqqqpgq0dsmyccxtlkrjvv0czyv2p4kcy72xvt3nzgq8j2q3y", + "nonce": 1166, + "function": "claim", + "type": "Transaction" + } + ], + "poolCount": 1902 +} +``` + +--- + +### Events (Pulse) + +#### Payload (DTO) + +| Field | Type | Required | +|-------|---------------|----------| +| from | number | YES | +| size | number (1–50) | YES | +| shard | number | NO | + +#### Example usage + +```js +import { io } from "socket.io-client"; + +async function main() { + const { url } = await fetch("https://api.multiversx.com/websocket/config") + .then((r) => r.json()); + + const socket = io(`https://${url}`, { path: "/ws/subscription" }); + + const payload = { from: 0, size: 25 }; + + socket.emit("subscribeEvents", payload); + + socket.on("eventsUpdate", (data) => { + console.log("Events update:", data); + }); +} + +main().catch(console.error); +``` + +#### Update Example + +```json +{ + "events": [ + { + "txHash": "b5bde891df72e26fb36e7ab3acc14b74044bd9aa82b4852692f5b9a767e0391f-1-0", + "identifier": "signalError", + "address": "erd1jv5m4v3yr0wy6g2jtz2v344sfx572rw6aclum9c6r7rd4ej4l6csjej2wh", + "timestamp": 1763718864, + "topics": [ + "9329bab2241bdc4d21525894c8d6b049a9e50ddaee3fcd971a1f86dae655feb1", + "4865616c7468206e6f74206c6f7720656e6f75676820666f72206c69717569646174696f6e2e" + ], + "shardID": 1 + } + ], + "eventsCount": 109432 +} +``` + +--- + +### Stats (Pulse) + +#### Payload (DTO) + +Stats subscription does not accept any payload. + +#### Example usage + +```js +import { io } from "socket.io-client"; + +async function main() { + const { url } = await fetch("https://api.multiversx.com/websocket/config") + .then((r) => r.json()); + + const socket = io(`https://${url}`, { path: "/ws/subscription" }); + + socket.emit("subscribeStats"); + + socket.on("statsUpdate", (data) => { + console.log("Stats update:", data); + }); +} + +main().catch(console.error); +``` + +#### Update Example + +```json +{ + "shards": 3, + "blocks": 111636242, + "accounts": 9126654, + "transactions": 569773975, + "scResults": 402596990, + "epoch": 1939, + "roundsPassed": 9478, + "roundsPerEpoch": 14400, + "refreshRate": 6000 +} +``` + +--- + +## Filtered Stream Subscriptions (Custom Streams) + +**Note:** These streams provide real-time data (with a small delay after the actions are committed on-chain) for specific criteria. You must provide at least one filter criteria when subscribing. + +### Custom Transactions (Filtered) + +Subscribes to transactions matching specific criteria (Sender, Receiver, or Function) as they happen. + +#### Subscribe Event +`subscribeCustomTransactions` + +#### Payload (DTO) + +| Field | Type | Required | Description | +|---|---|---|---| +| sender | string | NO* | Filter by sender address (bech32) | +| receiver | string | NO* | Filter by receiver address (bech32) | +| function | string | NO* | Filter by smart contract function name | + +*\*At least one field must be provided.* + +#### Example usage + +```js +import { io } from "socket.io-client"; + +async function main() { + const { url } = await fetch("https://api.multiversx.com/websocket/config") + .then((r) => r.json()); + + const socket = io(`https://${url}`, { path: "/ws/subscription" }); + + // Subscribe to all transactions sent by a specific address + const payload = { + sender: "erd1..." + }; + + socket.emit("subscribeCustomTransactions", payload); + + socket.on("customTransactionUpdate", (data) => { + // data.transactions: Transaction[] + // data.timestampMs: number + console.log("New Custom Transaction:", data); + }); +} +``` + +#### Update Example + +```json +{ + "transactions": [ + { + "txHash": "7f172e468e61210805815f33af8500d827aff36df6196cc96783c6d592a5fc76", + "sender": "erd1srdxd75cg7nkaxxy3llz4hmwqqkmcej0jelv8ults8m86g29aj3sxjkc45", + "receiver": "erd19waq9tlhj32ane9duhkv6jusm58ca5ylnthhg9h8fcumtp8srh4qrl3hjj", + "nonce": 211883, + "status": "pending", + "timestamp": 1763718888 + } + ], + "timestampMs": 1763718888000 +} +``` + +--- + +### Custom Transfers (Filtered) + +Subscribes to the complete stream of actions associated with a specific Address or Token. This includes not only standard transactions but all blockchain operations: Transactions, Smart Contract Results (SCRs), Rewards, ... . It is the preferred mode for tracking the full real-time activity of an account or the global movement of a specific token. + +#### Subscribe Event +`subscribeCustomTransfers` + +#### Payload (DTO) + +| Field | Type | Required | Description | +|---|---|---|---| +| sender | string | NO* | Filter by sender address (bech32) | +| receiver | string | NO* | Filter by receiver address (bech32) | +| relayer | string | NO* | Filter by the relayer address (for meta-transactions) | +| function | string | NO* | Filter by smart contract function name | +| token | string | NO* | Filter by Token Identifier (e.g., `USDC-c76f1f` or `EGLD` for native egld) | +| address | string | NO** | **Universal Filter:** Matches if the address is the Sender **OR** Receiver **OR** Relayer. | + +*\*At least one field from the list above must be provided.* +*\*\*The `address` field cannot be combined with `sender`, `receiver`, or `relayer` in the same payload.* + +#### Example usage + +**Scenario: Listen for specific Token transfers (e.g., USDC)** +Useful for tracking volume on a specific token. + +```js +import { io } from "socket.io-client"; + +async function main() { + const { url } = await fetch("https://api.multiversx.com/websocket/config") + .then((r) => r.json()); + + const socket = io(`https://${url}`, { path: "/ws/subscription" }); + + const payload = { + token: "USDC-c76f1f" + }; + + socket.emit("subscribeCustomTransfers", payload); + + socket.on("customTransferUpdate", (data) => { + // data.transfers: Transaction[] (filtered) + // data.timestampMs: number + console.log("New USDC Transfer:", data); + }); +} +``` + +**Scenario: Listen for ANY activity related to an address** +Using the `address` field is a shorthand to avoid creating 3 separate subscriptions (sender, receiver, relayer). + +```js + const payload = { + address: "erd1..." // Will capture incoming, outgoing, and relayed transfers + }; + + socket.emit("subscribeCustomTransfers", payload); +``` + +#### Update Example + +```json +{ + "transfers": [ + { + "txHash": "cb5d0644ef40943db8035ff50913c4a974a469c2479a73c3cd3ab8de9027be0f", + "receiver": "erd1qqqqqqqqqqqqqpgqxn6hj5m9x33zuq0xynjkusd8tsz3u6a94fvsn2m2ry", + "receiverShard": 1, + "sender": "erd1qqqqqqqqqqqqqpgqcc69ts8409p3h77q5chsaqz57y6hugvc4fvs64k74v", + "senderAssets": { + ... + }, + "senderShard": 1, + "status": "success", + "value": "0", + "timestamp": 1765963650, + "function": "exchange", + "action": { + "category": "esdtNft", + "name": "transfer", + "description": "Transfer", + "arguments": { + "transfers": [ + { + "type": "FungibleESDT", + "ticker": "USDC", + "svgUrl": "https://tools.multiversx.com/assets-cdn/tokens/USDC-c76f1f/icon.svg", + "token": "USDC-c76f1f", + "decimals": 6, + "value": "4087442" + } + ], + "receiver": "erd1qqqqqqqqqqqqqpgqxn6hj5m9x33zuq0xynjkusd8tsz3u6a94fvsn2m2ry", + "functionName": "exchange", + "functionArgs": [ + "01" + ] + } + }, + "type": "SmartContractResult", + "originalTxHash": "46bb841a087c5ce95ca28d0e95c860c661b4d32514bb2970137536036bf591b3" + }, + ], + "timestampMs": 1763718888000 +} +``` + +--- + +### Custom Events (Filtered) + +Subscribes to smart contract events matching specific criteria as they happen. + +#### Subscribe Event +`subscribeCustomEvents` + +#### Payload (DTO) + +| Field | Type | Required | Description | +|---|---|---|---| +| address | string | NO* | Filter by the address associated with the event | +| identifier | string | NO* | Filter by event identifier (name) | +| logAddress | string | NO* | Filter by the contract address that emitted the log | + +*\*At least one field must be provided.* + +#### Example usage + +```js +import { io } from "socket.io-client"; + +async function main() { + const { url } = await fetch("https://api.multiversx.com/websocket/config") + .then((r) => r.json()); + + const socket = io(`https://${url}`, { path: "/ws/subscription" }); + + // Subscribe to a specific event identifier + const payload = { + identifier: "swap" + }; + + socket.emit("subscribeCustomEvents", payload); + + socket.on("customEventUpdate", (data) => { + // data.events: Events[] + // data.timestampMs: number + console.log("New Custom Event:", data); + }); +} +``` + +#### Update Example + +```json +{ + "events": [ + { + "txHash": "b5bde891df72e26fb36e7ab3acc14b74044bd9aa82b4852692f5b9a767e0391f-1-0", + "identifier": "signalError", + "address": "erd1jv5m4v3yr0wy6g2jtz2v344sfx572rw6aclum9c6r7rd4ej4l6csjej2wh", + "timestamp": 1763718864, + "topics": [ + "9329bab2241bdc4d21525894c8d6b049a9e50ddaee3fcd971a1f86dae655feb1", + "4865616c7468206e6f74206c6f7720656e6f75676820666f72206c69717569646174696f6e2e" + ], + "shardID": 1 + } + ], + "timestampMs": 1763718864000 +} +``` + +--- + +## Unsubscribing + +To stop receiving updates for any stream, you must emit the corresponding unsubscribe event. + +**The Rule:** +1. Add the prefix `un` to the subscription event name (e.g., `subscribeTransactions` → `unsubscribeTransactions`, `subscribeCustomTransactions` → `unsubscribeCustomTransactions`). +2. Send the **exact same payload** used for the subscription. + +### Example: Unsubscribe from Custom Transactions + +If you subscribed with: +```js +const payload = { sender: "erd1..." }; +socket.emit("subscribeCustomTransactions", payload); +``` + +You must unsubscribe with: +```js +socket.emit("unsubscribeCustomTransactions", payload); +``` + +### Example: Unsubscribe from Custom Transfers +If you subscribed with: +```js +const payload = { token: "USDC-c76f1f" }; +socket.emit("subscribeCustomTransfers", payload); +``` + +You must unsubscribe with: +```js +socket.emit("unsubscribeCustomTransfers", payload); +``` + +### Example: Unsubscribe from Blocks +If you subscribed with: +```js +const payload = { from: 0, size: 25 }; +socket.emit("subscribeBlocks", payload); +``` + +You must unsubscribe with: +```js +socket.emit("unsubscribeBlocks", payload); +``` + +--- + +## Error Handling + +Unexpected behaviors, such as sending an invalid payload or exceeding the server's subscription limits, will trigger an `error` event emitted by the server. + +You should listen to this event to handle failures gracefully. + +### Payload (DTO) + +The error object contains context about which subscription failed and why. + +| Field | Type | Description | +|---------|--------|------------------------------------------------------------------------------------| +| pattern | string | The subscription topic (event name) that was requested (e.g., `subscribePool`). | +| data | object | The original payload sent by the client that caused the error. | +| error | object | The specific error returned by the server. | + +### Example usage + +```js +import { io } from "socket.io-client"; + +// ... setup socket connection ... + +// Listen for generic errors from the server +socket.on("error", (errorData) => { + console.error("Received error from server:"); + console.dir(errorData, { depth: null }); +}); +``` + +### Error Example + +**Scenario:** The client attempts to open more subscriptions than the server allows (e.g., limit of X). + +```json +{ + "pattern": "subscribePool", + "data": { + "from": 0, + "size": 25, + "type": "badInput" + }, + "error": [ + { + "target": { + "from": 0, + "size": 25, + "type": "badInput" + }, + "value": "badInput", + "property": "type", + "children": [], + "constraints": { + "isEnum": "type must be one of the following values: Transaction, SmartContractResult, Reward" + } + } + ] +} +``` + +--- + +## Summary + +- WebSocket endpoint is dynamically obtained via `/websocket/config`. +- **Pulse Stream Subscriptions:** periodic updates with possible duplicates (Transactions, Blocks, Pool, Events, Stats). +- **Filtered Stream Subscriptions:** real-time updates with only new data (CustomTransactions, **CustomTransfers**, CustomEvents). +- **Unsubscribing:** Use `un` prefix + same payload. +- Payload DTOs define allowed fields and required/optional rules. +- Update messages mirror REST API and include `Count` fields. +- Errors are emitted via the standard `error` event. +- Uses `socket.io-client`. + +This document contains everything required to use MultiversX WebSocket Subscriptions effectively. + +--- + +### MultiversX Smart Contracts + +## MultiversX Smart Contracts + +The MultiversX Blockchain's virtual machine (VM) executes [WebAssembly](https://en.wikipedia.org/wiki/WebAssembly). This means that it can execute smart contracts +written in _any programming language_ that can be compiled to WASM bytecode. **Though, we only provide support for Rust.** + +Developers are encouraged to use Rust for their smart contracts, however. MultiversX provides a [Rust framework](https://github.com/multiversx/mx-sdk-rs) +which allows for unusually clean and efficient code in smart contracts, a rarity in the blockchain field. +A declarative testing framework is bundled as well. + +Read more about the MultiversX VM [here](/learn/space-vm). + +Navigate through the sidebar to see some tutorials to get you started, as well as the Rust framework documentation. + +--- + +### MultiversX Smart Contracts API limits + +## MultiversX Smart Contracts API limits + +Starting with the Polaris release (February 2023), we have added blockchain data call limits for Smart Contracts. This means that in a single transaction, a Smart Contract cannot perform more than a protocol-level configured number of transfers, trie reads, or built-in function calls. + +This approach comes as a better alternative than increasing the gas costs of those operations since the limits are still very high, so most probably only the most expensive contracts' functions will suffer from these limitations. + +These limits are set in the gas schedule files in the `MaxPerTransaction` section. For example, this +gas schedule file https://github.com/multiversx/mx-chain-mainnet-config/blob/master/gasSchedules/gasScheduleV7.toml has the following limits: + +```toml +[MaxPerTransaction] + MaxBuiltInCallsPerTx = 100 + MaxNumberOfTransfersPerTx = 250 + MaxNumberOfTrieReadsPerTx = 1500 +``` + +which translates to: +* each transaction can make a maximum 100 built-in functions calls, such as "get last nonce", "get last randomness", "ESDT unpause", "ESDT NFT create" and so on; +* each transaction can create maximum 250 transfers (as in produced smart contract results). For example, a call to "ESDT NFT transfer" will create 1 +smart contract results and a call to "multi ESDT NFT transfer" will consume the number of transfers defined in the function call; +* each transaction can access its data trie for a maximum 1500 get operations (can read a maximum 1500 values stored in the contract). + +These limits are subject to change, a new release can activate a new gas schedule at a defined epoch. + +--- + +### MultiversX tools on multiple platforms + +Generally speaking, the MultiversX tools should work on all platforms. However, platform-specific issues can occur. This page aims to be an entry point for troubleshooting platform-specific issues, in regards to the MultiversX toolset. + +:::note +If you discover a platform-specific issue, please let us known, on the [corresponding GitHub repository](/sdk-and-tools/overview). + +If you are blocked by a platform-specific issue, please consider using a **devcontainer**, as described [here](/sdk-and-tools/devcontainers). +::: + +## Linux + +All tools are expected to work on Linux. They are generally tested on Ubuntu-based distributions. + +## MacOS + +All tools are expected to work on MacOS. Though, even if the tests within the continuous integration flows cover MacOS, some inconveniences might still occur. + +### Apple Silicon (M1, M2) + +As of February 2024, the Node can only be compiled using the AMD64 version of Go. Thus, dependent tools, such as [localnets](/developers/setup-local-testnet), the [Chain Simulator](/sdk-and-tools/chain-simulator) etc. will rely on the [Apple Rosetta binary translator](https://en.wikipedia.org/wiki/Rosetta_(software)). + +:::note +As of February 2024, a native ARM64 version of the Node is in the works. This will allow the dependent tools to run natively on Apple Silicon. +::: + +If you'd like to manually build a Go tool that only works on AMD64 (for now), download & extract the Go toolchain for AMD64. For example: + +```sh +wget https://go.dev/dl/go1.20.7.darwin-amd64.tar.gz +tar -xf go1.20.7.darwin-amd64.tar.gz +``` + +Then, export `GOPATH` and `GOENV` variables into your shell: + +```sh +export GOPATH=/(path to extracted toolchain)/go +export GOENV=/(path to extracted toolchain)/go/env +``` + +Afterwards, build the tools, as needed. The obtained binaries will be AMD64, and they will run on your ARM64 system. + +## Windows + +Some tools can be difficult to install or run **directly on Windows**. For example, when building smart contracts, the encountered issues might be harder to tackle, especially for beginners. + +Therefore, we recommend using the [Windows Subsystem for Linux (WSL)](https://learn.microsoft.com/en-us/windows/wsl/install), instead of running the tools directly on Windows. + +--- + +### mxpy CLI cookbook + +## mxpy (Command Line Interface) + +**mxpy**, as a command-line tool, can be used to simplify and automate the interaction with the MultiversX network - it can be easily used in shell scripts, as well. It implements a set of **commands**, organized within **groups**. + +:::important +In order to migrate to the newer `mxpy`, please follow [the migration guide](https://github.com/multiversx/mx-sdk-py-cli/issues?q=label:migration). +::: + +The complete Command Line Interface is listed [**here**](https://github.com/multiversx/mx-sdk-py-cli/blob/main/CLI.md). Command usage and description are available through the `--help` or `-h` flags. + +For example: + +```sh +mxpy --help +mxpy tx --help +mxpy tx new --help +``` + +This page will guide you through the process of handling common tasks using **mxpy**. + + +## Managing dependencies + +Using `mxpy` you can either check if a dependency is installed or install a new dependency. + +To check if a dependency is installed you can use: + +```sh +mxpy deps check +``` + +To install a new dependency you can use: + +```sh +mxpy deps install +``` + +Both `mxpy deps check ` and `mxpy deps install ` use the `` as a positional argument. + +To find out which dependencies can be managed using `mxpy`, you can type one of the following commands to see the positional arguments it accepts: + +```sh +mxpy deps check -h +mxpy deps install -h +``` + +For example, in order to check if the `testwallets` dependency is installed, you would type: + +```sh +mxpy deps check testwallets +``` + +For example, to install the `testwallets` dependency, you can simply type the command: + +```sh +mxpy deps install testwallets +``` + +When installing dependencies, the `--overwrite` argument can be used to overwrite an existing version. + +For example, to overwrite the installation of a dependency, you can simply type the command: + +```sh +mxpy deps install testwallets --overwrite +``` + +## Configuring mxpy + +The configuration can be altered using the `mxpy config` command. + +:::tip +mxpy's configuration is stored in the file `~/multiversx-sdk/mxpy.json`. +::: + + +### Viewing the current mxpy configuration + +As of `mxpy v11`, we've introduced more configuration options, such as `environments` and `wallets`. + +In order to view the current configuration, one can issue the command `mxpy config dump`. Output example: + +```json +{ + "dependencies.testwallets.tag": "" +} +``` + +For viewing the default configuration the following command can be used: + +```sh +mxpy config dump --defaults +``` + + +### Updating the mxpy configuration + +One can alter the current configuration using the command `mxpy config set`. + +The default config contains the **log level** of the CLI. The default log level is set to `info`, but can be changed. The available values are: [debug, info, warning, error]. To set the log level, we can use the following command: +```sh +mxpy config set log_level debug +``` + +:::note +Previously, the `default_address_hrp` was also stored in the config. As of `mxpy v11` it has been moved to the `env` config, which we'll talk about in the next section. +::: + +### Configuring environments + +An `env config` is a named environment configuration that stores commonly used settings (like proxy url, hrp, and flags) for use with the **mxpy CLI**. Environments can be useful when switching between networks, such as Mainnet and Devnet. + +The values that are available for configuration and their default values are the following: +```json +{ + "default_address_hrp": "erd", + "proxy_url": "", + "explorer_url": "", + "ask_confirmation": "false", +} +``` + +#### Creating a new env config + +To create a new env config, we use the following command: + +```sh +mxpy config-env new +``` + +Additionally, `--template` can be used to create a config from an existing env config. After a new env config is created, it becomes the active one. You can then set its values using the `mxpy config-env set` command. + +#### Setting the default hrp + +The `default_address_hrp` might need to be changed depending on the network you plan on using (e.g Sovereign Chain). Most of the commands that might need the `address hrp` already provide a parameter called `--hrp` or `--address-hrp`, that can be explicitly set, but there are system smart contract addresses that cannot be changed by providing the parameter. If those addresses need to be changed, we can use the following command to set the `default hrp` that will be used throughout mxpy. Here we set the default hrp to `test`: + +```sh +mxpy config-env set default_address_hrp test --env test-env +``` + +:::note +Explicitly providing `--hrp` will **always** be used over the one fetched from the network or the `hrp` set in the config. +::: + +#### Setting the proxy url + +If `proxy_url` is set in the active environment, the `--proxy` argument is no longer required for the commands that need this argument. + +To set the proxy url, use the following command: + +```sh +mxpy config-env set proxy_url https://devnet-api.multiversx.com --env devnet +``` + +#### Setting the explorer url + +**mxpy** already knows the explorer urls for all three networks (Mainnet, Devnet, Testnet). This is particularly useful when running the CLI on custom networks where an explorer is also available. This key is not required to be present in the `env config` for the config to be valid. To set the explorer url use the following command: + +```sh +mxpy config-env set explorer_url https://url-to-explorer.com --env test-env +``` + +#### Setting the ask for confirmation flag + +If set to `true`, whenever sending a transaction, mxpy will display the transaction and will ask for your confirmation. To set the flag, use the following command: + +```sh +mxpy config-env set ask_confirmation true --env mainnet +``` + +#### Dumping the active env config + +We can see the values set in our active env config. To do so, we use the following command: + +```sh +mxpy config-env dump +``` + +#### Dumping all the available env configs + +We may have multiple env configs, maybe one for each network. To dump all the available env configs, we use the following command: + +```sh +mxpy config-env list +``` + +#### Deleting a value from the active env config + +We can also delete the key-value pairs saved in the env config. For example, let's say we want to delete the explorer url, so we use the following command: + +```sh +mxpy config-env delete explorer_url --env test-env +``` + +#### Getting a value from the active env config + +If we want to see just the value of a env config key from a specific environment, we can use the following command: + +```sh +mxpy config-env get --env mainnet +``` + +#### Deleting an env config + +To delete an env config, we use the following command: + +```sh +mxpy config-env remove +``` + +#### Switching to a different env config + +To switch to a new env config, we use the following command: + +```sh +mxpy config-env switch --env +``` + +You can manage multiple environment configurations with ease using `mxpy config-env`. This feature helps streamline workflows when working with multiple networks or projects. Use `mxpy config-env list` to see all available configs, and `switch` to quickly toggle between them. + +### Configuring wallets + +Wallets can be configured in the wallet config. Among all configured wallets, one must be set as the active wallet. This active wallet will be used by default in all mxpy commands, unless another wallet is explicitly provided using `--pem`, `--keystore`, or `--ledger`. Alternatively, the `--sender` argument can be used to specify a particular sender address from the address config (e.g. --sender alice). + +The values that are available for configuring wallets are the following: +```json +{ + "path": "", + "index": "", +} +``` + +Supported wallet types include PEM files and keystores. The CLI will determine the type based on the given path and act accordingly. If the wallet is of type `keystore`, you'll be prompted to enter the wallet's password. + +The `path` field represents the absolute path to the wallet. + +The `index` field represents the index that will be used when deriving the wallet from the secret key. This field is optional, the default index is `0`. + +#### Creating a new wallet config + +When configuring a new wallet we need to give it an alias. An alias is a user-defined name that identifies a configured wallet (e.g. alice, bob, dev-wallet). To create a new wallet config, we use the following command: + +```sh +mxpy config-wallet new +``` + +This command accepts the `--path` argument, so the path to the wallet can be set directly, without needing to call `mxpy config-wallet set` afterwards. + +#### Setting the wallet config fields + +For a config to be valid, we need to set at least the `path` field for an already created wallet alias. To do so, we use the following command: + +```sh +mxpy config-wallet set path absolute/path/to/pem/wallet.pem --alias alice +``` + +#### Getting the value of a field + +We can get the value of a field from an alias using the following command: + +```sh +mxpy config-wallet get path --alias alice +``` + +#### Dumping the active wallet config + +To view all the properties of the active address, use the following command: + +```sh +mxpy config-wallet dump +``` + +#### Dumping all configured wallets + +To view all the wallets configured, use the following command: + +```sh +mxpy config-wallet list +``` + +#### Switching to a different wallet + +We may have multiple wallets configured, so to switch between them, we use the following command: + +```sh +mxpy config-wallet switch --alias alice +``` + +#### Removing an address from the config + +We can remove an address from the config using the alias of the address and the following command: + +```sh +mxpy config-wallet remove --alias alice +``` + +## Estimating the Gas Limit for transactions + +mxpy (version 11.1.0 and later) can automatically estimate the required gas limit for transactions when a proxy URL is provided. The estimation works by simulating the transaction before sending it. + +While the estimation is generally accurate, it's recommended to add a safety margin to account for potential state changes. This can be done in two ways: + +1. Per transaction, using the `--gas-limit-multiplier` flag: + +```sh +mxpy tx new --gas-limit-multiplier 1.1 ... +``` + +2. As a global default setting in the config: + +```sh +mxpy config set gas_limit_multiplier 1.1 +``` + +A multiplier of 1.1 (10% increase) is typically sufficient for most transactions. + + +## Creating wallets + +There are a couple available wallet formats: + +- `raw-mnemonic` - secret phrase in plain text +- `keystore-mnemonic` - secret phrase, as a password-encrypted JSON keystore file +- `keystore-secret-key` - secret key (irreversibly derived from the secret phrase), as a password-encrypted JSON keystore file +- `pem` - secret key (irreversibly derived from the secret phrase), as a PEM file + +For this example, we are going to create a `keystore-mnemonic` wallet. + +Let's create a keystore wallet: + +```sh +mxpy wallet new --format keystore-mnemonic --outfile test_wallet.json +``` + +The wallet's mnemonic will appear, followed by a prompt to set a password for the file. Once you input the password and press "Enter", the file will be generated at the location specified by the `--outfile` argument. + + +## Converting a wallet + +As you have read above, there are multiple ways in which you can store your secret keys. + +To convert a wallet from a type to another you can use: + +```sh +mxpy wallet convert +``` + +:::info +Keep in mind that the conversion isn't always possible (due to irreversible derivations of the secret phrase): + +- `raw-mnemonic` can be converted to any other format +- `keystore-mnemonic` can be converted to any other format +- `keystore-secret-key` can only be converted to `pem` +- `pem` can only be converted to `keystore-secret-key` + +It's mandatory that you keep a backup of your secret phrase somewhere safe. +::: + +Let's convert the previously created `keystore-mnemonic` to a `PEM` wallet. We discourage the use of PEM wallets for storing cryptocurrencies due to their lower security level. However, they prove to be highly convenient and user-friendly for application testing purposes. + +To convert the wallet we type the following command: + +```sh +mxpy wallet convert --infile test_wallet.json --in-format keystore-mnemonic --outfile converted_wallet.pem --out-format pem +``` + +After being prompted to enter the password you've previously set for the wallet the new `.pem` file will be created. + +The command arguments can be found [here](https://github.com/multiversx/mx-sdk-py-cli/blob/main/CLI.md#walletconvert) or by typing: + +```sh +mxpy wallet convert --help +``` + + +## Building a smart contract + +In order to deploy a smart contract on the network, you need to build it first. For this purpose, [sc-meta](/developers/meta/sc-build-reference#how-to-basic-build) should be used. To learn more about `sc-meta`, please check out [this page](/developers/meta/sc-meta). + +The contract we will be using for this examples can be found [here](https://github.com/multiversx/mx-contracts-rs/tree/main/contracts/adder). + +Build a contract as follows: + +```sh +sc-meta all build --path +``` + +If our working directory is already the contract's directory we can skip the `--path` argument as by default the contract's directory is the _current working directory_. + +The generated `.wasm` file will be created in a directory called `output` inside the contract's directory. + + +## Deploying a smart contract + +After you've built your smart contract, it can be deployed on the network. + +For deploying a smart contract the following command can be used: + +```sh +mxpy contract deploy +``` + +To deploy a smart contract you have to send a transaction to the **Smart Contract Deploy Address** and that address is `erd1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq6gq4hu`, but you don't have to worry about setting the receiver of the transaction because the above command takes care of it. + +The `--bytecode` argument specifies the path to your previously-built contract. If you've built the smart contract using `mxpy`, the generated `.wasm` file will be in a folder called `output`. + +For example, if your contract is in `~/contracts/adder`, the generated bytecode file `adder.wasm` will be in `~/contracts/adder/output`. So, when providing the `--bytecode` argument the path should be `~/contracts/adder/output/adder.wasm`. + +The `mxpy contract deploy` command needs a multitude of other parameters that can be checked out [here](https://github.com/multiversx/mx-sdk-py-cli/blob/main/CLI.md#contractdeploy) or by simply typing the following: + +```sh +mxpy contract deploy --help +``` + +We will use a `.pem` file for the sake of simplicity but you can easily use any wallet type. + +Let's see a simple example: + +```sh +mxpy contract deploy --bytecode ~/contracts/adder/output/adder.wasm \ + --proxy=https://devnet-gateway.multiversx.com \ + --arguments 0 --gas-limit 5000000 \ + --pem=~/multiversx-sdk/testwallets/latest/users/alice.pem \ + --send +``` + +The `--proxy` is used to specify the url of the proxy and the `--chain` is used to select the network the contract will be deployed to. The chain ID and the proxy need to match for our transaction to be executed. We can't prepare a transaction for the Devnet (using `--chain D`) and send it using the mainnet proxy (https://gateway.multiversx.com). + +The `--arguments` is used in case our contract needs any arguments for the initialization. We know our `adder` needs a value to start adding from, so we set that to `0`. + +The `--gas-limit` is used to set the gas we are willing to pay so our transaction will be executed. 5 million gas is a bit too much because our contract is very small and simple, but better to be sure. In case our transaction doesn't have enough gas the network will not execute it, saying something like `Insufficient gas limit`. + +The `--pem` argument is used to provide the sender of the transaction, the payer of the fee. The sender will also be the owner of the contract. The nonce of the sender is fetched from the network if the `--proxy` argument is provided. The nonce can also be explicitly set using the `--nonce` argument. If the nonce is explicitly set, mxpy will not fetch the nonce from the network. + + +### Deploying a smart contract providing the ABI file + +For functions that have complex arguments, we can use the ABI file generated when building the contract. The ABI can be provided using the `--abi` argument. When using the ABI, and only when using the ABI, the arguments should be written in a `json` file and should be provided via the `--arguments-file` argument. + +For this example, we'll use the [multisig contract](https://github.com/multiversx/mx-contracts-rs/tree/main/contracts/multisig). + +First, we'll prepare the file containing the constructors arguments. We'll refer to this file as `deploy_multisig_arguments.json`. The constructor requires two arguments, the first is of type `u32` and the second one is of type `variadic
`. All the arguments in this file **should** be placed inside a list. The arguments file should look like this: + +```json +[ + 2, + [ + { + "bech32": "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + }, + { + "hex": "8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8" + } + ] +] +``` + +Let's go a bit through our file and see why it looks like this. First, as mentioned above, we have to place all the arguments inside a list. Then, the value `2` corresponds to the type `u32`. After that, we have another list that corresponds to the type `variadic`. Inside this list, we need to insert our addresses. For `mxpy`to encode addresses properly, we need to provide the address values inside a dictionary that can contain two keys: we can provide the address as the `bech32` representation or as the `hex encoded` public key. + +After finishing the arguments file, we can run the following command to deploy the contract: + +```sh +mxpy contract deploy --bytecode ~/contracts/multisig/output/multisig.wasm \ + --proxy=https://devnet-gateway.multiversx.com \ + --abi ~/contracts/multisig/output/multisig.abi.json \ + --arguments-file deploy_multisig_arguments.json \ + --gas-limit 500000000 \ + --pem=~/multiversx-sdk/testwallets/latest/users/alice.pem \ + --send +``` + + +## Calling the Smart Contract + +After deploying our smart contract we can start interacting with it. The contract has a function called `add()` that we can call and it will increase the value stored in the contract with the value we provide. + +To call a function we use the `mxpy contract call` command. Here's an example of how we can do that: + +```sh +mxpy contract call erd1qqqqqqqqqqqqqpgq3zrpqj3sulnc9xq95sljetxhf9s07pqtd8ssfkxjv4 \ + --pem=~/multiversx-sdk/testwallets/latest/users/alice.pem \ + --proxy=https://devnet-gateway.multiversx.com --chain D \ + --function add --arguments 5 --gas-limit 1000000 \ + --send +``` + +The positional argument is the contract address that we want to interact with. The `--pem`, `--proxy` and `--chain` arguments are used the same as above in the deploy transaction. + +Using the `--function` argument we specify the function we want to call and with the `--arguments` argument we specify the value we want to add. We set the gas we are willing to pay for the transaction and finally we send the transaction. + + +### Calling the smart contract providing the ABI file + +Same as we did for deploying the contract, we can call functions by providing the ABI file and the arguments file. + +Since we deployed the [multisig contract](https://github.com/multiversx/mx-contracts-rs/tree/main/contracts/multisig), we'll call the `proposeTransferExecute` endpoint. + +First, we'll prepare the file containing the endpoints arguments. We'll refer to this file as `call_multisig_arguments.json`. The `proposeTransferExecute` endpoint requires four arguments, the first is of type `Address`, the second one is of type `BigUInt`, the third is of type `Option` and the fourth is of type `variadic`. All the arguments in this file **should** be placed inside a list. The arguments file should look like this: + +```json +[ + { + "bech32": "erd1qqqqqqqqqqqqqpgqs63rcpahnwtjnedj5y6uuqh096nzf75gczpsc4fgtu" + }, + 1000000000000000000, + 5000000, + [ + { + "hex": "616464403037" + } + ] +] +``` + +Let's go a bit through our file and see why it looks like this. First, as mentioned above, we have to place all the arguments inside a list. Then, the contract expects an address, so we provide the `bech32` representation. After that, we have a `BigUInt` value that we can provide as a number. The third value is `Option`, so we provide it as a number, as well. In case we wanted to skip this value, we could've simply used `0`. The last parameter is of type `variadic`. Because it's a variadic value, we have to place the arguments inside a list. Since we can't write bytes, we `hex encode` the value and place it in a dictionary containing the key-value pair `"hex": ""`, same as we did above for the address. + +After finishing the arguments file, we can run the following command to call the endpoint: + +```sh +mxpy contract call erd1qqqqqqqqqqqqqpgqjsg84gq5e79rrc2rm5ervval3jrrfvvfd8sswc6xjy \ + --proxy=https://devnet-gateway.multiversx.com \ + --abi ~/contracts/multisig/output/multisig.abi.json \ + --arguments-file call_multisig_arguments.json \ + --function proposeTransferExecute + --gas-limit 500000000 \ + --pem=~/multiversx-sdk/testwallets/latest/users/alice.pem \ + --send +``` + + +## Querying the Smart Contract + +Querying a contract is done by calling a so called `view function`. We can get data from a contract without sending a transaction to the contract, basically without spending money. + +As you know, our contract has a function called `add()` that we previously called, and a `view function` called `getSum()`. Using this `getSum()` function we can see the value that is currently stored in the contract. + +If you remember, when we deployed the contract we passed the value `0` as a contract argument, this means the contract started adding from `0`. When calling the `add()` function we used the value `5`. This means that now if we call `getSum()` we should get the value `5`. To do that, we use the `mxpy contract query` command. Let's try it! + +```sh +mxpy contract query erd1qqqqqqqqqqqqqpgq3zrpqj3sulnc9xq95sljetxhf9s07pqtd8ssfkxjv4 \ + --proxy https://devnet-gateway.multiversx.com \ + --function getSum +``` + +We see that `mxpy` returns our value as a base64 string, as a hex number and as a integer. Indee, we see the expected value. + + +### Querying the smart contract providing the ABI file + +We'll call the `signed` readonly endpoint of the [multisig contract](https://github.com/multiversx/mx-contracts-rs/tree/main/contracts/multisig). This endpoint accepts two arguments: the first is the address, and the second is the proposal ID, which will be used to verify if the address has signed the proposal. The endpoint returns a `boolean` value, `true` if the address has signed the proposal and `false` otherwise. + +Let's prepare the arguments file. The first argument is of type `Address` and the second one is of type `u32`, so our file looks like this: + +```json +[ + { + "bech32": "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx" + }, + 1 +] +``` + +As above, we encapsulate the address in a dictionary and the `u32` value is simply a number. We'll refer to this file as `query_multisig_arguments.json`. + +After preparing the file, we can run the following command: + +```sh +mxpy contract query erd1qqqqqqqqqqqqqpgqjsg84gq5e79rrc2rm5ervval3jrrfvvfd8sswc6xjy \ + --proxy https://devnet-gateway.multiversx.com \ + --function signed \ + --abi ~/contracts/multisig/output/multisig.abi.json \ + --arguments-file query_multisig_arguments.json +``` + + +## Upgrading a Smart Contract + +In case there's a new release of your Smart Contract, or perhaps you've patched a possible vulnerability you can upgrade the code of the Smart Contract deployed on the network. + +We've modified our adder contract to add `1` to every value added to the contract. Now every time the `add()` function is called will add the value provided with `1`. In order to do that we access the source code and navigate to the `add()` endpoint. We can see that `value` is added to `sum` each time the endpoint is called. Modify the line to look something like this `self.sum().update(|sum| *sum += value + 1u32);` + +Before deploying the contract we need to build it again to make sure we are using the latest version. We then deploy the newly built contract, then we call it and query it. + +First we build the contract: + +```sh +sc-meta all build +``` + +Then we upgrade the contract by running: + +```sh +mxpy contract upgrade erd1qqqqqqqqqqqqqpgq3zrpqj3sulnc9xq95sljetxhf9s07pqtd8ssfkxjv4 \ + --bytecode ~/contracts/adder/output/adder.wasm \ + --proxy=https://devnet-gateway.multiversx.com --chain D \ + --arguments 0 --gas-limit 5000000 \ + --pem=~/multiversx-sdk/testwallets/latest/users/alice.pem \ + --send +``` + +We provide as a positional argument the contract's address that we want to upgrade, in our case the previously deployed adder contract. The `--bytecode` is used to provide the new code that will replace the old code. We also set the `--arguments` to `0` as we didn't change the constructor and the contract will start counting from `0` again. The rest of the arguments you know from all the previous operations we've done. + +As shown above, we can also upgrade the contract by providing the ABI file and the arguments file: + +```sh +mxpy contract upgrade erd1qqqqqqqqqqqqqpgq3zrpqj3sulnc9xq95sljetxhf9s07pqtd8ssfkxjv4 \ + --bytecode ~/contracts/adder/output/adder.wasm \ + --proxy=https://devnet-gateway.multiversx.com --chain D \ + --gas-limit 5000000 \ + --pem=~/multiversx-sdk/testwallets/latest/users/alice.pem \ + --abi=~/contracts/multisig/output/multisig.abi.json, + --arguments-file=upgrade_arguments.json + --send +``` + +Now let's add `5` to the contract one more time. We do so by running the following: + +```sh +mxpy contract call erd1qqqqqqqqqqqqqpgq3zrpqj3sulnc9xq95sljetxhf9s07pqtd8ssfkxjv4 \ + --pem=~/multiversx-sdk/testwallets/latest/users/alice.pem \ + --proxy=https://devnet-gateway.multiversx.com --chain D \ + --function add --arguments 5 --gas-limit 1000000 \ + --send +``` + +Now, if we query the contract we should see the value `6`. We added `5` in the contract but modified the contract code to add `1` to every value. Let's see! + +```sh +mxpy contract query erd1qqqqqqqqqqqqqpgq3zrpqj3sulnc9xq95sljetxhf9s07pqtd8ssfkxjv4 --proxy https://devnet-gateway.multiversx.com --function getSum +``` + +We see that we indeed got the value `6`. Our upgrade was successful. + + +## Verifying a smart contract + +Verifying a smart contract means ensuring that the contract deployed on the network matches a specific version of the original source code. That is done by an external service that, under the hood, performs a reproducible build of the given contract and compares the resulting bytecode with the one deployed on the network. + +To learn more about reproducible builds, please follow [**this page**](/developers/reproducible-contract-builds). If you'd like to set up a Github Workflow that performs a reproducible build of your smart contract, follow the examples in [**this repository**](https://github.com/multiversx/mx-contracts-rs). + +The command used for verifying contracts is: + +```sh +mxpy contract verify +``` + +Let's see an example: + +```sh +export CONTRACT_ADDRESS="erd1qqqqqqqqqqqqqpgq6eynj8xra5v87qqzpjhc5fnzzh0fqqzld8ssqrez2g" + +mxpy --verbose contract verify ${CONTRACT_ADDRESS} \ + --packaged-src=adder-0.0.0.source.json \ + --verifier-url="https://devnet-play-api.multiversx.com" \ + --docker-image="multiversx/sdk-rust-contract-builder:v8.0.1" \ + --pem=~/multiversx-sdk/testwallets/latest/users/alice.pem +``` + +:::info +The account that triggers the code verification process must be the owner of the contract. +::: + +:::info +The _packaged source_ passed as `--packaged-src` can be obtained either from [the Github Workflows for reproducible builds](https://github.com/multiversx/mx-contracts-rs/tree/main/.github/workflows) set up on your own repository, or from locally invoking a reproducible build, as depicted [here](https://docs.multiversx.com/developers/reproducible-contract-builds/#reproducible-build-using-mxpy). +::: + + +## Creating and sending transactions + +To create a new transaction we use the `mxpy tx new` command. Let's see how that works: + +```sh +mxpy tx new --pem ~/multiversx-sdk/testwallets/latest/users/alice.pem \ + --receiver erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx \ + --gas-limit 50000 --value 1000000000000000000 \ + --proxy https://devnet-gateway.multiversx.com --chain D \ + --send +``` + +That's it! As easy as that. We sent a transaction from Alice to Bob. We choose the receiver of our transaction using the `--receiver` argument and set the gas limit to `50000` because that is the gas cost of a simple move balance transaction. Notice we used the `--value` argument to pass the value that we want to transfer but we passed in the denomintated value. We transferred 1 eGLD (1 \* 10^18). We then specify the proxy and the chain ID for the network we want to send our transaction to and use the `--send` argument to broadcast it. + +In case you want to save the transaction you can also provide the `--outfile` argument and a `json` file containing the transaction will be saved at the specified location. If you just want to prepare the transaction without broadcasting it simply remove the `--send` argument. + + +## Guarded transactions + +If your address is guarded, you'll have to provide some additional arguments because your transaction needs to be co-signed. + +The first extra argument we'll need is the `--guardian` argument. This specifies the guardian address of our address. Then, if our account is guarded by a service like our trusted co-signer service we have to provide the `--guardian-service-url` which specifies where the transaction is sent to be co-signed. + +Keep in mind that **mxpy** always calls the `/sign-transaction` endpoint of the `--guardian-service-url` you have provided. Another argument we'll need is `--guardian-2fa-code` which is the code generated by an external authenticator. + +Each guarded transaction needs an additional `50000` gas for the `gasLimit`. The `version` field needs to be set to `2`. The `options` field needs to have the second least significant bit set to "1". + +:::note +Here are the urls to our hosted co-signer services: + +- Mainnet: [https://tools.multiversx.com/guardian](https://tools.multiversx.com/guardian) +- Devnet: [https://devnet-tools.multiversx.com/guardian](https://devnet-tools.multiversx.com/guardian) +- Testnet: [https://testnet-tools.multiversx.com/guardian](https://testnet-tools.multiversx.com/guardian) + +::: + +```sh +mxpy tx new --pem ~/multiversx-sdk/testwallets/latest/users/alice.pem \ + --receiver erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx \ + --gas-limit 200000 --value 1000000000000000000 \ + --proxy https://devnet-gateway.multiversx.com --chain D \ + --guardian erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8 \ + --guardian-service-url https://devnet-tools.multiversx.com/guardian \ + --guardian-2fa-code 123456 --version 2 --options 2 + --send +``` + +If your address is guarded by another wallet, you'll still need to provide the `--guardian` argument and the guardian's wallet that will co-sign the transaction, but you don't need to provide the 2fa code and the service url. You can provide the guardian's wallet using one of the following arguments: `--guardian-pem`, `--guardian-keyfile`, or `--guardian-ledger`. + + +## Relayed transactions V3 + +Relayed transactions are transactions with the fee paid by a so-called relayer. In other words, if a relayer is willing to pay for a transaction, it is not mandatory for the sender to have any EGLD for fees. To learn more about relayed transactions check out [this page](/developers/relayed-transactions/). + +In this section we'll see how we can send `Relayed V3` transactions using `mxpy`. For a more detailed look on `Relayed V3` transactions, take a look [here](/developers/relayed-transactions/#relayed-transactions-version-3). For these kind of transactions two new transaction fields were introduced, `relayer` and `relayerSignature`. In this example we'll see how we can create the relayed transaction. + +For this, a new command `mxpy tx relay` has been added. The command can be used to relay a previously signed transaction. The saved transaction can be loaded from a file using the `--infile` argument. + +There are two options when creating the relayed transaction: +1. Create the relayed transaction separately. (Sender signature and relayer signature are added by different entities.) +2. Create the complete relayed transaction. (Both signatures are added by the same entity.) + +### Creating the inner transaction + +The inner transaction is any regular transaction, with the following notes: +- relayer address must be added +- extra 50000 (base cost) gas must be added for the relayed operation. For more details on how the gas is computed, check out [this page](/developers/relayed-transactions/#relayed-transactions-version-3). + +This can be generated through `mxpy tx new` command. A new argument `--relayer` has been added for this feature. + +```sh +mxpy tx new --pem ~/multiversx-sdk/testwallets/latest/users/alice.pem \ + --receiver erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx \ + --gas-limit 100000 --value 1000000000000000000 \ + --proxy https://devnet-gateway.multiversx.com --chain D \ + --relayer erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8 \ + --outfile inner_tx.json +``` + +After creating the inner transaction, we are ready to create the relayed transaction. + +### Creating the relayed transaction + +We can create the relayed transaction by running the following command: + +```sh +mxpy tx relay --relayer-pem ~/multiversx-sdk/testwallets/latest/users/carol.pem \ + --proxy https://devnet-gateway.multiversx.com --chain D \ + --infile inner_tx.json \ + --send +``` + +### Creating the relayed transaction in one step + +This can be done through `mxpy tx new` command, as follows: +```sh +mxpy tx new --pem ~/multiversx-sdk/testwallets/latest/users/alice.pem \ + --receiver erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx \ + --relayer erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8 \ + --relayer-pem ~/multiversx-sdk/testwallets/latest/users/carol.pem \ + --gas-limit 100000 --value 1000000000000000000 \ + --proxy https://devnet-gateway.multiversx.com --chain D \ + --send +``` + + +## Using the Ledger hardware wallet + +You can sign any transaction (regular transfers, smart contract deployments and calls) using a Ledger hardware wallet by leveraging the `--ledger` command-line argument. + +First, connect your device to the computer, unlock it and open the MultiversX Ledger app. + +Then, you can perform a trivial connectivity check by running: + +```sh +mxpy ledger version +``` + +The output should look like this: + +```sh +MultiversX App version: ... +``` + +Another trivial check is to ask the device for the (first 10) MultiversX addresses it manages: + +```sh +mxpy ledger addresses +``` + +The output should look like this: + +```sh +account index = 0 | address index = 0 | address: erd1... +account index = 0 | address index = 1 | address: erd1... +account index = 0 | address index = 2 | address: erd1... +... +``` + +Now let's sign and broadcast a transaction (EGLD transfer): + +```sh +mxpy tx new --proxy https://devnet-gateway.multiversx.com \ + --receiver erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th \ + --gas-limit 50000 --value 1000000000000000000 \ + --ledger \ + --send +``` + +By default, the first MultiversX address managed by the device is used as the sender (signer) of the transaction. In order to select a different address, you can use the `--ledger-address-index` CLI parameter: + +```sh +mxpy tx new --proxy https://devnet-gateway.multiversx.com \ + --receiver erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th \ + --gas-limit 50000 --value 1000000000000000000 \ + --ledger --ledger-address-index=42 \ + --send +``` + +:::info +For MultiversX, **the account index should always be `0`**, while the address index is allowed to vary. Therefore, you should not use the `--ledger-account-index` CLI parameter (it will be removed in a future release). +::: + +Now let's deploy a smart contract using the Ledger: + +```sh +mxpy contract deploy --proxy=https://devnet-gateway.multiversx.com \ + --bytecode=adder.wasm --gas-limit=5000000 \ + --ledger --ledger-address-index=42 \ + --send +``` + +Then, perform a contract call: + +```sh +mxpy contract call erd1qqqqqqqqqqqqqpgqwwef37kmegph97egvvrxh3nccx7xuygez8ns682zz0 \ + --proxy=https://devnet-gateway.multiversx.com \ + --function add --arguments 42 --gas-limit 5000000 \ + --ledger --ledger-address-index=42 \ + --send +``` + +:::note +As of October 2023, on Windows (or WSL), you might encounter some issues when trying to use Ledger in `mxpy`. +::: + +## Interacting with the Multisig Smart Contract + +As of `mxpy v11`, interacting with Multisig contracts has become a lot easier because a dedicated command group called `mxpy multisig` has been added. We can deploy a multisig contract, create proposals and query the contract. For a full list of all the possible actions, run the following command: + +```sh +mxpy multisig -h +``` + +#### Deploying a multisig contract + +```sh +mxpy multisig deploy \ + --pem ~/multiversx-sdk/testwallets/latest/users/alice.pem \ + --bytecode path/to/multisig.wasm \ + --abi path/to/multisig.abi.json + --quorum 2 + --board-members erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx + --proxy=https://devnet-gateway.multiversx.com \ + --gas-limit 100000000 \ + --send +``` + +#### Creating a new proposal + +```sh +mxpy multisig add-board-member --contract erd1qqqqqqqqqqqqqpgq2ukrsg73nwgu3uz6sp8vequuyrhtv2akd8ssyrg7wj \ + --abi path/to/multisig.abi.json + --board-member erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8 \ + --pem ~/multiversx-sdk/testwallets/latest/users/alice.pem \ + --proxy=https://devnet-gateway.multiversx.com \ + --gas-limit 10000000 \ + --send +``` + +#### Querying the contract + +```sh +mxpy multisig get-proposers --contract erd1qqqqqqqqqqqqqpgq2ukrsg73nwgu3uz6sp8vequuyrhtv2akd8ssyrg7wj \ + --abi path/to/multisig.abi.json + --proxy=https://devnet-gateway.multiversx.com +``` + +## Interacting with the Governance Smart Contract + +As of `mxpy v11`, mxpy allows for easier interaction with the Governance smart contract. We can create a new governance proposal, vote for a proposal and query the contract. For a full list of the available commands, run the following command: + +```sh +mxpy governance -h +``` + +#### Creating a new proposal + +```sh +mxpy governance propose \ + --commit-hash 30118901102b0bef11d675f4327565ae5246eeb5 \ + --start-vote-epoch 1000 \ + --end-vote-epoch 1010 \ + --pem ~/multiversx-sdk/testwallets/latest/users/alice.pem \ + --proxy=https://devnet-gateway.multiversx.com \ + --gas-limit 100000000 \ + --send +``` + +#### Voting for a proposal + +```sh +mxpy governance vote \ + --proposal-nonce 1 \ + --vote yes \ + --pem ~/multiversx-sdk/testwallets/latest/users/alice.pem \ + --proxy=https://devnet-gateway.multiversx.com \ + --gas-limit 100000000 \ + --send +``` + +#### Querying the contract + +```sh +mxpy governance get-proposal-info \ + --proposal-nonce 1 \ + --proxy=https://devnet-gateway.multiversx.com +``` + + +## Token Management Operations + +User can now perform token management operations, such as issuing fungible tokens, issuing semi-fungible tokens, creating NFTs and more, directly via `mxpy`. For a full list of available commands type: + +```sh +mxpy token -h +``` + +#### Issue a fungible token + +```sh +mxpy token issue-fungible \ + --pem ~/multiversx-sdk/testwallets/latest/users/alice.pem \ + --token-name test --token-ticker TEST \ + --initial-supply 1000000000000 \ + --num-decimals 6 \ + --proxy=https://devnet-gateway.multiversx.com \ + --send +``` + +#### Pause a token + +```sh +mxpy token pause \ + --token-identifier TEST-123456 \ + --pem ~/multiversx-sdk/testwallets/latest/users/alice.pem \ + --proxy=https://devnet-gateway.multiversx.com \ + --send +``` + +#### Set special roles on fungible tokens + +```sh +mxpy token set-special-role-fungible \ + --token-identifier TEST-123456 \ + --user erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx \ + --local-mint \ + --local-burn \ + --esdt-transfer-role \ + --pem ~/multiversx-sdk/testwallets/latest/users/alice.pem \ + --proxy=https://devnet-gateway.multiversx.com \ + --send +``` + +--- + +### NestJS SDK + +MultiversX NestJS Microservice Utilities + +**sdk-nestjs** contains a set of utilities commonly used in the MultiversX microservices ecosystem. + +| Package | Source code | Description | +|--------------------------------------------------------------------|-------------------------------------------------------|----------------------------------------------------------------------------| +| [sdk-nestjs](https://www.npmjs.com/package/@multiversx/sdk-nestjs) | [Github](https://github.com/multiversx/mx-sdk-nestjs) | A set of utilities commonly used in the MultiversX Microservice ecosystem. | + +:::tip +When developing microservices, we recommend starting from the **microservice-template** as it integrates off-the-shelf features like: public & private endpoints, cache warmer, transactions processor, queue worker +::: + +| Source code | Description | +|----------------------------------------------------------------------------|------------------------------------------------------------------------------------------| +| [microservice-template](https://github.com/multiversx/mx-template-service) | REST API facade template for microservices that interact with the MultiversX blockchain. | + + +## Packages + +The following table contains the NPM packages that are included inside the NestJS SDK: + +| Package | NPM | Description + additional docs | +|-----------------------|------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------| +| sdk-nestjs-common | [@multiversx/sdk-nestjs-common](https://www.npmjs.com/package/@multiversx/sdk-nestjs-common) | Common functionalities to be used in MultiversX microsevices. | +| sdk-nestjs-auth | [@multiversx/sdk-nestjs-auth](https://www.npmjs.com/package/@multiversx/sdk-nestjs-auth) | Native Auth functionalities to be used for securily handle sessions. | +| sdk-nestjs-http | [@multiversx/sdk-nestjs-http](https://www.npmjs.com/package/@multiversx/sdk-nestjs-http) | HTTP requests handling utilities | +| sdk-nestjs-monitoring | [@multiversx/sdk-nestjs-monitoring](https://www.npmjs.com/package/@multiversx/sdk-nestjs-monitoring) | Microservices monitoring helpers. | +| sdk-nestjs-elastic | [@multiversx/sdk-nestjs-elastic](https://www.npmjs.com/package/@multiversx/sdk-nestjs-elastic) | Elasticsearch interactions helpers. | +| sdk-nestjs-redis | [@multiversx/sdk-nestjs-redis](https://www.npmjs.com/package/@multiversx/sdk-nestjs-redis) | Redis interactions helpers. | +| sdk-nestjs-rabbitmq | [@multiversx/sdk-nestjs-rabbitmq](https://www.npmjs.com/package/@multiversx/sdk-nestjs-rabbitmq) | RabbitMQ interactions helpers. | +| sdk-nestjs-cache | [@multiversx/sdk-nestjs-cache](https://www.npmjs.com/package/@multiversx/sdk-nestjs-cache) | Common cache operations utilities. [docs](/sdk-and-tools/sdk-nestjs/sdk-nestjs-cache) | + +--- + +### NestJS SDK Auth utilities + +NPM Version + + +## MultiversX NestJS Microservice Native Authentication Utilities + +This package contains a set of utilities commonly used for authentication purposes in the MultiversX Microservice ecosystem. + The package relies on [@multiversx/sdk-native-auth-server](https://www.npmjs.com/package/@multiversx/sdk-native-auth-server) for validating access tokens signed by MultiversX wallets. + + +## Installation + + `sdk-nestjs-auth` is delivered via **npm** and it can be installed as follows: + +```bash + npm install @multiversx/sdk-nestjs-auth + ``` + +## Obtaining a Native Auth token + +This package validates a payload signed by a MultiversX wallet. You can use the MultiversX Utils website to get a token you can use for testing. + + ![image](https://github.com/multiversx/mx-sdk-nestjs/assets/6889483/374a4e3a-f7d3-4bd9-a212-a8615c89ab53) + +1. Navigate to [https://utils.multiversx.com/auth](https://utils.multiversx.com/auth) and choose the desired network from the select located in the upper right corner of the page +2. Click the **Generate** button +3. Select a wallet you prefer to use from the modal dialog, and give it access to the page +4. **Done!** You can now copy the token and use it as a Bearer token in your requests. + +To use it in your requests, you need to have an `Authorization` header with the value : + `Bearer `. + +You also need to add an `origin` header with the value `https://utils.multiversx.com`. +*Note: these steps are only needed while testing. In production, a frontend application will handle token generation* + +## Utility + +The package provides a series of [NestJS Guards](https://docs.nestjs.com/guards) that can be used for easy authorization on endpoints in your application. + It also provides some [NestJS Decorators](https://docs.nestjs.com/custom-decorators) that expose the decoded information found in the access token. + +## Configuration + +The authentication guards need 2 parameters on instantiation. + The fist parameter needs to be an instance of a class implementing the `ErdnestConfigService` interface. +The second one, needs to be an instance of a [Caching service](https://www.npmjs.com/package/@multiversx/sdk-nestjs-cache) + +```typescript + import { Injectable } from "@nestjs/common"; + import { ApiConfigService } from "./api.config.service"; + import { ErdnestConfigService } from "@multiversx/sdk-nestjs-common"; + + @Injectable() + export class SdkNestjsConfigServiceImpl implements ErdnestConfigService { + constructor( + private readonly apiConfigService: ApiConfigService, + ) { } + + getSecurityAdmins(): string[] { + return this.apiConfigService.getSecurityAdmins(); + } + + getJwtSecret(): string { + return this.apiConfigService.getJwtSecret(); + } + + getApiUrl(): string { + return this.apiConfigService.getApiUrl(); + } + + getNativeAuthMaxExpirySeconds(): number { + return this.apiConfigService.getNativeAuthMaxExpirySeconds(); + } + + getNativeAuthAcceptedOrigins(): string[] { + return this.apiConfigService.getNativeAuthAcceptedOrigins(); + } + } + ``` + +You can register it as a provider, and the DI mechanism of NestJS will handle instantiation for you. + +```typescript + import { Module } from '@nestjs/common'; + import { ERDNEST_CONFIG_SERVICE } from "@multiversx/sdk-nestjs-common"; + + @Module({ + providers: [ + { + provide: ERDNEST_CONFIG_SERVICE, + useClass: SdkNestjsConfigServiceImpl, + }, + ], + }) + export class AppModule {} + ``` + +## Using the Auth Guards + +NestJS guards can be controller-scoped, method-scoped, or global-scoped. Setting up a guard from the package is done through the `@UseGuards` decorator from the `@nestjs/common` package. + +### Native Auth Guard + +`NativeAuthGuard` performs validation of the block hash, verifies its validity, as well as origin verification on the access token. + +```typescript + import { NativeAuthGuard } from "@multiversx/sdk-nestjs-auth"; + + @Controller('projects') + @UseGuards(NativeAuthGuard) + export class ProjectsController { + // your methods... + } + ``` + +In the example above the `NativeAuthGuard` is controller-scoped. This means that all of the methods from `ProjectsController` will be protected by the guard. + +```typescript + import { NativeAuthGuard } from "@multiversx/sdk-nestjs-auth"; + + @Controller('projects') + export class ProjectsController { + @Get() + async getAll() { + return this.projectsService.getAll(); + } + + @Post() + @UseGuards(NativeAuthGuard) + async createProject(@Body() createProjectDto: CreateProjectDto) { + return this.projectsService.create(createProjectDto); + } + } + ``` + +In this case, the guard is method-scoped. Only `createProject` benefits from the native auth checks. + +### Native Auth Admin Guard + + `NativeAuthAdminGuard` allows only specific addresses to be authenticated. The addresses are defined in the [config](#configuration) file and are passed to the guard via the ErdnestConfigService. + +*This guard cannot be used by itself. It always has to be paired with a `NativeAuthGuard`* + +```typescript + import { NativeAuthGuard, NativeAuthAdminGuard } from "@multiversx/sdk-nestjs-auth"; + + @Controller('admin') + @UseGuards(NativeAuthGuard, NativeAuthAdminGuard) + export class AdminController { + // your methods... + } + ``` + +### JWT Authenticate Guard + + `JwtAuthenticateGuard` performs validation of a traditional [JSON web token](https://datatracker.ietf.org/doc/html/rfc7519). The usage is exactly the same as for the native auth guards. + +```typescript + import { JwtAuthenticateGuard } from "@multiversx/sdk-nestjs-auth"; + + @Controller('users') + @UseGuards(JwtAuthenticateGuard) + export class UsersController { + // your methods... + } + ``` + + +### JWT Admin Guard + + `JwtAdminGuard` relies on the same mechanism, only specific addresses can be authenticated. The addresses are defined in the [config](#configuration) file and are passed to the guard via the ErdnestConfigService. + +*There is one caveat: when creating the JWT, the client must include an `address` field in the payload, before signing it.* + +```typescript + import { JwtAuthenticateGuard, JwtAdminGuard } from "@multiversx/sdk-nestjs-auth"; + + @Controller('admin') + @UseGuards(JwtAuthenticateGuard, JwtAdminGuard) + export class AdminController { + // your methods... + } + ``` + +### Jwt Or Native Auth Guard + + `JwtOrNativeAuthGuard` guard will authorize requests containing either JWT or a native auth access token. The package will first look for a valid JWT. If that fails, it will look for a valid native auth access token. + +### Global-scoped guards + + ```typescript + const app = await NestFactory.create(AppModule); + app.useGlobalGuards(new NativeAuthGuard(new SdkNestjsConfigServiceImpl(apiConfigService), cachingService)); + ``` + +## Using the Auth Decorators + +The package exposes 3 decorators : `NativeAuth`, `Jwt` and `NoAuth` + +### NativeAuth Decorator + +The `NativeAuth` decorator accepts a single parameter. In can be one of the following values : + +- `issued` - block timestamp +- `expires` - expiration time +- `address` - address that signed the access token +- `origin` - URL of the page that generated the token +- `extraInfo` - optional arbitrary data + +Below is an example showing how to use the decorator to extract the signers address : + + ```typescript + import { NativeAuthGuard, NativeAuth } from "@multiversx/sdk-nestjs-auth"; + import { Controller, Get, UseGuards } from "@nestjs/common"; + + @Controller() + export class AuthController { + @Get("/auth") + @UseGuards(NativeAuthGuard) + authorize(@NativeAuth('address') address: string): string { + console.log(`Access granted for address ${address}`); + return address; + } + } + ``` + +### Jwt Decorator + +The `Jwt` decorator works just like `NativeAuth`. The fields accessible inside it are dependent on the client that created the token, and are out of scope for this documentation. + +### No Auth Decorator + +The `NoAuth` decorator can be used to skip authorization on a specific method. This is useful when a guard is scoped globally or at the controller level. + + ```typescript + import { NoAuth } from "@multiversx/sdk-nestjs-auth"; + import { Controller, Get, UseGuards } from "@nestjs/common"; + + @Controller() + export class PublicController { + @NoAuth() + @Get("/public-posts") + listPosts() { + // .... + } + } + ``` + +--- + +### NestJS SDK Cache utilities + +NPM Version + + +## MultiversX NestJS Microservice Cache Utilities + +This package contains a set of utilities commonly used for caching purposes in the MultiversX Microservice ecosystem. + + +## Installation + +`sdk-nestjs-cache` is delivered via **npm** and it can be installed as follows: + +```bash +npm install @multiversx/sdk-nestjs-cache +``` + + +## Utility + +The package exports two services: an **in-memory cache service** and **remote cache service**. + + +## Table of contents + +- [In Memory Cache](#in-memory-cache) - super fast in-memory caching system based on [LRU cache](https://www.npmjs.com/package/lru-cache) +- [Redis Cache](#redis-cache) - Caching system based on [Redis](https://www.npmjs.com/package/@multiversx/sdk-nestjs-redis) +- [Cache Service](#cache-service) - MultiversX caching system which combines in-memory and Redis cache, forming a two-layer caching system + + +## In memory cache + +In memory cache, available through `InMemoryCacheService`, is used to read and write data from and into the memory storage of your Node.js instance. + +*Note that if you have multiple instances of your application you must sync the local cache across all your instances.* + +Let's take as an example a `ConfigService` that loads some non-crucial configuration from the database and can be cached for 10 seconds. + +Usage example: + +```typescript +import { Injectable } from '@nestjs/common'; +import { InMemoryCacheService } from '@multiversx/sdk-nestjs-cache'; + +@Injectable() +export class ConfigService { + constructor( + private readonly inMemoryCacheService: InMemoryCacheService + ){} + + async loadConfiguration(){ + return await this.inMemoryCacheService.getOrSet( + 'configurationKey', + () => this.getConfigurationFromDb(), + 10, // 10 seconds + ) + } + + private async getConfigurationFromDb(){ + // fetch configuration from db + } +} +``` + +When the `.loadConfiguration()` method is called for the first time, the `.getConfigurationFromDb()` method will be executed and the value returned from it will be set in cache with `configurationKey` key. If another `.loadConfiguration()` call comes in 10 seconds interval, the data will be returned from cache and `.getConfigurationFromDb()` will not be executed again. + + +### In memory cache methods + + +#### `.get(key: string): Promise` + +- **Parameters:** + - `key`: The key of the item to retrieve from the cache. +- **Returns:** A `Promise` that resolves to the cached value or `undefined` if the key is not present. + + +#### `.getMany(keys: string[]): Promise<(T | undefined)[]>` + +- **Parameters:** + - `keys`: An array of keys to retrieve from the cache. +- **Returns:** A `Promise` that resolves to an array of cached values corresponding to the provided keys. + + +#### `.set(key: string, value: T, ttl: number, cacheNullable?: boolean): void` + +- **Parameters:** + - `key`: The key under which to store the value. + - `value`: The value to store in the cache. + - `ttl`: Time-to-live for the cached item in seconds. + - `cacheNullable` (optional): If set to `false`, the method will not cache null or undefined values. Default: `true` + + +#### `.setMany(keys: string[], values: T[], ttl: number, cacheNullable?: boolean): Promise` + +- **Parameters:** + - `keys`: An array of keys under which to store the values. + - `values`: An array of values to store in the cache. + - `ttl`: Time-to-live for the cached items in seconds. + - `cacheNullable` (optional): If set to `false`, the method will not cache null or undefined values. Default: `true` + + +#### `.delete(key: string): Promise` + +- **Parameters:** + - `key`: The key of the item to delete from the cache. +- **Returns:** A `Promise` that resolves when the item is successfully deleted. + + +#### `.getOrSet(key: string, createValueFunc: () => Promise, ttl: number, cacheNullable?: boolean): Promise` + +- **Parameters:** + - `key`: The key of the item to retrieve or create. + - `createValueFunc`: A function that creates the value if the key is not present in the cache. + - `ttl`: Time-to-live for the cached item in seconds. + - `cacheNullable` (optional): If set to `false`, the method will not cache null or undefined values. Default: `true` +- **Returns:** A `Promise` that resolves to the cached or newly created value. + + +#### `.setOrUpdate(key: string, createValueFunc: () => Promise, ttl: number, cacheNullable?: boolean): Promise` + +- **Parameters:** + - `key`: The key of the item to update or create. + - `createValueFunc`: A function that creates the new value for the key. + - `ttl`: Time-to-live for the cached item in seconds. + - `cacheNullable` (optional): If set to `false`, the method will not cache null or undefined values. Default: `true` +- **Returns:** A `Promise` that resolves to the updated or newly created value. + + +## Redis Cache + +Redis cache, available through `RedisCacheService`, is a caching system build on top of Redis. It is used as a shared cache among multiple microservices. + +Let's build the same config loader class but with data shared across multiple clusters using Redis. The implementation is almost identical since both `InMemoryCache` and `RedisCache` have similar class structure. + +Usage example: + +```typescript +import { Injectable } from '@nestjs/common'; +import { RedisCacheService } from '@multiversx/sdk-nestjs-cache'; + +@Injectable() +export class ConfigService { + constructor( + private readonly redisCacheService: RedisCacheService, + ){} + + async loadConfiguration(){ + return await this.redisCacheService.getOrSet( + 'configurationKey', + () => this.getConfigurationFromDb(), + 10, + ); + } + + private async getConfigurationFromDb(){ + // fetch configuration from db + } +} + +``` + + +### Redis cache methods + + +#### `get(key: string): Promise` + +- **Parameters:** + - `key`: The key of the item to retrieve. +- **Returns:** A `Promise` that resolves to the cached value or `undefined` if the key is not found. + + +#### `getMany(keys: string[]): Promise<(T | undefined | null)[]>` + +- **Parameters:** + - `keys`: An array of keys to retrieve. +- **Returns:** A `Promise` that resolves to an array of cached values corresponding to the input keys. Values may be `undefined` or `null` if not found. + + +#### `set(key: string, value: T, ttl: number, cacheNullable?: boolean): void` + +- **Parameters:** + - `key`: The key under which to store the value. + - `value`: The value to store in the cache. + - `ttl`: Time-to-live for the cached item in seconds. + - `cacheNullable` (optional): If set to `false`, the method will not cache null or undefined values. Default: `true` + + +#### `setMany(keys: string[], values: T[], ttl: number, cacheNullable?: boolean): void` + +- **Parameters:** + - `keys`: An array of keys for the items to update or create. + - `values`: An array of values to set in the cache. + - `ttl`: Time-to-live for the cached items in seconds. + - `cacheNullable` (optional): If set to `false`, the method will not cache null or undefined values. Default: `true` + + +#### `delete(key: string): void` + +- **Parameters:** + - `key`: The key of the item to delete. +- **Returns:** A `Promise` that resolves when the item is successfully deleted from the cache. + + +#### `getOrSet(key: string, createValueFunc: () => Promise, ttl: number, cacheNullable?: boolean): Promise` + +- **Parameters:** + - `key`: The key of the item to retrieve or create. + - `createValueFunc`: A function that creates the new value for the key. + - `ttl`: Time-to-live for the cached item in seconds. + - `cacheNullable` (optional): If set to `false`, the method will not cache null or undefined values. Default: `true` + +**Note:** These are just some of the methods available in the `RedisCacheService` class. + + +## Cache Service + +Cache service is using both [In Memory Cache](#in-memory-cache) and [Redis Cache](#redis-cache) to form a two-layer caching system. + +Usage example: + +```typescript +import { Injectable } from '@nestjs/common'; +import { CacheService } from '@multiversx/sdk-nestjs-cache'; + +@Injectable() +export class ConfigService { + constructor( + private readonly cacheService: CacheService, + ){} + + async loadConfiguration(){ + return await this.cacheService.getOrSet( + 'configurationKey', + () => this.getConfigurationFromDb(), + 5, // in memory TTL + 10, // redis TTL + ); + } + + private async getConfigurationFromDb(){ + // fetch configuration from db + } +} +``` + +Whenever `.loadConfigurationMethod()` is called, the service will first look into the in memory cache if there is a value stored for the specified key and return it. If the value is not found in the in memory cache it will look for the same key in Redis cache and return it if found. If the value is not found in Redis, the `.getConfigurationFromDb()` method is called and the returned value is stored in memory for 5 seconds (the TTL provided in the third parameter) and in Redis for 10 seconds (the value provided in the fourth parameter). + +*Note: we usually use smaller TTL for in memory cache because when it comes to in memory cache it takes longer to synchronize all instances and it is better to fall back to Redis and lose a bit of reading speed than to have inconsistent data.* + +All methods from `CacheService` use the two layer caching system except the ones that contains `local` and `remote` in their name. Those methods refer strictly to in memory cache and Redis cache. + +Examples: + +- `.getLocal()`, `.setLocal()`, `.getOrSetLocal()` are the same methods as [In Memory Cache](#in-memory-cache) +- `.getRemote()`, `.setRemove()`, `.getOrSetRemove()` are the same methods as [Redis Cache](#redis-cache) + + +### Cache service methods + + +#### `get(key: string): Promise` + +- **Parameters:** + - `key`: The key of the item to retrieve. The method first checks the local in-memory cache, and if not found, it retrieves the value from the Redis cache. +- **Returns:** A `Promise` that resolves to the cached value or `undefined` if the key is not found. + + +#### `getMany(keys: string[]): Promise<(T | undefined)[]>` + +- **Parameters:** + - `keys`: An array of keys to retrieve. The method first checks the local in-memory cache, and for missing keys, it retrieves values from the Redis cache. +- **Returns:** A `Promise` that resolves to an array of cached values corresponding to the input keys. Values may be `undefined` if not found. + + +#### `set(key: string, value: T, ttl: number, cacheNullable?: boolean): Promise` + +- **Parameters:** + - `key`: The key under which to store the value. The method sets the value in both the local in-memory cache and the Redis cache. + - `value`: The value to store in the cache. + - `ttl`: Time-to-live for the cached item in seconds. + - `cacheNullable` (optional): If set to `false`, the method will not cache null or undefined values. Default: `true` + + +#### `setMany(keys: string[], values: T[], ttl: number, cacheNullable?: boolean): Promise` + +- **Parameters:** + - `keys`: An array of keys for the items to update or create. The method sets values in both the local in-memory cache and the Redis cache. + - `values`: An array of values to set in the cache. + - `ttl`: Time-to-live for the cached items in seconds. + - `cacheNullable` (optional): If set to `false`, the method will not cache null or undefined values. Default: `true` + + +#### `delete(key: string): Promise` + +- **Parameters:** + - `key`: The key of the item to delete. The method deletes the item from both the local in-memory cache and the Redis cache. +- **Returns:** A `Promise` that resolves when the item is successfully deleted from both caches. + + +#### `deleteMany(keys: string[]): Promise` + +- **Parameters:** + - `keys`: An array of keys for the items to delete. The method deletes items from both the local in-memory cache and the Redis cache. +- **Returns:** A `Promise` that resolves when all items are successfully deleted from both caches. + + +#### `getOrSet(key: string, createValueFunc: () => Promise, ttl: number, cacheNullable?: boolean): Promise` + +- **Parameters:** + - `key`: The key of the item to retrieve or create. The method first checks the local in-memory cache, and if not found, it retrieves the value from the Redis cache or creates it using the provided function. + - `createValueFunc`: A function that creates the new value for the key. + - `ttl`: Time-to-live for the cached item in seconds. + - `cacheNullable` (optional): If set to `false`, the method will not cache null or undefined values. Default: `true` + +... (and many more) + +**Note:** These are just some of the methods available in the `CacheService` class. For a comprehensive list of methods and their descriptions, refer to the class implementation. + +--- + +### NestJS SDK Monitoring utilities + +NPM Version + + +## MultiversX NestJS Microservice Monitoring Utilities + +This package contains a set of utilities commonly used for monitoring purposes in the MultiversX Microservice ecosystem. +The package relies on Prometheus to aggregate the metrics, and it is using [prom-client](https://www.npmjs.com/package/prom-client) as a client for it. + + +## Installation + +`sdk-nestjs-monitoring` is delivered via **npm,** and it can be installed as follows: + +```bash +npm install @multiversx/sdk-nestjs-monitoring +``` + + +## Utility + +The package exports **performance profilers**, **interceptors** and **metrics**. + + +### Performance profiler + +`PerformanceProfiler` is a class exported by the package that allows you to measure the execution time of your code. + +```typescript +import { PerformanceProfiler } from '@multiversx/sdk-nestjs-monitoring'; + +const profiler = new PerformanceProfiler(); +await doSomething(); +const profilerDurationInMs = profiler.stop(); + +console.log(`doSomething() method execution time lasted ${profilerDurationInMs} ms`); +``` + +The `.stop()` method can receive two optional parameters: + +- `description` - text used for default logging. Default: `undefined` +- `log` - boolean to determine if log should be printed. If `log` is set to true, the logging class used to print will be `Logger` from `"@nestjs/common"`.``Default: `false` + +```typescript +import { PerformanceProfiler } from '@multiversx/sdk-nestjs-monitoring'; + +const profiler = new PerformanceProfiler(); +await doSomething(); +profiler.stop(`doSomething() execution time`, true); +``` + +The output of the code above will be "`doSomething() execution time: 1.532ms`" + +--- + + +### Cpu Profiler + +`CpuProfiler` is a class exported by the package that allows you to measure the CPU execution time of your code. Given that JavaScript is a single-threaded language, it's important to be mindful of the amount of CPU time allocated to certain operations, as excessive consumption can lead to slowdowns or even blockages in your process. + +```typescript +import { CpuProfiler } from '@multiversx/sdk-nestjs-monitoring'; + +const profiler = new CpuProfiler(); +await doHttpRequest() +const profilerDurationInMs = profiler.stop(); + +console.log(`doHttpRequest() method execution time lasted ${profilerDurationInMs} ms`); +``` + +The `.stop()` method can receive two optional parameters: + +- `description` - text used for default logging. Setting the description automatically triggers the printing of the `PerformanceProfiler` value. Default: `undefined` + +```typescript +import { CpuProfiler } from '@multiversx/sdk-nestjs-monitoring'; + +const httpReqCpuProfiler = new CpuProfiler(); +await doHttpRequest(); +httpReqCpuProfiler.stop(`doHttpRequest() execution time`); + +const cpuProfiler = new CpuProfiler(); +await doSomethingCpuIntensive(); +cpuProfiler.stop(`doSomethingCpuIntensive() execution time`); +``` + +The output of the code above will be
+ +`doHttpRequest() execution time: 100ms, CPU time: 1ms` +`doSomethingCpuIntensive() execution time: 20ms, CPU time 18ms` + +*Note that a big execution time does not necessarily have an impact on the CPU load of the application. That means that, for example, while waiting for an HTTP request, the JavaScript thread can process other things. That is not the case for CPU time. When a method consumes a lot of CPU time, Javascript will not be able to process other tasks, potentially causing a freeze until the CPU-intensive task is complete.* + +--- + + +## Interceptors + +The package provides a series of [Nestjs Interceptors](https://docs.nestjs.com/interceptors) which will automatically log and set the CPU and overall duration for each request in a [Prometheus](https://prometheus.io) histogram ready to be scrapped by Prometheus. + +`LoggingInterceptor` interceptor will set the execution time of each request in a Prometheus histogram using [performance profilers](#performance-profiler). + +`RequestCpuTimeInterceptor` interceptor will set the CPU execution time of each request in a Prometheus histogram using [cpu profiler](#cpu-profiler). + +*Both interceptors expect an instance of `metricsService` class as an argument.* + +```typescript +import { MetricsService, RequestCpuTimeInterceptor, LoggingInterceptor } from '@multiversx/sdk-nestjs-monitoring'; + +async function bootstrap() { + // AppModule imports MetricsModule + const publicApp = await NestFactory.create(AppModule); + const metricsService = publicApp.get(MetricsService); + + const globalInterceptors = []; + globalInterceptors.push(new RequestCpuTimeInterceptor(metricsService)); + globalInterceptors.push(new LoggingInterceptor(metricsService)); + + publicApp.useGlobalInterceptors(...globalInterceptors); +} +``` + + +## MetricsModule and MetricsService + +`MetricsModule` is a [Nestjs Module](https://docs.nestjs.com/modules) responsible for aggregating metrics data through `MetricsService` and exposing them to be consumed by Prometheus. `MetricsService` is extensible, you can define and aggregate your own metrics and expose them. By default it exposes a set of metrics created by the interceptors specified [here](#interceptors). Most of the MultiversX packages expose metrics by default through this service. For example [@multiversx/sdk-nestjs-redis](https://www.npmjs.com/package/@multiversx/sdk-nestjs-redis) automatically tracks the execution time of each redis query, overall redis health and much more, by leveraging the `MetricsService`. + + +### How to instantiate the MetricsModule and expose metrics endpoints for Prometheus + +In our example we will showcase how to expose response time and CPU time of HTTP requests. Make sure you have the interceptors in place as shown [here](#interceptors). After the interceptors are in place, as requests comes through your application, the metrics are being populated into `MetricsService` class and we just have to expose the output of the `.getMetrics()` method on `MetricsService` through a controller. + +```typescript +import { Controller, Get } from '@nestjs/common'; +import { MetricsService } from '@multiversx/sdk-nestjs-monitoring'; + +@Controller('metrics') +export class MetricsController { + constructor( + private readonly metricsService: MetricsService + ){} + + @Get() + getMetrics(): string { + return this.metricsService.getMetrics(); + } +} +``` + + +### How to add custom metrics + +Adding custom metrics is just a matter of creating another class which uses `MetricsService`. + +We can create a new class called `ApiMetricsService` which will have a new custom metric `heartbeatsHistogram`. + +```typescript +import { Injectable } from '@nestjs/common'; +import { MetricsService } from '@multiversx/sdk-nestjs-monitoring'; +import { register, Histogram } from 'prom-client'; + +@Injectable() +export class ApiMetricsService { + private static heartbeatsHistogram: Histogram; + + constructor(private readonly metricsService: MetricsService) { + if (!ApiMetricsService.heartbeatsHistogram) { + ApiMetricsService.heartbeatsHistogram = new Histogram({ + name: 'heartbeats', + help: 'Heartbeats', + labelNames: ['app'], + buckets: [], + }); + } + } + + async getMetrics(): Promise { + const baseMetrics = await this.metricsService.getMetrics(); + const currentMetrics = await register.metrics(); + + return baseMetrics + '\n' + currentMetrics; + } + + setHeartbeatDuration(app: string, duration: number) { + ApiMetricsService.heartbeatsHistogram.labels(app).observe(duration); + } +} +``` + +The only change we have to do is that we need to instantiate this class and call `.getMetrics()` method on it to return to us both default and our new custom metrics. + +The `.setHeartbeatDuration()` method will be used in our business logic whenever we want to add a new value to that histogram. + +--- + +### NFT & SFT tokens + +```mdx-code-block +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; +import TableWrapper from "@site/src/components/TableWrapper"; +``` + + +## **Introduction** + +MultiversX NFTs(non-fungible tokens) are a breed of digital assets that are revolutionizing the world of art, collectibles, and more. These NFTs are unique, one-of-a-kind tokens that are built on blockchain technology, allowing for secure ownership and transfer of these assets. With MultiversX NFTs, every token is assigned a unique identification code(ticker) and metadata that distinguishes it from every other token, making each NFT truly one-of-a-kind. Read the full page for a comprehensive guide on how to brand, issue, transfer, assign roles and many other features, for both NFTs and SFTs. + + + +### NFT and SFT + +The MultiversX protocol introduces native NFT support by adding metadata and attributes on top of the already existing [Fungible tokens](/tokens/fungible-tokens). +This way, one can issue a semi-fungible token or a non-fungible token which is quite similar to an ESDT, but has a few more attributes, as well as an assignable URI. +Once owning a quantity of a NFT/SFT, users will have their data store directly under their account, inside the trie. All the fields available inside a NFT/SFT token can be found [here](/tokens/nft-tokens#nftsft-fields). + +**The flow of issuing and transferring non-fungible or semi-fungible tokens is:** + +- register/issue the token +- set roles to the address that will create the NFT/SFTs +- create the NFT/SFT +- transfer quantity(es) + + +### Meta ESDT + +In addition to NFTs and SFTs, MultiversX introduced Meta ESDTs. +Meta ESDTs are a special case of semi-fungible-tokens. They can be seen as regular ESDT fungible tokens that also have properties. +In a particular example, LKMEX or XMEX are MetaESDTs and their properties help implement the release schedule. + + +## **Branding** + +Anyone can create NFTs and SFTs tokens on MultiversX Network. There are also no limits in tokens names or tickers. For example, +one issues an `AliceToken` with the ticker `ALC`. Anyone else is free to create a new token with the same token name and +the same token ticker. The only difference will be the random sequence of the token identifier. So the "original" token +could have received the random sequence `1q2w3e` resulting in the `ALC-1q2w3e` identifier, while the second token could +have received the sequence `3e4r5t` resulting in `ALC-3e4r5t`. + +In order to differentiate between an original token and other tokens with the same name or ticker, we have introduced a +branding mechanism that allows tokens owners to provide a logo, a description, a website, as well as social link for their tokens. MultiversX products such as Explorer, Wallet and so on +will display tokens in accordance to their branding, if any. + +A token owner can submit a branding request by opening a Pull Request on https://github.com/multiversx/mx-assets. + + +### **Submitting a branding request** + +Token owners can create a PR to the https://github.com/multiversx/mx-assets with the logo in .png and .svg format, as well as a .json file containing all the relevant information. + +Here’s a prefilled template for the .json file to get you started: + +```json +{ + "website": "https://www.multiversxtoken.com", + "description": "MultiversX Token is a collection of 10.000 unique and randomly generated tokens.", + "social": { + "email": "mxt-token@multiversxtoken.com", + "blog": "https://www.multiversxtoken.com/MXT-token-blog", + "twitter": "https://twitter.com/MXT-token-twitter" + }, + "status": "active" +} +``` + + +## **Issuance of Non-Fungible Tokens** + +One has to perform an issuance transaction in order to register a non-fungible token. +Non-Fungible Tokens are issued via a request to the Metachain, which is a transaction submitted by the Account which will manage the tokens. When issuing a token, one must provide a token name, a ticker and optionally additional properties. This transaction has the form: + +```rust +IssuanceTransaction { + Sender: + Receiver: erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u + Value: 50000000000000000 # (0.05 EGLD) + GasLimit: 60000000 + Data: "issueNonFungible" + + "@" + + + "@" + +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +Optionally, the properties can be set when issuing a token. Example: + +```rust +IssuanceTransaction { + Sender: + Receiver: erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u + Value: 50000000000000000 # (0.05 EGLD) + GasLimit: 60000000 + Data: "issueNonFungible" + + "@" + + + "@" + + + "@" + <"canFreeze" hexadecimal encoded> + "@" + <"true" or "false" hexadecimal encoded> + + "@" + <"canWipe" hexadecimal encoded> + "@" + <"true" or "false" hexadecimal encoded> + + "@" + <"canPause" hexadecimal encoded> + "@" + <"true" or "false" hexadecimal encoded> + + "@" + <"canTransferNFTCreateRole" hexadecimal encoded> + "@" + <"true" or "false" hexadecimal encoded> + + "@" + <"canChangeOwner" hexadecimal encoded> + "@" + <"true" or "false" hexadecimal encoded> + + "@" + <"canUpgrade" hexadecimal encoded> + "@" + <"true" or "false" hexadecimal encoded> + + "@" + <"canAddSpecialRoles" hexadecimal encoded> + "@" + <"true" or "false" hexadecimal encoded> + + ... +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +The receiver address `erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u` is a built-in system smart contract (not a VM-executable contract), which only handles token issuance and other token management operations, and does not handle any transfers. +The contract will add a random string to the ticker thus creating the **token identifier**. The random string starts with “-” and has 6 more random characters. For example, a token identifier could look like _ALC-6258d2_. + + +## **Issuance of Semi-Fungible Tokens** + +One has to perform an issuance transaction in order to register a semi-fungible token. +Semi-Fungible Tokens are issued via a request to the Metachain, which is a transaction submitted by the Account which will manage the tokens. When issuing a semi-fungible token, one must provide a token name, a ticker and optionally additional properties. This transaction has the form: + +```rust +IssuanceTransaction { + Sender: + Receiver: erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u + Value: 50000000000000000 # (0.05 EGLD) + GasLimit: 60000000 + Data: "issueSemiFungible" + + "@" + + + "@" + +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +Optionally, the properties can be set when issuing a token. Example: + +```rust +IssuanceTransaction { + Sender: + Receiver: erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u + Value: 50000000000000000 # (0.05 EGLD) + GasLimit: 60000000 + Data: "issueSemiFungible" + + "@" + + + "@" + + + "@" + <"canFreeze" hexadecimal encoded> + "@" + <"true" or "false" hexadecimal encoded> + + "@" + <"canWipe" hexadecimal encoded> + "@" + <"true" or "false" hexadecimal encoded> + + "@" + <"canPause" hexadecimal encoded> + "@" + <"true" or "false" hexadecimal encoded> + + "@" + <"canTransferNFTCreateRole" hexadecimal encoded> + "@" + <"true" or "false" hexadecimal encoded> + + "@" + <"canChangeOwner" hexadecimal encoded> + "@" + <"true" or "false" hexadecimal encoded> + + "@" + <"canUpgrade" hexadecimal encoded> + "@" + <"true" or "false" hexadecimal encoded> + + "@" + <"canAddSpecialRoles" hexadecimal encoded> + "@" + <"true" or "false" hexadecimal encoded> + + ... +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +The receiver address `erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u` is a built-in system smart contract (not a VM-executable contract), which only handles token issuance and other token management operations, and does not handle any transfers. +The contract will add a random string to the ticker thus creating the **token identifier**. The random string starts with “-” and has 6 more random characters. For example, a token identifier could look like _ALC-6258d2_. + + +## **Issuance of Meta-ESDT Tokens** + +One has to perform an issuance transaction in order to register a Meta-ESDT token. +Meta-ESDT Tokens are issued via a request to the Metachain, which is a transaction submitted by the Account which will manage the tokens. When issuing a semi-fungible token, one must provide a token name, a ticker and optionally additional properties. This transaction has the form: + +```rust +IssuanceTransaction { + Sender: + Receiver: erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u + Value: 50000000000000000 # (0.05 EGLD) + GasLimit: 60000000 + Data: "registerMetaESDT" + + "@" + + + "@" + + + "@" + +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +Optionally, the properties can be set when issuing a token. Example: + +```rust +IssuanceTransaction { + Sender: + Receiver: erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u + Value: 50000000000000000 # (0.05 EGLD) + GasLimit: 60000000 + Data: "registerMetaESDT" + + "@" + + + "@" + + + "@" + + "@" + <"canFreeze" hexadecimal encoded> + "@" + <"true" or "false" hexadecimal encoded> + + "@" + <"canWipe" hexadecimal encoded> + "@" + <"true" or "false" hexadecimal encoded> + + "@" + <"canPause" hexadecimal encoded> + "@" + <"true" or "false" hexadecimal encoded> + + "@" + <"canTransferNFTCreateRole" hexadecimal encoded> + "@" + <"true" or "false" hexadecimal encoded> + + "@" + <"canChangeOwner" hexadecimal encoded> + "@" + <"true" or "false" hexadecimal encoded> + + "@" + <"canUpgrade" hexadecimal encoded> + "@" + <"true" or "false" hexadecimal encoded> + + "@" + <"canAddSpecialRoles" hexadecimal encoded> + "@" + <"true" or "false" hexadecimal encoded> + + ... +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +The receiver address `erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u` is a built-in system smart contract (not a VM-executable contract), which only handles token issuance and other token management operations, and does not handle any transfers. +The contract will add a random string to the ticker thus creating the **token identifier**. The random string starts with “-” and has 6 more random characters. For example, a token identifier could look like _ALC-6258d2_. + + +### **Converting an SFT into Meta-ESDT** + +An already existing _semi-fungible token_ can be converted into a Meta-ESDT token if the owner sends the following transaction: + +```rust +ConvertSftToMetaESDTTransaction { + Sender: + Receiver: erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u + Value: 0 + GasLimit: 60000000 + Data: "changeSFTToMetaESDT" + + "@" + + + "@" + +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + + +## **Parameters format** + +Token Name: + +- length between 3 and 50 characters +- alphanumeric characters only + +Token Ticker: + +- length between 3 and 10 characters +- alphanumeric UPPERCASE only + + +## **Issuance examples** + +For example, a user named Alice wants to issue an ESDT called "AliceTokens" with the ticker "ALC". The issuance transaction would be: + +```rust +IssuanceTransaction { + Sender: erd1sg4u62lzvgkeu4grnlwn7h2s92rqf8a64z48pl9c7us37ajv9u8qj9w8xg + Receiver: erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u + Value: 50000000000000000 # (0.05 EGLD) + GasLimit: 60000000 + Data: "issueSemiFungible" + + "@416c696365546f6b656e73" + + "@414c43" + +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +Once this transaction is processed by the Metachain, Alice becomes the designated **manager of AliceTokens**. She can add quantity later using `ESDTNFTCreate`. For more operations available to ESDT token managers, see [Token management](/tokens/fungible-tokens#token-management). + +In that smart contract result, the `data` field will contain a transfer syntax which is explained below. What is important to note is that the token identifier can be fetched from +here in order to use it for transfers. Alternatively, the token identifier can be fetched from the API (explained also in section [REST API - Get NFT data](/tokens/nft-tokens#get-nft-data-for-an-address) ). + + +## **Roles** + +In order to be able to perform actions over a token, one needs to have roles assigned. +The existing roles are: + +For NFT: + +- ESDTRoleNFTCreate : this role allows one to create a new NFT +- ESDTRoleNFTBurn : this role allows one to burn a specific NFT +- ESDTRoleNFTUpdateAttributes : this role allows one to change the attributes of a specific NFT +- ESDTRoleNFTAddURI : this role allows one add URIs for a specific NFT +- ESDTTransferRole : this role enables transfer only to specified addresses. The addresses with the transfer role can transfer anywhere. +- ESDTRoleNFTUpdate : this role allows one to update meta data attributes of a specific NFT +- ESDTRoleModifyRoyalties : this role allows one to modify royalities of a specific NFT +- ESDTRoleSetNewURI : this role allows one to set new uris of a specific NFT +- ESDTRoleModifyCreator : this role allows one to rewrite the creator of a specific token +- ESDTRoleNFTRecreate : this role allows one to recreate the whole NFT with new attributes + +For SFT: + +- ESDTRoleNFTCreate : this role allows one to create a new SFT +- ESDTRoleNFTBurn : this role allows one to burn quantity of a specific SFT +- ESDTRoleNFTAddQuantity : this role allows one to add quantity of a specific SFT +- ESDTTransferRole : this role enables transfer only to specified addresses. The addresses with the transfer role can transfer anywhere. +- ESDTRoleNFTUpdate : this role allows one to update meta data attributes of a specific SFT +- ESDTRoleModifyRoyalties : this role allows one to modify royalities of a specific SFT +- ESDTRoleSetNewURI : this role allows one to set new uris of a specific SFT +- ESDTRoleModifyCreator : this role allows one to rewrite the creator of a specific token +- ESDTRoleNFTRecreate : this role allows one to recreate the whole NFT with new attributes + +To see how roles can be assigned, please refer to [this](/tokens/nft-tokens#assigning-roles) section. + + +## **Assigning roles** + +Roles can be assigned by sending a transaction to the Metachain from the ESDT manager. + +Within a transaction of this kind, any number of roles can be assigned (minimum 1). + +```rust +RolesAssigningTransaction { + Sender:
+ Receiver: erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u + Value: 0 + GasLimit: 60000000 + Data: "setSpecialRole" + + "@" + + + "@" +
+ + "@" + + + "@" + + + ... +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +For example, `ESDTRoleNFTCreate` = `45534454526f6c654e4654437265617465` + +Unset transactions are very similar. You can find an example [here](/tokens/fungible-tokens#unset-special-role). + + +## **NFT/SFT fields** + +Below you can find the fields involved when creating an NFT. + +**NFT Name** + +- The name of the NFT or SFT + +**Quantity** + +- The quantity of the token. If NFT, it must be `1` + +**Royalties** + +- Allows the creator to receive royalties for any transaction involving their NFT +- Base format is a numeric value between 0 an 10000 (0 meaning 0% and 10000 meaning 100%) + +**Hash** + +- Arbitrary field that should contain the hash of the NFT metadata +- Optional filed, should be left `null` when building the transaction to create the NFT + +**Attributes** + +- Represents additional information about the NFT or SFT, like picture traits or tags for your NFT/collection +- The field should follow a `metadata:ipfsCID/fileName.json;tags:tag1,tag2,tag3` format +- Below you can find a sample for the extra metadata format that should be stored on IPFS: + +```json +{ + "description": "This is a sample description", + "attributes": [ + { + "trait_type": "Background", + "value": "Yellow", + "{key}": "{value}", + "{...}": "{...}", + "{key}": "{value}" + }, + { + "trait_type": "Headwear", + "value": "BlackBeanie" + }, + { + "trait_type": "SampleTrait3", + "value": "SampleValue3" + } + ], + "collection": "ipfsCID/fileName.json" +} +``` + +**URI(s)** + +- Mandatory field that represents the URL to a [supported](#supported-media-types) media file ending with the file extension as described in the [example](#example) below +- Field should contain the `Uniform Resource Identifier` + +Note: As a best practice, we recommend storing the files for media & extra metadata(from attributes field) within same folder on your storage provider, ideally IPFS. Also, in order to have a thumbnail generated for the uploaded file the size of the file should be less or equal to 64MB. + +:::important +Please note that each argument must be encoded in hexadecimal format with an even number of characters. +::: + + +### **Supported Media Types** + +Below you can find a table with the supported media types for NFTs available on MultiversX network. + +| Media Extension | Media Type | +|-----------------|-----------------| +| .png | image/png | +| .jpeg | image/jpeg | +| .jpg | image/jpg | +| .gif | image/gif | +| .webp | image/webp | +| .svg | image/svg | +| .svg | image/svg+xml | +| .acc | audio/acc | +| .flac | audio/flac | +| .m4a | audio/m4a | +| .mp3 | audio/mp3 | +| .wav | audio/wav | +| .mov | video/mov | +| .quicktime | video/quicktime | +| .mp4 | video/mp4 | +| .webm | video/webm | + + +### **Example** + +Below you can find a table representing an example of the fields for a non-fungible token that resembles a song. + + +| Property | Plain value | Encoded value | +|----------------|--------------------------------------------------------|----------------------------------------------------------------------------------------------------------| +| **NFT Name** | Beautiful song | 42656175746966756c20736f6e67 | +| **Quantity** | 1 | 01 | +| **Royalties** | 7500 _=75%_ | 1d4c | +| **Hash** | 00 | 00 | +| **Attributes** | metadata:_ipfsCID/song.json_;tags:song,beautiful,music | 6d657461646174613a697066734349442f736f6e672e6a736f6e3b746167733a736f6e672c62656175746966756c2c6d75736963 | +| **URI** | _URL_to_decentralized_storage/song.mp3_ | 55524c5f746f5f646563656e7472616c697a65645f73746f726167652f736f6e672e6d7033 | + + +In this example we are creating a NFT representing a song. Hash is left null, we are sharing media location URL and we are also providing the location of the extra metadata within the attributes field. + + +## **Creation of an NFT** + +A single address can own the role of creating an NFT for an ESDT token. This role can be transferred by using the `ESDTNFTCreateRoleTransfer` function. + +An NFT can be created on top of an existing ESDT by sending a transaction to self that contains the function call that triggers the creation. +Any number of URIs can be assigned (minimum 1) + +```rust +NFTCreationTransaction { + Sender:
+ Receiver: + Value: 0 + GasLimit: 3000000 + Additional gas (see below) + Data: "ESDTNFTCreate" + + "@" + + + "@" + + + "@" + + + "@" + + + "@" + + + "@" + + + "@" + + + "@" + + + ... +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +Additional gas refers to: + +- Transaction payload cost: Data field length \* 1500 (GasPerDataByte = 1500) +- Storage cost: Size of NFT data \* 50000 (StorePerByte = 50000) + +To see more about the required fields, please refer to [this](/tokens/nft-tokens#nftsft-fields) section. + +:::tip +Note that because NFTs are stored in accounts trie, every transaction involving the NFT will require a gas limit depending on NFT data size. +::: + +Most of the times you will be able to create the NFTs by issuing one single transaction. +This assumes that the metadata file as well as the NFT media is already uploaded to IPFS. + +There are times, however, when uploading the metadata file before issuing the NFT is not possible (eg. when issued from a smart contract) +In these cases it is possible to update an NFT with the metadata file after it was issued by sending an additional transaction. You can find more information [here](/tokens/nft-tokens#change-nft-attributes) about how to update the attributes + + +## **Other management operations** + +Managing non-fungible tokens (NFTs) and semi-fungible tokens (SFTs) presents a greater degree of complexity compared to the management of simple fungible tokens. These unique tokens require specialized transactions for proper identification, ownership, and transfer, making the process of managing them more intricate than that of fungible tokens. + + +### **Transfer NFT Creation Role** + +:::tip +This role can be transferred only if the `canTransferNFTCreateRole` property of the token is set to `true`. +::: + +The role of creating an NFT can be transferred by a Transaction like this: + +```rust +TransferCreationRoleTransaction { + Sender:
+ Receiver: erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u + Value: 0 + GasLimit: 60000000 + length of Data field in bytes * 1500 + Data: "transferNFTCreateRole" + + "@" + + + "@" + + + "@" + +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + + +### **Stop NFT creation** + +The ESDT manager can stop the creation of an NFT for the given ESDT forever by removing the only `ESDTRoleNFTCreate` role available. +This is done by performing a transaction like this: + +```rust +StopNFTCreationTransaction { + Sender: + Receiver: erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u + Value: 0 + GasLimit: 60000000 + Data: "stopNFTCreate" + + "@" + + +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + + +### **Change NFT Attributes** + +An user that has the `ESDTRoleNFTUpdateAttributes` role set for a given ESDT, can change the attributes of a given NFT/SFT. + +:::tip +`ESDTNFTUpdateAttributes` will remove the old attributes and add the new ones. Therefore, if you want to keep the old attributes you will have to pass them along with the new ones. +::: +This is done by performing a transaction like this: + +```rust +ESDTNFTUpdateAttributesTransaction { + Sender:
+ Receiver: + Value: 0 + GasLimit: 10000000 + Data: "ESDTNFTUpdateAttributes" + + "@" + + + "@" + + + "@" + +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +To see how you can assign this role in case it is not set, please refer to [this](/tokens/nft-tokens#assigning-roles) section. + + +### **Add URIs to NFT** + +An user that has the `ESDTRoleNFTAddURI` role set for a given ESDT, can add uris to a given NFT/SFT. +This is done by performing a transaction like this: + +```rust +ESDTNFTAddURITransaction { + Sender:
+ Receiver: + Value: 0 + GasLimit: 10000000 + Data: "ESDTNFTAddURI" + + "@" + + + "@" + + + "@" + + + "@" + + + ... +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +To see how you can assign this role in case it is not set, please refer to [this](/tokens/nft-tokens#assigning-roles) section. + + +### **Add quantity (SFT only)** + +A user that has the `ESDTRoleNFTAddQuantity` role set for a given Semi-Fungible Token, can increase its quantity. This function will not work for NFTs, because in that case the quantity cannot be higher than 1. + +```rust +AddQuantityTransaction { + Sender:
+ Receiver: + Value: 0 + GasLimit: 10000000 + Data: "ESDTNFTAddQuantity" + + "@" + + + "@" + + "@" + +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +If successful, the balance of the address for the given SFT will be increased with the number specified in the argument. + + +### **Burn quantity** + +A user that has the `ESDTRoleNFTBurn` role set for a given semi-fungible Token, can burn some (or all) of the quantity. + +```rust +BurnQuantityTransaction { + Sender:
+ Receiver: + Value: 0 + GasLimit: 10000000 + Data: "ESDTNFTBurn" + + "@" + + + "@" + + "@" + +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +If successful, the quantity from the argument will be decreased from the balance of the address for that given token. + + +### **Freezing and Unfreezing a single NFT** + +The manager of an ESDT token may freeze the NFT held by a specific Account. As a consequence, no NFT can be transferred to or from the frozen Account. Freezing and unfreezing a single NFT of an Account are operations designed to help token managers to comply with regulations. The transaction that freezes a single NFT of an Account has the form: + +```rust +FreezeTransaction { + Sender: + Receiver: erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u + Value: 0 + GasLimit: 60000000 + Data: "freezeSingleNFT" + + "@" + + + "@" + + "@" + +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +The reverse operation, unfreezing, will allow further transfers to and from the Account: + +```rust +UnfreezeTransaction { + Sender: + Receiver: erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u + Value: 0 + GasLimit: 60000000 + Data: "unFreezeSingleNFT" + + "@" + + + "@" + + + "@" + +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + + +### **Wiping a single NFT** + +The manager of an ESDT token may wipe out a single NFT held by a frozen Account. This operation is similar to burning the quantity, but the Account must have been frozen beforehand, and it must be done by the token manager. Wiping the tokens of an Account is an operation designed to help token managers to comply with regulations. Such a transaction has the form: + +```rust +WipeTransaction { + Sender: + Receiver: erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u + Value: 0 + GasLimit: 60000000 + Data: "wipeSingleNFT" + + "@" + + + "@" + + + "@" + +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + + +### **Modify Royalties** + +The manager of an ESDT token may want to set new royalities. This operation will rewrite the royalities on the specified token ID. +It requires `ESDTRoleNFTModifyRoyalties` role. +This is done by performing a transaction like this: + +```rust +ModifyRoyalitiesTransaction { + Sender: + Receiver: + Value: 0 + GasLimit: 60000000 + Data: "ESDTModifyRoyalties" + + "@" + + + "@" + + + "@" + +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + + +### **Set new URIs** + +The manager of an ESDT token may want to set new URIs. This operation will rewrite the URIs on the specified token ID. +It requires `ESDTRoleNFTSetNewURIs` role. +This is done by performing a transaction like this: + +```rust +SetNewURIsTransaction { + Sender: + Receiver: + Value: 0 + GasLimit: 60000000 + Data: "ESDTSetNewURIs" + + "@" + + + "@" + + + "@" + + + "@" + + + ... +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + + +### **Modify Creator** + +The creator of a token can be changed. For this, the token has to be moved to the new creator account. The new creator +account requires `ESDTRoleModifyCreator` role. Also, the token has to be of dynamic type in order for this to work. + +The creator can be modified using a transaction like this: + +```rust +ModifyCreatorTransaction { + Sender: + Receiver: + Value: 0 + GasLimit: 60000000 + Data: "ESDTModifyCreator" + + "@" + + + "@" + +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + + +### **MetaData Update** + +The manager of an ESDT token may want to update token metadata. This operation will update token metadata on the specified token ID. +It requires `ESDTRoleNFTUpdate` role. If nothing is received for a given attribute, the old version of that attribute will be kept. + +This is done by performing a transaction like this: + +```rust +MetaDataUpdateTransaction { + Sender: + Receiver: + Value: 0 + GasLimit: 60000000 + Data: "ESDTMetaDataUpdate" + + "@" + + + "@" + + + "@" + + + "@" + + + "@" + + + "@" + + + "@" + +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + + +### **MetaData Recreate** + +The whole NFT can be recreated with new attributes using a transaction like this: + +The manager of an ESDT token may want to recreate the whole token with new attributes. This operation will recreate token attributes on the specified token ID. +It requires `ESDTRoleNFTRecreate` role. If an argument is not being set, that field is set to zero. + +This is done by performing a transaction like this: + +```rust +MetaDataRecreateTransaction { + Sender: + Receiver: + Value: 0 + GasLimit: 60000000 + Data: "ESDTMetaDataRecreate" + + "@" + + + "@" + + + "@" + + + "@" + + + "@" + + + "@" + + + "@" + +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + + +### **Make token dynamic** + +The ESDT manager can change token type to dynamic using a transaction like this: + +```rust +ChangeToDynamicTransaction { + Sender: + Receiver: erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u + Value: 0 + GasLimit: 60000000 + Data: "changeToDynamic" + + "@" + +} +``` + +The following token types cannot be changed to dynamic: `FungibleESDT`, `NonFungibleESDT`, `NonFungibleESDTv2` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + + +### **Update token** + +The token type can be updated to the latest version, which will update token type and propagate it to shard's +system account. Currently, token type is correctly saved only on metachain and there is no type related information +on shard level, the shard only knows if the token is non fungible with implicit type `NonFungibleESDT`, but it +does not know specifically if it's `NonFungibleESDT`, `MetaESDT` or `SemiFungibleESDT`. So, the update operation will +proceed in the following way: +- if token type is `NonFungibleESDT` it will be set to `NonFungibleESDTv2`, and it will be propagated to shard's system account +- if token type is `MetaESDT` or `SemiFungibleESDT` it will be propagated to shard's system account + +This can be done using a transaction like this: + +```rust +UpdateTokenIDTransaction { + Sender: + Receiver: erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u + Value: 0 + GasLimit: 60000000 + Data: "updateTokenID" + + "@" + +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + + +### **Register dynamic token** + +A token can be registered directly as dynamic. + +This can be done using a transaction like this: + +```rust +RegisterDynamicTransaction { + Sender: + Receiver: erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u + Value: 50000000000000000 # (0.05 EGLD) + GasLimit: 60000000 + Data: "registerDynamic" + + "@" + + + "@" + + + "@" + + # For META token type only + "@" + + # (number of decimals) +} +``` + +The following token types cannot be registered as dynamic: `FungibleESDT` + + +### **Register and set all roles to dynamic** + +A token can be registered directly as dynamic together will all roles set for the specific type. + +This can be done using a transaction like this: + +```rust +RegisterAndSetAllRolesDynamicTransaction { + Sender: + Receiver: erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u + Value: 50000000000000000 # (0.05 EGLD) + GasLimit: 60000000 + Data: "registerAndSetAllRolesDynamic" + + "@" + + + "@" + + + "@" + + + # For META token type only + "@" + + # (number of decimals) +} +``` + +The following token types cannot be registered as dynamic: `FungibleESDT` + + +### **Transferring token management rights** + +The manager of an ESDT token can transfer the ownership if the ESDT was created as upgradable. Check the [ESDT - Upgrading (changing properties)](/tokens/fungible-tokens#upgrading-changing-properties) section for more details. + + +### **Upgrading (changing properties)** + +The manager of an ESDT token may individually change any of the properties of the token, or multiple properties at once, only if the ESDT was created as upgradable. +Check the [ESDT - Transferring token management rights](/tokens/fungible-tokens#transferring-token-management-rights) section for more details. + + +## **Transfers** + +Performing an ESDT NFT transfer is done by specifying the receiver's address inside the `Data` field, alongside other details. An ESDT NFT transfer transaction has the following form: + +```rust +TransferTransaction { + Sender: + Receiver: + Value: 0 + GasLimit: 1000000 + length of Data field in bytes * 1500 + Data: "ESDTNFTTransfer" + + "@" + + + "@" + + + "@" + + + "@" + +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +:::tip +Here is an example of an NFT identifier: `ABC-1a9c7d-05dc` + +The collection identifier is `ABC-1a9c7d` and the NFT nonce is `05dc`. Note that the `05dc` is hexadecimal encoded, it represents decimal 1500. + +Also note that a MultiversX address is in bech32, so you will need to convert the address from bech32 to hexadecimal. This can be done with the `hex()` method of mx-sdk-js-core for address (all the methods for addresses can be found [here](https://github.com/multiversx/mx-sdk-js-core/blob/main/src/address.ts)) or manually with an external converter which you can find [here.](https://utils.multiversx.com/converters#addresses-bech32-to-hexadecimal) +::: + + +## **Transfers to a Smart Contract** + +To perform the transfer from your account to the smart contract, you have to use the following transaction format: + +```rust +TransferTransaction { + Sender: + Receiver: + Value: 0 + GasLimit: 1000000 + extra for smart contract call + Data: "ESDTNFTTransfer" + + "@" + + + "@" + + + "@" + + + "@" + + + "@" + + + "@" + + + "@" + + + <...> +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + + +## **Multiple tokens transfer** + +Multiple semi-fungible and/or non-fungible tokens can be transferred in a single transaction to a single receiver. + +More details can be found [here](/tokens/fungible-tokens#multiple-tokens-transfer). + + +## **Example flow** + +Let's see a complete flow of creating and transferring a Semi-Fungible Token. + +**Step 1: Issue/Register a Semi-Fungible Token** + +```rust +{ + Sender: + Receiver: erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u + Value: 50000000000000000 # 0.05 EGLD + GasLimit: 60000000 + Data: "issueSemiFungible" + + "@416c696365546f6b656e73" + # AliceTokens + "@414c43" + # ALC +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +**Step 2: Fetch the token identifier** + +For doing this, one has to check the previously sent transaction and see the Smart Contract Result of it. +It will look similar to `@ok@414c432d317132773365`. The `414c432d317132773365` represents the token identifier in hexadecimal encoding. + +**Step 3: Set roles** + +Assign `ESDTRoleNFTCreate` and `ESDTRoleNFTAddQuantity` roles to an address. You can set these roles to your very own address. + +```rust +{ + Sender: + Receiver: erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u + Value: 0 + GasLimit: 60000000 + Data: "setSpecialRole" + + "@414c432d317132773365" + # previously fetched token identifier + "@" +
+ + "@45534454526f6c654e4654437265617465" + # ESDTRoleNFTCreate + "@45534454526f6c654e46544164645175616e74697479" # ESDTRoleNFTAddQuantity + ... +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +**Step 4: Create NFT** + +Now, the NFT creation transaction for the example case defined [here](/tokens/nft-tokens#creation-of-an-nft) looks like this: + +```rust +{ + Sender:
+ Receiver: + Value: 0 + GasLimit: 3000000 + Data: "ESDTNFTCreate" + + "@414c432d317132773365" + # previously fetched token identifier + "@01" + # quantity: 1 + "@42656175746966756c20736f6e67" + # NFT name: 'Beautiful song' in hexadecimal encoding + "@1d4c" + # Royalties: 7500 =75%c in hexadecimal encoding + "@00" + # Hash: 00 in hexadecimal encoding + "@6d657461646174613a697066734349442f736f6e672e6a736f6e3b746167733a736f6e672c62656175746966756c2c6d75736963" + # Attributes: metadata:ipfsCID/song.json;tags:song,beautiful,music in hexadecimal encoding> + + "@55524c5f746f5f646563656e7472616c697a65645f73746f726167652f736f6e672e6d7033" + # URI: URL_to_decentralized_storage/song.mp3 in hexadecimal encoding> + + "@" + + +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +:::tip +Note that the nonce is very important when creating an NFT. You must save the nonce after NFT creation because you will need it for further actions. + +The `NFT nonce` is different from the creator's nonce. + +It can be fetched by viewing all the tokens for the address via API. +::: + +**Step 5: Transfer** + +```rust +{ + Sender: + Receiver: + Value: 0 + GasLimit: 1000000 + length of Data field in bytes * 1500 + Data: "ESDTNFTTransfer" + + "@414c432d317132773365" + # previously fetched token identifier + "@" + + + "@" + + + "@" + +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + + +## **REST API** + +There are a number of API endpoints that one can use to interact with ESDT NFT data. These are: + + +### GET **Get NFT data for an address** {#get-nft-data-for-an-address} + + + + +Returns the balance of an address for specific ESDT Tokens. + +```bash +https://gateway.multiversx.com/address//nft//nonce/ +``` + +| Param | Required | Type | Description | +| --------------- | ----------------------------------------- | --------- | -------------------------------------- | +| bech32Address | REQUIRED | `string` | The Address to query in bech32 format. | +| tokenIdentifier | REQUIRED | `string` | The token identifier. | +| nonce | REQUIRED | `numeric` | The nonce after the NFT creation. | + + + + +```json +{ + "data": { + "tokenData": { + "attributes": "YXR0cmlidXRl", + "balance": "2", + "creator": "erd1ukn0tukrdhuv0zzxn0zlr53g7h0fr68dz9dd56mkksev59nwuvnswnlyuy", + "hash": "aGFzaA==", + "name": "H", + "nonce": 1, + "properties": "", + "royalties": "9000", + "tokenIdentifier": "4W97C-32b5ce", + "uris": ["bmZ0IHVyaQ=="] + } + }, + "error": "", + "code": "successful" +} +``` + + + + + +### GET **Get NFTs/SFTs registered by an address** {#get-nftssfts-registered-by-an-address} + + + + +Returns the identifiers of the tokens that have been registered by the provided address. + +```bash +https://gateway.multiversx.com/address//registered-nfts +``` + +| Param | Required | Type | Description | +| ------------- | ----------------------------------------- | -------- | -------------------------------------- | +| bech32Address | REQUIRED | `string` | The Address to query in bech32 format. | + + + + +```json +{ + "data": { + "tokens": ["ABC-36tg72"] + }, + "error": "", + "code": "successful" +} +``` + + + + + +### GET **Get tokens where an address has a given role** {#get-tokens-where-an-address-has-a-given-role} + + + + +Returns the identifiers of the tokens where the given address has the given role. + +```bash +https://gateway.multiversx.com/address//esdts-with-role/ +``` + +| Param | Required | Type | Description | +| ------------- | ----------------------------------------- | -------- | -------------------------------------- | +| bech32Address | REQUIRED | `string` | The Address to query in bech32 format. | +| role | REQUIRED | `string` | The role to query for. | + +The role can be one of the roles specified in the documentation (for example: ESDTRoleNFTCreate) + + + + +```json +{ + "data": { + "tokens": ["ABC-36tg72"] + }, + "error": "", + "code": "successful" +} +``` + + + + + +### GET **Parse non/semi fungible tokens transfer logs** {#parse-nonsemi-fungible-tokens-transfer-logs} + +Each **successful** nft/sft transfer generates logs and events that can be used to parse all the details about a transfer +(token identifier, sent amount and receiver). +In order to get the logs and events generated by the transfer, one should know the transaction's hash. + + + + +| Param | Required | Type | Description | +| ------ | ----------------------------------------- | -------- | --------------------------- | +| txHash | REQUIRED | `string` | The hash of the transaction | + +```bash +https://gateway.multiversx.com/transaction/*txHash*?withResults=true +``` + + + + +```rust +{ + "data": { + "transaction": { + ... + "logs": { + "address": "...", + "events": [ + { + "address": "...", + "identifier": "ESDTNFTTransfer", + "topics": [ + "VFNGVC1jODY3ZzM=", // TSFT-c867g3 + "CEI=", // 2114 + "Ag==", // 2 + "givNK+JiLZ5VA5/dP11QKoYEn7qoqnD8uPchH3ZMLw4=" // erd1sg4u62lzvgkeu4grnlwn7h2s92rqf8a64z48pl9c7us37ajv9u8qj9w8xg + ], + "data": null + }, + } + } + "error": "", + "code": "successful" +} +``` + +The event with the identifier `ESDTNFTTransfer` will have the following topics: + +- 1st topic: token identifier (decoding: base64 to string) +- 2nd topic: token nonce (decoding: base64 to hex string + hex string to big number / integer) +- 3rd topic: the amount to be sent (decoding: base64 to hex string + hex string to big number) +- 4th topic: the recipient of the tokens (decoding: base64 to hex string + hex string to bech32 address) + +In this example, `erd1sg4u62lzvgkeu4grnlwn7h2s92rqf8a64z48pl9c7us37ajv9u8qj9w8xg` received 2 tokens of the collection +`TSFT-c867g3` with nonce `2114`. + + + + + +### GET **Get all ESDT tokens for an address** {#get-all-esdt-tokens-for-an-address} + +One can use [get all esdt tokens for an address endpoint](/tokens/fungible-tokens#get-all-esdt-tokens-for-an-address) used for ESDT. + + +### GET **Get all issued ESDT tokens** {#get-all-issued-esdt-tokens} + +One can use [get all issued esdt tokens endpoint](/tokens/fungible-tokens#get-all-issued-esdt-tokens) used for ESDT. + + +### POST **Get ESDT properties** {#get-esdt-properties} + +Properties can be queried via the [getTokenProperties function](/tokens/fungible-tokens#get-esdt-token-properties) provided by ESDT. + + +### POST **Get special roles** {#get-special-roles} + +Special roles can be queried via the [getSpecialRoles function](/tokens/fungible-tokens#get-special-roles-for-a-token) provided by ESDT. + +--- + +### operations + +This page describes the structure of the `operations` index (Elasticsearch), and also depicts a few examples of how to query it. + + +## _id + +The _id field of this index is represented by the transactions OR smart contract result hash, in a hexadecimal encoding. + + +## Fields + +| Field | Description | +|-------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| miniBlockHash | The miniBlockHash field represents the hash of the miniblock in which the transaction was included. | +| nonce | The nonce field represents the transaction sequence number of the sender address. | +| round | The round field represents the round of the block when the transaction was executed. | +| value | The value field represents the amount of EGLD to be sent from the sender to the receiver. | +| receiver | The receiver field represents the destination address of the transaction. | +| sender | The sender field represents the address of the transaction sender. | +| receiverShard | The receiverShard field represents the shard ID of the receiver address. | +| senderShard | The senderShard field represents the shard ID of the sender address. | +| gasPrice | The gasPrice field represents the amount to be paid for each gas unit. | +| gasLimit | The gasLimit field represents the maximum gas units the sender is willing to pay for. | +| gasUsed | The gasUsed field represents the amount of gas used by the transaction. | +| fee | The fee field represents the amount of EGLD the sender paid for the transaction. | +| initialPaidFee | The initialPaidFee field represents the initial amount of EGLD the sender paid for the transaction, before the refund. | +| data | The data field holds additional information for a transaction. It can contain a simple message, a function call, an ESDT transfer payload, and so on. | +| signature | The signature of the transaction, hex-encoded. | +| timestamp | The timestamp field represents the timestamp of the block in which the transaction was executed. | +| status | The status field represents the status of the transaction. | +| senderUserName | The senderUserName field represents the username of the sender address. | +| receiverUserName | The receiverUserName field represents the username of the receiver address. | +| hasScResults | The hasScResults field is true if the transaction has smart contract results. | +| isScCall | The isScCall field is true if the transaction is a smart contract call. | +| hasOperations | The hasOperations field is true if the transaction has smart contract results. | +| tokens | The tokens field contains a list of ESDT tokens that are transferred based on the data field. The indices from the `tokens` list are linked with the indices from `esdtValues` list. | +| esdtValues | The esdtValues field contains a list of ESDT values that are transferred based on the data field. | +| receivers | The receivers field contains a list of receiver addresses in case of ESDTNFTTransfer or MultiESDTTransfer. | +| receiversShardIDs | The receiversShardIDs field contains a list of receiver addresses' shard IDs. | +| type | The type field represents the type of the transaction based on the data field. | +| operation | The operation field represents the operation of the transaction based on the data field. | +| function | The function field holds the name of the function that is called in case of a smart contract call. | +| isRelayed | The isRelayed field is true if the transaction is a relayed transaction. | +| version | The version field represents the version of the transaction. | +| hasLogs | The hasLogs field is true if the transaction has logs. | + + +This index contains both transactions and smart contract results. This is useful because one can query both of them in a single request. + +The unified structure will contain an extra field in order to be able to differentiate between them. + + +| Field | Description | +|-------|------------------------------------------------------------------------------------------------| +| type | It can be `normal` in case of a transaction and `unsigned` in case of a smart contract result. | + + +## Query examples + + +### Fetch the latest transactions of an address + +``` +curl --request GET \ + --url ${ES_URL}/operations/_search \ + --header 'Content-Type: application/json' \ + --data '{ + "query": { + "bool": { + "must": [ + { + "match": { + "type": "normal" + } + } + ], + "should": [ + { + "match": { + "sender": "erd..." + } + }, + { + "match": { + "receiver": "erd..." + } + }, + { + "match": { + "receivers": "erd..." + } + } + ] + } + }, + "sort": [ + { + "timestamp": { + "order": "desc" + } + } + ] +}' +``` + +### Fetch the latest operations of an address + +``` +ADDRESS="erd1..." + +curl --request GET \ + --url ${ES_URL}/operations/_search \ + --header 'Content-Type: application/json' \ + --data '{ + "query": { + "bool": { + "should": [ + { + "match": { + "sender": "${ADDRESS}" + } + }, + { + "match": { + "receiver": "${ADDRESS}" + } + }, + { + "match": { + "receivers": "${ADDRESS}" + } + } + ] + } + }, + "sort": [ + { + "timestamp": { + "order": "desc" + } + } + ] +}' +``` + + +### Fetch all the smart contract results generated by a transaction + +``` +curl --request GET \ + --url ${ES_URL}/operations/_search \ + --header 'Content-Type: application/json' \ + --data '{ + "query": { + "bool": { + "must": [ + { + "match": { + "originalTxHash": "d6.." + } + }, + { + "match": { + "type": "unsigned" + } + } + ] + } + } +}' +``` + +--- + +### Overview + +[comment]: # "mx-abstract" + +## Overview + +There are several ways to write smart contract tests in Rust directly. This is for the largest part possible because of the Rust VM and debugger that can act as an execution backend. + +This is a simplified diagram of what a Rust test will do during its execution: + +```mermaid +graph TD + test[Test] + vm[Rust VM] --> bcm[BlockchainMock] + test -->|"register contract"| bcm + test -->|"set accounts"| bcm + test -->|"check accounts"| bcm + test -->|"call
deploy
query"| scexec[SC code] + scexec --> vm +``` + +Of course, a local test environment is not a blockchain, so many things need to be mocked. The test state is held on a `BlockchainMock`, which needs to be initialized with user accounts, tokens, smart contracts, etc. + + + +### ScenarioWorld (facade) + +In order to simplify interactions with the system, all tests use a unique facade for all operations. It is called `ScenarioWorld`, and it gets created at the beginning of each test in a `world()` function. + + + +### Registering contracts + +Since we don't have native execution in the Rust backend yet, the only way to run contracts is to register the contract implementation for the given contract code identifier. In simpler words, we tell the environment "whenever you encounter this contract code, run this code that I've written instead". + +Since this operation is specific to only the Rust debugger, it doesn't go through the mandos pipeline. + + + +### Calling contract code + +There are many ways to call contract code, but the one we recommend is [black-box style](/developers/testing/rust/sc-blackbox-calls) using the [unified transaction syntax](/developers/transactions/tx-overview). + +The call styles are: +- unified transaction syntax + - [**black-box**](/developers/testing/rust/sc-blackbox-calls) (recommended) + - white-box (coming soon) +- Mandos steps in Rust (no longer recommended) +- [Whitebox framework (legacy)](whitebox-legacy) + + + +## Rust testing architecture + +We saw the simplified diagram in the introduction, now let's go in a little more depth. + +```mermaid +graph TD + scenworld["ScenarioWorld (facade)"] + bb[Blackbox test] --> scenworld + wb[Whitebox test] --> scenworld + unit[Unit test] --> scenworld + scenrunner["ScenarioRunner (interface)"] + scenworld -->|"set step
check step
tx step"| scenrunner + vm[Rust VM] --> bcm[BlockchainMock] + scenworld -->|"register contract"| bcm + scenrunner -->|"set step"| bcm + scenrunner -->|"check step"| bcm + scenrunner -->|"tx step"| scexec[SC code] + scexec --> vm + scenrunner -->|"save trace"| trace["trace.scen.json"] +``` + +The ScenarioWorld and its builders are in fact constructing mandos steps in the background and are sending them to the backends. + +The tests are also allowed to construct these mandos steps themselves, but this is currently discouraged, because mandos syntax is very weakly typed and prone to error. + + + +### ScenarioExecutor (Mandos) + +All our tests run through a scenario (Mandos) execution layer, which is the reason why we are able to export mandos traces out of almost any test. + +All test actions are converted to mandos steps before execution. This mechanism is designed to decouple the high-level code from the backend it is run on. In principle, these mandos steps could be executed on other backends too. + +However, there are only two mandos executors right now: +- Rust VM backend +- save to file (trace). + +The system can also load mandos scenarios from files and execute them as such, on the Rust VM backend. + +--- + +### Overview + +Here you can find some common issues and their solutions, in the context of [MultiversX SDKs and Tools](/sdk-and-tools/overview). + +1. [Fix Rust installation](/sdk-and-tools/troubleshooting/fix-rust-setup) +2. [Fix IDEs configuration](/sdk-and-tools/troubleshooting/ide-setup) + +--- + +### Overview + +The processing cost of a MultiversX transaction is determined by its gas limit, the actual gas consumption, and the gas price per gas unit, leading to a processing fee in EGLD that may be subject to a gas refund in certain cases. + + +## Cost of processing (gas units) + +Each MultiversX transaction has a **processing cost**, expressed as **an amount of _gas units_**. At broadcast time, each transaction must be provided a **gas limit** (`gasLimit`), which acts as an _upper limit_ of the processing cost. + + +### Constraints + +For any transaction, the `gasLimit` must be greater or equal to `erd_min_gas_limit` but smaller or equal to `erd_max_gas_per_transaction`, these two being [parameters of the Network](/sdk-and-tools/rest-api/network#get-network-configuration): + +``` +networkConfig.erd_min_gas_limit <= tx.gasLimit <= networkConfig.erd_max_gas_per_transaction +``` + + +### Cost components + +The **actual gas consumption** - also known as **used gas** - is the consumed amount from the provided **gas limit** - the amount of gas units actually required by the Network in order to process the transaction. The unconsumed amount is called **remaining gas**. + +At processing time, the Network breaks the **used gas** down into two components: + +- gas used by **value movement and data handling** +- gas used by **contract execution** (for executing System or User-Defined Smart Contract) + +:::note +Simple transfers of value (EGLD transfers) only require the _value movement and data handling_ component of the gas usage (that is, no _execution_ gas), while Smart Contract calls require both components of the gas consumption. This includes ESDT and NFT transfers as well, because they are in fact calls to a System Smart Contract. +::: + +The **value movement and data handling** cost component is easily computable, using on the following formula: + +``` +tx.gasLimit = + networkConfig.erd_min_gas_limit + + networkConfig.erd_gas_per_data_byte * lengthOf(tx.data) +``` + +The **contract execution** cost component is easily computable for System Smart Contract calls (based on formulas specific to each contract), but harder to determine _a priori_ for user-defined Smart Contracts. This is where _simulations_ and _estimations_ are employed. + + +## Processing fee (EGLD) + +The **processing fee**, measured in EGLD, is computed with respect to the **actual gas cost** - broken down into its components - and the **gas price per gas unit**, which differs between the components. + +The **gas price per gas unit** for the **value movement and data handling** must be specified by the transaction, and it must be equal or greater than a Network parameter called `erd_min_gas_price`. + +While the price of a gas unit for the **value movement and data handling** component equals the **gas price** provided in the transaction, the price of a gas unit for the **contract execution** component is computed with respect to another Network parameter called `erd_gas_price_modifier`: + +``` +value_movement_and_data_handling_price_per_unit = tx.GasPrice +contract_execution_price_per_unit = tx.GasPrice * networkConfig.erd_gas_price_modifier +``` + +:::note +Generally speaking, the price of a gas unit for **contract execution** is lower than the price of a gas unit for **value movement and data handling**, due to the gas price modifier for contracts (`erd_gas_price_modifier`). +::: + +The **processing fee** formula looks like this: + +``` +processing_fee = + value_movement_and_data_handling_cost * value_movement_and_data_handling_price_per_unit + + contract_execution_cost * contract_execution_price_per_unit +``` + +After processing the transaction, the Network will send a value called **gas refund** back to the sender of the transaction, computed with respect to the unconsumed (component of the) gas, if applicable (if the **paid fee** is higher than the **necessary fee**). + +--- + +### Payload (data) + +## Overview + +The data field can hold arbitrary data, but for practical purposes, it is normally one of three: + +- a function call, +- deploy data, or +- an upgrade call. + +We can always give this data in raw form, however, we usually prefer using a proper type system, for safety. + +:::caution +Always use [proxies](./tx-proxies.md) when the target contract ABI is known. A contract proxy is a Rust equivalent of its ABI, and using adds invaluable type safety to your calls. + +Using raw data is acceptable only when we are forwarding calls to unknown contracts, for instance in contracts like the multisig, governance of other forwarders. +::: + + +## Diagram + +The basic transition diagram for constructing the data field is the one below. It shows the allowed data types and how to get from one to the other. + +Notice how the deploy and upgrade calls are further specified by the code source: either explicit code, or the code of another deployed contract. + +```mermaid +graph LR + subgraph "Data: raw" + data-unit["()"] + deploy["DeployCall<()>"] + data-unit -->|raw_deploy| deploy + deploy -->|from_source| deploy-src["DeployCall<FromSource<_>>"] + deploy -->|code| deploy-code["DeployCall<Code<_>>"] + data-unit -->|raw_upgrade| upgrade["UpgradeCall<()>"] + upgrade -->|from_source| upgrade-src["UpgradeCall<CodeSource<_>>"] + upgrade -->|code| upgrade-code["UpgradeCall<Code<_>>"] + data-unit -->|"raw_call(endpoint_name)"| fc[FunctionCall] + end +``` + +What the first diagram does **not** contain are some additional methods that change the values, but not the type. They are: + +- `code_metadata` (deploy & upgrade only), +- `argument` +- `argument_raw`. + +If we also add them to the diagram we get a more complex version of it: + +```mermaid +graph LR + subgraph "Data: raw, detailed" + data-unit["()"] + deploy["DeployCall<()>"] + data-unit -->|raw_deploy| deploy + deploy -->|from_source| deploy-src["DeployCall<FromSource<_>>"] + deploy -->|code| deploy-code["DeployCall<Code<_>>"] + data-unit -->|raw_upgrade| upgrade["UpgradeCall<()>"] + upgrade -->|from_source| upgrade-src["UpgradeCall<CodeSource<_>>"] + upgrade -->|code| upgrade-code["UpgradeCall<Code<_>>"] + data-unit -->|"raw_call(endpoint_name)"| fc[FunctionCall] + + deploy -->|"code_metadata
argument
argument_raw"| deploy + deploy-code -->|"code_metadata
argument
argument_raw"| deploy-code + deploy-src -->|"code_metadata
argument
argument_raw"| deploy-src + upgrade -->|"code_metadata
argument
argument_raw"| upgrade + upgrade-code -->|"code_metadata
argument
argument_raw"| upgrade-code + upgrade-src -->|"code_metadata
argument
argument_raw"| upgrade-src + fc -->|"argument
argument_raw"| fc + end +``` + +:::info +These are diagrams for the raw calls, without proxies. You can find the one involving proxies [here](./tx-proxies.md#diagram). +::: + + +## No data + +Transactions with no data are classified as simple transfers. These simple transactions can be transferred using: + +- **`.transfer()`**: executes simple transfers with a zero gas limit. +```rust title=lib.rs + self.tx() + .to(&caller) + .egld_or_single_esdt(&token_identifier, 0, &balance) + .transfer(); +``` +- **`.transfer_if_not_empty()`**: it facilitates the transfer of funds with a zero gas limit only if the amount exceeds zero; otherwise, no action is taken. +```rust title=lib.rs + self.tx() + .to(ToCaller) + .payment(&token_payment) + .transfer_if_not_empty(); +``` + + + +## Untyped function call + +**`.raw_call(...)`** starts a contract call serialised by hand. It is used in proxy functions. It is safe to use [proxies](./tx-proxies.md) instead since manual serialisation is not type-safe. + + +### Argument + +**`.argument(...)`** serializes the value, but does not enforce type safety. It adds one argument to a function call. + +Can be called multiple times, once for each argument. + +```rust +tx().raw_call("example").argument(&arg1).argument(&arg2) +``` + +It is safe to user [proxies](./tx-proxies.md) instead, whenever possible. + + +### Raw arguments + +**`arguments_raw(...)`** overrides the entire argument buffer. It takes one argument of type `ManagedArgBuffer`. The arguments need to have been serialized beforehand. + +```rust +tx().raw_call("example").arguments_raw(&arguments) +``` + + +### Code metadata + +**`.code_metadata()`** explicitly sets code metadata. +```rust +tx().raw_call("example").code_metadata(code_metadata) +``` + + +## Untyped deploy + +**`.raw_deploy()`** starts a contract deploy call serialised by hand. It is used in proxy deployment functions. It is safe to use [proxies](./tx-proxies.md) instead since manual serialisation is not type-safe. + +Deployment calls needs to set: + + +### Argument + +Same as for [function call arguments](#argument). + +```rust +tx().raw_deploy().argument(&argument) +``` + + +### Raw arguments + +Same as for [function call raw arguments](#raw-arguments). + +```rust +tx().raw_deploy().arguments_raw(&arguments) +``` + + +### Code + +**`.code(...)`** explicitly sets the deployment code source as bytes. + +Argument will normally be a `ManagedBuffer`, but can be any type that implements trait `CodeValue`. + +```rust +tx().raw_deploy().code(code_bytes) +``` + + + +### From source + +**`.from_source(...)`** will instruct the VM to copy the code from another previously deployed contract. + +Argument will normally be a `ManagedAddress`, but can be any type that implements trait `FromSourceValue`. + +```rust +tx().raw_deploy().from_source(other_address) +``` + + +### New address + +**`.new_address(...)`** defines a mock address for the deployed contract (allowed only in testing environments). +```rust +tx().raw_deploy().new_address(address) +``` + +The example below is a blackbox test for deploy functionality. This call encapsulates a raw_deploy that explicitly sets the deployment code source with *"adder.mxsc.json"* and the returned address of the deploy with *"sc: adder"*. + +```rust title=adder_blackbox_test.rs +fn deploy(&mut self) { + self.world + .tx() + .from(OWNER_ADDRESS) + .raw_deploy() + .argument(5u32) + .code(CODE_PATH) + .new_address(ADDER_ADDRESS) + .run(); +} +``` + + +## Untyped upgrade + +`.raw_upgrade()` starts a contract deployment upgrade serialised by hand. It is used in a proxy upgrade call. It is safe to use [proxies](./tx-proxies.md) instead since manual serialisation is not type-safe. All upgrade calls require: + + +### Argument + +Same as for [function call arguments](#argument). + +```rust +tx().raw_upgrade().argument(&argument) +``` + + +### Raw arguments + +Same as for [function call raw arguments](#raw-arguments). + +```rust +tx().raw_upgrade().arguments_raw(&arguments) +``` + + +### Code metadata + +Same as for [function call raw arguments](#code-metadata). +```rust +tx().raw_upgrade().code_metadata(code_metadata) +``` + + +### Code + +Same as for [deploy code](#code). + +Argument will normally be a `ManagedBuffer`, but can be any type that implements trait `CodeValue`. + +```rust +tx().raw_upgrade().code(code_bytes) +``` + + +### From source + +Same as for [deploy from source](#from-source). + +**`.from_source(...)`** will instruct the VM to copy the code from another previously deployed contract. + +Argument will normally be a `ManagedAddress`, but can be any type that implements trait `FromSourceValue`. + +```rust +tx().raw_upgrade().from_source(other_address) +``` + +The example below is an endpoint that contains upgrade functionality. This call encapsulates a raw_upgrade that explicitly sets the upgrade call source with a specific ManagedAddress and *upgradeable* code metadata. + +```rust title=contract.rs +#[endpoint] +fn upgrade_from_source( + &self, + child_sc_address: ManagedAddress, + source_address: ManagedAddress, + opt_arg: OptionalValue, +) { + self.tx() + .to(child_sc_address) + .typed(contract_proxy::ContractProxy) + .upgrade(opt_arg) + .code_metadata(CodeMetadata::UPGRADEABLE) + .from_source(source_address) + .upgrade_async_call_and_exit(); +} +``` + +--- + +### Payments + +## Overview + +Payments can be easily attached to the transaction with the new syntax through the `Payment` generic. In order to specialize the generic, the framework provides the `.payment(...)` method, which accepts all legal types (all types that `can` be payment) such as: `EsdtTokenPayment`, `(TokenIdentifier, u64, BigUint)`, `ManagedVec`, etc. The framework also provides other various helper methods, basically wrappers around `.payment(...)` for accessibility. + + +## Diagram + +The payment is a little more complex than the previous fields. The `.payment(...)` method is sufficient to set any kind of acceptable payment object. However, we have several more functions to help setup the payment field: + +```mermaid +graph LR + payment-unit["()"] + payment-unit --->|"<proxy>"| not-payable["NotPayable"] + payment-unit --->|egld| egld-biguint["Egld(BigUint)"] + payment-unit --->|egld| egld-u64["Egld(u64)"] + payment-unit --->|egld| egld-num["Egld(NumExpr)"] + payment-unit --->|"payment
esdt"| EsdtTokenPayment + payment-unit --->|"payment
single_esdt"| EsdtTokenPaymentRefs + EsdtTokenPayment -->|esdt| MultiEsdtPayment + MultiEsdtPayment -->|esdt| MultiEsdtPayment + payment-unit --->|"payment
multi_esdt"| MultiEsdtPayment + payment-unit --->|"payment"| EgldOrEsdtTokenPayment + payment-unit --->|"payment
egld_or_single_esdt"| EgldOrEsdtTokenPaymentRefs + payment-unit --->|"payment
egld_or_multi_esdt"| EgldOrMultiEsdtPayment +``` + + +## No payments + +When no payments are added, the `Payment` fields remain of type `()`. This makes sense when dealing with contract or built-in function calls that don't involve payment. + +```rust title=contract.rs + self.tx() // tx with sc environment + .to(&to) + .raw_call(endpoint_name) // raw endpoint call + .sync_call() // synchronous call +``` + +In this example, the transaction is a smart contract call to a non payable endpoint with no arguments. + + + +### `NotPayable` + +The `NotPayable` object is a variation of the no-payment indicator `()`. It acts the same way, with only one difference: no payment can be added on top of it. + +[Proxies](tx-proxies#notpayable-protection) will add the `NotPayable` flag to transactions to non-payable endpoints, to provide an additional layer of security. + + + +## EGLD payment + +We represent EGLD payments with the wrapper type `Egld`. This wrapper makes sure that there is no ambiguity as to what the given amount represents. + +The `Egld` contains a single field, holding the amount. This amount will eventually be resolved as a `BigUint`, but the developer can also provide other types, if convenient, such as `u64`, `i32`, or `NumExpr`. In general, any type that implements trait `EgldValue` can be used. + +EGLD payments can be specified using `.payment(Egld(amount))`, but for brevity it is common to use method `.egld(amount)`, which does the same. + +:::caution +The amounts are expressed in indivisible atto-EGLD (10^-18) units. You must always take the denomination into account. +::: + +Some examples of specifying EGLD payments: + + +### `BigUint` + +The most straightforward type to encode amounts. + +```rust title=contract.rs + #[endpoint] + #[payable("EGLD")] + fn send_egld( + &self, + to: ManagedAddress, + egld_amount: BigUint + ) { + self + .tx() // tx with sc environment + .to(to) + .egld(egld_amount) // BigUint value + .transfer() + } +``` + + +### `&BigUint` , `ManagedRef` + + +References are also allowed. A slightly less common variation is the `ManagedRef` type, which is used in certain places of the framework. It is semantically equivalent to a readonly reference (`&...`). You can see it in this example: + +```rust title=contract.rs + #[endpoint] + #[payable("EGLD")] + fn forward_egld( + &self, + to: ManagedAddress, + endpoint_name: ManagedBuffer, + args: MultiValueEncoded, + ) { + let payment = self.call_value().egld(); // readonly BigUint managed reference + self + .tx() // tx with sc environment + .to(to) + .egld(payment) // BigUint value + .raw_call(endpoint_name) // endpoint call + .argument(&args) + .sync_call() + } +``` + + +### `u64` + +In tests it might be unwieldy to keep creating `BigUint` instances, and the amounts might not be so large, so it can be more comfortable to work with `u64` values. + +```rust title=blackbox_test.rs + const STAKE_AMOUNT: u64 = 20; + self.world + .tx() // tx with test exec environment + .from(&address.to_address()) + .to(PRICE_AGGREGATOR_ADDRESS) + .typed(price_aggregator_proxy::PriceAggregatorProxy) // typed call + .stake() // endpoint call + .egld(STAKE_AMOUNT) // u64 value + .run(); +``` + + +### `NumExpr` + +This type helps us control how the values will be exported in the mandos trace. Its contents will be a numeric mandos expression. Use if you are insterested in a readable mandos output. + +Alternatively, it can visually convey certain information, in this case the mandos separators are (mis-)used to indicate the EGLD decimals. + +```rust title=interact.rs + self.interactor + .tx() // tx with interactor exec environment + .from(&self.wallet_address) + .to(self.state.current_adder_address()) + .egld(NumExpr("0,050000000000000000")) // 0,05 EGLD => 5 * 10^16 + .prepare_async() + .run() + .await; +``` + +In this example, the value put inside `NumExpr` is equal to 0,05 EGLD. The `0,` component is just stylistic and can be ignored, and there are 16 digits after `5` from a total of 18, marking the decimal point. + + +### `i32` + +`i32` is only supported because it is the default Rust numeric type and unannotated numeric constants are of this type. + +Make sure you don't pass a negative value! + +```rust title=blackbox_test.rs + state + .world + .tx() // tx with test exec environment + .from(FIRST_USER_ADDRESS) + .to(CROWDFUNDING_ADDRESS) + .typed(crowdfunding_esdt_proxy::CrowdfundingProxy) + .fund() + .egld(1000) // i32 value + .with_result(ExpectError(4, "wrong token")) + .run(); +``` + + + +## General ESDT payment + +Any ESDT payment can be easily attached to a transaction through the `.esdt(...)` method. This method accepts as argument any type that can be converted into an `EsdtTokenPayment` and can be called multiple times on the same call. + + +```mermaid +graph LR + payment-unit["()"] + payment-unit -->|esdt| EsdtTokenPayment + EsdtTokenPayment -->|esdt| MultiEsdtPayment + MultiEsdtPayment -->|esdt| MultiEsdtPayment +``` + +```rust title=contract.rs +self.tx() // tx with sc environment + .to(caller) + .esdt((esdt_token_id.clone(), nonce, amount.clone())) // a tuple with three values + .esdt(EsdtTokenPayment::new(esdt_token_id, nonce, amount)) // an EsdtTokenPayment + .transfer(); +``` + +In this example, calling `.esdt(...)` will attach an ESDT payment load to the transaction. When adding subsequent `.esdt(...)` calls, the payload automatically converts into a `multi payment`. + + + +## Single ESDT payment with references + +Sometimes we don't have ownership of the token identifier object, or amount, and we would like to avoid unnecessary clones. For this reason, we have created the `EsdtTokenPaymentRefs`, which contains references and can be used as the payment object. + +For brevity, instead of `payment(EsdtTokenPaymentRefs::new(&token_identifier, token_nonce, &amount))`, we can use `.single_esdt(&token_identifier, token_nonce, &amount)`. + +```rust title=contract.rs + #[payable] + #[endpoint] + fn send_esdt(&self, to: ManagedAddress) { + let payment = self.call_value().single(); + let half_payment = &payment.amount / 2u32; + + self.tx() + .to(&to) + .payment(PaymentRefs::new( + &payment.token_identifier, + 0, + &half_payment, + )) + .transfer(); + + self.tx() + .to(&self.blockchain().get_caller()) + .payment(PaymentRefs::new( + &payment.token_identifier, + 0, + &half_payment, + )) + .transfer(); + } +``` + +In this case, adding the ESDT token as payment through `.single_esdt(...)` gives the developer the possibility to keep using the referenced values afterwards without cloning. + + + +## Multi ESDT payment + +The framework defines the alias `type MultiEsdtPayment = ManagedVec>;`, which is how multi-esdt payments are held in memory. If we have an object of this type, we can pass it directly as payment. + +```rust +let tokens_to_claim = MultiEsdtPayment::::new(); // multiple tokens +self.tx().to(&caller).payment(tokens_to_claim).transfer(); // multi token payment +``` + +It is also possible to pass a reference or `ManagedRef` as payment, e.g.: + +```rust +// type annotation added for clarity, normally inferred +let payments: ManagedRef<'static, MultiEsdtPayment> = self.call_value().all_esdt_transfers(); +self.tx().to(&caller).payments(payments).transfer(); +``` + +The input type is enough for the `payment` method. We also have `.multi_esdt(...)`, which does the same as `payment`, but will additionally convert the argument to `MultiEsdtPayment`. For instance, sending an `EsdtTokenPayment` to `multi_esdt` will set the payment to `MultiEsdtPayment` instead of `EsdtTokenPayment`. + +It is also possible to construct a `MultiEsdtPayment` by calling `.esdt(...)` at least twice, as seen [earlier](#general-esdt-payment). + + + +## Mixed transfers + +Sometimes we don't know at compile time what kind of transfers we are going to perform. For this reason, we also provide contract call types that work with both EGLD and ESDT tokens. + + +### EGLD or single ESDT + +`EgldOrEsdtTokenPayment` allows us to decide at runtime for either EGLD or single ESDT payments. The object can be used as payment. + +```rust +// type annotation added for clarity, normally inferred +let payment: EgldOrEsdtTokenPayment = self.call_value().egld_or_single_esdt(); +self.tx().to(to).payment(payment).transfer(); +``` + + +### EGLD or single ESDT references + +We also have the type `EgldOrEsdtTokenPaymentRefs`, which contains references to the token identifier and amount. + +For brevity, instead of `payment(EgldOrEsdtTokenPaymentRefs::new(&egld_or_esdt_token_identifier, token_nonce, &amount))`, we can use `.single_esdt(&egld_or_esdt_token_identifier, token_nonce, &amount)`. + + + +### EGLD or multi-ESDT + +`EgldOrMultiEsdtPayment` allows us to decide at runtime for either EGLD or multiple ESDT payments. The object can be used as payment. + +```rust +// type annotation added for clarity, normally inferred +let payments: EgldOrMultiEsdtPayment = self.call_value().any_payment(); +self.tx().to(to).payment(payment).transfer(); +``` + + +## Normalization + +We call normalization the logic of converting transactions with an ESDT payments fields into ESDT built-in function calls. Depending on the type of payment, the generated call be to either one of: +- ESDTTransfer, +- ESDTNFTTransfer, +- MultiESDTNFTTransfer. + +For ESDTNFTTransfer and MultiESDTNFTTransfer, the recipient field also needs to be changed into the sender, and the real recipient added to the arguments. + +This operation is done automatically by the framework before sending transactions, so the developer should normally not worry about it. + +--- + +### Preparing SCs for Supernova + +The MultiversX Supernova upgrade reduces block time from **6 seconds to 0.6 seconds**, enabling sub-second blocks. While this is a major improvement, it can impact existing smart contracts, especially those relying on assumptions about timestamp behavior. + +This guide explains how to prepare your contracts for Supernova safely. + + + + +## Understand What Changes — and What Doesn’t + +All existing timestamp APIs continue returning **seconds** unless you explicitly call the millisecond versions. Nothing changes silently in the VM, the framework, or deployed contracts. + +Obviously, contracts that have the 6 seconds between blocks hardcoded will have to change. But more importantly, block times expressed in seconds no longer uniquely identify a block, which leads to potential problems. + +We are going to go through the most important patterns to look out for. + + + + +## Potential problems to look out for + + +### Replace Hardcoded Block Timing + +If your contract uses hard-coded constants (like `6 seconds` or `6000 milliseconds`) to estimate the time between blocks, this logic will need to be changed. + +It might estimate durations from nonce deltas, or vice-versa, it might estimate number of blocks by timestamps. + +**Fix:** Use the API instead: `self.blockchain().get_block_round_time_millis()`. This returns `6000` (as `DurationMillis`) today and `600` after Supernova. + + + + +### Avoid Timestamp-Based Monotonicity + +Logic such as: + +``` +require!(ts_now > last_ts) +``` + +may break because multiple blocks can share the same timestamp. + +**Fix:** Use **block nonces** for guaranteed monotonicity. + + + + +### Prevent Rate-Limit Bypasses + +If your contract allows one action “per block” but checks the difference in **seconds**, multiple blocks in the same second can bypass restrictions. + +**Fix:** Use block nonces or switch to millisecond timestamps. + + + + +### Revisit Expiration Logic + +Expiration logic written assuming a fixed block interval may accidentally allow: + +* extra blocks before expiration +* longer-than-intended windows for execution + +If your expiration logic uses seconds, double-check assumptions. + + + + +### Stop Using Timestamps as Block Identifiers + +Before Supernova, a timestamp could uniquely identify a block. +After Supernova, multiple blocks may share the same timestamp. + +If you use timestamps as map keys or identifiers: + +* collisions will occur +* data may be overwritten or skipped + +**Fix:** Use block **nonces**. + + + + +### Review Reward and Accumulation Calculations + +Reward logic that uses: + +``` +delta = ts_now - last_ts +``` + +may behave unexpectedly: + +* delta may be zero over multiple blocks +* rewards might accumulate slower or unevenly +* divisions by delta may cause division-by-zero errors + +Consider switching to milliseconds for finer granularity. + + + + +## Migration Guidance + +Just as important as fixing potential issues with Supernova, it is essential not to introduce new bugs in the process. + +Make sure to take the following into consideration when migrating: + + + + +### Seconds vs. Milliseconds + +Switching from second to millisecond timestamp can be error-prone. + +The most dangerous bug is accidentally mixing second and millisecond values, causing storage and logic corruption. + +To prevent this issue, use the [strongly typed timestamp and duration objects](time-types). + +Starting in **multiversx-sc v0.63.0**, all timestamp APIs have typed replacements: + +* `get_block_timestamp_seconds()` +* `get_block_timestamp_millis()` +* `get_prev_block_timestamp_seconds()` +* `get_prev_block_timestamp_millis()` +* `get_block_round_time_millis()` +* `epoch_start_block_timestamp_millis()` + +And avoid using raw `u64` for time values. + +Make sure to convert form `u64` to type timestamps **before** doing any other refactoring. It is much safer this way. + + + + +### Backwards compatibility + +When upgrading an existing contract from second to millisecond timestamps, it is essential to: + +* Ensure storage remains consistent +* Update ESDT metadata carefully +* Add compatibility logic if needed + +If you are unsure that this can be done safely, it might be safer to keep the contract running on second timestamps. + + + + +## Summary Checklist + +To prepare for Supernova: + +* [ ] Upgrade to `multiversx-sc v0.63.0` +* [ ] Use typed timestamp/duration APIs +* [ ] Remove all hardcoded 6-second or 6000-millisecond assumptions +* [ ] Avoid using timestamps as block identifiers +* [ ] Review expiration, reward, and accumulation logic +* [ ] Switch to millisecond timestamps where appropriate +* [ ] Ensure storage/metadata compatibility +* [ ] Update tests to use for millisecond block timestamps, where needed + +--- + +### Proxies + +## Overview + +Proxies are objects that mimic the contract, they provide methods with the same names and the same argument types. When called, they will format a transaction for the contract. So they act as translators: you can call them just like regular functions, and they will translate it for the blockchain and pass on the call. They also tell you what type the function is expected to return. + +New proxies are generated as structures. If you have the proxy, you have all its methods. The generated code is in plain sight and readable, designed to add no overhead to the contract binaries once compiled. + +Proxies can be simply copied between crates, so there is no more need for dependencies between contract crates. Proxies do not mind what their source contract code looks like or what framework version it uses. + + +## How to generate + +Proxy generation can be triggered by calling `sc-meta all proxy` in the contract root folder. This command generates the proxy of the main contract in `/output` folder with name `proxy.rs` if it has no configuration. To configure it, you need to modify sc-config.toml. + +This first image represents the structure of the project without calling a proxy generator, while the second one shows how it is called and what files it generates. + +![img](/img/tree_before_proxy.png) + +![img](/img/tree_after_proxy.png) + + +## How to set up project to re-generate easily + +In order to configure the proxy, you need to change configuration file. If this file is absent from the project directory, you have to create it. More details about how to set up a configuration file are available [here](../meta/sc-config/). + +### Path + +First, you need to specify the output path where the generated proxy file will be saved. This ensures that whenever you regenerate the proxy, the configured path will be automatically updated with the latest version. + +```toml title=sc-config.toml +[settings] + +[[proxy]] +path = "src/adder_proxy.rs" +``` + +After the proxy is generated via "sc-meta all proxy" command, you have to import the module in the contract. + +```rust title=adder.rs +#![no_std] + +use multiversx_sc::imports::*; + +pub mod adder_proxy; + +#[multiversx_sc::contract] +pub trait Adder { + ... +} +``` + +:::note +Changing the settings requires configuring the output path. +::: +### Override imports +If you need to override the proxy imports that are encapsulated in the line `use multiversx_sc::proxy_imports::*;` from proxy, you can achieve this by adding the `override_import`. + + +```toml title=sc-config.toml +[settings] + +[[proxy]] +path = "src/adder_proxy.rs" +override-import = "use multiversx_sc::abi::{ContractAbi, EndpointAbi};" +``` + +### Rename paths +If you want to rename paths from structures and enumerations in a generated proxy, use the `path-rename` setting. Specify both the original path and the new name you want. + +```toml title=sc-config.toml +[[proxy]] +path = "src/adder_proxy.rs" +[[proxy.path-rename]] +from = "adder" +to = "new_path::adder" +``` + +### Generate variant from multi-contract +To generate a proxy for a specific variant within a multi-contract project, use the `variant` setting and specify the desired variant. + +```toml title=multicontract.toml +[settings] +main = "multi-contract-main" + +[[proxy]] +variant = "multi_contract_example_feature" +path = "src/multi_contract_example_feature_proxy.rs" +``` + +### Generate custom proxy + +First, it needs to be specified that all unlabelled endpoints should **not** be added to this contract. This can be configured using: +- `add-unlabelled` + - values: `true` | `false` + - default: `true` + +```toml title=multicontract.toml +[[proxy]] +path = "src/multisig_view_proxy.rs" +add-unlabelled = false # unlabelled endpoints are not included +``` +If you want to generate a proxy for all endpoints labelled with a certain tag, you can use: +- `add-labels` + - values: `list of strings`, e.g. add-labels = ["label1", "label2"] + - default: `[]` + +The example below will create a proxy for all endpoints that are tagged with *"label(proxy_one)"* and *"label(proxy_two)"*. + +```toml title=multicontract.toml +[[proxy]] +path = "src/multisig_view_proxy.rs" +add-unlabelled = false +add-labels = ["proxy_one", "proxy_two"] +``` + +If you want to generate a proxy based on a specific list of endpoints, there is: +- `add-endpoints` + - values: `list of strings`, e.g. add-endpoints = ["endpoint_one", "endpoint_two"] + - default: `[]` + +The following example will create a proxy only with the functions specified in the list of `add-endpoints`. + +```toml title=multicontract.toml +[[proxy]] +path = "src/multisig_view_proxy.rs" +add-unlabelled = false +add-endpoints = ["init", "user_role"] +``` +The custom proxy can be generated using simultaneously both configurations: specified endpoints and endpoints labelled with a particular tag. +```toml title=multicontract.toml +[[proxy]] +path = "src/multisig_view_proxy.rs" +add-unlabelled = false +add-labels = ["proxy_one"] +add-endpoints = ["user_role"] +``` + + +## Adjustments in contracts + +Before generating a proxy, you have to change every structure and enumeration that contains `#[derive(TypeAbi)]` with `#[type_abi]`. If you do not update structures and enumerations, they will not be correctly generated in the proxy. + +Before version 0.49.0: +```rust title=lib.rs +#[derive(TopEncode, TopDecode, PartialEq, Eq, Clone, Copy, Debug, TypeAbi)] +pub enum Status { + FundingPeriod, + Successful, + Failed, +} +``` +After version 0.49.0: +```rust title=lib.rs +#[type_abi] +#[derive(TopEncode, TopDecode, PartialEq, Eq, Clone, Copy, Debug)] +pub enum Status { + FundingPeriod, + Successful, + Failed, +} +``` +If the custom type is in a private module or another crate is better to replace it because the proxy will be useless. + + + +## Diagram + +This is the diagram for how to populate the data field using proxies. For the raw setup, see [here](tx-data#diagram) + +```mermaid +graph LR + subgraph "Data (via proxies)" + data-unit["()"] + data-unit -->|typed| Proxy + Proxy -->|"init(args, ...)"| deploy + Proxy -->|"upgrade(args, ...)"| upgrade + Proxy -->|"«endpoint»(args, ...)"| fc[Function Call] + deploy -->|from_source| deploy-from-source["DeployCall<FromSource<ManagedAddress>>"] + deploy -->|code| deploy-code["DeployCall<Code<ManagedBuffer>>"] + deploy -->|code_metadata| deploy + upgrade -->|from_source| upgrade-from-source["UpgradeCall<CodeSource<ManagedAddress>>"] + upgrade -->|code| upgrade-code["UpgradeCall<Code<ManagedBuffer>>"] + upgrade -->|code_metadata| upgrade + end +``` + + + +## Original type + +Proxies have the power to define the **original type** by themselves because, when they are generated, they have access to the ABI. + +The proxy will add the type definition by calling `.original_type()`, which places an [`OriginalResultMarker`](tx-result-handlers#original-result-marker) object as the transaction result handler. This is a zero-sized type, it is only there for type inference and safety in result handlers. + + + +## NotPayable protection + +All non-payment endpoints in the generated proxy have a safeguard to prevent accidental payments. This involves automatically setting the payment to [**NotPayable**](tx-payment#notpayable). + +--- + +### Proxy architecture + +Overview of the MultiversX Proxy + + +## **Introduction** + +The MultiversX Proxy acts as an entry point into the MultiversX Network, through a set of Observer Nodes, and (partly) abstracts away the particularities and complexity of sharding. + +The Proxy is a project written in **go**, and it serves as foundation for *gateway.multiversx.com*. + +The source code of the Proxy can be found here: [mx-chain-proxy-go](https://github.com/multiversx/mx-chain-proxy-go). + + +## **Architectural Overview** + +While any Node in the Network can accept Transaction requests, the Transactions are usually submitted to the **Proxy** application, which maintains a list of Nodes - **Observers** - to forward Transaction requests to - these Observers are selected in such manner that any Transaction submitted to them will be processed by the Network **as soon and as efficiently as possible**. + +The Proxy will submit a Transaction on behalf of the user to the REST API of one of its listed Observers, selected for **(a)** being _online_ at the moment and **(b)** being located **within the Shard to which the Sender's Account belongs**. After receiving the Transaction on its REST API, that specific Observer will propagate the Transaction throughout the Network, which will lead to its execution. + +The Observer Nodes of the Proxy thus act as a **default dedicated entry point into the Network**. + +It is worth repeating here, though, that submitting a Transaction through the Proxy is completely optional - any Node of the Network will accept Transactions to propagate, given it has not disabled its REST API. + +![img](/technology/proxy-overview.png) + +Overview of the MultiversX Proxy + +In the figure above: + +1. The **MultiversX Network** - consisting of Nodes grouped within Shards. Some of these Nodes are **Observers**. +2. One or more instances of the **MultiversX Proxy** - including the official one - connect to Observer Nodes in order to forward incoming user Transactions to the Network and to query state within the Blockchain. +3. The **client applications** connect to the Network through the MultiversX Proxy. It is also possible for a blockchain-powered application to talk directly to an Observer or even to a Validator. + + +## **Official MultiversX Proxy** + +The official instance of the MultiversX Proxy is located at [https://gateway.multiversx.com](https://gateway.multiversx.com/). + + +## **Swagger docs** + +The Swagger docs of the proxy can be found at the root of the gateway. For example: [https://gateway.multiversx.com](https://gateway.multiversx.com/). + + +## **Set up a Proxy Instance** + +:::caution +Documentation for setting up a Proxy is preliminary and subject to change +::: + +In order to host a Proxy instance on a web server, one has to first clone and build the repository: + +```bash +git clone https://github.com/multiversx/mx-chain-proxy-go.git +cd elrond-proxy-go/cmd/proxy +go build . +``` + + +### **Configuration** + +The Proxy holds its configuration within the `config` folder: + +- `config.toml` - this is the main configuration file. It has to be adjusted so that the Proxy points to a list of chosen Observer Nodes. +- `external.toml` - this file holds configuration necessary to Proxy components that interact with external systems. An example of such an external system is **Elasticsearch** - currently, MultiversX Proxy requires an Elasticsearch instance to implement some of its functionality. +- `apiConfig/credentials.toml` - this file holds the configuration needed for enabling secured endpoints - only accessible by using BasicAuth. +- `apiConfig/v1_0.toml` - this file contains all the endpoints with their settings (open, secured and rate limit). + +:::note +If the provided observers' addresses from `config.toml` are not physical addresses of each observer, but rather addresses of providers that manage multiple observers for each shard, with self handling of health checks, the Proxy must be started with `--no-status-check` flag. +::: + + +## **Snapshotless observers support** + +Instead of nodes that perform regular trie operations, such as snapshots and so on, one could use snapshotless nodes, which are, as the name suggests, nodes that have a different configuration which allows them to "bypass" certain costly trie operations, with the downside of losing access to anything but real-time. + +A proxy that only needs real-time data (that is, they are not interested in historical data such as "give me block with nonce X from last month") are a very good use-case for snapshotless observers + + +### **Proxy snapshotless endpoints** + +Although there are more endpoints that can be used exclusively with snapshotless observers, here's a list with the most common ones: +- `/address/{address}` : returns data about an address (balance, nonce, username, root hash and so on) +- `/address/{address}/balance` : returns the balance of an address +- `/address/{address}/nonce` : returns the nonce of an address +- `/address/{address}/username` : returns the username of an address +- `/address/{address}/esdt` : returns all the ESDT tokens managed by an address +- `/address/{address}/esdt/{tokenIdentifier}` : returns the token data of an address with a specific token identifier +- `/address/{address}/nft/{tokenIdentifier}/nonce/{nonce}` : returns the NFT/SFT/MetaESDT data of an address for a specific token identifier and nonce +- `/address/{address}/guardian-data` : returns guardian data of an address +- `/address/{address}/keys` : returns the storage key-value pairs of an address +- `/address/{address}/key/{key}` : returns the value of a specific key under an address +- `/network/config` : returns the network configuration +- `/network/status/{shard}` : returns the status of the specific shard +- `/node/heartbeatstatus` : return the heartbeat status of the entire network's nodes +- `/transaction/send` : broadcasts a transaction to the network +- `/vm-values/int` : performing SC Queries (gas-free queries) expecting the result as int +- `/vm-values/hex` : performing SC Queries (gas-free queries) expecting the result as hex +- `/vm-values/string` : performing SC Queries (gas-free queries) expecting the result as string +- `/vm-values/query` : performing SC Queries (gas-free queries) expecting the result as raw +- and so on + +Basically, every endpoint that doesn't require historical data access can be used with snapshotless observers + + +## **Dependency on Elasticsearch** + +Currently, Proxy uses the dependency to Elasticsearch in order to satisfy the [Get Address Transactions](/sdk-and-tools/rest-api/addresses/#get-address-transactions) endpoint. + +In order to connect a Proxy instance to an Elasticsearch cluster, one must update the `external.toml` file. + +--- + +### Python SDK + +## Overview + +This page will guide you through the process of handling common tasks using the MultiversX Python SDK (libraries) **v2 (latest, stable version)**. + +:::important +This cookbook makes use of `sdk-py v2`. In order to migrate from `sdk-py v1` to `sdk-py v2`, please also follow [the migration guide](https://github.com/multiversx/mx-sdk-py/issues?q=label:migration). +::: + +:::note +All examples depicted here are captured in **(interactive) [Jupyter notebooks](https://github.com/multiversx/mx-sdk-py/blob/main/examples/Cookbook.ipynb)**. +::: + +We are going to use the [multiversx-sdk-py](https://github.com/multiversx/mx-sdk-py) package. This package can be installed directly from GitHub or from [**PyPI**](https://pypi.org/project/multiversx-sdk/). + + + +## Creating an Entrypoint + +The Entrypoint represents a network client that makes the most common operations easily accessible. We have an entrypoint for each network: `MainnetEntrypoint`, `DevnetEntrypoint`, `TestnetEntrypoint` and `LocalnetEntrypoint`. For example, this is how to create a Devnet entrypoint: + +```py +from multiversx_sdk import DevnetEntrypoint + +entrypoint = DevnetEntrypoint() +``` + +If we want to create an entrypoint that uses a third party API, we can do so as follows: + +```py +from multiversx_sdk import DevnetEntrypoint + +entrypoint = DevnetEntrypoint(url="https://custom-multiversx-devnet-api.com") +``` + +By default, an Entrypoint, in our case the `DevnetEntrypoint`, uses the API, but we can also create a custom one that interacts with the proxy. + +```py +from multiversx_sdk import DevnetEntrypoint + +custom_entrypoint = DevnetEntrypoint(url="https://devnet-gateway.multiversx.com", kind="proxy") +``` + +We can create an entrypoint from a network provider. + +```py +from multiversx_sdk import NetworkEntrypoint, ApiNetworkProvider + +api = ApiNetworkProvider("https://devnet-api.multiversx.com") +entrypoint = NetworkEntrypoint.new_from_network_provider(network_provider=api, chain_id="D") +``` + +## Creating Accounts + +We can create an account directly from the entrypoint. Keep in mind that the account you create is network agnostic, it does not matter which entrypoint is used. + +The account can be used for signing and for storing the nonce of the account. It can also be saved to a `pem` or `keystore` file. + +```py +from multiversx_sdk import DevnetEntrypoint + +entrypoint = DevnetEntrypoint() +account = entrypoint.create_account() +``` + +There are also other ways to instantiate an `Account`. + +#### Instantiating an Account using a secret key + +```py +from multiversx_sdk import Account, UserSecretKey + +secret_key_hex = "413f42575f7f26fad3317a778771212fdb80245850981e48b58a4f25e344e8f9" +secret_key = UserSecretKey(bytes.fromhex(secret_key_hex)) + +account = Account(secret_key) +``` + +#### Instantiating an Account from a PEM file + +```py +from pathlib import Path +from multiversx_sdk import Account + +account = Account.new_from_pem(Path("../multiversx_sdk/testutils/testwallets/alice.pem")) +``` + +#### Instantiating an Account from a Keystore file + +```py +from pathlib import Path +from multiversx_sdk import Account + +account = Account.new_from_keystore( + file_path=Path("../multiversx_sdk/testutils/testwallets/alice.json"), + password="password" +) +``` + +#### Instantiating an Account from a mnemonic + +```py +from multiversx_sdk import Account, Mnemonic + +mnemonic = Mnemonic.generate() +account = Account.new_from_mnemonic(mnemonic.get_text()) +``` + +#### Instantiating an Account from a KeyPair + +```py +from multiversx_sdk import Account, KeyPair + +keypair = KeyPair.generate() +account = Account.new_from_keypair(keypair) +``` + +### Managing the Account nonce + +The account has a `nonce` property that the user is responsible for keeping up to date. We can fetch the nonce of the account from the network once and then we can increment it with each transaction we create. Each transaction sent **must** have the correct nonce set, otherwise it will not be executed. For more details check out the [Creating Transactions](#creating-transactions) section below. + +```py +from multiversx_sdk import Account, DevnetEntrypoint, UserSecretKey + +secret_key_hex = "413f42575f7f26fad3317a778771212fdb80245850981e48b58a4f25e344e8f9" +secret_key = UserSecretKey(bytes.fromhex(secret_key_hex)) + +account = Account(secret_key) + +entrypoint = DevnetEntrypoint() +account.nonce = entrypoint.recall_account_nonce(account.address) + +# create any sort of transaction +... + +# When needed, we can get the nonce and increment it +nonce = account.get_nonce_then_increment() +``` + +### Saving the Account to a file + +We can save the account to either a `pem` file or a `keystore` file. **We discourage the use of PEM wallets for storing cryptocurrencies due to their lower security level.** However, they prove to be highly convenient and user-friendly for application testing purposes. + +#### Saving the Account for a PEM file + +```py +from pathlib import Path +from multiversx_sdk import Account, UserSecretKey + +secret_key_hex = "413f42575f7f26fad3317a778771212fdb80245850981e48b58a4f25e344e8f9" +secret_key = UserSecretKey(bytes.fromhex(secret_key_hex)) + +account = Account(secret_key) +account.save_to_pem(path=Path("wallet.pem")) +``` + +#### Saving the Account to a Keystore file + +```py +from pathlib import Path +from multiversx_sdk import Account, UserSecretKey + +secret_key_hex = "413f42575f7f26fad3317a778771212fdb80245850981e48b58a4f25e344e8f9" +secret_key = UserSecretKey(bytes.fromhex(secret_key_hex)) + +account = Account(secret_key) +account.save_to_keystore(path=Path("keystoreWallet.json"), password="password") +``` + +### Ledger Account + +It is possible to use a Ledger Device to manage your account. The Ledger account allows you to sign both transactions and messages, but it can also store the nonce of the account. + +By default, the package does not include all the dependencies required to communicate with a Ledger device. To enable Ledger support, install the package with the following command: + +```sh +pip install multiversx-sdk[ledger] +``` + +This will install the necessary dependencies for interacting with a Ledger device. + +When instantiating a `LedgerAccount`, the index of the address that will be used should be provided. By default, the index `0` is used. + +```py +from multiversx_sdk import LedgerAccount + +account = LedgerAccount() +``` + +When signing transactions with a Ledger device, the transaction details will appear on the device, awaiting your confirmation. The same process applies when signing messages. + +Both **Account** and **LedgerAccount** are compatible with the **IAccount** interface and can be used wherever the interface is expected (e.g. in transaction controllers). + +## Calling the Faucet + +This functionality is not yet available through the entrypoint, but we recommend using the faucet available within the Web Wallet. + +- [Testnet Wallet](https://testnet-wallet.multiversx.com/) +- [Devnet Wallet](https://devnet-wallet.multiversx.com/) + +## Interacting with the network + +The entrypoint exposes a few methods to directly interact with the network, such as: + +- `recall_account_nonce(address: Address) -> int;` +- `send_transaction(transaction: Transaction) -> bytes;` +- `send_transactions(transactions: list[Transaction]) -> tuple[int, list[bytes]];` +- `get_transaction(tx_hash: str | bytes) -> TransactionOnNetwork;` +- `await_transaction_completed(tx_hash: str | bytes) -> TransactionOnNetwork;` + +Some other methods are exposed through a so called network provider. There are two types of network providers: ApiNetworkProvider and ProxyNetworkProvider. The ProxyNetworkProvider interacts directly with the proxy of an observing squad. The ApiNetworkProvider, as the name suggests, interacts with the API, that is a layer over the proxy. It fetches data from the network but also from Elastic Search. + +To get the underlying network provider from our entrypoint, we can do as follows: + +```py +from multiversx_sdk import DevnetEntrypoint + +entrypoint = DevnetEntrypoint() +api = entrypoint.create_network_provider() +``` + +## Creating a network provider + +Additionally, when manually instantiating a network provider, a config can be provided to specify the client name and set custom request options. + +```py +from multiversx_sdk import NetworkProviderConfig, ApiNetworkProvider + +config = NetworkProviderConfig( + client_name="hello-multiversx", + requests_options={ + "timeout": 1, + "auth": ("user", "password") + } +) + +api = ApiNetworkProvider(url="https://devnet-api.multiversx.com", config=config) +``` + +The network providers support a retry mechanism for failing requests. If you'd like to change the default values you can do so as follows: + +```py +from multiversx_sdk import NetworkProviderConfig, ApiNetworkProvider, RequestsRetryOptions + +retry_options = RequestsRetryOptions( + retries=5, + backoff_factor=0.1, + status_forcelist=[500, 502, 503] +) + +config = NetworkProviderConfig( + client_name="hello-multiversx", + requests_options={ + "timeout": 1, + "auth": ("user", "password") + }, + requests_retry_options=retry_options, +) + +api = ApiNetworkProvider(url="https://devnet-api.multiversx.com", config=config) +``` + +A list of all the available methods from the `ApiNetworkProviders` can be found [here](https://multiversx.github.io/mx-sdk-py/multiversx_sdk.network_providers.html#module-multiversx_sdk.network_providers.api_network_provider). + +Both the `ApiNetworkProvider` and the `ProxyNetworkProvider` implement a common interface, that can be seen [here](https://multiversx.github.io/mx-sdk-py/multiversx_sdk.network_providers.html#multiversx_sdk.network_providers.interface.INetworkProvider). Therefore, the two network providers can be used interchangeably. + +The classes returned by the API have the most used fields easily accessible, but each object has a `raw` field where the raw API response is stored in case some other fields are needed. + +## Fetching data from the network + +### Fetching the network config + +```py +from multiversx_sdk import DevnetEntrypoint + +entrypoint = DevnetEntrypoint() +api = entrypoint.create_network_provider() + +network_config = api.get_network_config() +``` + +### Fetching the network status + +The status is fetched by default from the metachain, but a specific shard number can be provided. + +```py +from multiversx_sdk import DevnetEntrypoint + +entrypoint = DevnetEntrypoint() +api = entrypoint.create_network_provider() + +network_status = api.get_network_status() # fetches status from metachain +network_status = api.get_network_status(shard=1) # fetches status from shard 1 +``` + +### Fetching a block from the network + +We instantiate the args and we are going to fetch the block using it's hash. The `API` only supports fetching blocks by hash, while the `PROXY` can fetch blocks by hash or by nonce. Keep in mind, that for the `PROXY` the shard should also be specified in the arguments. + +#### Fetching a block using the API + +```py +from multiversx_sdk import ApiNetworkProvider + +api = ApiNetworkProvider("https://devnet-api.multiversx.com") + +block_hash="1147e111ce8dd860ae43a0f0d403da193a940bfd30b7d7f600701dd5e02f347a" +block = api.get_block(block_hash=block_hash) +``` + +Additionally, we can fetch the latest block from the network: + +```py +from multiversx_sdk import ApiNetworkProvider + +api = ApiNetworkProvider("https://devnet-api.multiversx.com") +latest_block = api.get_latest_block() +``` + +#### Fetching a block using the PROXY + +When using the proxy, we have to provide the shard, as well. + +```py +from multiversx_sdk import ProxyNetworkProvider + +proxy = ProxyNetworkProvider("https://devnet-gateway.multiversx.com") + +block_hash="1147e111ce8dd860ae43a0f0d403da193a940bfd30b7d7f600701dd5e02f347a" +block = proxy.get_block(shard=1, block_hash=block_hash) +``` + +We can also fetch the latest block from the network. The default shard will be the metachain, but we can specify a shard to fetch the latest block from. + +```py +from multiversx_sdk import ProxyNetworkProvider + +proxy = ProxyNetworkProvider("https://devnet-gateway.multiversx.com") +block = proxy.get_latest_block() +``` + +### Fetching an account + +To fetch an account we'll need its address. Once we have the address, we simply create an `Address` object and pass it as an argument to the method. + +```py +from multiversx_sdk import Address, DevnetEntrypoint + +entrypoint = DevnetEntrypoint() +api = entrypoint.create_network_provider() + +alice = Address.new_from_bech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th") +account = api.get_account(address=alice) +``` + +### Fetching an account's storage + +We can also fetch an account's storage, which means we can get all the key-value pairs saved for an account. + +```py +from multiversx_sdk import Address, DevnetEntrypoint + +entrypoint = DevnetEntrypoint() +api = entrypoint.create_network_provider() + +address = Address.new_from_bech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th") +account = api.get_account_storage(address=address) +``` + +If we only want a specific key, we can fetch it as follows: + +```py +from multiversx_sdk import Address, DevnetEntrypoint + +entrypoint = DevnetEntrypoint() +api = entrypoint.create_network_provider() + +address = Address.new_from_bech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th") +account = api.get_account_storage_entry(address=address, entry_key="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 are waiting for external funds to be sent to Alice, allowing her to then transfer the required amount to another recipient. + +We need to define our condition that will be checked each time the account is fetched from the network. For this, we create a function that takes as an argument an `AccountOnNetwork` object and returns a `bool`. + +Keep in mind that, this method has a default timeout that can be adjusted using the `AwaitingOptions` class. + +```py +from multiversx_sdk import Address, AccountOnNetwork, DevnetEntrypoint + +entrypoint = DevnetEntrypoint() +api = entrypoint.create_network_provider() + + +def condition_to_be_satisfied(account: AccountOnNetwork) -> bool: + return account.balance >= 7000000000000000000 # 7 EGLD + + +alice = Address.new_from_bech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th") +account = api.await_account_on_condition(address=alice, condition=condition_to_be_satisfied) +``` + +### Sending and simulating transactions + +In order for our transactions to be executed, we use the network providers to broadcast them to the network. Keep in mind that, in order for transactions to be processed they need to be signed. + +#### Sending a transaction + +```py +from multiversx_sdk import Address, DevnetEntrypoint, Transaction + +entrypoint = DevnetEntrypoint() +api = entrypoint.create_network_provider() + +alice = Address.new_from_bech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th") +bob = Address.new_from_bech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx") + +transaction = Transaction( + sender=alice, + receiver=bob, + gas_limit=50000, + chain_id="D" +) + +# set correct nonce and sign the transaction +... + +# broadcast the transaction to the network +transaction_hash = api.send_transaction(transaction) +``` + +#### Sending multiple transactions + +```py +from multiversx_sdk import Address, DevnetEntrypoint, Transaction + +entrypoint = DevnetEntrypoint() +api = entrypoint.create_network_provider() + +alice = Address.new_from_bech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th") +bob = Address.new_from_bech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx") + +first_transaction = Transaction( + sender=alice, + receiver=bob, + gas_limit=50000, + chain_id="D", + nonce=2 +) +# set correct nonce and sign the transaction +... + +second_transaction = Transaction( + sender=bob, + receiver=alice, + gas_limit=50000, + chain_id="D", + nonce=1 +) +# set correct nonce and sign the transaction +... + +third_transaction = Transaction( + sender=alice, + receiver=alice, + gas_limit=60000, + chain_id="D", + nonce=3, + data=b"hello" +) +# set correct nonce and sign the transaction +... + +# broadcast the transactions to the network +num_of_txs, hashes = api.send_transactions([first_transaction, second_transaction, third_transaction]) +``` + +#### Simulating transactions + +A transaction can be simulated before being sent to be processed by the network. It is mostly used for smart contract calls to see what smart contract results are produced. + +```py +from multiversx_sdk import Address, DevnetEntrypoint, Transaction + +entrypoint = DevnetEntrypoint() +api = entrypoint.create_network_provider() + +alice = Address.new_from_bech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th") +contract = Address.new_from_bech32("erd1qqqqqqqqqqqqqpgqccmyzj9sade2495w78h42erfrw7qmqxpd8sss6gmgn") + +transaction = Transaction( + sender=alice, + receiver=contract, + gas_limit=5000000, + chain_id="D", + nonce=entrypoint.recall_account_nonce(alice), # nonce needs to be properly set + data=b"add@07", + signature=b'0' * 64, # signature is not checked by default, but a dummy value must be provided +) +transaction_on_network = api.simulate_transaction(transaction) +``` + +#### Estimating the gas cost of a transaction + +Before sending a transaction to the network to be processed, one can get the estimated gas limit that is required for the transaction to be executed. + +```py +from multiversx_sdk import Address, DevnetEntrypoint, Transaction + +entrypoint = DevnetEntrypoint() +api = entrypoint.create_network_provider() + +alice = Address.new_from_bech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th") +contract = Address.new_from_bech32("erd1qqqqqqqqqqqqqpgqccmyzj9sade2495w78h42erfrw7qmqxpd8sss6gmgn") + +nonce = entrypoint.recall_account_nonce(alice) + +transaction = Transaction( + sender=alice, + receiver=contract, + gas_limit=5000000, + chain_id="D", + data=b"add@07", + nonce=nonce +) + +transaction_cost_response = api.estimate_transaction_cost(transaction) +``` + +### Waiting for transaction completion + +After sending a transaction, we may want to wait until the transaction is processed in order to proceed with another action. Keep in mind that, this method has a default timeout that can be adjusted using the `AwaitingOptions` class. + +```py +from multiversx_sdk import DevnetEntrypoint + +entrypoint = DevnetEntrypoint() +api = entrypoint.create_network_provider() + +tx_hash = "exampletransactionhash" +transaction_on_network = api.await_transaction_completed(transaction_hash=tx_hash) +``` + +### Waiting for a transaction to specify a condition + +Similar to accounts, we can wait until a transaction satisfies a specific condition. + +```py +from multiversx_sdk import DevnetEntrypoint, TransactionOnNetwork + +entrypoint = DevnetEntrypoint() +api = entrypoint.create_network_provider() + + +def condition_to_be_satisfied(transaction_on_network: TransactionOnNetwork) -> bool: + # can be the creation of an event or something else + ... + + +tx_hash = "exampletransactionhash" +transaction_on_network = api.await_transaction_on_condition(transaction_hash=tx_hash, condition=condition_to_be_satisfied) +``` + +### Fetching transactions from the network + +After sending transactions, we can fetch the transactions from the network. To do so, we need the transaction hash that we got after broadcasting the transaction. + +```py +from multiversx_sdk import DevnetEntrypoint + +entrypoint = DevnetEntrypoint() +api = entrypoint.create_network_provider() + +tx_hash = "exampletransactionhash" +transaction_on_network = api.get_transaction(tx_hash) +``` + +### Fetching a token from an account + +We can fetch a specific token (ESDT, MetaESDT, SFT, NFT) of an account by providing the address and the token. + +```py +from multiversx_sdk import Address, DevnetEntrypoint, Token + +entrypoint = DevnetEntrypoint() +api = entrypoint.create_network_provider() + +alice = Address.new_from_bech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th") + +# these tokens are just for the example, they do not belong to Alice +token = Token(identifier="TEST-123456") # ESDT +token_on_network = api.get_token_of_account(address=alice, token=token) + +token = Token(identifier="NFT-987654", nonce=11) # NFT +token_on_network = api.get_token_of_account(address=alice, token=token) +``` + +### Fetching all fungible tokens of an account + +Fetches all fungible tokens held by an account. This method does not handle pagination, that can be achieved by using `do_get_generic`. + +```py +from multiversx_sdk import Address, DevnetEntrypoint + +entrypoint = DevnetEntrypoint() +api = entrypoint.create_network_provider() + +alice = Address.new_from_bech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th") +fungible_tokens = api.get_fungible_tokens_of_account(address=alice) +``` + +### Fetching all non-fungible tokens of an account + +Fetches all non-fungible tokens held by an account. This method does not handle pagination, but can be achieved by using `do_get_generic`. + +```py +from multiversx_sdk import Address, DevnetEntrypoint + +entrypoint = DevnetEntrypoint() +api = entrypoint.create_network_provider() + +alice = Address.new_from_bech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th") +nfts = api.get_non_fungible_tokens_of_account(address=alice) +``` + +### Fetching token metadata + +If we want to fetch the metadata of a token, like `owner`, `decimals` and so on, we can use the following methods: + +```py +from multiversx_sdk import DevnetEntrypoint + +entrypoint = DevnetEntrypoint() +api = entrypoint.create_network_provider() + +# used for ESDT +fungible_token_definition = api.get_definition_of_fungible_token(token_identifier="TEST-123456") + +# used for MetaESDT, SFT, NFT +non_fungible_token_definition = api.get_definition_of_tokens_collection( + collection_name="NFT-987654") +``` + +### Querying Smart Contracts + +Smart contract queries or view functions, are endpoints of a contract that only read data from the contract. To send a query to the observer nodes, we can proceed as follows: + +```py +from multiversx_sdk import Address, DevnetEntrypoint, SmartContractQuery + +entrypoint = DevnetEntrypoint() +api = entrypoint.create_network_provider() + +query = SmartContractQuery( + contract=Address.new_from_bech32("erd1qqqqqqqqqqqqqpgq076flgeualrdu5jyyj60snvrh7zu4qrg05vqez5jen"), + function="getSum", + arguments=[] +) +response = api.query_contract(query=query) +``` + +### Custom Api/Proxy calls + +The methods exposed by the `ApiNetworkProvider` or `ProxyNetworkProvider` are the most common and used ones. There might be times when custom API calls are needed. For that we have createad generic methods for both `GET` and `POST` requests. + +Let's assume we want to get all the transactions that are sent by Alice where the `delegate` function was called. + +```py +from multiversx_sdk import Address, DevnetEntrypoint + +entrypoint = DevnetEntrypoint() +api = entrypoint.create_network_provider() + +alice = Address.new_from_bech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th") +url_params = { + "sender": alice.to_bech32(), + "function": "delegate" +} + +transactions = api.do_get_generic(url="transactions", url_parameters=url_params) +``` + +## Creating transactions + +In this section, we'll learn how to create different types of transactions. For creating transactions, we can use `controllers` or `factories`. The `controllers` can be used for scripts or quick network interactions, while the `factories` provide a more granular and lower-level approach, usually needed for DApps. Usually, the `controllers` use the same parameters as the `factories` but also take an `Account` and the `nonce` of the sender as arguments. The `controllers` also hold some extra functionality, like waiting for transaction completion and parsing transactions. The same functionality can be obtained for transactions built using the `factories` as well, we'll see how in the sections below. In the following section we'll learn how to create transactions using both. + +### Instantiating controllers and factories + +There are two ways to create controllers and factories: the first one is to get them from the entrypoint and the second one is to manually create them. + +```py +from multiversx_sdk import DevnetEntrypoint, TransfersController, TransferTransactionsFactory, TransactionsFactoryConfig + +entrypoint = DevnetEntrypoint() + +# getting the controller and the factory from the entrypoint +transfers_controller = entrypoint.create_transfers_controller() +transfers_factory = entrypoint.create_transfers_transactions_factory() + +# manually instantiating the controller and the factory +controller = TransfersController(chain_id="D") + +config = TransactionsFactoryConfig(chain_id="D") +factory = TransferTransactionsFactory(config=config) +``` + +### Estimating the Gas Limit for a Transaction + +When creating transaction factories or controllers, we can pass an additional argument, a **gas limit estimator**. This gas estimator simulates the transaction before being sent and computes the `gasLimit` that it will require. The `GasLimitEstimator` can be initialized with a multiplier, so that the estimated value will be multiplied by the specified value. It is recommended to use a small multiplier (e.g. 1.1) to cover any possible changes that may occur from the time the transaction is simulated to the time it is actually sent and processed on-chain. The gas limit estimator can be provided to any factory or controller available. Let's see how we can create a `GasLimitEstimator` and use it. + +```py +from multiversx_sdk import ApiNetworkProvider, GasLimitEstimator, TransferTransactionsFactory, TransactionsFactoryConfig + +api = ApiNetworkProvider("https://devnet-api.multiversx.com") +gas_estimator = GasLimitEstimator(network_provider=api) # create a gas limit estimator with default multiplier of 1.0 +gas_estimator = GasLimitEstimator(network_provider=api, gas_multiplier=1.1) # create a gas limit estimator with a multiplier of 1.1 + +config = TransactionsFactoryConfig(chain_id="D") +transfers_factory = TransferTransactionsFactory(config=config, gas_limit_estimator=gas_estimator) +``` + +Also, factories or controllers created through the entrypoints can use the `GasLimitEstimator` as well: + +```py +from multiversx_sdk import DevnetEntrypoint + +entrypoint = DevnetEntrypoint(with_gas_limit_estimator=True, gas_limit_multiplier=1.1) + +transfers_controller = entrypoint.create_transfers_controller() # will create the controller using the GasLimitEstimator with a 1.1 multiplier +transfers_factory = entrypoint.create_transfers_transactions_factory() # will create the factory using the GasLimitEstimator with a 1.1 multiplier +``` + +### Token transfers + +We can send native tokens (EGLD) and ESDT tokens using both the `controller` and the `factory`. + +#### Native token transfers using the controller + +Because we'll use an `Account`, the transaction will be signed. + +```py +from pathlib import Path +from multiversx_sdk import Account, DevnetEntrypoint + +entrypoint = DevnetEntrypoint() + +account = Account.new_from_keystore( + file_path=Path("../multiversx_sdk/testutils/testwallets/withDummyMnemonic.json"), + password="password", + address_index=0 +) +# the developer is responsible for managing the nonce +account.nonce = entrypoint.recall_account_nonce(account.address) + +transfers_controller = entrypoint.create_transfers_controller() +transaction = transfers_controller.create_transaction_for_transfer( + sender=account, + nonce=account.get_nonce_then_increment(), + receiver=Address.new_from_bech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"), + native_transfer_amount=1000000000000000000, # 1 EGLD +) + +tx_hash = entrypoint.send_transaction(transaction) +``` + +If you know you'll only send native tokens, the same transaction can be created using the `create_transaction_for_native_token_transfer` method. + +#### Native token transfers using the factory + +Because we only use the address of the sender, the transactions are not going to be signed or have the nonce field set properly. This should be taken care after the transaction is created. + +```py +from pathlib import Path +from multiversx_sdk import Account, DevnetEntrypoint + +entrypoint = DevnetEntrypoint() +factory = entrypoint.create_transfers_transactions_factory() + +alice = Account.new_from_keystore( + file_path=Path("../multiversx_sdk/testutils/testwallets/withDummyMnemonic.json"), + password="password", + address_index=0 +) +# the developer is responsible for managing the nonce +alice.nonce = entrypoint.recall_account_nonce(alice.address) + +bob = Address.new_from_bech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx") + +transaction = factory.create_transaction_for_transfer( + sender=alice.address, + receiver=bob, + native_amount=1000000000000000000 # 1 EGLD +) +# set the sender's nonce +transaction.nonce = alice.get_nonce_then_increment() + +# sign the transaction using the sender's account +transaction.signature = alice.sign_transaction(transaction) + +tx_hash = entrypoint.send_transaction(transaction) +``` + +If you know you'll only send native tokens, the same transaction can be created using the `create_transaction_for_native_token_transfer` method. + +#### Custom token transfers using the controller + +```py +from pathlib import Path +from multiversx_sdk import Account, DevnetEntrypoint, Token, TokenTransfer + +entrypoint = DevnetEntrypoint() + +alice = Account.new_from_keystore( + file_path=Path("../multiversx_sdk/testutils/testwallets/withDummyMnemonic.json"), + password="password", + address_index=0 +) +# the developer is responsible for managing the nonce +alice.nonce = entrypoint.recall_account_nonce(alice.address) + +esdt = Token(identifier="TEST-123456") +first_transfer = TokenTransfer(token=esdt, amount=1000000000) + +nft = Token(identifier="NFT-987654", nonce=10) +second_transfer = TokenTransfer(token=nft, amount=1) # when sending NFTs we set the amount to `1` + +sft = Token(identifier="SFT-123987", nonce=10) +third_transfer = TokenTransfer(token=nft, amount=7) + +transfers_controller = entrypoint.create_transfers_controller() +transaction = transfers_controller.create_transaction_for_transfer( + sender=alice, + nonce=alice.get_nonce_then_increment(), + receiver=Address.new_from_bech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"), + token_transfers=[first_transfer, second_transfer, third_transfer] +) + +tx_hash = entrypoint.send_transaction(transaction) +``` + +If you know you'll only send ESDT tokens, the same transaction can be created using `create_transaction_for_esdt_token_transfer`. + +#### Custom token transafers using the factory + +Because we only use the address of the sender, the transactions are not going to be signed or have the nonce field set properly. This should be taken care after the transaction is created. + +```py +from pathlib import Path + +from multiversx_sdk import Account, DevnetEntrypoint, Token, TokenTransfer + +entrypoint = DevnetEntrypoint() + +alice = Account.new_from_keystore( + file_path=Path("../multiversx_sdk/testutils/testwallets/withDummyMnemonic.json"), + password="password", + address_index=0 +) +# the developer is responsible for managing the nonce +alice.nonce = entrypoint.recall_account_nonce(alice.address) + +bob = Address.new_from_bech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx") + +esdt = Token(identifier="TEST-123456") # fungible tokens don't have nonce +first_transfer = TokenTransfer(token=esdt, amount=1000000000) # we set the desired amount we want to send + +nft = Token(identifier="NFT-987654", nonce=10) +second_transfer = TokenTransfer(token=nft, amount=1) # when sending NFTs we set the amount to `1` + +sft = Token(identifier="SFT-123987", nonce=10) +third_transfer = TokenTransfer(token=nft, amount=7) # for SFTs we set the desired amount we want to send + +factory = entrypoint.create_transfers_transactions_factory() +transaction = factory.create_transaction_for_transfer( + sender=alice.address, + receiver=bob, + token_transfers=[first_transfer, second_transfer, third_transfer] +) + +# set the sender's nonce +transaction.nonce = alice.get_nonce_then_increment() + +# sign the transaction using the sender's account +transaction.signature = alice.sign_transaction(transaction) + +tx_hash = entrypoint.send_transaction(transaction) +``` + +If you know you'll only send ESDT tokens, the same transaction can be created using `create_transaction_for_esdt_token_transfer`. + +#### Sending native and custom tokens + +Also, sending both native and custom tokens is now supported. If a `native_amount` is provided together with `token_transfers`, the native token will also be included in the `MultiESDTNFTTrasfer` built-in function call. + +We can send both types of tokens using either the `controller` or the `factory`, but we'll use the controller for the sake of simplicity. + +```py +from pathlib import Path +from multiversx_sdk import Account, DevnetEntrypoint, Token, TokenTransfer + +entrypoint = DevnetEntrypoint() + +account = Account.new_from_keystore( + file_path=Path("../multiversx_sdk/testutils/testwallets/withDummyMnemonic.json"), + password="password", + address_index=0 +) +# the developer is responsible for managing the nonce +account.nonce = entrypoint.recall_account_nonce(account.address) + +esdt = Token(identifier="TEST-123456") +first_transfer = TokenTransfer(token=esdt, amount=1000000000) + +nft = Token(identifier="NFT-987654", nonce=10) +second_transfer = TokenTransfer(token=nft, amount=1) + +transfers_controller = entrypoint.create_transfers_controller() +transaction = transfers_controller.create_transaction_for_transfer( + sender=account, + nonce=account.get_nonce_then_increment(), + receiver=Address.new_from_bech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"), + native_transfer_amount=1000000000000000000, # 1 EGLD + token_transfers=[first_transfer, second_transfer] +) + +tx_hash = entrypoint.send_transaction(transaction) +``` + +### Decoding transaction data + +For example, when sending multiple ESDT and NFT tokens, the receiver field of the transaction is the same as the sender field and also the value is set to `0` because all the information is encoded in the `data` field of the transaction. + +For decoding the data field we have a so called `TransactionDecoder`. We fetch the transaction from the network and then use the decoder. + +```py +from multiversx_sdk import DevnetEntrypoint, TransactionDecoder + +entrypoint = DevnetEntrypoint() +transaction = entrypoint.get_transaction("3e7b39f33f37716186b6ffa8761d066f2139bff65a1075864f612ca05c05c05d") + +decoder = TransactionDecoder() +decoded_transaction = decoder.get_transaction_metadata(transaction) + +print(decoded_transaction.to_dict()) +``` + +### Smart Contracts + +#### Contract ABIs + +A contract's ABI describes the endpoints, data structure and events that a contract exposes. While contract interactions are possible without the ABI, they are easier to implement when the definitions are available. + +##### Loading the ABI from a file + +```py +from pathlib import Path +from multiversx_sdk.abi import Abi + +abi = Abi.load(Path("./contracts/adder.abi.json")) +``` + +#### Manually construct the ABI + +If an ABI file isn't directly available, but you do have knowledge of the contract's endpoints and types, you can manually construct the ABI. + +```py +from multiversx_sdk.abi import Abi, AbiDefinition + +abi_definition = AbiDefinition.from_dict({ + "endpoints": [{ + "name": "add", + "inputs": [ + { + "name": "value", + "type": "BigUint" + } + ], + "outputs": [] + }] +}) + +abi = Abi(definition=abi_definition) +``` + +### Smart Contract deployments + +For creating smart contract deploy transactions, we have two options, as well: a `controller` and a `factory`. Both of these are similar to the ones presented above for transferring tokens. + +When creating transactions that interact with smart contracts, we should provide the ABI file to the `controller` or `factory` if possible, so we can pass the arguments as native values. If the abi is not provided and we know what types the contract expects, we can pass the arguments as `typed values` (ex: BigUIntValue, ListValue, StructValue, etc.) or `bytes`. + +#### Deploying a smart contract using the controller + +```py +from pathlib import Path + +from multiversx_sdk import Account, DevnetEntrypoint +from multiversx_sdk.abi import Abi, BigUIntValue + +# prepare the account +account = Account.new_from_keystore( + file_path=Path("../multiversx_sdk/testutils/testwallets/withDummyMnemonic.json"), + password="password", + address_index=0 +) +# the developer is responsible for managing the nonce +account.nonce = entrypoint.recall_account_nonce(account.address) + +# load the abi file +abi = Abi.load(Path("contracts/adder.abi.json")) + +# get the smart contracts controller +entrypoint = DevnetEntrypoint() +controller = entrypoint.create_smart_contract_controller(abi=abi) + +# load the contract bytecode +bytecode = Path("contracts/adder.wasm").read_bytes() + +# For deploy arguments, use typed value objects if you haven't provided an ABI +args = [BigUIntValue(42)] +# Or use simple, plain Python values and objects if you have provided an ABI +args = [42] + +deploy_transaction = controller.create_transaction_for_deploy( + sender=account, + nonce=account.get_nonce_then_increment(), + bytecode=bytecode, + gas_limit=5000000, + arguments=args, + is_upgradeable=True, + is_readable=True, + is_payable=True, + is_payable_by_sc=True +) + +# broadcasting the transaction +tx_hash = entrypoint.send_transaction(deploy_transaction) +``` + +When creating transactions using `SmartContractController` or `SmartContractTransactionsFactory`, even if the ABI is available and provided, you can still use _typed value_ objects as arguments for deployments and interactions. + +Even further, you can use a mix of typed value objects and plain Python values and objects. For example: +```py +args = [U32Value(42), "hello", { "foo": "bar" }, TokenIdentifierValue("TEST-123456")] +``` + +#### Parsing contract deployment transactions + +After broadcasting the transaction, we can wait for it's execution to be completed and parse the processed transaction to extract the address of newly deployed smart contract. + +```py +# we use the transaction hash we got when broadcasting the transaction +contract_deploy_outcome = controller.await_completed_deploy(tx_hash) # waits for transaction completion and parses the result +contract_address = contract_deploy_outcome.contracts[0].address +print(contract_address.to_bech32()) +``` + +If we want to wait for transaction completion and parse the result in two different steps, we can do as follows: + +```py +# we use the transaction hash we got when broadcasting the transaction +# waiting for transaction completion +transaction_on_network = entrypoint.await_transaction_completed(tx_hash) + +# parsing the transaction +contract_deploy_outcome = controller.parse_deploy(transaction_on_network) +``` + +#### Computing the smart 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: + +```py +from multiversx_sdk import Address, AddressComputer + +# we used Alice for deploying the contract, so we are using her address +alice = Address.new_from_bech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th") + +address_computer = AddressComputer() +contract_address = address_computer.compute_contract_address( + deployer=alice, + deployment_nonce=deploy_transaction.nonce # the same nonce we set on the deploy transaction +) + +print("Contract address:", contract_address.to_bech32()) +``` + +#### 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. + +```py +from pathlib import Path + +from multiversx_sdk import Address, DevnetEntrypoint, SmartContractTransactionsOutcomeParser +from multiversx_sdk.abi import Abi, BigUIntValue + + +# load the abi file +abi = Abi.load(Path("contracts/adder.abi.json")) + +# get the smart contracts transaction factory +entrypoint = DevnetEntrypoint() +factory = entrypoint.create_smart_contract_transactions_factory(abi=abi) + +# load the contract bytecode +bytecode = Path("contracts/adder.wasm").read_bytes() + +# For deploy arguments, use typed value objects if you haven't provided an ABI to the factory: +args = [BigUIntValue(42)] +# Or use simple, plain Python values and objects if you have provided an ABI to the factory: +args = [42] + +alice_address = Address.new_from_bech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th") + +deploy_transaction = factory.create_transaction_for_deploy( + sender=alice_address, + bytecode=bytecode, + gas_limit=5000000, + arguments=args, + is_upgradeable=True, + is_readable=True, + is_payable=True, + is_payable_by_sc=True +) + +# load the account +alice = Account.new_from_keystore( + file_path=Path("../multiversx_sdk/testutils/testwallets/withDummyMnemonic.json"), + password="password", + address_index=0 +) +# the developer is responsible for managing the nonce +alice.nonce = entrypoint.recall_account_nonce(alice.address) + +# set the nonce +deploy_transaction.nonce = alice.nonce + +# sign transaction +deploy_transaction.signature = alice.sign_transaction(deploy_transaction) + +# broadcasting the transaction +tx_hash = entrypoint.send_transaction(deploy_transaction) +print(tx_hash.hex()) + +# waiting for transaction to complete +transaction_on_network = entrypoint.await_transaction_completed(tx_hash) + +# parsing transaction +parser = SmartContractTransactionsOutcomeParser(abi) +contract_deploy_outcome = parser.parse_deploy(transaction_on_network) + +contract_address = contract_deploy_outcome.contracts[0].address +print(contract_address.to_bech32()) +``` + +### 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 + +```py +from pathlib import Path + +from multiversx_sdk import Account, DevnetEntrypoint +from multiversx_sdk.abi import Abi, BigUIntValue + +# prepare the account +account = Account.new_from_keystore( + file_path=Path("../multiversx_sdk/testutils/testwallets/withDummyMnemonic.json"), + password="password", + address_index=0 +) +# the developer is responsible for managing the nonce +account.nonce = entrypoint.recall_account_nonce(account.address) + +# load the abi file +abi = Abi.load(Path("contracts/adder.abi.json")) + +# get the smart contracts controller +entrypoint = DevnetEntrypoint() +controller = entrypoint.create_smart_contract_controller(abi=abi) + +contract_address = Address.new_from_bech32("erd1qqqqqqqqqqqqqpgq7cmfueefdqkjsnnjnwydw902v8pwjqy3d8ssd4meug") + +# For deploy arguments, use typed value objects if you haven't provided an ABI +args = [BigUIntValue(42)] +# Or use simple, plain Python values and objects if you have provided an ABI +args = [42] + +deploy_transaction = controller.create_transaction_for_execute( + sender=account, + nonce=account.get_nonce_then_increment(), + contract=contract_address, + gas_limit=5000000, + function="add", + arguments=args +) + +# broadcasting the transaction +tx_hash = entrypoint.send_transaction(deploy_transaction) +print(tx_hash.hex()) +``` + +#### 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. + +```py +# waits for transaction completion and parses the result +# we use the transaction hash we got when broadcasting the transaction +contract_call_outcome = controller.await_completed_execute(tx_hash) +values = contract_call_outcome.values +``` + +#### Calling a smart contract and sending tokens (transfer & execute) + +Additionally, if our endpoint requires a payment when called, we can also send tokens to the contract when creating a smart contract call transaction. We can send EGLD, ESDT tokens or both. This is supported both on the `controller` and the `factory`. + +```py +from pathlib import Path + +from multiversx_sdk import Account, DevnetEntrypoint, Token, TokenTransfer +from multiversx_sdk.abi import Abi, BigUIntValue + +# prepare the account +account = Account.new_from_keystore( + file_path=Path("../multiversx_sdk/testutils/testwallets/withDummyMnemonic.json"), + password="password", + address_index=0 +) +# the developer is responsible for managing the nonce +account.nonce = entrypoint.recall_account_nonce(account.address) + +# load the abi file +abi = Abi.load(Path("contracts/adder.abi.json")) + +# get the smart contracts controller +entrypoint = DevnetEntrypoint() +controller = entrypoint.create_smart_contract_controller(abi=abi) + +contract_address = Address.new_from_bech32("erd1qqqqqqqqqqqqqpgq7cmfueefdqkjsnnjnwydw902v8pwjqy3d8ssd4meug") + +# For deploy arguments, use typed value objects if you haven't provided an ABI +args = [BigUIntValue(42)] +# Or use simple, plain Python values and objects if you have provided an ABI +args = [42] + +# creating the transfer +first_token = Token("TEST-38f249", 10) +first_transfer = TokenTransfer(first_token, 1) + +second_token = Token("BAR-c80d29") +second_transfer = TokenTransfer(second_token, 10000000000000000000) + +execute_transaction = controller.create_transaction_for_execute( + sender=account, + nonce=account.get_nonce_then_increment(), + contract=contract_address, + gas_limit=5000000, + function="add", + arguments=args, + native_transfer_amount=1000000000000000000, # 1 EGLD, + token_transfers=[first_transfer, second_transfer] +) + +# broadcasting the transaction +tx_hash = entrypoint.send_transaction(execute_transaction) +print(tx_hash.hex()) +``` + +#### Calling a smart contract using the factory + +Let's create the same smart contract call transaction, but using the `factory`. + +```py +from pathlib import Path + +from multiversx_sdk import Account, DevnetEntrypoint, Token, TokenTransfer +from multiversx_sdk.abi import Abi, BigUIntValue + +# prepare the account +account = Account.new_from_keystore( + file_path=Path("../multiversx_sdk/testutils/testwallets/withDummyMnemonic.json"), + password="password", + address_index=0 +) +# the developer is responsible for managing the nonce +account.nonce = entrypoint.recall_account_nonce(account.address) + +# load the abi file +abi = Abi.load(Path("contracts/adder.abi.json")) + +# get the smart contracts factory +entrypoint = DevnetEntrypoint() +factory = entrypoint.create_smart_contract_transactions_factory(abi=abi) + +contract_address = Address.new_from_bech32("erd1qqqqqqqqqqqqqpgq7cmfueefdqkjsnnjnwydw902v8pwjqy3d8ssd4meug") + +# For deploy arguments, use typed value objects if you haven't provided an ABI to the factory: +args = [BigUIntValue(42)] +# Or use simple, plain Python values and objects if you have provided an ABI to the factory: +args = [42] + +# creating the transfer +first_token = Token("TEST-38f249", 10) +first_transfer = TokenTransfer(first_token, 1) + +second_token = Token("BAR-c80d29") +second_transfer = TokenTransfer(second_token, 10000000000000000000) + +execute_transaction = factory.create_transaction_for_execute( + sender=account.address, + contract=contract_address, + gas_limit=5000000, + function="add", + arguments=args, + native_transfer_amount=1000000000000000000, # 1 EGLD, + token_transfers=[first_transfer, second_transfer] +) + +execute_transaction.nonce = account.get_nonce_then_increment() +execute_transaction.signature = account.sign_transaction(execute_transaction) + +# broadcasting the transaction +tx_hash = entrypoint.send_transaction(execute_transaction) +print(tx_hash.hex()) +``` + +### 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: + +```py +from pathlib import Path + +from multiversx_sdk import SmartContractTransactionsOutcomeParser +from multiversx_sdk.abi import Abi + +# load the abi file +abi = Abi.load(Path("contracts/adder.abi.json")) + +# create the parser +parser = SmartContractTransactionsOutcomeParser(abi=abi) + +# fetch the transaction of the network +transaction_on_network = entrypoint.get_transaction(tx_hash) # the tx_hash from the transaction sent above + +outcome = parser.parse_execute(transaction=transaction_on_network, function="add") +``` + +### 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. + +```py +from pathlib import Path + +from multiversx_sdk import DevnetEntrypoint, TransactionEventsParser, find_events_by_first_topic +from multiversx_sdk.abi import Abi + +# load the abi file +abi = Abi.load(Path("contracts/multisig-full.abi.json")) + +# fetch the transaction of the network +network_provider = DevnetEntrypoint().create_network_provider() +transaction_on_network = network_provider.get_transaction("exampleTransactionHash") + +# extract the event from the transaction +[event] = find_events_by_first_topic(transaction_on_network, "startPerformAction") + +# create the parser +events_parser = TransactionEventsParser(abi=abi) + +# parse the event +parsed_event = events_parser.parse_event(event) +``` + +### Encoding/Decoding custom types + +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. + +```py +from pathlib import Path +from multiversx_sdk.abi import Abi + +abi = Abi.load(Path("contracts/multisig-full.abi.json")) +encoded = abi.encode_custom_type("EsdtTokenPayment", ["TEST-8b028f", 0, 10000]) +print(encoded) +``` + +Now, let's decode a struct using the ABI. + +```py +from multiversx_sdk.abi import Abi, AbiDefinition + +abi_definition = AbiDefinition.from_dict( + { + "endpoints": [], + "events": [], + "types": { + "DepositEvent": { + "type": "struct", + "fields": [ + {"name": "tx_nonce", "type": "u64"}, + {"name": "opt_function", "type": "Option"}, + {"name": "opt_arguments", "type": "Option>"}, + {"name": "opt_gas_limit", "type": "Option"}, + ], + } + }, + } +) +abi = Abi(abi_definition) + +decoded_type = abi.decode_custom_type(name="DepositEvent", data=bytes.fromhex("00000000000003db000000")) +print(decoded_type) +``` + +If you don't wish to use the ABI, there is another way to do it. First, let's encode a struct. + +```py +from multiversx_sdk.abi import Serializer, U64Value, StructValue, Field, StringValue, BigUIntValue + +struct = StructValue([ + Field(name="token_identifier", value=StringValue("TEST-8b028f")), + Field(name="token_nonce", value=U64Value()), + Field(name="amount", value=BigUIntValue(10000)), +]) + +serializer = Serializer() +serialized_struct = serializer.serialize([struct]) +print(serialized_struct) +``` + +Now, let's decode a struct without using the ABI. + +```py +from multiversx_sdk.abi import Serializer, U64Value, OptionValue, BytesValue, ListValue, StructValue, Field + +tx_nonce = U64Value() +function = OptionValue(BytesValue()) +arguments = OptionValue(ListValue([BytesValue()])) +gas_limit = OptionValue(U64Value()) + +attributes = StructValue([ + Field("tx_nonce", tx_nonce), + Field("opt_function", function), + Field("opt_arguments", arguments), + Field("opt_gas_limit", gas_limit) +]) + +serializer = Serializer() +serializer.deserialize("00000000000003db000000", [attributes]) + +print(tx_nonce.get_payload()) +print(function.get_payload()) +print(arguments.get_payload()) +print(gas_limit.get_payload()) +``` + +### Smart Contract queries + +When querying a smart contract, a **view function** is called. That function does not modify the state of the contract, thus we don't need to send a transaction. + +To query a smart contract, we need to use the `SmartContractController`. Of course, we can use the contract's abi file to encode the arguments of the query, but also parse the result. In this example, we are going to use the [adder](https://github.com/multiversx/mx-contracts-rs/tree/main/contracts/adder) smart contract and we'll call the `getSum` endpoint. + +```py +from pathlib import Path + +from multiversx_sdk import Address, DevnetEntrypoint +from multiversx_sdk.abi import Abi + +# load the abi file +abi = Abi.load(Path("contracts/adder.abi.json")) + +# the contract address we'll query +contract_address = Address.new_from_bech32("erd1qqqqqqqqqqqqqpgq7cmfueefdqkjsnnjnwydw902v8pwjqy3d8ssd4meug") + +# create the controller +sc_controller = DevnetEntrypoint().create_smart_contract_controller(abi=abi) + +# creates the query, runs the query, parses the result +response = sc_controller.query( + contract=contract_address, + function="getSum", + arguments=[] # our function expects no arguments, so we provide an empty list +) +``` + +If we need more granular control, we can split the process in three steps: create the query, run the query and parse the query response. This does the exact same as the example above. + +```py +from pathlib import Path + +from multiversx_sdk import Address, DevnetEntrypoint +from multiversx_sdk.abi import Abi + +# load the abi file +abi = Abi.load(Path("contracts/adder.abi.json")) + +# the contract address we'll query +contract_address = Address.new_from_bech32("erd1qqqqqqqqqqqqqpgq7cmfueefdqkjsnnjnwydw902v8pwjqy3d8ssd4meug") + +# create the controller +sc_controller = DevnetEntrypoint().create_smart_contract_controller(abi=abi) + +# creates the query +query = sc_controller.create_query( + contract=contract_address, + function="getSum", + arguments=[] # our function expects no arguments, so we provide an empty list +) + +# run the query +result = sc_controller.run_query(query) + +# parse the result +parsed_result = sc_controller.parse_query_response(result) +``` + +### Upgrading a smart contract + +Contract upgrade transactions are similar to deployment transactions (see above), in the sense that they also require a contract bytecode. In this context though, the contract address is already known. Similar to deploying a smart contract, we can upgrade a smart contract using either the `controller` or the `factory`. + +#### Uprgrading a smart contract using the controller + +```py +from pathlib import Path + +from multiversx_sdk import Account, Address, DevnetEntrypoint +from multiversx_sdk.abi import Abi, BigUIntValue + +# prepare the account +account = Account.new_from_keystore( + file_path=Path("../multiversx_sdk/testutils/testwallets/withDummyMnemonic.json"), + password="password", + address_index=0 +) + +entrypoint = DevnetEntrypoint() + +# the developer is responsible for managing the nonce +account.nonce = entrypoint.recall_account_nonce(account.address) + +# load the abi file +abi = Abi.load(Path("contracts/adder.abi.json")) + +# get the smart contracts controller +controller = entrypoint.create_smart_contract_controller(abi=abi) + +# load the contract bytecode; this is the new contract code, the one we want to upgrade to +bytecode = Path("contracts/adder.wasm").read_bytes() + +# For deploy arguments, use typed value objects if you haven't provided an ABI +args = [BigUIntValue(42)] +# Or use simple, plain Python values and objects if you have provided an ABI +args = [42] + +contract_address = Address.new_from_bech32( + "erd1qqqqqqqqqqqqqpgq7cmfueefdqkjsnnjnwydw902v8pwjqy3d8ssd4meug") + +deploy_transaction = controller.create_transaction_for_upgrade( + sender=account, + nonce=account.get_nonce_then_increment(), + contract=contract_address, + bytecode=bytecode, + gas_limit=5000000, + arguments=args, + is_upgradeable=True, + is_readable=True, + is_payable=True, + is_payable_by_sc=True +) + +# broadcasting the transaction +tx_hash = entrypoint.send_transaction(deploy_transaction) +print(tx_hash.hex()) +``` + +#### Upgrading a smart contract using the factory + +Let's create the same upgrade transaction using the `factory`. + +```py +from pathlib import Path + +from multiversx_sdk import Account, Address, DevnetEntrypoint +from multiversx_sdk.abi import Abi, BigUIntValue + + +# load the abi file +abi = Abi.load(Path("contracts/adder.abi.json")) + +# get the smart contracts factory +entrypoint = DevnetEntrypoint() +factory = entrypoint.create_smart_contract_transactions_factory(abi=abi) + +# load the contract bytecode; this is the new contract code, the one we want to upgrade to +bytecode = Path("contracts/adder.wasm").read_bytes() + +# For deploy arguments, use typed value objects if you haven't provided an ABI to the factory: +args = [BigUIntValue(42)] +# Or use simple, plain Python values and objects if you have provided an ABI to the factory: +args = [42] + +alice_address = Address.new_from_bech32( + "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th") + +contract_address = Address.new_from_bech32( + "erd1qqqqqqqqqqqqqpgq7cmfueefdqkjsnnjnwydw902v8pwjqy3d8ssd4meug") + +deploy_transaction = factory.create_transaction_for_upgrade( + sender=alice_address, + contract=contract_address, + bytecode=bytecode, + gas_limit=5000000, + arguments=args, + is_upgradeable=True, + is_readable=True, + is_payable=True, + is_payable_by_sc=True +) + +# load the account +alice = Account.new_from_keystore( + file_path=Path("../multiversx_sdk/testutils/testwallets/withDummyMnemonic.json"), + password="password", + address_index=0 +) +# the developer is responsible for managing the nonce +alice.nonce = entrypoint.recall_account_nonce(alice.address) + +# set the nonce +deploy_transaction.nonce = alice.nonce + +# sign transaction +deploy_transaction.signature = alice.sign_transaction(deploy_transaction) + +# broadcasting the transaction +tx_hash = entrypoint.send_transaction(deploy_transaction) +print(tx_hash.hex()) +``` + +### 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). + +Of course, the methods used here are available through the `TokenManagementController` or through the `TokenManagementTransactionsFactory`. The controller also contains methods for awaiting transaction completion and for parsing the transaction outcome. The same can be achieved for the transactions factory by using the `TokenManagementTransactionsOutcomeParser`. For scripts or quick network interactions we advise you use the controller, but for a more granular approach (e.g. DApps) we suggest using the factory. + +#### Issuing fungible tokens using the controller + +```py +from pathlib import Path +from multiversx_sdk import Account, DevnetEntrypoint + +# create the entrypoint and the token management controller +entrypoint = DevnetEntrypoint() +controller = entrypoint.create_token_management_controller() + +# create the issuer of the token +alice = Account.new_from_pem(Path("../multiversx_sdk/testutils/testwallets/alice.pem")) + +# fetch the nonce of the network +alice.nonce = entrypoint.recall_account_nonce(alice.address) + +transaction = controller.create_transaction_for_issuing_fungible( + sender=alice, + nonce=alice.get_nonce_then_increment(), + token_name="NEWFNG", + token_ticker="FNG", + initial_supply=1_000_000_000000, # 1 million tokens, with 6 decimals + num_decimals=6, + can_freeze=False, + can_wipe=True, + can_pause=False, + can_change_owner=True, + can_upgrade=True, + can_add_special_roles=True +) + +# sending the transaction +tx_hash = entrypoint.send_transaction(transaction) + +# wait for transaction to execute, extract the token identifier +outcome = controller.await_completed_issue_fungible(tx_hash) + +token_identifier = outcome[0].token_identifier +print(token_identifier) +``` + +#### Issuing fungible tokens using the factory + +```py +from pathlib import Path +from multiversx_sdk import Account, DevnetEntrypoint, TokenManagementTransactionsOutcomeParser + +# create the entrypoint and the token management transactions factory +entrypoint = DevnetEntrypoint() +factory = entrypoint.create_token_management_transactions_factory() + +# create the issuer of the token +alice = Account.new_from_pem(Path("../multiversx_sdk/testutils/testwallets/alice.pem")) + +transaction = factory.create_transaction_for_issuing_fungible( + sender=alice.address, + token_name="NEWFNG", + token_ticker="FNG", + initial_supply=1_000_000_000000, # 1 million tokens, with 6 decimals + num_decimals=6, + can_freeze=False, + can_wipe=True, + can_pause=False, + can_change_owner=True, + can_upgrade=True, + can_add_special_roles=True +) + +# fetch the nonce of the network +alice.nonce = entrypoint.recall_account_nonce(alice.address) +transaction.nonce = alice.get_nonce_then_increment() + +# sign the transaction +transaction.signature = alice.sign_transaction(transaction) + +# sending the transaction +tx_hash = entrypoint.send_transaction(transaction) + +# if we know that the transaction is completed, we can simply call `entrypoint.get_transaction(tx_hash)` +transaction_on_network = entrypoint.await_transaction_completed(tx_hash) + +# extract the token identifier +parser = TokenManagementTransactionsOutcomeParser() +outcome = parser.parse_issue_fungible(transaction_on_network) + +token_identifier = outcome[0].token_identifier +print(token_identifier) +``` + +#### Setting special roles for fungible tokens using the controller + +```py +from pathlib import Path +from multiversx_sdk import Account, Address, DevnetEntrypoint + +# create the entrypoint and the token management controller +entrypoint = DevnetEntrypoint() +controller = entrypoint.create_token_management_controller() + +# create the issuer of the token +alice = Account.new_from_pem(Path("../multiversx_sdk/testutils/testwallets/alice.pem")) + +# fetch the nonce of the network +alice.nonce = entrypoint.recall_account_nonce(alice.address) + +bob = Address.new_from_bech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx") + +transaction = controller.create_transaction_for_setting_special_role_on_fungible_token( + sender=alice, + nonce=alice.get_nonce_then_increment(), + user=bob, + token_identifier="TEST-123456", + add_role_local_mint=True, + add_role_local_burn=True, + add_role_esdt_transfer_role=True +) + +# sending the transaction +tx_hash = entrypoint.send_transaction(transaction) + +# wait for transaction to execute, extract the roles +outcome = controller.await_completed_set_special_role_on_fungible_token(tx_hash) + +roles = outcome[0].roles +uaser = outcome[0].user_address +``` + +#### Setting special roles for fungible tokens using the factory + +```py +from pathlib import Path +from multiversx_sdk import Account, DevnetEntrypoint, TokenManagementTransactionsOutcomeParser + +# create the entrypoint and the token management transactions factory +entrypoint = DevnetEntrypoint() +factory = entrypoint.create_token_management_transactions_factory() + +# create the issuer of the token +alice = Account.new_from_pem(Path("../multiversx_sdk/testutils/testwallets/alice.pem")) + +transaction = factory.create_transaction_for_setting_special_role_on_fungible_token( + sender=alice.address, + user=bob, + token_identifier="TEST-123456", + add_role_local_mint=True, + add_role_local_burn=True, + add_role_esdt_transfer_role=True +) + +# fetch the nonce of the network +alice.nonce = entrypoint.recall_account_nonce(alice.address) +transaction.nonce = alice.get_nonce_then_increment() + +# sign the transaction +transaction.signature = alice.sign_transaction(transaction) + +# sending the transaction +tx_hash = entrypoint.send_transaction(transaction) + +# waits until the transaction is processed and fetches it from the network +transaction_on_network = entrypoint.await_transaction_completed(tx_hash) + +# extract the roles +parser = TokenManagementTransactionsOutcomeParser() +outcome = parser.parse_set_special_role(transaction_on_network) + +roles = outcome[0].roles +uaser = outcome[0].user_address +``` + +#### Issuing semi-fungible tokens using the controller + +```py +from pathlib import Path +from multiversx_sdk import Account, DevnetEntrypoint + +# create the entrypoint and the token management controller +entrypoint = DevnetEntrypoint() +controller = entrypoint.create_token_management_controller() + +# create the issuer of the token +alice = Account.new_from_pem(Path("../multiversx_sdk/testutils/testwallets/alice.pem")) + +# fetch the nonce of the network +alice.nonce = entrypoint.recall_account_nonce(alice.address) + +transaction = controller.create_transaction_for_issuing_semi_fungible( + sender=alice, + nonce=alice.get_nonce_then_increment(), + token_name="NEWSEMI", + token_ticker="SEMI", + can_freeze=False, + can_wipe=True, + can_pause=False, + can_transfer_nft_create_role=True, + can_change_owner=True, + can_upgrade=True, + can_add_special_roles=True +) + +# sending the transaction +tx_hash = entrypoint.send_transaction(transaction) + +# wait for transaction to execute, extract the token identifier +outcome = controller.await_completed_issue_semi_fungible(tx_hash) + +token_identifier = outcome[0].token_identifier +print(token_identifier) +``` + +#### Issuing semi-fungible tokens using the factory + +```py +from pathlib import Path +from multiversx_sdk import Account, DevnetEntrypoint, TokenManagementTransactionsOutcomeParser + +# create the entrypoint and the token management transactions factory +entrypoint = DevnetEntrypoint() +factory = entrypoint.create_token_management_transactions_factory() + +# create the issuer of the token +alice = Account.new_from_pem(Path("../multiversx_sdk/testutils/testwallets/alice.pem")) + +transaction = factory.create_transaction_for_issuing_semi_fungible( + sender=alice.address, + token_name="NEWSEMI", + token_ticker="SEMI", + can_freeze=False, + can_wipe=True, + can_pause=False, + can_transfer_nft_create_role=True, + can_change_owner=True, + can_upgrade=True, + can_add_special_roles=True +) + +# fetch the nonce of the network +alice.nonce = entrypoint.recall_account_nonce(alice.address) +transaction.nonce = alice.get_nonce_then_increment() + +# sign the transaction +transaction.signature = alice.sign_transaction(transaction) + +# sending the transaction +tx_hash = entrypoint.send_transaction(transaction) + +# waits until the transaction is processed and fetches it from the network +transaction_on_network = entrypoint.await_transaction_completed(tx_hash) + +# extract the token identifier +parser = TokenManagementTransactionsOutcomeParser() +outcome = parser.parse_issue_semi_fungible(transaction_on_network) + +token_identifier = outcome[0].token_identifier +print(token_identifier) +``` + +#### Issuing NFT collection & creating NFTs using the controller + +```py +from pathlib import Path +from multiversx_sdk import Account, DevnetEntrypoint + +# create the entrypoint and the token management controller +entrypoint = DevnetEntrypoint() +controller = entrypoint.create_token_management_controller() + +# create the issuer of the token +alice = Account.new_from_pem(Path("../multiversx_sdk/testutils/testwallets/alice.pem")) + +# fetch the nonce of the network +alice.nonce = entrypoint.recall_account_nonce(alice.address) + +# issue NFT collection +transaction = controller.create_transaction_for_issuing_non_fungible( + sender=alice, + nonce=alice.get_nonce_then_increment(), + token_name="NEWNFT", + token_ticker="NFT", + can_freeze=False, + can_wipe=True, + can_pause=False, + can_transfer_nft_create_role=True, + can_change_owner=True, + can_upgrade=True, + can_add_special_roles=True +) + +# sending the transaction +tx_hash = entrypoint.send_transaction(transaction) + +# wait for transaction to execute, extract the collection identifier +outcome = controller.await_completed_issue_non_fungible(tx_hash) + +collection_identifier = outcome[0].token_identifier + +# set roles +transaction = controller.create_transaction_for_setting_special_role_on_non_fungible_token( + sender=alice, + nonce=alice.get_nonce_then_increment(), + user=alice.address, + token_identifier=collection_identifier, + add_role_nft_create=True, + add_role_nft_burn=True, + add_role_nft_update_attributes=True, + add_role_nft_add_uri=True, + add_role_esdt_transfer_role=True, +) + +# sending the transaction and waiting for completion +tx_hash = entrypoint.send_transaction(transaction) +entrypoint.await_transaction_completed(tx_hash) + +# create a NFT +transaction = controller.create_transaction_for_creating_nft( + sender=alice, + nonce=alice.get_nonce_then_increment(), + token_identifier=collection_identifier, + initial_quantity=1, + name="TEST", + royalties=2500, # 25% + hash="", + attributes=b"", + uris=["emptyUri"] +) + +# sending the transaction +tx_hash = entrypoint.send_transaction(transaction) + +# wait for transaction to execute, extract the nft identifier +outcome = controller.await_completed_create_nft(tx_hash) + +identifier = outcome[0].token_identifier +nonce = outcome[0].nonce +initial_quantity = outcome[0].initial_quantity +print(identifier) +print(nonce) +print(initial_quantity) +``` + +#### Issuing NFT collection & creating NFTs using the factory + +```py +from pathlib import Path +from multiversx_sdk import Account, DevnetEntrypoint, TokenManagementTransactionsOutcomeParser + +# create the entrypoint and the token management transactions factory +entrypoint = DevnetEntrypoint() +factory = entrypoint.create_token_management_transactions_factory() + +# create the issuer of the token +alice = Account.new_from_pem(Path("../multiversx_sdk/testutils/testwallets/alice.pem")) + +# issue NFT collection +transaction = factory.create_transaction_for_issuing_non_fungible( + sender=alice.address, + token_name="NEWTOKEN", + token_ticker="TKN", + can_freeze=False, + can_wipe=True, + can_pause=False, + can_transfer_nft_create_role=True, + can_change_owner=True, + can_upgrade=True, + can_add_special_roles=True +) + +# fetch the nonce of the network +alice.nonce = entrypoint.recall_account_nonce(alice.address) +transaction.nonce = alice.get_nonce_then_increment() + +# sign the transaction +transaction.signature = alice.sign_transaction(transaction) + +# sending the transaction +tx_hash = entrypoint.send_transaction(transaction) + +# if we know that the transaction is completed, we can simply call `get_transaction(tx_hash)` +transaction_on_network = entrypoint.await_transaction_completed(tx_hash) + +# extract the collection identifier +parser = TokenManagementTransactionsOutcomeParser() +outcome = parser.parse_issue_non_fungible(transaction_on_network) + +collection_identifier = outcome[0].token_identifier + +# set roles +transaction = factory.create_transaction_for_setting_special_role_on_non_fungible_token( + sender=alice.address, + user=alice.address, + token_identifier=collection_identifier, + add_role_nft_create=True, + add_role_nft_burn=True, + add_role_nft_update_attributes=True, + add_role_nft_add_uri=True, + add_role_esdt_transfer_role=True, +) +transaction.nonce = alice.get_nonce_then_increment() + +# sign the transaction +transaction.signature = alice.sign_transaction(transaction) + +# sending the transaction and waiting for completion +tx_hash = entrypoint.send_transaction(transaction) +entrypoint.await_transaction_completed(tx_hash) + +# create a NFT +transaction = factory.create_transaction_for_creating_nft( + sender=alice.address, + token_identifier=collection_identifier, + initial_quantity=1, + name="TEST", + royalties=2500, # 25% + hash="", + attributes=b"", + uris=["emptyUri"] +) +# set the nonce +transaction.nonce = alice.get_nonce_then_increment() + +# sign the transaction +transaction.signature = alice.sign_transaction(transaction) + +# sending the transaction +tx_hash = entrypoint.send_transaction(transaction) + +# waits until the transaction is processed and fetches it from the network +transaction_on_network = entrypoint.await_transaction_completed(tx_hash) + +# extract the nft identifier +parser = TokenManagementTransactionsOutcomeParser() +outcome = parser.parse_nft_create(transaction_on_network) + +identifier = outcome[0].token_identifier +nonce = outcome[0].nonce +initial_quantity = outcome[0].initial_quantity +print(identifier) +print(nonce) +print(initial_quantity) +``` + +These are just a few examples of what we can do using the token management controller or factory. For a full list of what methods are supported for both, check out the autogenerated documentation: +- [TokenManagementController](https://multiversx.github.io/mx-sdk-py/multiversx_sdk.token_management.html#module-multiversx_sdk.token_management.token_management_controller) +- [TokenManagementTransactionsFactory](https://multiversx.github.io/mx-sdk-py/multiversx_sdk.token_management.html#module-multiversx_sdk.token_management.token_management_transactions_factory) + +### Account management + +The account management controller and factory allow us to create transactions for managing accounts, like guarding and unguarding accounts and saving key-value pairs. + +To read more about Guardians, check out the [documentation](/developers/built-in-functions/#setguardian). + +A guardian can also be set using the WebWallet. The wallet uses our hosted `Trusted Co-Signer Service`. Check out the steps to guard an account using the wallet [here](/wallet/web-wallet/#guardian). + +#### Guarding an account using the controller + +```py +from pathlib import Path +from multiversx_sdk import Account, Address, DevnetEntrypoint + +# create the entrypoint and the account controller +entrypoint = DevnetEntrypoint() +controller = entrypoint.create_account_controller() + +# create the account to guard +alice = Account.new_from_pem(Path("../multiversx_sdk/testutils/testwallets/alice.pem")) + +# fetch the nonce of the network +alice.nonce = entrypoint.recall_account_nonce(alice.address) + +# we can use a trusted service that provides a guardian, or simply set another address we own or trust +guardian = Address.new_from_bech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx") + +transaction = controller.create_transaction_for_setting_guardian( + sender=alice, + nonce=alice.get_nonce_then_increment(), + guardian_address=guardian, + service_id="SelfOwnedAddress" # this is just an example +) + +tx_hash = entrypoint.send_transaction(transaction) +``` + +#### Guarding an account using the factory + +```py +from pathlib import Path +from multiversx_sdk import Account, Address, DevnetEntrypoint + +# create the entrypoint and the account transactions factory +entrypoint = DevnetEntrypoint() +factory = entrypoint.create_account_transactions_factory() + +# create the account to guard +alice = Account.new_from_pem(Path("../multiversx_sdk/testutils/testwallets/alice.pem")) + +# we can use a trusted service that provides a guardian, or simply set another address we own or trust +guardian = Address.new_from_bech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx") + +transaction = factory.create_transaction_for_setting_guardian( + sender=alice.address, + guardian_address=guardian, + service_id="SelfOwnedAddress" # this is just an example +) + +# fetch the nonce of the network +alice.nonce = entrypoint.recall_account_nonce(alice.address) + +# set the nonce +transaction.nonce = alice.get_nonce_then_increment() + +# sign the transaction +transaction.signature = alice.sign_transaction(transaction) + +tx_hash = entrypoint.send_transaction(transaction) +``` + +After we've set a guardian, we have to wait 20 epochs until we can activate the guardian. After the guardian is set, all the transactions we send should be signed by the guardian, as well. + +#### Activating the guardian using the controller + +```py +from pathlib import Path +from multiversx_sdk import Account, DevnetEntrypoint + +# create the entrypoint and the account controller +entrypoint = DevnetEntrypoint() +controller = entrypoint.create_account_controller() + +# create the account to guard +alice = Account.new_from_pem(Path("../multiversx_sdk/testutils/testwallets/alice.pem")) + +# fetch the nonce of the network +alice.nonce = entrypoint.recall_account_nonce(alice.address) + +transaction = controller.create_transaction_for_guarding_account( + sender=alice, + nonce=alice.get_nonce_then_increment() +) + +tx_hash = entrypoint.send_transaction(transaction) +``` + +#### Activating the guardian using the factory + +```py +from pathlib import Path +from multiversx_sdk import Account, DevnetEntrypoint + +# create the entrypoint and the account transactions factory +entrypoint = DevnetEntrypoint() +factory = entrypoint.create_account_transactions_factory() + +# create the account to guard +alice = Account.new_from_pem(Path("../multiversx_sdk/testutils/testwallets/alice.pem")) + +transaction = factory.create_transaction_for_guarding_account( + sender=alice.address, +) + +# fetch the nonce of the network +alice.nonce = entrypoint.recall_account_nonce(alice.address) + +# set the nonce +transaction.nonce = alice.get_nonce_then_increment() + +# sign the transaction +transaction.signature = alice.sign_transaction(transaction) + +tx_hash = entrypoint.send_transaction(transaction) +``` + +#### Unguarding the account using the controller + +```py +from pathlib import Path +from multiversx_sdk import Account, DevnetEntrypoint + +# create the entrypoint and the account controller +entrypoint = DevnetEntrypoint() +controller = entrypoint.create_account_controller() + +# the account to unguard +alice = Account.new_from_pem(Path("../multiversx_sdk/testutils/testwallets/alice.pem")) + +# fetch the nonce of the network +alice.nonce = entrypoint.recall_account_nonce(alice.address) + +# the guardian account +guardian = Account.new_from_pem(Path("../multiversx_sdk/testutils/testwallets/bob.pem")) + +transaction = controller.create_transaction_for_unguarding_account( + sender=alice, + nonce=alice.get_nonce_then_increment(), + guardian=guardian.address +) + +# the transaction should also be signed by the guardian before being sent, otherwise it won't be executed +transaction.guardian_signature = guardian.sign_transaction(transaction) + +# broadcast the transaction +tx_hash = entrypoint.send_transaction(transaction) +``` + +#### Unguarding the account using the factory + +```py +from pathlib import Path +from multiversx_sdk import Account, DevnetEntrypoint + +# create the entrypoint and the account transactions factory +entrypoint = DevnetEntrypoint() +factory = entrypoint.create_account_transactions_factory() + +# the account to unguard +alice = Account.new_from_pem(Path("../multiversx_sdk/testutils/testwallets/alice.pem")) + +# the guardian account +guardian = Account.new_from_pem(Path("../multiversx_sdk/testutils/testwallets/bob.pem")) + +transaction = factory.create_transaction_for_unguarding_account( + sender=alice.address, + guardian=guardian.address +) + +# fetch the nonce of the network +alice.nonce = entrypoint.recall_account_nonce(alice.address) + +# set the nonce +transaction.nonce = alice.get_nonce_then_increment() + +# sign the transaction +transaction.signature = alice.sign_transaction(transaction) + +# the transaction should also be signed by the guardian before being sent otherwise it won't be executed +transaction.guardian_signature = guardian.sign_transaction(transaction) + +# broadcast the transaction +tx_hash = entrypoint.send_transaction(transaction) +``` + +#### Saving a key-value pair to an account using the controller + +We can store key-value pairs for an account on the network. To do so, we create the following transaction: + +```py +from pathlib import Path +from multiversx_sdk import Account, DevnetEntrypoint + +# create the entrypoint and the account controller +entrypoint = DevnetEntrypoint() +controller = entrypoint.create_account_controller() + +# create the account to guard +alice = Account.new_from_pem(Path("../multiversx_sdk/testutils/testwallets/alice.pem")) + +# fetch the nonce of the network +alice.nonce = entrypoint.recall_account_nonce(alice.address) + +# creating the key-value pairs we want to save +values = { + "testKey".encode(): "testValue".encode(), + b"anotherKey": b"anotherValue" +} + +transaction = controller.create_transaction_for_saving_key_value( + sender=alice, + nonce=alice.get_nonce_then_increment(), + key_value_pairs=values +) + +# broadcast the transaction +tx_hash = entrypoint.send_transaction(transaction) +``` + +#### Saving a key-value pair to an account using the factory + +```py +from pathlib import Path +from multiversx_sdk import Account, DevnetEntrypoint + +# create the entrypoint and the account transactions factory +entrypoint = DevnetEntrypoint() +factory = entrypoint.create_account_transactions_factory() + +# create the account to guard +alice = Account.new_from_pem(Path("../multiversx_sdk/testutils/testwallets/alice.pem")) + +# creating the key-value pairs we want to save +values = { + "testKey".encode(): "testValue".encode(), + b"anotherKey": b"anotherValue" +} + +transaction = factory.create_transaction_for_saving_key_value( + sender=alice.address, + key_value_pairs=values +) + +# fetch the nonce of the network +alice.nonce = entrypoint.recall_account_nonce(alice.address) + +# set the nonce +transaction.nonce = alice.get_nonce_then_increment() + +# sign the transaction +transaction.signature = alice.sign_transaction(transaction) + +# broadcast the transaction +tx_hash = entrypoint.send_transaction(transaction) +``` + +### Delegation management + +To read more about staking providers and delegation, please check out the [docs](/validators/delegation-manager/#introducing-staking-providers). + +In this section, we are going to create a new delegation contract, get the address of the contract, delegate funds to the contract, redelegate rewards, claim rewards, undelegate and withdraw funds from the contract. The operations can be performed using both the `controller` and the `factory`. For a full list of all the methods supported check out the auto-generated documentation: +- [DelegationController](https://multiversx.github.io/mx-sdk-py/multiversx_sdk.delegation.html#module-multiversx_sdk.delegation.delegation_controller) +- [DelegationTransactionsFactory](https://multiversx.github.io/mx-sdk-py/multiversx_sdk.delegation.html#module-multiversx_sdk.delegation.delegation_transactions_factory) + +#### Creating a new delegation contract using the controller + +```py +from pathlib import Path +from multiversx_sdk import Account, DevnetEntrypoint + +# create the entrypoint and the delegation controller +entrypoint = DevnetEntrypoint() +controller = entrypoint.create_delegation_controller() + +# the owner of the contract +alice = Account.new_from_pem(Path("../multiversx_sdk/testutils/testwallets/alice.pem")) + +# fetch the nonce of the network +alice.nonce = entrypoint.recall_account_nonce(alice.address) + +transaction = controller.create_transaction_for_new_delegation_contract( + sender=alice, + nonce=alice.get_nonce_then_increment(), + total_delegation_cap=0, # uncapped, + service_fee=0, + amount=1250000000000000000000 # 1250 EGLD +) + +tx_hash = entrypoint.send_transaction(transaction) + +# wait for transaction completion, extract delegation contract's address +outcome = controller.await_completed_create_new_delegation_contract(tx_hash) + +contract_address = outcome[0].contract_address +``` + +#### Creating a new delegation contract using the factory + +```py +from pathlib import Path +from multiversx_sdk import Account, DevnetEntrypoint, DelegationTransactionsOutcomeParser + +# create the entrypoint and the delegation transactions factory +entrypoint = DevnetEntrypoint() +factory = entrypoint.create_delegation_transactions_factory() + +# the owner of the contract +alice = Account.new_from_pem(Path("../multiversx_sdk/testutils/testwallets/alice.pem")) + +transaction = factory.create_transaction_for_new_delegation_contract( + sender=alice.address, + total_delegation_cap=0, # uncapped, + service_fee=0, + amount=1250000000000000000000 # 1250 EGLD +) + +# fetch the nonce of the network +alice.nonce = entrypoint.recall_account_nonce(alice.address) + +# set the nonce +transaction.nonce = alice.get_nonce_then_increment() + +# sign the transaction +transaction.signature = alice.sign_transaction(transaction) + +# send the transaction +tx_hash = entrypoint.send_transaction(transaction) + +# waits until the transaction is processed and fetches it from the network +transaction_on_network = entrypoint.await_transaction_completed(tx_hash) + +# extract the contract's address +parser = DelegationTransactionsOutcomeParser() +outcome = parser.parse_create_new_delegation_contract(transaction_on_network) + +contract_address = outcome[0].contract_address +``` + +#### Delegating funds to the contract using the controller + +We can send funds to a delegation contract to earn rewards. + +```py +from pathlib import Path +from multiversx_sdk import Account, Address, DevnetEntrypoint + +# create the entrypoint and the delegation controller +entrypoint = DevnetEntrypoint() +controller = entrypoint.create_delegation_controller() + +# create the account delegating funds +alice = Account.new_from_pem(Path("../multiversx_sdk/testutils/testwallets/alice.pem")) + +# fetch the nonce of the network +alice.nonce = entrypoint.recall_account_nonce(alice.address) + +# delegation contract +contract = Address.new_from_bech32("erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqf8llllswuedva") + +transaction = controller.create_transaction_for_delegating( + sender=alice, + nonce=alice.get_nonce_then_increment(), + delegation_contract=contract, + amount=5000000000000000000000 # 5000 EGLD +) + +tx_hash = entrypoint.send_transaction(transaction) +``` + +#### Delegating funds to the contract using the factory + +```py +from pathlib import Path +from multiversx_sdk import Account, DevnetEntrypoint + +# create the entrypoint and the delegation transactions factory +entrypoint = DevnetEntrypoint() +factory = entrypoint.create_delegation_transactions_factory() + +# create the account delegating funds +alice = Account.new_from_pem(Path("../multiversx_sdk/testutils/testwallets/alice.pem")) + +transaction = factory.create_transaction_for_delegating( + sender=alice.address, + delegation_contract=contract, + amount=5000000000000000000000 # 5000 EGLD +) + +# fetch the nonce of the network +alice.nonce = entrypoint.recall_account_nonce(alice.address) + +# set the nonce +transaction.nonce = alice.get_nonce_then_increment() + +# sign the transaction +transaction.signature = alice.sign_transaction(transaction) + +# send the transaction +tx_hash = entrypoint.send_transaction(transaction) +``` + +#### Redelegating rewards using the controller + +After a period of time, we might have enough rewards that we want to redelegate to the contract to earn even more rewards. + +```py +from pathlib import Path +from multiversx_sdk import Account, Address, DevnetEntrypoint + +# create the entrypoint and the delegation controller +entrypoint = DevnetEntrypoint() +controller = entrypoint.create_delegation_controller() + +alice = Account.new_from_pem(Path("../multiversx_sdk/testutils/testwallets/alice.pem")) + +# fetch the nonce of the network +alice.nonce = entrypoint.recall_account_nonce(alice.address) + +# delegation contract +contract = Address.new_from_bech32("erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqf8llllswuedva") + +transaction = controller.create_transaction_for_redelegating_rewards( + sender=alice, + nonce=alice.get_nonce_then_increment(), + delegation_contract=contract +) + +tx_hash = entrypoint.send_transaction(transaction) +``` + +#### Redelegating rewards using the factory + +```py +from pathlib import Path +from multiversx_sdk import Account, DevnetEntrypoint + +# create the entrypoint and the delegation transactions factory +entrypoint = DevnetEntrypoint() +factory = entrypoint.create_delegation_transactions_factory() + +alice = Account.new_from_pem(Path("../multiversx_sdk/testutils/testwallets/alice.pem")) + +transaction = factory.create_transaction_for_redelegating_rewards( + sender=alice.address, + delegation_contract=contract +) + +# fetch the nonce of the network +alice.nonce = entrypoint.recall_account_nonce(alice.address) + +# set the nonce +transaction.nonce = alice.get_nonce_then_increment() + +# sign the transaction +transaction.signature = alice.sign_transaction(transaction) + +# send the transaction +tx_hash = entrypoint.send_transaction(transaction) +``` + +#### Claiming rewards using the controller + +We can also claim our rewards. + +```py +from pathlib import Path +from multiversx_sdk import Account, Address, DevnetEntrypoint + +# create the entrypoint and the delegation controller +entrypoint = DevnetEntrypoint() +controller = entrypoint.create_delegation_controller() + +alice = Account.new_from_pem(Path("../multiversx_sdk/testutils/testwallets/alice.pem")) + +# fetch the nonce of the network +alice.nonce = entrypoint.recall_account_nonce(alice.address) + +# delegation contract +contract = Address.new_from_bech32("erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqf8llllswuedva") + +transaction = controller.create_transaction_for_claiming_rewards( + sender=alice, + nonce=alice.get_nonce_then_increment(), + delegation_contract=contract +) + +tx_hash = entrypoint.send_transaction(transaction) +``` + +#### Claiming rewards using the factory + +```py +from pathlib import Path +from multiversx_sdk import Account, DevnetEntrypoint + +# create the entrypoint and the delegation transactions factory +entrypoint = DevnetEntrypoint() +factory = entrypoint.create_delegation_transactions_factory() + +alice = Account.new_from_pem(Path("../multiversx_sdk/testutils/testwallets/alice.pem")) + +transaction = factory.create_transaction_for_claiming_rewards( + sender=alice.address, + delegation_contract=contract +) + +# fetch the nonce of the network +alice.nonce = entrypoint.recall_account_nonce(alice.address) + +# set the nonce +transaction.nonce = alice.get_nonce_then_increment() + +# sign the transaction +transaction.signature = alice.sign_transaction(transaction) + +# send the transaction +tx_hash = entrypoint.send_transaction(transaction) +``` + +#### Undelegating funds using the controller + +By undelegating we let the contract know we want to get back our staked funds. This operation has a 10 epochs unbonding period. + +```py +from pathlib import Path +from multiversx_sdk import Account, Address, DevnetEntrypoint + +# create the entrypoint and the delegation controller +entrypoint = DevnetEntrypoint() +controller = entrypoint.create_delegation_controller() + +alice = Account.new_from_pem(Path("../multiversx_sdk/testutils/testwallets/alice.pem")) + +# fetch the nonce of the network +alice.nonce = entrypoint.recall_account_nonce(alice.address) + +# delegation contract +contract = Address.new_from_bech32("erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqf8llllswuedva") + +transaction = controller.create_transaction_for_undelegating( + sender=alice, + nonce=alice.get_nonce_then_increment(), + delegation_contract=contract, + amount=1000000000000000000000 # 1000 EGLD +) + +tx_hash = entrypoint.send_transaction(transaction) +``` + +#### Undelegating funds using the factory + +```py +from pathlib import Path +from multiversx_sdk import Account, DevnetEntrypoint + +# create the entrypoint and the delegation transactions factory +entrypoint = DevnetEntrypoint() +factory = entrypoint.create_delegation_transactions_factory() + +alice = Account.new_from_pem(Path("../multiversx_sdk/testutils/testwallets/alice.pem")) + +transaction = factory.create_transaction_for_undelegating( + sender=alice.address, + delegation_contract=contract, + amount=1000000000000000000000 # 1000 EGLD +) + +# fetch the nonce of the network +alice.nonce = entrypoint.recall_account_nonce(alice.address) + +# set the nonce +transaction.nonce = alice.get_nonce_then_increment() + +# sign the transaction +transaction.signature = alice.sign_transaction(transaction) + +# send the transaction +tx_hash = entrypoint.send_transaction(transaction) +``` + +#### Withdrawing funds using the controller + +After the unbonding period has passed, we can withdraw our funds from the contract. + +```py +from pathlib import Path +from multiversx_sdk import Account, Address, DevnetEntrypoint + +# create the entrypoint and the delegation controller +entrypoint = DevnetEntrypoint() +controller = entrypoint.create_delegation_controller() + +alice = Account.new_from_pem(Path("../multiversx_sdk/testutils/testwallets/alice.pem")) + +# fetch the nonce of the network +alice.nonce = entrypoint.recall_account_nonce(alice.address) + +# delegation contract +contract = Address.new_from_bech32("erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqf8llllswuedva") + +transaction = controller.create_transaction_for_withdrawing( + sender=alice, + nonce=alice.get_nonce_then_increment(), + delegation_contract=contract +) + +tx_hash = entrypoint.send_transaction(transaction) +``` + +#### Withdrawing funds using the factory + +```py +from pathlib import Path +from multiversx_sdk import Account, DevnetEntrypoint + +# create the entrypoint and the delegation transactions factory +entrypoint = DevnetEntrypoint() +factory = entrypoint.create_delegation_transactions_factory() + +alice = Account.new_from_pem(Path("../multiversx_sdk/testutils/testwallets/alice.pem")) + +transaction = factory.create_transaction_for_withdrawing( + sender=alice.address, + delegation_contract=contract +) + +# fetch the nonce of the network +alice.nonce = entrypoint.recall_account_nonce(alice.address) + +# set the nonce +transaction.nonce = alice.get_nonce_then_increment() + +# sign the transaction +transaction.signature = alice.sign_transaction(transaction) + +# send the transaction +tx_hash = entrypoint.send_transaction(transaction) +``` + +### Relayed transactions + +We are currently on the third iteration (V3) of relayed transactions. V1 and V2 will be deactivated soon, so we'll focus on V3. + +For V3, two new fields have been added on transactions: `relayer` and `relayerSignature`. + +Note that: +1. the sender and the relayer can sign the transaction in any order. +2. before any of the sender or relayer can sign the transaction, the `relayer` field must be set. +3. relayed transactions require an additional `50,000` of gas. +4. the sender and the relayer must be in the same network shard. + +Let’s see how to create a relayed transaction: + +```py +from pathlib import Path +from multiversx_sdk import Account, Address, DevnetEntrypoint, Transaction + +alice = Account.new_from_pem(Path("../multiversx_sdk/testutils/testwallets/alice.pem")) +bob = Address.new_from_bech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx") + +# carol will be our relayer, that means she is paying the gas for the transaction +carol = Account.new_from_pem(Path("../multiversx_sdk/testutils/testwallets/carol.pem")) + +# fetch the sender's nonce of the network +alice.nonce = entrypoint.recall_account_nonce(alice.address) + +# create the transaction +transaction = Transaction( + sender=alice.address, + receiver=bob, + gas_limit=110_000, + chain_id="D", + nonce=alice.get_nonce_then_increment(), + relayer=carol.address, + data="hello".encode() +) + +# sender signs the transaction +transaction.signature = alice.sign_transaction(transaction) + +# relayer signs the transaction +transaction.relayer_signature = carol.sign_transaction(transaction) + +# broadcast the transaction +entrypoint = DevnetEntrypoint() +tx_hash = entrypoint.send_transaction(transaction) +``` + +#### Creating relayed transactions using controllers + +We can create relayed transactions using any of the controllers. Each controller has a `relayer` argument, that can be set if we want to create a relayed transaction. Let's issue a fungible token creating a relayed transaction: + +```py +from pathlib import Path +from multiversx_sdk import Account, DevnetEntrypoint + +# create the entrypoint and the token management controller +entrypoint = DevnetEntrypoint() +controller = entrypoint.create_token_management_controller() + +# create the issuer of the token +alice = Account.new_from_pem(Path("../multiversx_sdk/testutils/testwallets/alice.pem")) + +# carol will be our relayer, that means she is paying the gas for the transaction +carol = Account.new_from_pem(Path("../multiversx_sdk/testutils/testwallets/carol.pem")) + +# fetch the nonce of the network +alice.nonce = entrypoint.recall_account_nonce(alice.address) + +transaction = controller.create_transaction_for_issuing_fungible( + sender=alice, + nonce=alice.get_nonce_then_increment(), + token_name="NEWTOKEN", + token_ticker="TKN", + initial_supply=1_000_000_000000, # 1 million tokens, with 6 decimals + num_decimals=6, + can_freeze=False, + can_wipe=True, + can_pause=False, + can_change_owner=True, + can_upgrade=True, + can_add_special_roles=True, + relayer=carol.address +) + +# relayer also signs the transaction +transaction.relayer_signature = carol.sign_transaction(transaction) + +# sending the transaction +tx_hash = entrypoint.send_transaction(transaction) +``` + +#### Create relayed transactions using factories + +The transactions factories do not have a `relayer` argument, the relayer needs to be set after creating the transaction. This is good because the transaction is not signed by the sender when created. Let's issue a fungible token using the `TokenManagementTransactionsFactory`: + +```py +from pathlib import Path +from multiversx_sdk import Account, DevnetEntrypoint + +# create the entrypoint and the token management transactions factory +entrypoint = DevnetEntrypoint() +factory = entrypoint.create_token_management_transactions_factory() + +# create the issuer of the token +alice = Account.new_from_pem(Path("../multiversx_sdk/testutils/testwallets/alice.pem")) + +# carol will be our relayer, that means she is paying the gas for the transaction +carol = Account.new_from_pem(Path("../multiversx_sdk/testutils/testwallets/carol.pem")) + +transaction = factory.create_transaction_for_issuing_fungible( + sender=alice.address, + token_name="NEWTOKEN", + token_ticker="TKN", + initial_supply=1_000_000_000000, # 1 million tokens, with 6 decimals + num_decimals=6, + can_freeze=False, + can_wipe=True, + can_pause=False, + can_change_owner=True, + can_upgrade=True, + can_add_special_roles=True +) + +# fetch the nonce of the network +alice.nonce = entrypoint.recall_account_nonce(alice.address) + +# set the nonce of the sender +transaction.nonce = alice.get_nonce_then_increment() + +# set the relayer +transaction.relayer = carol.address + +# sender signs the transaction +transaction.signature = alice.sign_transaction(transaction) + +# relayer signs the transaction +transaction.relayer_signature = carol.sign_transaction(transaction) + +# broadcast the transaction +tx_hash = entrypoint.send_transaction(transaction) +``` + +### Guarded Transactions + +#### Creating guarded transactions using controllers + +Very similar to relayers, we have a field `guardian` and a field `guardianSignature`. Each controller has an argument for the guardian. The transaction can be sent to a service that signs it using the guardian's account or we can use another account as a guardian. Let's issue a token using a guarded account. + +```py +from pathlib import Path +from multiversx_sdk import Account, DevnetEntrypoint + +# create the entrypoint and the token management controller +entrypoint = DevnetEntrypoint() +controller = entrypoint.create_token_management_controller() + +# create the issuer of the token +alice = Account.new_from_pem(Path("../multiversx_sdk/testutils/testwallets/alice.pem")) + +# carol is the guardian +carol = Account.new_from_pem(Path("../multiversx_sdk/testutils/testwallets/carol.pem")) + +# fetch the nonce of the network +alice.nonce = entrypoint.recall_account_nonce(alice.address) + +transaction = controller.create_transaction_for_issuing_fungible( + sender=alice, + nonce=alice.get_nonce_then_increment(), + token_name="NEWTOKEN", + token_ticker="TKN", + initial_supply=1_000_000_000000, # 1 million tokens, with 6 decimals + num_decimals=6, + can_freeze=False, + can_wipe=True, + can_pause=False, + can_change_owner=True, + can_upgrade=True, + can_add_special_roles=True, + guardian=carol.address +) + +# guardian also signs the transaction +transaction.guardian_signature = carol.sign_transaction(transaction) + +# sending the transaction +tx_hash = entrypoint.send_transaction(transaction) +``` + +#### Creating guarded transactions using factories + +The transactions factories do not have a `guardian` argument, the guardian needs to be set after creating the transaction. This is good because the transaction is not signed by the sender when created. Let's issue a fungible token using the `TokenManagementTransactionsFactory`: + +```py +from pathlib import Path +from multiversx_sdk import Account, DevnetEntrypoint + +# create the entrypoint and the token management transactions factory +entrypoint = DevnetEntrypoint() +factory = entrypoint.create_token_management_transactions_factory() + +# create the issuer of the token +alice = Account.new_from_pem(Path("../multiversx_sdk/testutils/testwallets/alice.pem")) + +# carol is the guardian +carol = Account.new_from_pem(Path("../multiversx_sdk/testutils/testwallets/carol.pem")) + +transaction = factory.create_transaction_for_issuing_fungible( + sender=alice.address, + token_name="NEWTOKEN", + token_ticker="TKN", + initial_supply=1_000_000_000000, # 1 million tokens, with 6 decimals + num_decimals=6, + can_freeze=False, + can_wipe=True, + can_pause=False, + can_change_owner=True, + can_upgrade=True, + can_add_special_roles=True +) + +# fetch the nonce of the network +alice.nonce = entrypoint.recall_account_nonce(alice.address) + +# set the nonce of the sender +transaction.nonce = alice.get_nonce_then_increment() + +# set the guardian +transaction.guardian = carol.address + +# sender signs the transaction +transaction.signature = alice.sign_transaction(transaction) + +# guardian signs the transaction +transaction.guardian_signature = carol.sign_transaction(transaction) + +# broadcast the transaction +tx_hash = entrypoint.send_transaction(transaction) +``` + +We can also create guarded relayed transactions the same way we did before. Keep in mind that, only the sender can be guarded, the relayer cannot. The same flow can be used. Using controllers, we set both `guardian` and `relayer` fields and then the transaction should be signed by both. Using a factory, we create the transaction, set both both fields and then sign the transaction using the sender's account, then the the guardian and the relayer sign the transaction. + +### Multisig + +The sdk contains components to interact with the [Multisig Contract](https://github.com/multiversx/mx-contracts-rs/releases/tag/v0.45.5). We can deploy a multisig smart contract, add members, propose and execute actions and query the contract. The same as the other components, to interact with a multisig smart contract we can use either the `MultisigController` or the `MultisigTransactionsFactory`. + +#### Deploying a Multisig Smart Contract using the controller + +```py +from pathlib import Path +from multiversx_sdk import Account, Address, ApiNetworkProvider, MultisigController +from multiversx_sdk.abi import Abi + + +alice = Account.new_from_pem(Path("../multiversx_sdk/testutils/testwallets/alice.pem")) +api = ApiNetworkProvider(url="https://devnet-api.multiversx.com") +alice.nonce = api.get_account(alice.address).nonce + +abi = Abi.load(Path("../multiversx_sdk/testutils/testdata/multisig-full.abi.json")) +controller = MultisigController(chain_id="D", network_provider=api, abi=abi) + +transaction = controller.create_transaction_for_deploy( + sender=alice, + nonce=alice.get_nonce_then_increment(), + bytecode=Path("../multiversx_sdk/testutils/testdata/multisig-full.wasm"), + quorum=2, + board=[alice.address, Address.new_from_bech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx")], + gas_limit=100_000_000, +) + +# send the transaction +tx_hash = api.send_transaction(transaction) +``` + +#### Deploying a Multisig Smart Contract using the factory + +```py +from pathlib import Path +from multiversx_sdk import Account, Address, ApiNetworkProvider, MultisigTransactionsFactory, TransactionsFactoryConfig +from multiversx_sdk.abi import Abi + + +alice = Account.new_from_pem(Path("../multiversx_sdk/testutils/testwallets/alice.pem")) +api = ApiNetworkProvider(url="https://devnet-api.multiversx.com") +alice.nonce = api.get_account(alice.address).nonce + +abi = Abi.load(Path("../multiversx_sdk/testutils/testdata/multisig-full.abi.json")) +config = TransactionsFactoryConfig(chain_id="D") +factory = MultisigTransactionsFactory(config=config, abi=abi) + +transaction = factory.create_transaction_for_deploy( + sender=alice.address, + bytecode=Path("../multiversx_sdk/testutils/testdata/multisig-full.wasm"), + quorum=2, + board=[alice.address, Address.new_from_bech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx")], + gas_limit=100_000_000, +) +transaction.nonce = alice.get_nonce_then_increment() +transaction.signature = alice.sign_transaction(transaction) + +tx_hash = api.send_transaction(transaction) +``` + +#### Propose an action using the controller + +We'll propose an action to send some EGLD to Carol. After we sent the proposal, we'll also parse the outcome of the transaction to get the `proposal id`. The id can be later for signing and performing the proposal. + +```py +from pathlib import Path +from multiversx_sdk import Account, Address, ApiNetworkProvider, MultisigController +from multiversx_sdk.abi import Abi + + +alice = Account.new_from_pem(Path("../multiversx_sdk/testutils/testwallets/alice.pem")) +api = ApiNetworkProvider(url="https://devnet-api.multiversx.com") +alice.nonce = api.get_account(alice.address).nonce + +abi = Abi.load(Path("../multiversx_sdk/testutils/testdata/multisig-full.abi.json")) +controller = MultisigController(chain_id="D", network_provider=api, abi=abi) + +transaction = controller.create_transaction_for_propose_transfer_execute( + sender=alice, + nonce=alice.get_nonce_then_increment(), + contract=Address.new_from_bech32("erd1qqqqqqqqqqqqqpgq2ukrsg73nwgu3uz6sp8vequuyrhtv2akd8ssyrg7wj"), + receiver=Address.new_from_bech32("erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8"), + gas_limit=10_000_000, + native_token_amount=1_000000000000000000, # 1 EGLD +) + +# send the transaction +tx_hash = api.send_transaction(transaction) + +# parse the outcome and get the proposal id +action_id = controller.await_completed_execute_propose_any(tx_hash) +``` + +#### Propose an action using the factory + +Proposing an action for a multisig contract using the `MultisigFactory` is very similar to using the controller, but in order to get the proposal id we need to use `MultisigTransactionsOutcomeParser`. + +```py +from pathlib import Path +from multiversx_sdk import Account, Address, ApiNetworkProvider, MultisigTransactionsFactory, TransactionsFactoryConfig, MultisigTransactionsOutcomeParser, TransactionAwaiter +from multiversx_sdk.abi import Abi + + +alice = Account.new_from_pem(Path("../multiversx_sdk/testutils/testwallets/alice.pem")) +api = ApiNetworkProvider(url="https://devnet-api.multiversx.com") +alice.nonce = api.get_account(alice.address).nonce + +abi = Abi.load(Path("../multiversx_sdk/testutils/testdata/multisig-full.abi.json")) +config = TransactionsFactoryConfig(chain_id="D") +factory = MultisigTransactionsFactory(config=config, abi=abi) + +transaction = factory.create_transaction_for_propose_transfer_execute( + sender=alice.address, + contract=Address.new_from_bech32("erd1qqqqqqqqqqqqqpgq2ukrsg73nwgu3uz6sp8vequuyrhtv2akd8ssyrg7wj"), + receiver=Address.new_from_bech32("erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8"), + gas_limit=10_000_000, + native_token_amount=1_000000000000000000, # 1 EGLD +) +transaction.nonce = alice.get_nonce_then_increment() +transaction.signature = alice.sign_transaction(transaction) + +tx_hash = api.send_transaction(transaction) + +# wait for the transaction to execute +transaction_awaiter = TransactionAwaiter(fetcher=api) +transaction_awaiter.await_completed(tx_hash) + +# parse the outcome of the transaction +parser = MultisigTransactionsOutcomeParser(abi=abi) +transaction_on_network = api.get_transaction(tx_hash) +action_id = parser.parse_propose_action(transaction_on_network) +``` + +#### Querying the Multisig Smart Contract + +Unlike creating transactions, querying the multisig can be performed only using the controller. Let's query the contract to get all board members. + +```py +from pathlib import Path +from multiversx_sdk import Address, ApiNetworkProvider, MultisigController +from multiversx_sdk.abi import Abi + +abi = Abi.load(Path("../multiversx_sdk/testutils/testdata/multisig-full.abi.json")) +api = ApiNetworkProvider(url="https://devnet-api.multiversx.com") +controller = MultisigController(chain_id="D", network_provider=api, abi=abi) + +contract = Address.new_from_bech32("erd1qqqqqqqqqqqqqpgq2ukrsg73nwgu3uz6sp8vequuyrhtv2akd8ssyrg7wj") +board_members = controller.get_all_board_members(contract) +``` + +### Governance + +We can create transactions for creating a new governance proposal, vote for a proposal or query the governance contract. + +#### Creating a new proposal using the controller + +```py +from multiversx_sdk import Account, ProxyNetworkProvider, GovernanceController + +alice = Account.new_from_pem(Path("../multiversx_sdk/testutils/testwallets/alice.pem")) +proxy = ProxyNetworkProvider("https://devnet-gateway.multiversx.com") +alice.nonce = proxy.get_account(alice.address).nonce + +controller = GovernanceController(chain_id="D", network_provider=proxy) +commit_hash = "1db734c0315f9ec422b88f679ccfe3e0197b9d67" + +transaction = controller.create_transaction_for_new_proposal( + sender=alice, + nonce=alice.get_nonce_then_increment(), + commit_hash=commit_hash, + start_vote_epoch=10, + end_vote_epoch=15, + native_token_amount=500_000000000000000000, +) + +# send the transaction +tx_hash = proxy.send_transaction(transaction) + +# get proposal outcome +[proposal] = controller.await_completed_new_proposal(tx_hash) +print(proposal.proposal_nonce) +print(proposal.commit_hash) +print(proposal.start_vote_epoch) +print(proposal.end_vote_epoch) +``` + +#### Creating a new proposal using the factory + +```py +from multiversx_sdk import (Account, ProxyNetworkProvider, GovernanceTransactionsFactory, + GovernanceTransactionsOutcomeParser, TransactionsFactoryConfig, TransactionAwaiter) + +alice = Account.new_from_pem(Path("../multiversx_sdk/testutils/testwallets/alice.pem")) +proxy = ProxyNetworkProvider("https://devnet-gateway.multiversx.com") +alice.nonce = proxy.get_account(alice.address).nonce + +config = TransactionsFactoryConfig(chain_id="D") +factory = GovernanceTransactionsFactory(config) +commit_hash = "1db734c0315f9ec422b88f679ccfe3e0197b9d67" + +transaction = factory.create_transaction_for_new_proposal( + sender=alice.address, + commit_hash=commit_hash, + start_vote_epoch=10, + end_vote_epoch=15, + native_token_amount=500_000000000000000000, +) +transaction.nonce = alice.get_nonce_then_increment() +transaction.signature = alice.sign_transaction(transaction) + +# send the transaction +tx_hash = proxy.send_transaction(transaction) + +# make sure the transaction is complete +awaiter = TransactionAwaiter(fetcher=proxy) +transaction_on_network = awaiter.await_completed(tx_hash) + +# get proposal outcome +parser = GovernanceTransactionsOutcomeParser() +[proposal] = parser.parse_new_proposal(transaction_on_network=transaction_on_network) + +print(proposal.proposal_nonce) +print(proposal.commit_hash) +print(proposal.start_vote_epoch) +print(proposal.end_vote_epoch) +``` + +#### Vote for a proposal using the controller + +```py +from multiversx_sdk import Account, ProxyNetworkProvider, GovernanceController, VoteType + + +alice = Account.new_from_pem(Path("../multiversx_sdk/testutils/testwallets/alice.pem")) +proxy = ProxyNetworkProvider("https://devnet-gateway.multiversx.com") +alice.nonce = proxy.get_account(alice.address).nonce + +controller = GovernanceController(chain_id="D", network_provider=proxy) + +transaction = controller.create_transaction_for_voting( + sender=alice, + nonce=alice.get_nonce_then_increment(), + proposal_nonce=1, + vote=VoteType.YES +) + +# send the transaction +tx_hash = proxy.send_transaction(transaction) + +# get vote outcome +[vote] = controller.await_completed_vote(tx_hash) +print(vote.proposal_nonce) +print(vote.vote) +print(vote.total_stake) +print(vote.total_voting_power) +``` + +#### Vote for a proposal using the factory + +```py +from multiversx_sdk import (Account, ProxyNetworkProvider, GovernanceTransactionsFactory, + GovernanceTransactionsOutcomeParser, TransactionsFactoryConfig, + TransactionAwaiter, VoteType) + +alice = Account.new_from_pem(Path("../multiversx_sdk/testutils/testwallets/alice.pem")) +proxy = ProxyNetworkProvider("https://devnet-gateway.multiversx.com") +alice.nonce = proxy.get_account(alice.address).nonce + +config = TransactionsFactoryConfig(chain_id="D") +factory = GovernanceTransactionsFactory(config) + +transaction = factory.create_transaction_for_voting( + sender=alice.address, + proposal_nonce=1, + vote=VoteType.YES, +) +transaction.nonce = alice.get_nonce_then_increment() +transaction.signature = alice.sign_transaction(transaction) + +# send the transaction +tx_hash = proxy.send_transaction(transaction) + +# make sure the transaction is complete +awaiter = TransactionAwaiter(fetcher=proxy) +transaction_on_network = awaiter.await_completed(tx_hash) + +# get vote outcome +parser = GovernanceTransactionsOutcomeParser() +[vote] = parser.parse_vote(transaction_on_network=transaction_on_network) + +print(vote.proposal_nonce) +print(vote.vote) +print(vote.total_stake) +print(vote.total_voting_power) +``` + +#### Querying the governance contract + +Unlike creating transactions, querying the contract is only possible using the controller. Let's query the contract to get more details about a proposal. + +```py +from multiversx_sdk import ProxyNetworkProvider, GovernanceController + + +proxy = ProxyNetworkProvider("https://devnet-gateway.multiversx.com") +controller = GovernanceController(chain_id="D", network_provider=proxy) + +proposal_info = controller.get_proposal(proposal_nonce=1) +``` + +## Addresses + +Create an `Address` object from a _bech32-encoded_ string: + +```py +from multiversx_sdk import Address + +address = Address.new_from_bech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th") + +print("Address (bech32-encoded)", address.to_bech32()) +print("Public key (hex-encoded):", address.to_hex()) +print("Public key (hex-encoded):", address.get_public_key().hex()) +``` + +Create an address from a _hex-encoded_ string - note that you have to provide the address prefix, also known as the **HRP** (_human-readable part_ of the address). If not provided, the default one will be used. + +```py +from multiversx_sdk import Address + +address = Address.new_from_hex("0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1", "erd") +``` + +Create an address from a raw public key: + +```py +from multiversx_sdk import Address + +pubkey = bytes.fromhex("0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1") +address = Address(pubkey, "erd") +``` + +Alternatively, you can use an `AddressFactory` (initialized with a specific **HRP**) to create addresses. If the hrp is not provided, the default one will be used. + +```py +from multiversx_sdk import AddressFactory + +factory = AddressFactory("erd") + +address = factory.create_from_bech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th") +address = factory.create_from_hex("0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1") +address = factory.create_from_public_key(bytes.fromhex("0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1")) +``` + +### Getting the shard of an address + +```py +from multiversx_sdk import Address, AddressComputer + +address_computer = AddressComputer() +address = Address.new_from_bech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th") + +print("Shard:", address_computer.get_shard_of_address(address)) +``` + +### Checking if the address is a smart contract address + +```py +from multiversx_sdk import Address + +address = Address.new_from_bech32("erd1qqqqqqqqqqqqqpgquzmh78klkqwt0p4rjys0qtp3la07gz4d396qn50nnm") +print("Is contract address:", address.is_smart_contract()) +``` + +### Changing the default hrp + +We have a configuration class, called `LibraryConfig`, that only stores (for the moment) the **default hrp** of the addresses. The default value is `erd`. The hrp can be changed when instantiating an address, or it can be changed in the `LibraryConfig` class, and all the addresses created will have the newly set hrp. + +```py +from multiversx_sdk import Address +from multiversx_sdk import LibraryConfig + + +print(LibraryConfig.default_address_hrp) +address = Address.new_from_hex("0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1") +print(address.to_bech32()) + +LibraryConfig.default_address_hrp = "test" +address = Address.new_from_hex("0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1") +print(address.to_bech32()) + +# setting back the default value +LibraryConfig.default_address_hrp = "erd" +``` + +## Wallets + +### Generating a mnemonic + +Mnemonic generation is based on [`trezor/python-mnemonic`](https://github.com/trezor/python-mnemonic) and can be achieved as follows: + +```py +from multiversx_sdk import Mnemonic + +mnemonic = Mnemonic.generate() +words = mnemonic.get_words() + +print(words) +``` + +### Saving the mnemonic to a keystore file + +The mnemonic can be saved to a keystore file: + +```py +from pathlib import Path +from multiversx_sdk import Mnemonic, UserWallet + +mnemonic = Mnemonic.generate() + +# saves the mnemonic to a keystore file with kind=mnemonic +wallet = UserWallet.from_mnemonic(mnemonic.get_text(), "password") +wallet.save(Path("walletWithMnemonic.json")) +``` + +#### Deriving secret keys from a mnemonic + +Given a mnemonic, we can derive keypairs: + +```py +from multiversx_sdk import Mnemonic + +mnemonic = Mnemonic.generate() + +secret_key = mnemonic.derive_key(0) +public_key = secret_key.generate_public_key() + +print("Secret key:", secret_key.hex()) +print("Public key:", public_key.hex()) +``` + +#### Saving a secret key to a keystore file + +The secret key can also be saved to a keystore file: + +```py +from pathlib import Path +from multiversx_sdk import Mnemonic, UserWallet + +mnemonic = Mnemonic.generate() + +# by default, derives using the index = 0 +secret_key = mnemonic.derive_key() + +# saves the mnemonic to a keystore file with kind=secretKey +wallet = UserWallet.from_secret_key(secret_key, "password") +wallet.save(Path("walletWithSecretKey.json")) +``` + +#### Saving a secrey 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.** + +```py +from pathlib import Path +from multiversx_sdk import Address, UserPEM + +mnemonic = Mnemonic.generate() + +# by default, derives using the index = 0 +secret_key = mnemonic.derive_key() + +label = Address(public_key.buffer, "erd").to_bech32() +pem = UserPEM(label=label, secret_key=secret_key) +pem.save(Path("wallet.pem")) +``` + +### 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. + +```py +from multiversx_sdk import KeyPair + +keypair = KeyPair.generate() + +# get secret key +secret_key = keypair.get_secret_key() + +# get public key +public_key = keypair.get_public_key() +``` + +### Loading a wallets from keystore mnemonic file + +Load a keystore that holds an _encrypted mnemonic_ (and perform wallet derivation at the same time): + +```py +from pathlib import Path +from multiversx_sdk import UserWallet + +# loads the mnemonic and derives the a secret key; default index = 0 +secret_key = UserWallet.load_secret_key(Path("walletWithMnemonic.json"), "password") +address = secret_key.generate_public_key().to_address("erd") + +print("Secret key:", secret_key.hex()) +print("Address:", address.to_bech32()) + +# derive secret key with index = 7 +secret_key = UserWallet.load_secret_key( + path=Path("walletWithMnemonic.json"), + password="password", + address_index=7 +) +address = secret_key.generate_public_key().to_address() + +print("Secret key:", secret_key.hex()) +print("Address:", address.to_bech32()) +``` + +#### Loading a wallet from a keystore secret key file + +```py +from pathlib import Path +from multiversx_sdk import UserWallet + +secret_key = UserWallet.load_secret_key(Path("walletWithSecretKey.json"), "password") +address = secret_key.generate_public_key().to_address("erd") + +print("Secret key:", secret_key.hex()) +print("Address:", address.to_bech32()) +``` + +#### Loading a wallet from a PEM file + +```py +from pathlib import Path +from multiversx_sdk import UserPEM + +pem = UserPEM.from_file(Path("wallet.pem")) + +print("Secret key:", pem.secret_key.hex()) +print("Public key:", pem.public_key.hex()) +``` + +## Signing objects + +The signing is performed using the **secret key** of an account. We have a few wrappers over the secret key, like [Account](#creating-accounts) that make siging easier. We'll first learn how we can sign using an `Account` and then we'll see how we can sign using the secret key. + +#### Signing a Transaction using an Account + +We are going to assume we have an account at this point. If you don't fell free to check out the [creating an account section](#creating-accounts). + +```py +from pathlib import Path +from multiversx_sdk import Account, Address, Transaction + +account = Account.new_from_pem(Path("../multiversx_sdk/testutils/testwallets/alice.pem")) + +transaction = Transaction( + nonce=90, + sender=account.address, + receiver=Address.new_from_bech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"), + value=1000000000000000000, + gas_limit=50000, + chain_id="D" +) + +# apply the signature on the transaction +transaction.signature = account.sign_transaction(transaction) + +print(transaction.to_dictionary()) +``` + +#### Signing a Transaction using a SecretKey + +```py +from multiversx_sdk import Transaction, TransactionComputer, UserSecretKey + +secret_key_hex = "413f42575f7f26fad3317a778771212fdb80245850981e48b58a4f25e344e8f9" +secret_key = UserSecretKey(bytes.fromhex(secret_key_hex)) +public_key = secret_key.generate_public_key() + +transaction = Transaction( + nonce=90, + sender=public_key.to_address(), + receiver=Address.new_from_bech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"), + value=1000000000000000000, + gas_limit=50000, + chain_id="D" +) + +# serialize the transaction +transaction_computer = TransactionComputer() +serialized_transaction = transaction_computer.compute_bytes_for_signing(transaction) + +# apply the signature on the transaction +transaction.signature = secret_key.sign(serialized_transaction) + +print(transaction.to_dictionary()) +``` + +#### Signing a Transaction by hash + +```py +from pathlib import Path +from multiversx_sdk import Account, Address, Transaction, TransactionComputer + +account = Account.new_from_pem(Path("../multiversx_sdk/testutils/testwallets/alice.pem")) + +transaction = Transaction( + nonce=90, + sender=account.address, + receiver=Address.new_from_bech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"), + value=1000000000000000000, + gas_limit=50000, + chain_id="D" +) + +transaction_computer = TransactionComputer() + +# sets the least significant bit of the options field to `1` +transaction_computer.apply_options_for_hash_signing(transaction) + +# compute a keccak256 hash for signing +hash = transaction_computer.compute_hash_for_signing(transaction) + +# sign and apply the signature on the transaction +transaction.signature = account.sign(hash) + +print(transaction.to_dictionary()) +``` + +#### Signing a Message using an Account + +```py +from pathlib import Path +from multiversx_sdk import Account, Message + +account = Account.new_from_pem(Path("../multiversx_sdk/testutils/testwallets/alice.pem")) + +# creating a message +message = Message(data="this is a test message".encode(), address=account.address) + +# signing the message +message.signature = account.sign_message(message) +``` + +#### Signing a message using a SecretKey + +```py +from multiversx_sdk import UserSecretKey, Message, MessageComputer + +secret_key_hex = "413f42575f7f26fad3317a778771212fdb80245850981e48b58a4f25e344e8f9" +secret_key = UserSecretKey(bytes.fromhex(secret_key_hex)) +public_key = secret_key.generate_public_key() + +message_computer = MessageComputer() + +# creating a message +message = Message(data="this is a test message".encode(), address=public_key.to_address()) + +# serialize the message +serialized_message = message_computer.compute_bytes_for_signing(message) + +# signing the message +message.signature = secret_key.sign(serialized_message) +``` + +## Verifying signatures + +The verification of a signature is done using the **public key** of an account. We have a few wrappers over public keys that make the verification of signatures a little bit easier. + +#### Verifying Transaction signature using a UserVerifier + +```py +from pathlib import Path +from multiversx_sdk import Account, Address, Transaction, TransactionComputer, UserVerifier + +account = Account.new_from_pem(Path("../multiversx_sdk/testutils/testwallets/alice.pem")) + +transaction = Transaction( + nonce=90, + sender=account.address, + receiver=Address.new_from_bech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"), + value=1000000000000000000, + gas_limit=50000, + chain_id="D" +) + +# apply the signature on the transaction +transaction.signature = account.sign_transaction(transaction) + +# instantiating a user verifier; basically gets the public key +alice = Address.new_from_bech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th") +alice_verifier = UserVerifier.from_address(alice) + +# serialize the transaction for verification +transaction_computer = TransactionComputer() +serialized_transaction = transaction_computer.compute_bytes_for_verifying(transaction) + +# verify the signature +is_signed_by_alice = alice_verifier.verify( + data=serialized_transaction, + signature=transaction.signature +) + +print("Transaction is signed by Alice:", is_signed_by_alice) +``` + +#### Verifying Message signature using a UserVerifier + +```py +from pathlib import Path +from multiversx_sdk import Account, Address, Message, MessageComputer, UserVerifier + +account = Account.new_from_pem(Path("../multiversx_sdk/testutils/testwallets/alice.pem")) + +message = Message( + data="this is a test message".encode(), + address=account.address +) + +# apply the signature on the message +message.signature = account.sign_message(message) + +# instantiating a user verifier; basically gets the public key +alice = Address.new_from_bech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th") +alice_verifier = UserVerifier.from_address(alice) + +# serialize the message for verification +message_computer = MessageComputer() +serialized_message = message_computer.compute_bytes_for_verifying(message) + +# verify the signature +is_signed_by_alice = alice_verifier.verify( + data=serialized_message, + signature=message.signature +) + +print("Message is signed by Alice:", is_signed_by_alice) +``` + +#### Verifying a signature using the public key + +```py +from pathlib import Path +from multiversx_sdk import Account, Address, Transaction, TransactionComputer, UserPublicKey + +account = Account.new_from_pem(Path("../multiversx_sdk/testutils/testwallets/alice.pem")) + +transaction = Transaction( + nonce=90, + sender=account.address, + receiver=Address.new_from_bech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"), + value=1000000000000000000, + gas_limit=50000, + chain_id="D" +) + +# apply the signature on the transaction +transaction.signature = account.sign_transaction(transaction) + +# instantiating a public key +alice = Address.new_from_bech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th") +public_key = UserPublicKey(alice.get_public_key()) + +# serialize the transaction for verification +transaction_computer = TransactionComputer() +serialized_transaction = transaction_computer.compute_bytes_for_verifying(transaction) + +# verify the signature +is_signed_by_alice = public_key.verify( + data=serialized_transaction, + signature=transaction.signature +) + +print("Transaction is signed by Alice:", is_signed_by_alice) +``` + +#### Sending messages over boundaries + +Generally speaking, signed `Message` objects are meant to be sent to a remote party (e.g. a service), which can then verify the signature. + +In order to prepare a message for transmission, you can use the `MessageComputer.packMessage()` utility method: + +```py +from pathlib import Path +from multiversx_sdk import Account, Message, MessageComputer + +account = Account.new_from_pem(Path("../multiversx_sdk/testutils/testwallets/alice.pem")) + +# creating a message +message = Message(data="this is a test message".encode(), address=account.address) + +# signing the message +message.signature = account.sign_message(message) + +message_computer = MessageComputer() +packed_message = message_computer.pack_message(message) +print(packed_message) +``` + +Then, on the receiving side, you can use `MessageComputer.unpackMessage()` to reconstruct the message, prior verification: + +```py +from multiversx_sdk import Address, MessageComputer, UserPublicKey + +alice = Address.new_from_bech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th") + +message_computer = MessageComputer() + +# restore message +message = message_computer.unpack_message(packed_message) + +# verify signature +public_key = UserPublicKey(alice.get_public_key()) + +is_signed_by_alice = public_key.verify(message_computer.compute_bytes_for_verifying(message), message.signature) +print("Is signed by Alice:", is_signed_by_alice) +``` + +## Generating a Native Auth Token + +The sdk implements a native auth client that can be used to generate a native auth token. + +```py +from pathlib import Path + +from multiversx_sdk import Account, Message, NativeAuthClient, NativeAuthClientConfig + +config = NativeAuthClientConfig(origin="https://devnet-api.multiversx.com", api_url="https://devnet-api.multiversx.com") +client = NativeAuthClient(config) + +account = Account.new_from_keystore( + file_path=Path("../multiversx_sdk/testutils/testwallets/withDummyMnemonic.json"), + password="password", + address_index=0 +) + +init_token = client.initialize() +token_for_signing = client.get_token_for_signing(account.address, init_token) +signature = account.sign_message(Message(token_for_signing)) +access_token = client.get_token(address=account.address, token=init_token, signature=signature.hex()) + +print(access_token) +``` + +## Validating a Native Auth Token + +The sdk implements native auth server-side components that can be used to validate a native auth token. If you want to see the validated token, you can simply do the following: + +```py +from multiversx_sdk import NativeAuthServerConfig, NativeAuthServer + +config = config = NativeAuthServerConfig( + api_url="https://devnet-api.multiversx.com", + accepted_origins=["https://devnet-api.multiversx.com"], + max_expiry_seconds=86400, +) + +server = NativeAuthServer(config) + +# we are using the token generated above +validated_token = server.validate(access_token) + +print(validated_token.address.to_bech32()) +print(validated_token.signer_address.to_bech32()) +print(validated_token.issued) +print(validated_token.expires) +print(validated_token.origin) +print(validated_token.extra_info) +``` + +Or, alternatively, if you just want to check if the token is valid, you can do the following: + +```py +from multiversx_sdk import NativeAuthServerConfig, NativeAuthServer + +config = config = NativeAuthServerConfig( + api_url="https://devnet-api.multiversx.com", + accepted_origins=["https://devnet-api.multiversx.com"], + max_expiry_seconds=86400, +) + +server = NativeAuthServer(config) + +# we are using the token generated above +is_valid = server.is_valid(access_token) +print(is_valid) +``` + +## Start your first project + +We recommend using a Python version equal to `3.11` or higher, but the sdk should work with any version higher than `3.8`. We also recommend using a virtual environment for managing dependencies. Make sure you also have `pip` installed on your machine. + +Using a Terminal or Console, create a directory on your system (hello-multiversx in this example) and make it the working directory. + +```sh +mkdir hello-multiversx +cd hello-multiversx +``` + +### Create a virtual environment + +Run the following command to create and activate your virtual environment: + +```sh +python3 -m venv ./venv +source ./venv/bin/activate +``` + +After the virtual environment is created, we can install the sdk running the following command: + +```sh +pip install multiversx-sdk +``` + +If you wish to interact with Ledger devices through the sdk, install the sdk as follows: + +```sh +pip install multiversx-sdk[ledger] +``` + +If your project has multiple dependencies, we recommend using a `requirements.txt` file for having all dependencies in one place. Inside the file we are going to place each dependency on a new line: + +```sh +multiversx-sdk +``` + +Additionally, we can also install it directly from GitHub. Place this line on a new line of your `requirements.txt` file. In this example, we are going to install the version `2.0.0`: + +```sh +git+https://git@github.com/multiversx/mx-sdk-py.git@v2.0.0#egg=multiversx_sdk +``` + +If you've places all dependencies in a `requirements.txt` file, make sure you also install them by running: + +```sh +pip install -r requirements.txt +``` + +We can then create a `main.py` file where we can write our code. + +### Importing objects from the sdk + +The most common classes can be imported from package level: + +```py +from multiversx_sdk import Address, Transaction +``` + +When interacting with smart contracts, we might want to make use of the abi file or other contract types. We should import those from the abi subpackage. + +```py +from multiversx_sdk.abi import Abi, BigUIntValue, StringValue +``` + + +--- + +### Random Numbers in Smart Contracts + +## Introduction + +Randomness in the blockchain environment is a challenging task to accomplish. Due to the nature of the environment, nodes must all have the same "random" generator to be able to reach consensus. This is solved by using Golang's standard seeded random number generator, directly in the VM: https://cs.opensource.google/go/go/+/refs/tags/go1.17.5:src/math/rand/ + +The VM function `mBufferSetRandom` uses this library, seeded with the concatenation of: + +- previous block random seed +- current block random seed +- tx hash + +We're not going to go into details about how exactly the Golang library uses the seed or how it generates said random numbers, as that's not the purpose of this tutorial. + + +## Random numbers in smart contracts + +The `ManagedBuffer` type has two methods you can use for this: + +- `fn new_random(nr_bytes: usize) -> Self`, which creates a new `ManagedBuffer` of `nr_bytes` random bytes +- `fn set_random(&mut self, nr_bytes: usize)`, which sets an already existing buffer to random bytes + +For convenience, a wrapper over these methods was created, namely the `RandomnessSource` struct, which contains methods for generating a random number for all base rust unsigned numerical types, and a method for generating random bytes. + +For example, let's say you wanted to generate `n` random `u16`: + +```rust +let mut rand_source = RandomnessSource::new(); +for _ in 0..n { + let my_rand_nr = rand_source.next_u16(); + // do something with the number +} +``` + +Similar methods exist for all Rust unsigned numerical types. + + +## Random numbers in a specific range + +Let's say you wanted to implement a Fisher-Yates shuffling algorithm inside your smart contract (https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle). + +The `RandomnessSource` struct provides methods for generating numbers within a range, namely `fn next_usize_in_range(min: usize, max: usize) -> usize`, which generates a random `usize` in the `[min, max)` range. These methods are available for the rest of the numerical types as well, but for this example, we need `usize` (in Rust, indexes are `usize`). + +For that, you would probably have a vector of some kind. For example, let's assume you wanted to shuffle a vector of `ManagedBuffer`s. + +```rust +let mut my_vec = ManagedVec::new(); +// ... +// fill my_vec with elements +// ... + +let vec_len = my_vec.len(); +let mut rand_source = RandomnessSource::new(); +for i in 0..vec_len { + let rand_index = rand_source.next_usize_in_range(i, vec_len); + let first_item = my_vec.get(i).unwrap(); + let second_item = my_vec.get(rand_index).unwrap(); + + my_vec.set(i, &second_item); + my_vec.set(rand_index, &first_item); +} +``` + +This algorithm will shuffle each element at position `i`, with an element from position `[i, vec_len)`. + + +## Random bytes + +Let's say you want to create some NFTs in your contract, and want to give each of them a random hash of 32 bytes. To do that, you would use the `next_bytes(len: usize)` method of the `RandomnessSource` struct: + +```rust +let mut rand_source = RandomnessSource::new(); +let rand_hash = rand_source.next_bytes(32); +// NFT create logic here +``` + + +## Considerations + +:::caution +NEVER have logic in your smart contract that only depends on the current state. +::: + +Example of BAD implementation: + +```rust +#[payable("EGLD")] +#[endpoint(rollDie)] +fn roll_die(&self) { + // ... + let payment = self.call_value().egld(); + let rand_nr = rand_source.next_u8(); + if rand_nr % 6 == 0 { + let prize = payment * 2u32; + self.send().direct(&caller, &prize); + } + // ... +} +``` + +This is very easy to abuse, as you can simply simulate your transactions, and only send them when you see you've won. Therefore, guaranteeing a 100% win chance! + +Keep in mind you are not running this on your own private server, you are running it on a public blockchain, so you need a complete shift in design. + +Example of GOOD implementation: + +```rust +#[payable("EGLD")] +#[endpoint(signUp)] +fn sign_up(&self) { + let already_signed_up = self.user_list().insert(caller.clone()); + if already_signed_up { + sc_panic!("Already signed up"); + } +} + +#[only_owner] +#[endpoint(selectWinners)] +fn select_winners(&self) { + for user in self.user_list().iter() { + let rand_nr = rand_source.next_u8(); + if rand_nr % 6 == 0 { + self.winners_list().insert(user.clone()); + } + } +} + +#[endpoint] +fn claim(&self) { + let was_winner = self.winners_list().swap_remove(&caller); + if was_winner { + self.send().direct_egld(&caller, &prize); + } +} + +#[storage_mapper("userList")] +fn user_list(&self) -> UnorderedSetMapper; + +#[storage_mapper("winnersList")] +fn winners_list(&self) -> UnorderedSetMapper; +``` + + +## Conclusion + +This random number generator should be enough for most purposes. Enjoy using it for your lotteries and such! + +--- + +### rating + +This page describes the structure of the `rating` index (Elasticsearch), and also depicts a few examples of how to query it. + + +## _id + +The `_id` field of this index is composed in this way: `{validator_bls_key}_{epoch}` (example: `blskey_37`). + + +## Fields + + +| Field | Description | +|-----------|------------------------------------------------------------------| +| rating | The rating of the validator, which can be in the [0, 100] range. | + + +## Query examples + + +### Fetch rating of a validator for a specific epoch + +``` +curl --request GET \ + --url ${ES_URL}/rating/_search \ + --header 'Content-Type: application/json' \ + --data '{ + "query": { + "match": { + "_id":"${BLS_KEY}_600" + } + } +}' +``` + +--- + +### React Development + +## Introduction + +Every developer has his/her own code style that has been developed along the way, with bits and quirks that, at some point, become a part of oneself. + +However, in a big team and in a big project, small quirks and personal preferences can add up and turn the codebase into a big lasagna. + +Given this, we have established some basic principles and a code style we would like to follow. These are, of course, not set in stone, and can be changed, given a valid reason. + + +## Using Git + +:::important +* We use **yarn** as a package manager. +::: + + +### Branch naming +We use a system for **branch naming**: \[your initials\]/-[feature || fix || redesign\]/-\[2-3 words describing the branch\] +> e.g. John Doe creates `jd/feature/fix-thing-called-twice` + +:::note +All branch names are lowercase +::: + + +## Basic principles + + +### Imports and exports +We import **lodash-specific functions** instead of the whole library for the tree shaking to take effect. + +```jsx +// DON'T import _ from 'lodash'; +// this also doesn't shake that tree, unfortunately. +// You can find more info on webpack website about tree shaking. +import {cloneDeep, isEmpty} from 'lodash'; // DO import cloneDeep from 'lodash/cloneDeep'; +import isEmpty from 'lodash/isEmpty'; +import last from 'lodash/last'; +import uniqBy from 'lodash/uniqBy'; +import get from 'lodash/get';` +``` + +Do not use `default` exports. **Use named exports** instead. + + +### Using conditionals +Avoid using nested conditionals. **Use early returns** instead. + +```jsx +// 🚫 DON'T +if (condition) { + if (anotherCondition) { + // do stuff + } +} +// ✅ DO +if (!condition) { + return; +} +if (!anotherCondition) { + return; +} +// do stuff +``` + + +### Defining function arguments +If a function has more than 2 arguments and the second argument is not optional, **use an object** instead. + +```jsx +// 🚫 DON'T +const myFunction = (arg1, arg2, arg3) => { + // do stuff +} + +// ⚠️ AVOID +const myFunction = (arg1: string, arg2?: boolean) => { // not recommended but acceptable + // do stuff +} + +// ✅ DO +const myFunction = ({arg1, arg2, arg3}) => { + // do stuff +} +``` + + +### Validity checks +We use **`!=` or `== null` verifications** for all variables, and `!myBool` for booleans only. + +```jsx +// 🚫 DON'T +const user = userSelector(state); +if (!user) { + //do something + if (!refetchAttempted){ + refetch(); + } +} + +// ✅ DO +const user = userSelector(state); +if (user == null) { + //do something + if (!refetchAttempted) { + refetch(); + } +} +``` + +When using a property from an object inside a condition, check for null with **optional chaining operator**; + +```jsx +// 🚫 DON'T +if (array != null && array.length){ + // do stuff +} +// ✅ DO +if (array?.length > 0){ + //do stuff +} +``` + + +### Folder structure +For folder and file naming we're using the following convention: +**camelCase for all folders and files, except when it's a React Component or a Module Root Folder**, in which case we're using PascalCase. +Also, for components' and containers' subcomponents, we create separate folders, even if there is no style file present. + +Each folder that has an exportable component will have an **`index.tsx`** file for ease of import.
+Each folder that has an exportable file will have an **`index.ts`** file for ease of import. + + +### File length conventions +- < 100 lines of code - ✅ OK +- 100 - 200 lines of code - try to split the file into smaller files +- 200 - 300 lines of code - should be split the file into smaller files +- \> 300 lines of code 🚫 DON'T + + +### Naming conventions +* When naming types, use the suffix **`Type`**. This helps us differentiate between types and components. When naming component props types, use MyComponentPropsType. When naming a type that is not a component, use MyFunctionType. When naming return values, use MyFunctionReturnType. + +**Try to extract at the top of the function all constants** such as strings, numbers, objects, instead of declaring this ad hoc inside the code. + +```jsx +// 🚫 DON'T +if (x === 'rejected' && y === 4) { + // do stuff +} +// ✅ DO +enum PermissionEnum { // all enums should be in PascalCase and suffixed with "Enum" + rejected = "rejected" +} +const ACCESS_LEVEL = 4; // all constants declared on top of functions should be in UPPER_CASE +if (x === PermissionsEnum.rejected && y === ACCESS_LEVEL) +{ + //do stuff +} +``` + + +## React guidelines + + +### Using functional components +We're using **functional components** for almost all new components, no classes, except when strictly necessary (e.g. error boundaries); + + +### Using selectors +We use `useSelector` and `useDispatch` hooks to connect to redux store via react-redux. 🚫 **No** `mapStateToProps` in functional components. + +We use **reselect** for memoizing complex state variables and composing those into optimized selectors that don't rerender the whole tree when the values don't change. This package needs to be added only when there is a performance bottleneck, either existing or expected. + + +### Defining handlers + +We're using **"handle" prefix** for handlers defined in the function and **"on"** prefix for handlers passed via props. `handleTouchStart` vs `props.onTouchStart`, to distinguish between own handlers and parent handlers. + +```jsx +function handleClick(e) { + props.onClickClick(); +} + +
+ +//destructured before, instantly known to be from parent +
` +``` + + +### Number of props per component + +If a component has more than 7 props, it should draw a red flag and be refactored. If it has >= 10 props, it should be refactored immediately. Strategies for refactoring: +- split into smaller components and pass them as props +- use a local context provider + +```jsx +// ⚠️ AVOID + + +// ✅ DO group props into logical components + + } + prop={ + + } +> + + +``` + + +### Inline functions +No **inline functions** in TSX. + +```jsx +// 🚫 DON'T + setPressed(true)}/> +// ✅ DO +const handlePress = () => { + setPressed(true) +} + +``` + + +### Implicit values +Use implicit `true` for **boolean** props + +```jsx +// 🚫 DON'T + +// ✅ DO + +``` + + +### Destructuring arguments +Always destructure arguments, with minor exceptions. + +```jsx +// 🚫 DON'T +function printUser(user) { + console.log(user.name, user.name); +} +// ✅ DO +function printUser({ name, age }) { + console.log(name, age); +} +``` + +There are exceptions to this rule like: +1. The arguments are optional + +```jsx +function logWithOptions(options?: {green?: boolean}) { + if (options?.green) { + return console.log('\x1b[42m%s\x1b[0m', 'Some green text'); + } + console.log('Some normal text'); +} + +``` + +2. There is a name clash with variables defined above + +```jsx +const type = 'admin'; +function verifyUser(user) { + console.log(user.type === type); +} + +``` +3. Same props are passed below to a component, or are used for further processing + +```jsx +// 🚫 DON'T +const DisplayUser = ({name, age}: UserType) { + return ; +} +// ✅ DO +const DisplayUser = (user: UserType) { + return ; +} +const UserList = (users: UserType[]) { + return users.map((user, index) => { + // destructuring avoids typechecking so always specify the type + // before passing destructured props to a component + const userProps: UserType = processUser(user); + return ; + }) + +} +``` + + +### Over-optimization +No **`useCallback` or `useMemo` or `React.memo` unless really necessary**. Since the release of hooks, over-optimization has become a big problem. + +```jsx +// 🚫 DON'T +const handlePress = useCallback(() => setPressed(true), []); + +// 🚫 DON'T +const value = useMemo(() => user.level * multiplicator); + +// ✅ DO +const handlePress = useCallback(() => setPressed(true), []); + + {children} + +``` + + +### Conditionally rendered TSX + +In React, conditionally rendered TSX is very common. Given the ability to render it inline, it's very easy to include it inside normal TSX: + +```jsx + + {hasAchievements ? : } + + {title} + {mysteryBoxEnabled && } + + +``` + +However, TSX sometimes tends to grow very big and it requires a certain amount of mental load to stop at these conditionals and understand what's rendered inside. + +One could argue that there's an "organism" inside, a certain piece of logic that results in a component being rendered after some calculations and state changes. We try to give names to these operations that result in a TSX, so the developer knows what's in that TSX. + +**Thus, all conditionally rendered TSX** **goes into a constant**. We don't render conditional TSX inline + +```jsx +const achievementsContainer = hasAchievements? : ; +const mysteryBoxesContainer = mysteryBoxEnabled && ; + + + {achievementsContainer} + + {title} + {mysteryBoxesContainer} + + +``` + + +## Rules for hooks + +1. **Fake modularization**: + * Custom hooks may give the impression of modularization, but their logic runs inline in the parent component. + * State changes and reactive behavior in the hook will cause a rerender of the host component and any other hooks that depend on the updated hook. +2. **Hook return values**: + * Custom hooks should return either a single function (for a lazy hook) or an object containing a function and some properties; + * Avoid returning objects with multiple functions to ensure consistency and maintainability; + * Use interfaces for hook return type: + ```jsx + useMyHook({firstParam, secondParam}: UseMyHookParamsType): UseMyHookReturnType => {} + ``` +3. **State management and data fetching**: + * Lifting state up should be used sparingly; hooks should primarily be used to gather state in one container and distribute it to child components. + * Strive to couple data fetching with UI rendering as isolating as possible to prevent unnecessary rerenders. + * Ensure one hook does not trigger the rerender of another by carefully managing dependencies and side effects. + * If only 10% of the lines of code in a hook do specific React logic like state management, or calling a selector from Redux, consider splitting the hook into: + - a smaller hook that does the React logic and returns the state, and + - a statless function that will be called inside the hook and will do the rest of the logic. This stateless function should be tested separately. +4. **Hook interfaces**: + * Use interfaces for hook params if you're passing more than one argument to the hook invocation: + ```jsx + useMyHook({firstParam, secondParam}: UseMyHookParamsType) => {} + ``` +5. If a hook exports > 4 values, it should draw a red flag and be refactored. If it exports >= 7 values, it should be refactored immediately. + + +## Modularisation + +Given the size of the project, we have agreed on a couple of modularisation techniques that will help us to: + +* Split the logic into more readable chunks of logic; +* Test all bits of logic with unit tests; +* Reuse components/utils/hooks as needed; + +There are a couple of rules that we agreed upon and will be enforced in all PRs, to try and maintain the code in a state that is easy to navigate, read, debug and change. We try to move as much mental load as we can to the develop who is writing the code, instead of the developer who is reading the code. + +Therefore, we agreed on the certain principles: + + +### Abstracting the logic away into hooks and functions + +If there is a piece of code in a component or container that holds a certain amount of logic and can be converted to a testable hook or utils function, we should move it to a separate function/hook and add props interface, return type interface and a test file. + +For example: + +```jsx +const lastExpiringNotificationMissionId = useSelector( expiringNotificationMissionIdSelector ); +useEffect(() => { + if ( missionEndDate && missionId && missionId !== lastExpiringNotificationMissionId ) { + const earlierWith = ONE_HOUR_MS * 2; + const calculatedDate = missionEndDate - earlierWith; + const instantiateLocalNotification = async () => { + NotificationManager.startLocalNotification( + t('modules.mysteryBox.notification.title'), + t('modules.mysteryBox.notification.description'), + calculatedDate?.toString(), + NotificationPayloadTypesEnum.MYSTERY_BOX_EXPIRING + ); + }; + dispatch(setExpiringNotificationMissionId(missionId)); + instantiateLocalNotification(); + } +}, [lastExpiringNotificationMissionId]); +``` + +👆 This piece of logic is a perfect candidate to be moved to a separate hook file, because it contains +a very specific piece of logic, can be tested and the behavior is easier to predict and debug. + +```jsx +const sanitizedHerotag = name ? sanitizeHerotag(name) : undefined; +const herotagName = sanitizedHerotag != null && sanitizedHerotag !== name ? sanitizedHerotag : undefined; +const nameWithInitials = name || savedAddress; +const initials = getInitials(nameWithInitials); +return herotagName ? getHerotagPrefix(avatarIconTextMaxLength, herotagName) : initials; +``` + +👆 This is another piece of logic that is a single "organism", meaning it can be moved to a function that takes in a certain set of arguments and returns a specific value. + +We can abstract these kind of calculations into separate functions, where the logic doesn't pollute the container's file, is easily testable and can be debugged and changed more easily. + +Try to identify this kind of "organisms" in your code and move them to a separate file only if the logic is worth it. **Don't overdo it for simple pieces of logic, **unless they are either taking a lot of space or mental load to read through. + +```jsx +const sanitizedHerotag = name ? sanitizeHerotag(name) : undefined; +const herotagName = sanitizedHerotag != null && sanitizedHerotag !== name ? sanitizedHerotag : undefined; +``` + +👆 This piece of code would not be worth it, since the logic is very simple, straightforward and there is not much to test. + +However, + +```jsx +const avatar = useSelector(avatarSelector); +const sanitizedHerotag = name ? sanitizeHerotag(name) : undefined; +const herotagName = sanitizedHerotag != null && sanitizedHerotag !== name ? sanitizedHerotag : undefined; +const isHerotagValid = herotagName == null; +const shouldAllowHerotagCreation = !isHerotagValid; +const canUserCreateAvatar = !shouldAllowHerotagCreation && avatar == null; +``` + +👆 This logic, even though might seem simple, has a lot of steps that need to be take to reach the final solution, `canUserCreateAvatar`, and would be a good candidate for a separate function. + +In this case, it would be a good idea to abstract away the mental load needed to read through all this just to understand the container's code. The logic is abstracted away and tested, and if the `canUserCreateAvatar` result is buggy, there is a start and an end for debugging. + +If a piece of logic is a bit complex, works like an entity that could have an input and an output and has more than 7-10 lines of code, consider moving it to a hook/function. + +> input → **function** → output + +###Abstracting complex calculations into constants + +Certain inline calculations are not worth moving into a hook/function, but a constant will help remove some complexity and will attach a "name" to the calculation, making it easier to understand what's inside: + +```jsx +if (!isMissionCountdownLoading && missionCountdownData && isMIssionCountdownDataReady && currentMysteryBox && missionCountdownData?.status === MysteryBoxStatus.FINISHED ) +{ + //... +} +``` + +👆 This would be a very good example of an inline if that we try to avoid. It does have a lot of simple conditions that are being tested, but there is a certain mental load needed to parse every single && and the comparison seems to ask for a name. + +We could rewrite it to something like this: + +```jsx +const isMissionCountdownDataReady = !isMissionCountdownLoading && missionCountdownData; +const isMissionAlreadyFinished = !currentMysteryBox && missionCountdownData?.status === MysteryBoxStatus.FINISHED; +if (isMysteryBoxMissionStautsChanged && isMIssionCountdownDataReady ) +{ + //... +} +``` + +If we assign complex or even simple but long operations to local variables, we give them a name that can be used to infer what's inside, instead of calculating it ourselves. Sort of like a memoization. By naming a piece of logic, we memoize it and avoid recomputing it inside our heads, unless necessary. + +Again, as with hooks and functions, **don't overdo it. **There are certain calculations that, like in JavaScript, are easy for the brain to parse and understand, so it's not worth moving them to a local variable: + +```jsx +if (myClaimableAuctions != null && myClaimableAuctions.length > 0) { + //... +} +``` + +👆 Here, it's not worth moving the if logic inside a local variable, it would be redundant, as it's very easy to read through it. + + +### New functions/hooks + +When creating new functions and hooks, the new entity must have: + +* A props interface, if it accepts any arguments, declared in the function's file; +* A return interface, it the function or hook returns more than a simple primitive, declared in the function's file; +* A test function that tests the function and covers all test cases; As far as possible try to adhere to the ZOMBIES testing technique. The test should be created in the \_\_tests\_ folder; +* At most 50 lines of code, ideally 20 lines. + +```jsx +interface UseKYCModalStatePropsType { + isStatusFailed: boolean; + handleOpenInitiateKYCModal: () => void; +} + +interface UseKYCModalStateReturnType { + KYCInitialModalState: KYCInitialModalStateEnum; + setKYCInitialModalState: (newState: KYCInitialModalStateEnum) => void; +} + +export const useKYCModalState = ({ isStatusFailed, handleOpenInitiateKYCModal }: UseKYCModalStatePropsType): UseKYCModalStateReturnType => {} +``` + +We believe that adhering to these concepts will help us maintain the codebase at a sane level and will allow us a lot of manoeuvrability in the long run, both in building new features and in solving bugs quickly and reliably in times of crisis. + +--- + +### receipts + +This page describes the structure of the `receipts` index (Elasticsearch), and also depicts a few examples of how to query it. + + +## _id + +The `_id` field of this index is composed of hex encoded receipt hash. +(example: `ced4692a092226d68fde24840586bdf36b30e02dc4bf2a73516730867545d53c`) + + +## Fields + + +| Field | Description | +|-----------|-----------------------------------------------------------------------------------------------------| +| value | The value field represents the amount of EGLD that was refunded/penalized from the transaction fee. | +| sender | The sender field represents the sender of the transaction that generated the receipt. | +| data | The data field holds a message with the reason why the receipt was generated. | +| txHash | The txHash field represents the hash of the transaction that generated the receipt. | +| timestamp | The timestamp field represents the timestamp of the block in which the receipt was generated. | + + +## Query examples + + +### Fetch the receipt generated by a transaction + +``` +curl --request GET \ + --url ${ES_URL}/receipts/_search \ + --header 'Content-Type: application/json' \ + --data '{ + "query": { + "match": { + "txHash":"d6.." + } + } +}' +``` + +--- + +### Receiver + +[comment]: # "mx-abstract" + +## Overview + +Among the seven distinct generics defining a transaction, `To` signifies the **third** generic field - **the entity that receives the transaction**. With the exception of deployments, it is required to be specified for every transaction in any environment. + + +## Diagram + +The sender is being set using the `.to(...)` method. Several types can be specified: + +```mermaid +graph LR + subgraph To + to-unit["()"] + to-unit -->|to| to-man-address[ManagedAddress] + to-unit -->|to| to-address[Address] + to-unit -->|to| to-bech32[Bech32Address] + to-unit -->|to| to-esdt-system-sc[ESDTSystemSCAddress] + to-unit -->|to| to-caller[ToCaller] + to-unit -->|to| to-self[ToSelf] + end +``` + + +## No recipient + +Across the three distinct environments in which a transaction can be initialised, `deploy`, also known as the `init` function, stands alone as the only invocation that cannot designate the recipient. + +```rust title=blackbox.rs +fn deploy() { + self.world + .tx() + .from(OWNER_ADDRESS) + .typed(proxy::Proxy) + .init(init_value) + .code(CODE_PATH) + .new_address(DEPLOY_ADDRESS) // Sets the new mock address to be used for the newly deployed contract. + .run(); +} +``` + + + +## Explicit recipient + +Transactions, excluding deployments, require the designation of a recipient. This means that most transaction calls must utilise the `.to` method, explicitly specifying the receiving entity. + +In the subsequent section, we will go into the various data types that are permissible for recipient nomination. + + +### Address + +Below there is an interactor that funds a specific contract with an amount of EGLD. The recipient contract is instantiated as an address object within the interactor's context. + +```rust title=interactor.rs +async fn feed_contract_egld(&mut self) { + self.interactor + .tx() + .from(&self.wallet_address) + .to(self.state.current_adder_address()) + .egld(NumExpr("0,050000000000000000")) + .prepare_async() + .run() + .await; +} +``` + +This function, which is a sample from a **blackbox test**, increases a value and sends it to a particular wallet. This example specifically focuses on the `.to` call, which establishes the **receiver**. In this case, it is a hardcoded **ManagedAddress** instance. + +```rust title=blackbox_test.rs +fn add_one(&mut self, from: &AddressValue) { + let to_wallet: ManagedAddress = ManagedAddress::new_from_bytes(&[7u8; 32]); + self.world + .tx() + .from(OWNER_ADDRESS) + .to(to_wallet) + .typed(proxy::Proxy) + .add(1u32) + .run(); +} +``` + +## **TestSCAddress** + +For parametric testing, there are particular address types. `TestSCAddress` encodes a dummy smart contract address, equivalent to `"sc:{}"`; For the example below it is equivalent to `"sc:example_contract"`; + - contains two functions: + - **`.eval_to_array()`** parses the address into an array of u8. + - **`.eval_to_expr()`** returns the address as a string object. + - **`.to_address()`** returns the actual Address object. + +The following example is a fragment from a blackbox test designed for *Price Aggregator Smart Contract* (code available [here](https://github.com/multiversx/mx-sdk-rs/tree/master/contracts/core/price-aggregator)). This function simulates the staking of an amount of EGLD within the *Price Aggregator Smart Contract*. +```rust title=price_aggregator_blackbox_test.rs +const STAKE_AMOUNT: u64 = 20; +const PRICE_AGGREGATOR_ADDRESS: TestSCAddress = TestSCAddress::new("price-aggregator"); + +fn stake(&mut self) { + self.world + .tx() + .from(self.address) + .to(PRICE_AGGREGATOR_ADDRESS) + .typed(price_aggregator_proxy::PriceAggregatorProxy) + .stake() + .egld(STAKE_AMOUNT) + .run(); +} +``` + +### Bech32Address +In order to avoid repeated conversions, it keeps the **Bech32** representation **inside**. It wraps the address and presents it as a Bech32 expression. + +The example below is a piece of an interactor for the *Adder Smart Contract* (code available [here](https://github.com/multiversx/mx-sdk-rs/tree/master/contracts/examples/adder)). The aim of the function is to print the total sum of the contract. The receiver is set via the Bech32Address variable. + +```rust title=interact.rs +async fn print_sum(&mut self, adder_address: &Bech32Address) { + let sum = self + .interactor + .query() + .to(adder_address) + .typed(adder_proxy::AdderProxy) + .sum() + .returns(ReturnsResultUnmanaged) + .prepare_async() + .run() + .await; + + println!("sum: {sum}"); +} +``` + + +## Special recipient + + +### ESDTSystemSCAddress +This type indicates the system smart contract address, which is the same on any MultiversX blockchain. + - **`.to_managed_address()`**: converts the address to **ManagedAddress**. + - **`.to_bech32_str()`**: returns the **str** value of the address. + - **`.to_bech32_string()`**: returns the **String** value of the address. + +The next example represents a part of a **smart contract** whose aim is to issue semi-fungible tokens. The call is made via a system proxy for the ESDT system smart contract. More details regarding the system proxy can be found [here TBD](https://github.com/multiversx/mx-sdk-rs/tree/master/contracts/examples/adder). +```rust title=lib.rs +fn sft_issue( + issue_cost: BigUint, + token_display_name: ManagedBuffer, + token_ticker: ManagedBuffer, +) -> IssueCallTo { + Tx::new_tx_from_sc() + .to(ESDTSystemSCAddress) + .typed(ESDTSystemSCProxy) + .issue_semi_fungible( + issue_cost, + &token_display_name, + &token_ticker, + SemiFungibleTokenProperties::default(), + ) +} +``` + +### ToSelf +It indicates that the transaction should be sent to itself. + +The following lines illustrate an example of changing attributes of an NFT via a system proxy function. +```rust title=lib.rs +pub fn nft_update_attributes( + &self, + token_id: &TokenIdentifier, + nft_nonce: u64, + new_attributes: &T, +) { + Tx::new_tx_from_sc() + .to(ToSelf) + .gas(GasLeft) + .typed(system_proxy::UserBuiltinProxy) + .nft_update_attributes(token_id, nft_nonce, new_attributes) + .sync_call() +} +``` + +### ToCaller +It indicates that the transaction should be sent to the caller, which is the sender of the current transaction. + +The next example is a snippet from an endpoint that transfers ESDT to the sender of the transaction. +```rust +self.tx().to(ToCaller).single_esdt(&id, nonce, &BigUint::from(1u8)).transfer(); +``` + +--- + +### Relayed Transactions + +On this page, you will find comprehensive information on all aspects of relayed transactions. + + +## Introduction + +Relayed transactions (or meta-transactions) are transactions with the fee paid by a so-called relayer. +In other words, if a relayer is willing to pay for an interaction, it is not mandatory that the address +interacting with a Smart Contract has any EGLD for fees. + +More details and specifications can be found on [MultiversX Specs](https://github.com/multiversx/mx-specs/blob/main/sc-meta-transactions.md). + + +## Types of relayed transactions + +Currently, there are 3 versions of relayed transactions: v1, v2 and v3. In the end, they all have the same effect. + +Relayed v2 was meant to bring optimisations in terms of gas usage. But v3 reduces the costs even further, **making it our recommendation**. + +Once all applications will completely transition to relayed v3 model, v1 and v2 will be removed. + + +## Relayed transactions version 1 + +:::note +Legacy version. Please use [version 3](#relayed-transactions-version-3), instead. +::: + + + +## Relayed transactions version 2 + +:::note +Legacy version. Please use [version 3](#relayed-transactions-version-3), instead. +::: + + +## Relayed transactions version 3 + +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. + +That being said, relayed transactions v3 will look and behave very similar to a regular transaction, the only difference being the gas consumption from the relayer. It is no longer needed to specify the user transaction in the data field. + +In terms of gas limit computation, an extra base cost will be consumed. Let's consider the following example: relayed transaction with inner transaction of type move balance, that also has a data field `test` of length 4. +```js + gasLimitInnerTx = + * length(txData) + gasLimitInnerTx = 50_000 + 4 * 1_500 + gasLimitInnerTx = 56_000 + + gasLimitRelayedTx = + + gasLimitRelayedTx = 50_000 + 56_000 + gasLimitRelayedTx = 106_000 +``` + +It would look like: + +```rust +RelayedV3Transaction { + Sender: + Receiver: + Value: + GasLimit: + + * length(txData) + Relayer: + RelayerSignature: + Signature: +} +``` + +Therefore, in order to build such a transaction, one has to follow the next steps: + - set the `relayer` field to the address that would pay the gas + - add the extra base cost for the relayed operation + - add sender's signature + - add relayer's signature + +:::note +1. For a guarded relayed transaction, the guarded operation fee will also be consumed from the relayer. +2. Relayer must be different from guardian, in case of guarded sender. +3. Guarded relayers are not allowed. +4. Relayer address must be in the same shard as the transaction sender. +::: + +### Example + +Here's an example of a relayed v3 transaction. Its intent is to call the `add` method of a previously deployed adder contract, with parameter `01` + +```json +{ + "nonce": 0, + "value": "0", + "receiver": "erd1qqqqqqqqqqqqqpgqeunf87ar9nqeey5ssvpqwe74ehmztx74qtxqs63nmx", + "sender": "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", + "data": "YWRkQDAx", + "gasPrice": 1000000000, + "gasLimit": 5000000, + "signature": "...", + "chainID": "T", + "version": 2, + "relayer": "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + "relayerSignature": "..." +} +``` + +### Preparing relayed transactions using the SDKs + +The SDKs have built-in support for relayed transactions. Please follow: + - [mxpy support](/sdk-and-tools/mxpy/mxpy-cli/#relayed-transactions-v3) + - [sdk-py support](/sdk-and-tools/sdk-py/#relayed-transactions) + - [sdk-js v15 support](/sdk-and-tools/sdk-js/sdk-js-cookbook#relayed-transactions) + +--- + +### Reproducible Builds + +This page will guide you through the process of supporting [reproducible contract builds](https://en.wikipedia.org/wiki/Reproducible_builds), by leveraging Docker and a set of [_frozen_ Docker images available on DockerHub](https://hub.docker.com/r/multiversx/sdk-rust-contract-builder/tags). + +You will also learn how to reproduce a contract build, given its source code and the name (tag) of a _frozen_ Docker image that was used for its previous build (that we want to reproduce). + +> **Reproducible builds**, also known as **deterministic compilation**, is a process of compiling software which ensures the resulting binary code can be reproduced. Source code compiled using deterministic compilation will always output the same binary [[Wikipedia]](https://en.wikipedia.org/wiki/Reproducible_builds). + +:::important +As of May 2024, the Rust toolchain does not support reproducible builds out-of-the-box, thus we recommend smart contract developers to follow this tutorial in order to achieve deterministic compilation. +::: + + +## Smart Contract "codehash" + +Before diving into contract build reproducibility, let's grasp the concept of `codehash`. + +When a smart contract is deployed, the network stores the bytecode, and also computes its `blake2b` checksum (using a digest length of 256 bits). This is called the `codehash`. + +Assume that we are interested into the following contract (a simple on-chain **adder**), deployed on _devnet_: [erd1qqqqqqqqqqqqqpgqws44xjx2t056nn79fn29q0rjwfrd3m43396ql35kxy](https://devnet-explorer.multiversx.com/accounts/erd1qqqqqqqqqqqqqpgqws44xjx2t056nn79fn29q0rjwfrd3m43396ql35kxy). It's source code is published on [GitHub](https://github.com/multiversx/mx-contracts-rs). + +We can fetch the _codehash_ of the contract from the API: + +```bash +curl -s https://devnet-api.multiversx.com/accounts/erd1qqqqqqqqqqqqqpgqws44xjx2t056nn79fn29q0rjwfrd3m43396ql35kxy \ +| jq -r -j .codeHash \ +| base64 -d \ +| xxd -p \ +| tr -d '\n' +``` + +The output is: + +``` +384b680df7a95ebceca02ffb3e760a2fc288dea1b802685ef15df22ae88ba15b +``` + +If the `WASM` file is directly available, we can also use the utility `b2sum` to locally compute the _codehash_: + +```bash +b2sum -l 256 adder.wasm +``` + +The output would be the same: + +``` +384b680df7a95ebceca02ffb3e760a2fc288dea1b802685ef15df22ae88ba15b +``` + +All in all, in order to verify the bytecode equality of two given builds of a contract we can simply compare the _codehash_ property. + + +## Supporting reproducible builds + +As of May 2024, the recommended approach to support reproducible builds for your smart contract is to use a build script relying on a specially-designed, [publicly-available, tagged Docker image](https://hub.docker.com/r/multiversx/sdk-rust-contract-builder/tags), that includes tagged, explicit versions of the build tools (_Rust_, _wasm-opt_ etc.). + +This approach is recommended in order to counteract eventual pieces of non-determinism related to `cargo`'s (essential component of the Rust toolchain) sensibility on the environment. + +:::important +If the code source of your smart contract is hosted on GitHub, then it's a good practice to define a GitHub Workflow similar to [release.yml](https://github.com/multiversx/mx-contracts-rs/blob/main/.github/workflows/release.yml), which performs the deployment (production-ready) build within the _release_ procedure. Additionally, define a dry-run reproducible build on all your branches. See this workflow as an example: [on_pull_request_build_contracts.yml](https://github.com/multiversx/mx-contracts-rs/blob/main/.github/workflows/on_pull_request_build_contracts.yml). +::: + + +### Choose an image tag + +For a new smart contract that isn't released yet (deployed on the network), it's recommended to pick the tag with the **largest index number**, which typically includes recent versions of `rust` and other necessary dependencies. + +However, for minor releases or patches, it's wise to stick to the previously chosen image tag, for the same (nuanced) reasons you would not embrace an update of your development tools in the middle of fixing a critical bug (in any development context). + +The chosen, _frozen_ image tag **should accompany the versioned source code (e.g. via _release notes_), in order to inform others on how to reproduce a specific build** (of a specific source code version). In this context, a _frozen_ image tag refers to a Docker image tag that will never get any updates after its initial publishing. + +:::tip +It's perfectly normal to switch to a newer image tag on each (major) release of your contract. Just make sure you spread this information - i.e. using _release notes_. +::: + +:::caution +Never pick the tag called `latest` or `next` for production-ready builds. +::: + + +## Building via Docker (reproducible build) + +In this section, you'll learn how to run a reproducible build, or, to put it differently, how to reproduce a previous build (made by you or by someone else in the past), on the local machine, using Docker - without the need to install other tools such as _mxpy_ (nor its dependencies). + + +### Fetch the source code + +Let's clone [mx-contracts-rs](https://github.com/multiversx/mx-contracts-rs) locally, and switch to [a certain version](https://github.com/multiversx/mx-contracts-rs/releases/tag/v0.45.4) that we'd like to build: + +```bash +mkdir -p ~/contracts && cd ~/contracts +git clone https://github.com/multiversx/mx-contracts-rs.git --branch=v0.45.4 --depth=1 +``` + +By inspecting the release notes, we see that [`v0.45.4`](https://github.com/multiversx/mx-contracts-rs/releases/tag/v0.45.4) was built using the `image:tag = multiversx/sdk-rust-contract-builder:v5.4.1`. + + +### Download the build wrapper + +The build process (via Docker) is wrapped in a easy-to-use, friendly Python script. Let's download it: + +```bash +wget https://raw.githubusercontent.com/multiversx/mx-sdk-build-contract/main/build_with_docker.py +``` + + +### Prepare environment variables + +Export the following variables: + +```bash +export PROJECT=~/contracts/mx-contracts-rs +export BUILD_OUTPUT=~/contracts/output-from-docker +# Below, the image tag is just an example: +export IMAGE=multiversx/sdk-rust-contract-builder:v1.2.3 +``` + +The latter export statement explicitly selects the **chosen, _frozen_ Docker image tag** to be used. + + +### Perform the build + +Now let's build the contract by invoking the previously-downloaded build wrapper: + +```bash +python3 ./build_with_docker.py --image=${IMAGE} \ + --project=${PROJECT} \ + --output=${BUILD_OUTPUT} +``` + +In the `output` folder(s), you should see the following files (example): + +- `adder.wasm`: the actual bytecode of the smart contract, to be deployed on the network; +- `adder.abi.json`: the ABI of the smart contract (a listing of endpoints and types definitions), to be used when developing dApps or simply interacting with the contract (e.g. using _erdjs_); +- `adder.codehash.txt`: a file containing the computed `codehash` of the contract. +- **`adder.source.json`** : packaged (bundled) source code. + + +### TL;DR build snippet + +These being said, let's summarize the steps above into a single bash snippet: + +```bash +wget https://raw.githubusercontent.com/multiversx/mx-sdk-build-contract/main/build_with_docker.py + +export PROJECT=~/contracts/mx-contracts-rs +export BUILD_OUTPUT=~/contracts/output-from-docker +# Below, the image tag is just an example: +export IMAGE=multiversx/sdk-rust-contract-builder:v1.2.3 + +python3 ./build_with_docker.py --image=${IMAGE} \ + --project=${PROJECT} \ + --output=${BUILD_OUTPUT} +``` + + +### Reproducible build using mxpy + +A more straightforward alternative to the previous bash script is to use **mxpy** to build a contract in a reproducible manner. + +First, make sure you have the: + +- latest [mxpy](/sdk-and-tools/mxpy/installing-mxpy) installed, +- latest [docker engine](https://docs.docker.com/engine/install/) installed. + +Then, use the `reproducible-build` command (below, the image tag is just an example): + +``` +mxpy contract reproducible-build --docker-image="multiversx/sdk-rust-contract-builder:v1.2.3" +``` + +This will build all the smart contracts inside the current working directory. If you want to build the smart contracts inside another directory, you can specify an input directory: + +``` +mxpy contract reproducible-build ~/contracts/mx-contracts-rs --docker-image="multiversx/sdk-rust-contract-builder:v1.2.3" +``` + +Upon a successful build, an output folder named `output-docker` will be generated. It contains one subfolder for each contract, each holding the following files: + +- `contract.wasm`: the actual bytecode of the smart contract, to be deployed on the network; +- `contract.abi.json`: the ABI of the smart contract (a listing of endpoints and types definitions), to be used when developing dApps or simply interacting with the contract (e.g. using _sdk-js_); +- `contract.codehash.txt`: the computed `codehash` of the contract. +- **`contract-1.2.3.source.json`** : packaged (bundled) source code. + +:::tip +You can run a local test using [these example contracts](https://github.com/multiversx/mx-contracts-rs). +::: + + +### Comparing the codehashes + +Once the build is ready, you can check the codehash of the generated `*.wasm`, by inspecting the file `*.codehash.txt` + +For our example, that should be: + +``` +adder.codehash.txt: 384b680df7a95ebceca02ffb3e760a2fc288dea1b802685ef15df22ae88ba15b +``` + +We can see that it matches the previously fetched (or computed) codehash. That is, the contract deployed at [erd1qqqqqqqqqqqqqpgqws44xjx2t056nn79fn29q0rjwfrd3m43396ql35kxy](https://devnet-explorer.multiversx.com/accounts/erd1qqqqqqqqqqqqqpgqws44xjx2t056nn79fn29q0rjwfrd3m43396ql35kxy) is guaranteed to have been built from the same source code version as the one that we've checked out. + +**Congratulations!** You've achieved a reproducible contract build 🎉 + + +## How to verify a smart contract on Explorer? + +The new MultiversX Explorer provides a convenient way to visualise the source code of deployed smart contracts on blockchain. This is the beauty of the Web3 vision for a decentralized internet, where anyone can deploy contracts that everyone can interact with. + +:::caution +Please note that as a **Beta** feature still in development, certain steps described may undergo changes. +::: + +:::tip +Make sure that you have the latest `mxpy` installed. In order to install mxpy, follow the instructions at [install mxpy](/sdk-and-tools/mxpy/installing-mxpy). +::: + +1. The contract must be deterministically built as described [above](/developers/reproducible-contract-builds#building-via-docker-reproducible-build). +2. To start with the verification process, we need to first deploy the smart contract. For deploying contracts have a look [here](/sdk-and-tools/mxpy/mxpy-cli#deploying-a-smart-contract). +3. Upon deploying, the output will not only provide information such as the transaction hash and data, but also the address of the newly deployed contract. +4. In order to verify your contract the command you have to use is (below, the image tag is just an example): + +``` +mxpy --verbose contract verify "erd1qqqqqqqqqqqqqpgq6u07hhkfsvuk5aae92g549s6pc2s9ycq0dps368jr5" --packaged-src=./output-docker/contract/contract-0.0.0.source.json --verifier-url="https://play-api.multiversx.com" --docker-image="multiversx/sdk-rust-contract-builder:v1.2.3" --pem=contract-owner.pem +``` + +:::tip +For the above code snippet: +- `erd1qqqqqqqqqqqqqpgq6u07hhkfsvuk5aae92g549s6pc2s9ycq0dps368jr5` - should be your contract address (in this case is a dummy address); +- `--packaged-src=./output-docker/contract/contract-0.0.0.source.json` - should be found in the output folder after deterministically building your contract; +- `--verifier-url="https://play-api.multiversx.com"` - this is the verifier api address. Be advised that it may be subject to change; +- `--docker-image="multiversx/sdk-rust-contract-builder:v1.2.3"` - the same version utilized in constructing the contract must be utilized here too; +- `--pem=contract-owner.pem` - represents the owner of the contract. +::: + +5. Given the current limited bandwidth, it might take some time to be processed. + +--- + +### Rest API Addresses + +```mdx-code-block +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; +``` + + +This component of the REST API allows one to query information about Addresses (Accounts). + + +## GET **Get Address** {#get-address} + +`https://gateway.multiversx.com/address/:bech32Address` + +This endpoint allows one to retrieve basic information about an Address (Account). + + + + +Path Parameters + +| Param | Required | Type | Description | +| ----- | ----------------------------------------- | -------- | ------------------------- | +| bech32Address | REQUIRED | `string` | The Address to query. | + + + + +🟢 200: OK + +Address details retrieved successfully. + +```json +{ + "data": { + "account": { + "address": "erd1...", + "nonce": 11, + "balance": "100000000000000000000", + "username": "", + "code": "", + "codeHash": null, + "rootHash": "qBFvpFeF6...", + "codeMetadata": null, + "developerReward": "0", + "ownerAddress": "", + } + }, + "blockInfo":{ + "nonce": 555, + "hash": "f55fe00...", + "rootHash": "294360..." + } + "error": "", + "code": "successful" +} +``` + + + + +:::important +If an account (that is not a smart contract, smart contracts cannot be guarded) has an ```activeGuardian``` and is ```guarded```, the ```codeMetadata``` of the account should be [Guarded](https://github.com/multiversx/mx-chain-vm-common-go/blob/master/codeMetadata.go). +::: + + +## GET **Get Address Guardian Data** {#get-address-guardian-data} + +`https://gateway.multiversx.com/address/:bech32Address/guardian-data` + +This endpoint allows one to retrieve the guardian data of an Address. + + + + +Path Parameters + +| Param | Required | Type | Description | +| ------------- | ----------------------------------------- | -------- | --------------------- | +| bech32Address | REQUIRED | `string` | The Address to query. | + + + + +🟢 200: OK + +Guardian Data successfully retrieved. + +```json +{ + "data": { + "blockInfo": { + "hash":"a11aa...", + "nonce":197, + "rootHash":"a6d70..." + }, + "guardianData": { + "activeGuardian": { + "activationEpoch": 15, + "address": "erd1...", + "serviceUID": "uuid" + }, + "guarded": true, + "pendingGuardian": { + "activationEpoch": 35, + "address": "erd1...", + "serviceUID": "uuid" + }, + }, + }, + "error": "", + "code": "successful" +} +``` + + + + +:::caution +In the response example mentioned above, the account has already set the ```activeGuardian``` (using a ```SetGuardian``` transaction), guarded the account (with a ```GuardAccount``` transaction), and also set the ```pendingGuardian``` (using an unguarded ```SetGuardian``` transaction). We intentionally chose this scenario to display all the fields for ```blockInfo``` and ```guardianData```. +::: + + +## GET **Get Address Nonce** {#get-address-nonce} + +`https://gateway.multiversx.com/address/:bech32Address/nonce` + +This endpoint allows one to retrieve the nonce of an Address. + + + + +Path Parameters + +| Param | Required | Type | Description | +| ------------- | ----------------------------------------- | -------- | --------------------- | +| bech32Address | REQUIRED | `string` | The Address to query. | + + + + +🟢 200: OK + +Nonce successfully retrieved. + +```json +{ + "data": { + "nonce": 5 + }, + "error": "", + "code": "successful" +} +``` + + + + + +## GET **Get Address Balance** {#get-address-balance} + +`https://gateway.multiversx.com/address/:bech32Address/balance` + +This endpoint allows one to retrieve the balance of an Address. + + + + +Path Parameters + +| Param | Required | Type | Description | +| ------------- | ----------------------------------------- | -------- | --------------------- | +| bech32Address | REQUIRED | `string` | The Address to query. | + + + + +🟢 200: OK + +Balance successfully retrieved. + +```json +{ + "data": { + "balance": "100000000000000000000" + }, + "error": "", + "code": "successful" +} +``` + + + + + +## GET **Get Address Username (herotag)** {#get-address-username-herotag} + +`https://gateway.multiversx.com/address/:bech32Address/username` + +This endpoint allows one to retrieve the username / herotag of an Address (if any). + + + + +Path Parameters + +| Param | Required | Type | Description | +| ------------- | ----------------------------------------- | -------- | --------------------- | +| bech32Address | REQUIRED | `string` | The Address to query. | + + + + +🟢 200: OK + +Balance successfully retrieved. + +```json +{ + "data": { + "username": "docs.elrond" + }, + "error": "", + "code": "successful" +} +``` + + + + + +## GET **Get Address Transactions (deprecated)** {#get-address-transactions} + +`https://gateway.multiversx.com/address/:bech32Address/transactions` + +:::caution +This endpoint is deprecated. In order to fetch the Transactions involving an Address, use the [transactions](https://api.multiversx.com/#/accounts/AccountController_getAccountTransactions) endpoint of MultiversX API, instead. +::: + +This endpoint allows one to retrieve the latest 20 Transactions sent from an Address. + + + + +Path Parameters + +| Param | Required | Type | Description | +| ------------- | ----------------------------------------- | -------- | --------------------- | +| bech32Address | REQUIRED | `string` | The Address to query. | + + + + +🟢 200: OK + +Transactions successfully retrieved. + +```json +{ + "data": { + "transactions": [ + { + "hash": "1a3e...", + "fee": "10000000000000000", + "miniBlockHash": "9673...", + "nonce": 68, + "round": 33688, + "value": "1000000000000000000", + "receiver": "erd1...", + "sender": "erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz", + "receiverShard": 0, + "senderShard": 0, + "gasPrice": 200000000000, + "gasLimit": 50000, + "gasUsed": 50000, + "data": "", + "signature": "ed75...", + "timestamp": 1591258128, + "status": "Success", + "scResults": null + }, + { + "hash": "d72d...", + "fee": "10000000000000000", + "miniBlockHash": "fd45...", + "nonce": 67, + "round": 27353, + "value": "100000000000000000000000000", + "receiver": "erd1...", + "sender": "erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz", + "receiverShard": 1, + "senderShard": 0, + "gasPrice": 200000000000, + "gasLimit": 50000, + "gasUsed": 50000, + "data": "", + "signature": "bb98...", + "timestamp": 1591220142, + "status": "Success", + "scResults": null + }, + ... + ] + }, + "error": "", + "code": "successful" +} +``` + + + + +:::caution +This endpoint is not available on Observer Nodes. It is only available on MultiversX Proxy. + +**Currently, this endpoint is only available on the Official MultiversX Proxy instance.** + +This endpoint requires the presence of an Elasticsearch instance (populated through Observers) as well. +::: + + +## GET **Get Storage Value for Address** {#get-storage-value-for-address} + +`https://gateway.multiversx.com/address/:bech32Address/key/:key` + +This endpoint allows one to retrieve a value stored within the Blockchain for a given Address. + + + + +Path Parameters + +| Param | Required | Type | Description | +| ------------- | ----------------------------------------- | -------- | ----------------------- | +| bech32Address | REQUIRED | `string` | The Address to query. | +| key | REQUIRED | `string` | The key entry to fetch. | + +The key must be hex-encoded. + + + + +🟢 200: OK + +Value (hex-encoded) successfully retrieved. + +```json +{ + "data": { + "value": "abba" + }, + "error": "", + "code": "successful" +} +``` + + + + + +## GET **Get all storage for Address** {#get-all-storage-for-address} + +`https://gateway.multiversx.com/address/:bech32Address/keys` + +This endpoint allows one to retrieve all the key-value pairs stored under a given account. + + + + +Path Parameters + +| Param | Required | Type | Description | +| ------------- | ----------------------------------------- | -------- | --------------------- | +| bech32Address | REQUIRED | `string` | The Address to query. | + + + + +🟢 200: OK + +Key-value pairs (both hex-encoded) successfully retrieved. + +```json +{ + "data": { + "pairs": { + "abba": "6f6b" + ... + } + }, + "error": "", + "code": "successful" +} +``` + + + + + +## **ESDT tokens endpoints** + +There are a number of ESDT tokens endpoints that one can use to check all tokens of an address, balance for +specific fungible or non-fungible tokens or so on. + +Fungible tokens endpoints can be found [here](/tokens/fungible-tokens/#rest-api) and non-fungible tokens +endpoints can be found [here](/tokens/nft-tokens/#rest-api). + +--- + +### Rest API Blocks + +```mdx-code-block +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; +``` + + +This component of the REST API allows one to query information about Blocks and Hyperblocks. + + +## GET **Get Hyperblock by Nonce** {#get-hyperblock-by-nonce} + +`https://gateway.multiversx.com/hyperblock/by-nonce/:nonce` + +This endpoint allows one to query a Hyperblock by its nonce. + + + + +Path Parameters + +| Param | Required | Type | Description | +| ----- | ----------------------------------------- | -------- | ------------------------- | +| nonce | REQUIRED | `number` | The Block nonce (height). | + + + + +🟢 200: OK + +Block details retrieved successfully. + +```json +{ + "hyperblock": { + "nonce": 185833, + "round": 186582, + "hash": "6a33...", + "prevBlockHash": "aa7e...", + "epoch": 12, + "numTxs": 1, + "shardBlocks": [ + { + "hash": "cba4...", + "nonce": 186556, + "shard": 0 + }, + { + "hash": "50a16...", + "nonce": 186535, + "shard": 1 + }, + { + "hash": "7981...", + "nonce": 186536, + "shard": 2 + } + ], + "transactions": [ + { + "type": "normal", + "hash": "b035...", + "nonce": 3, + "value": "1000000000000000000", + "receiver": "erd1...", + "sender": "erd1...", + "gasPrice": 1000000000, + "gasLimit": 70000, + "data": "Zm9yIHRlc3Rz", + "signature": "1047...", + "status": "executed" + } + ] + } +} +``` + + + + +:::important +This endpoint is only defined by the Proxy. The Observer does not expose this endpoint. +::: + +:::tip +A **Hyperblock** is a block-like abstraction that reunites the data from all shards, and contains only **fully-executed transactions** (that is, transactions executed both in _source_ and in _destination_ shard). + +A **hyperblock** is composed using a **metablock** as a starting point - therefore, the `nonce` or `hash` of a hyperblock is the same as the `nonce` or `hash` of the base metablock. +::: + + +## GET **Get Hyperblock by Hash** {#get-hyperblock-by-hash} + +`https://gateway.multiversx.com/hyperblock/by-hash/:hash` + +This endpoint allows one to query a Hyperblock by its hash. + + + + +Path Parameters + +| Param | Required | Type | Description | +| ----- | ----------------------------------------- | -------- | --------------- | +| hash | OPTIONAL | `string` | The Block hash. | + + + + +🟢 200: OK + +```json +{ + "hyperblock": { + "nonce": 185833, + "round": 186582, + "hash": "6a33...", + "prevBlockHash": "aa7e...", + "epoch": 12, + "numTxs": 1, + "shardBlocks": [ + { + "hash": "cba4...", + "nonce": 186556, + "shard": 0 + }, + { + "hash": "50a16...", + "nonce": 186535, + "shard": 1 + }, + { + "hash": "7981...", + "nonce": 186536, + "shard": 2 + } + ], + "transactions": [ + { + "type": "normal", + "hash": "b035...", + "nonce": 3, + "value": "1000000000000000000", + "receiver": "erd1...", + "sender": "erd1...", + "gasPrice": 1000000000, + "gasLimit": 70000, + "data": "Zm9yIHRlc3Rz", + "signature": "1047...", + "status": "executed" + } + ] + } +} +``` + + + + +:::important +This endpoint is only is only defined by the Proxy. The Observer does not expose this endpoint. +::: + + +## GET **Get Block by Nonce** {#get-block-by-nonce} + +`https://gateway.multiversx.com/block/:shard/by-nonce/:nonce` + +This endpoint allows one to query a Shard Block by its nonce (or height). + + + + +Path Parameters + +| Param | Required | Type | Description | +| ----- | ----------------------------------------- | -------- | ------------------------- | +| shard | OPTIONAL | `number` | The Shard. | +| nonce | REQUIRED | `number` | The Block nonce (height). | + +Query Parameters + +| Param | Required | Type | Description | +| ------- | ----------------------------------------- | --------- | ---------------------------------------------------- | +| withTxs | OPTIONAL | `boolean` | Whether to include the transactions in the response. | + + + + +🟢 200: OK + +Block retrieved successfully, with transactions included. + +```json +{ + "data": { + "block": { + "nonce": 186532, + "round": 186576, + "hash": "7aa3...", + "prevBlockHash": "2580...", + "epoch": 12, + "shard": 2, + "numTxs": 1, + "miniBlocks": [ + { + "hash": "e927...", + "type": "TxBlock", + "sourceShard": 2, + "destinationShard": 1, + "transactions": [ + { + "type": "normal", + "hash": "b035...", + "nonce": 3, + "value": "1000000000000000000", + "receiver": "erd1...", + "sender": "erd1...", + "gasPrice": 1000000000, + "gasLimit": 70000, + "data": "Zm9yIHRlc3Rz", + "signature": "1047...", + "status": "partially-executed" + } + ] + } + ] + } + }, + "error": "", + "code": "successful" +} +``` + + + + +:::important +For Observers, the `shard` parameter should not be set. +::: + + +## GET **Get Block by Hash** {#get-hyperblock-by-hash} + +`https://gateway.multiversx.com/block/:shard/by-hash/:hash` + +This endpoint allows one to query a Shard Block by its hash. + + + + +Path Parameters + +| Param | Required | Type | Description | +| ----- | ----------------------------------------- | -------- | --------------- | +| shard | OPTIONAL | `number` | The Shard. | +| hash | REQUIRED | `string` | The Block hash. | + +Query Parameters + +| Param | Required | Type | Description | +| ------- | ----------------------------------------- | --------- | ---------------------------------------------------- | +| withTxs | OPTIONAL | `boolean` | Whether to include the transactions in the response. | + + + + +🟢 200: OK + +Block retrieved successfully, with transactions included. + +```json +{ + "data": { + "block": { + "nonce": 186532, + "round": 186576, + "hash": "7aa3...", + "prevBlockHash": "2580...", + "epoch": 12, + "shard": 2, + "numTxs": 1, + "miniBlocks": [ + { + "hash": "e927...", + "type": "TxBlock", + "sourceShard": 2, + "destinationShard": 1, + "transactions": [ + { + "type": "normal", + "hash": "b035...", + "nonce": 3, + "value": "1000000000000000000", + "receiver": "erd1...", + "sender": "erd1...", + "gasPrice": 1000000000, + "gasLimit": 70000, + "data": "Zm9yIHRlc3Rz", + "signature": "1047...", + "status": "partially-executed" + } + ] + } + ] + } + }, + "error": "", + "code": "successful" +} +``` + + + + +:::important +For Observers, the `shard` parameter should not be set. +::: + +--- + +### Rest API Network + +```mdx-code-block +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +``` + + +This component of the REST API allows one to query information about the Network, such as network configuration and parameters. + + +## GET **Get Network Configuration** {#get-network-configuration} + +`https://gateway.multiversx.com/network/config` + +This endpoint allows one to query basic details about the configuration of the Network. + + + + +🟢 200: OK + +Configuration details retrieved successfully. + +```json +{ + "data": { + "config": { + "erd_chain_id": "1", + "erd_denomination": 18, + "erd_gas_per_data_byte": 1500, + "erd_latest_tag_software_version": "v1.1.0.0", + "erd_meta_consensus_group_size": 400, + "erd_min_gas_limit": 50000, + "erd_min_gas_price": 1000000000, + "erd_min_transaction_version": 1, + "erd_num_metachain_nodes": 400, + "erd_num_nodes_in_shard": 400, + "erd_num_shards_without_meta": 3, + "erd_round_duration": 6000, + "erd_shard_consensus_group_size": 63, + "erd_start_time": 1596117600 + } + }, + "error": "", + "code": "successful" +} +``` + + + + + +## GET **Get Shard Status** {#get-shard-status} + +`https://gateway.multiversx.com/network/status/:shardId` + +This endpoint allows one to query the status of a given Shard. + + + + +Path Parameters + +| Param | Required | Type | Description | +| ------- | ----------------------------------------- | -------- | -------------------------------------------------------------------------- | +| shardID | REQUIRED | `number` | The Shard ID. 0, 1, 2 etc. Use 4294967295 in order to query the Metachain. | + + + + +🟢 200: OK + +Shard Status retrieved successfully. + +```json +{ + "data": { + "status": { + "erd_current_round": 187068, + "erd_epoch_number": 12, + "erd_highest_final_nonce": 187019, + "erd_nonce": 187023, + "erd_nonce_at_epoch_start": 172770, + "erd_nonces_passed_in_current_epoch": 14253, + "erd_round_at_epoch_start": 172814, + "erd_rounds_passed_in_current_epoch": 14254, + "erd_rounds_per_epoch": 14400 + } + }, + "error": "", + "code": "successful" +} +``` + + + + +:::important +The path parameter `**shardId**` is only applicable on the Proxy endpoint. The Observer endpoint does not define this parameter. +::: + +--- + +### Rest API Nodes + +```mdx-code-block +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +``` + + +This component of the REST API allows one to query information about Nodes within the Network (peers). + + +## GET **Get Heartbeat Status** {#get-heartbeat-status} + +`https://gateway.multiversx.com/node/heartbeatstatus` + +This endpoint allows one to query the status of the Nodes. + + + + +🟢 200: OK + +Heartbeat status is retrieved successfully. + +```json +{ + "data": { + "heartbeats": [ + ... + { + "timeStamp": "2020-06-04T16:02:41.191947208Z", + "publicKey": "006d...", + "versionNumber": "v1.0.125-0-g2164f5f04/go1.13.4/linux-amd64", + "nodeDisplayName": "DrDelphi4", + "identity": "stakingagency", + "totalUpTimeSec": 7367, + "totalDownTimeSec": 0, + "maxInactiveTime": "1m41.148001375s", + "receivedShardID": 4294967295, + "computedShardID": 4294967295, + "peerType": "eligible", + "isActive": true + }, + { + "timeStamp": "2020-06-04T16:02:29.567740999Z", + "publicKey": "667a...", + "versionNumber": "v1.0.125-0-g2164f5f04/go1.13.4/linux-amd64", + "nodeDisplayName": "DrDelphi0", + "identity": "stakingagency", + "totalUpTimeSec": 7367, + "totalDownTimeSec": 0, + "maxInactiveTime": "1m9.537847751s", + "receivedShardID": 4294967295, + "computedShardID": 4294967295, + "peerType": "eligible", + "isActive": true + }, + ... + ] + }, + "error": "", + "code": "successful" +} +``` + + + + + +## GET **Get Node Status** {#get-node-status} + +`http://localhost:8080/node/status` + +This endpoint allows one to query all the metrics of the Node. + + + + +🟢 200: OK + +Statistics retrieved successfully. + +```json +{ + "data": { + "metrics": { + "erd_shard_id": 2, + "erd_nonce": 403, + "erd_min_gas_limit": 50000, + "erd_min_gas_price": 1000000000, + "erd_denomination": 18, + ... + } + }, + "error": "", + "code": "successful" +} +``` + + + + +:::important +This endpoint is not available on the Proxy. Only Nodes (Observers) expose this endpoint. +::: + + +## GET **Get P2P Status** {#get-p2p-status} + +`http://localhost:8080/node/p2pstatus` + +This endpoint allows one to query the P2P status of the Node. + + + + +🟢 200: OK + +P2P status retrieved successfully. + +```json +{ + "data": { + "metrics": { + "erd_p2p_cross_shard_observers": "...", + "erd_p2p_cross_shard_validators": "...", + "erd_p2p_intra_shard_observers": "...", + "erd_p2p_intra_shard_validators": "...", + "erd_p2p_num_connected_peers_classification": "...", + "erd_p2p_num_receiver_peers_fast_reacting": 2, + "erd_p2p_num_receiver_peers_out_of_specs": 2, + "erd_p2p_num_receiver_peers_slow_reacting": 13, + "erd_p2p_peak_num_receiver_peers_fast_reacting": 8, + "erd_p2p_peak_num_receiver_peers_out_of_specs": 8, + "erd_p2p_peak_num_receiver_peers_slow_reacting": 13, + "erd_p2p_peak_peer_num_processed_messages_fast_reacting": 3, + "erd_p2p_peak_peer_num_processed_messages_out_of_specs": 3, + "erd_p2p_peak_peer_num_processed_messages_slow_reacting": 8, + "erd_p2p_peak_peer_num_received_messages_fast_reacting": 3, + "erd_p2p_peak_peer_num_received_messages_out_of_specs": 3, + "erd_p2p_peak_peer_num_received_messages_slow_reacting": 8, + "erd_p2p_peak_peer_size_processed_messages_fast_reacting": 1291, + "erd_p2p_peak_peer_size_processed_messages_out_of_specs": 1291, + "erd_p2p_peak_peer_size_processed_messages_slow_reacting": 4363, + "erd_p2p_peak_peer_size_received_messages_fast_reacting": 1291, + "erd_p2p_peak_peer_size_received_messages_out_of_specs": 1291, + "erd_p2p_peak_peer_size_received_messages_slow_reacting": 4363, + "erd_p2p_peer_info": "...", + "erd_p2p_peer_num_processed_messages_fast_reacting": 1, + "erd_p2p_peer_num_processed_messages_out_of_specs": 1, + "erd_p2p_peer_num_processed_messages_slow_reacting": 5, + "erd_p2p_peer_num_received_messages_fast_reacting": 1, + "erd_p2p_peer_num_received_messages_out_of_specs": 1, + "erd_p2p_peer_num_received_messages_slow_reacting": 5, + "erd_p2p_peer_size_processed_messages_fast_reacting": 289, + "erd_p2p_peer_size_processed_messages_out_of_specs": 289, + "erd_p2p_peer_size_processed_messages_slow_reacting": 2711, + "erd_p2p_peer_size_received_messages_fast_reacting": 289, + "erd_p2p_peer_size_received_messages_out_of_specs": 289, + "erd_p2p_peer_size_received_messages_slow_reacting": 2711, + "erd_p2p_unknown_shard_peers": "..." + } + }, + "error": "", + "code": "successful" +} +``` + + + + +:::important +This endpoint is not available on the Proxy. Only Nodes (Observers) expose this endpoint. +::: + + +## GET **Get Peer Information** {#get-peer-information} + +`http://localhost:8080/node/peerinfo` + +This endpoint allows one to query specific information about its peers. + + + + +🟢 200: OK + +Peer info retrieved successfully. + +```json +{ + "data": { + "info": [ + { + "isblacklisted": false, + "pid": "...", + "pk": "", + "peertype": "observer", + "addresses": [ + "/ip4/172.17.0.1/tcp/21100", + "/ip4/127.0.0.1/tcp/21100", + "/ip4/192.168.2.104/tcp/21100", + "/ip4/172.17.0.1/tcp/21100" + ] + }, + { + "isblacklisted": false, + "pid": "...", + "pk": "", + "peertype": "unknown", + "addresses": [ + "/ip4/127.0.0.1/tcp/9999", + "/ip4/127.0.0.1/tcp/9999", + "/ip4/192.168.2.104/tcp/9999", + "/ip4/172.17.0.1/tcp/9999" + ] + }, + { + "isblacklisted": false, + "pid": "...", + "pk": "...", + "peertype": "validator", + "addresses": [ + "/ip4/192.168.2.104/tcp/21504", + "/ip4/192.168.2.104/tcp/21504", + "/ip4/172.17.0.1/tcp/21504", + "/ip4/127.0.0.1/tcp/21504" + ] + }, + { + "isblacklisted": false, + "pid": "...", + "pk": "...", + "peertype": "validator", + "addresses": [ + "/ip4/172.17.0.1/tcp/21503", + "/ip4/172.17.0.1/tcp/21503", + "/ip4/127.0.0.1/tcp/21503", + "/ip4/192.168.2.104/tcp/21503" + ] + }, + { + "isblacklisted": false, + "pid": "...", + "pk": "...", + "peertype": "validator", + "addresses": [ + "/ip4/172.17.0.1/tcp/21502", + "/ip4/127.0.0.1/tcp/21502", + "/ip4/192.168.2.104/tcp/21502", + "/ip4/172.17.0.1/tcp/21502" + ] + }, + { + "isblacklisted": false, + "pid": "...", + "pk": "...", + "peertype": "validator", + "addresses": [ + "/ip4/127.0.0.1/tcp/21507", + "/ip4/127.0.0.1/tcp/21507", + "/ip4/192.168.2.104/tcp/21507", + "/ip4/172.17.0.1/tcp/21507" + ] + }, + { + "isblacklisted": false, + "pid": "...", + "pk": "...", + "peertype": "validator", + "addresses": [ + "/ip4/172.17.0.1/tcp/21501", + "/ip4/192.168.2.104/tcp/21501", + "/ip4/172.17.0.1/tcp/21501", + "/ip4/127.0.0.1/tcp/21501" + ] + }, + { + "isblacklisted": false, + "pid": "...", + "pk": "...", + "peertype": "validator", + "addresses": [ + "/ip4/172.17.0.1/tcp/21508", + "/ip4/127.0.0.1/tcp/21508", + "/ip4/192.168.2.104/tcp/21508", + "/ip4/172.17.0.1/tcp/21508" + ] + }, + { + "isblacklisted": false, + "pid": "...", + "pk": "...", + "peertype": "validator", + "addresses": [ + "/ip4/172.17.0.1/tcp/21506", + "/ip4/127.0.0.1/tcp/21506", + "/ip4/192.168.2.104/tcp/21506", + "/ip4/172.17.0.1/tcp/21506" + ] + }, + { + "isblacklisted": false, + "pid": "...", + "pk": "", + "peertype": "observer", + "addresses": [ + "/ip4/127.0.0.1/tcp/38188", + "/ip4/192.168.2.104/tcp/38188", + "/ip4/172.17.0.1/tcp/38188" + ] + }, + { + "isblacklisted": false, + "pid": "...", + "pk": "...", + "peertype": "validator", + "addresses": [ + "/ip4/172.17.0.1/tcp/21505", + "/ip4/127.0.0.1/tcp/21505", + "/ip4/192.168.2.104/tcp/21505", + "/ip4/172.17.0.1/tcp/21505" + ] + }, + { + "isblacklisted": false, + "pid": "...", + "pk": "", + "peertype": "observer", + "addresses": [ + "/ip4/172.17.0.1/tcp/21102", + "/ip4/127.0.0.1/tcp/21102", + "/ip4/192.168.2.104/tcp/21102", + "/ip4/172.17.0.1/tcp/21102" + ] + }, + { + "isblacklisted": false, + "pid": "...", + "pk": "...", + "peertype": "validator", + "addresses": [ + "/ip4/172.17.0.1/tcp/21500", + "/ip4/127.0.0.1/tcp/21500", + "/ip4/192.168.2.104/tcp/21500", + "/ip4/172.17.0.1/tcp/21500" + ] + }, + { + "isblacklisted": false, + "pid": "...", + "pk": "", + "peertype": "observer", + "addresses": [ + "/ip4/192.168.2.104/tcp/21101", + "/ip4/192.168.2.104/tcp/21101", + "/ip4/172.17.0.1/tcp/21101", + "/ip4/127.0.0.1/tcp/21101" + ] + } + ] + }, + "error": "", + "code": "successful" +} +``` + + + + +:::important +This endpoint is not available on the Proxy. Only Nodes (Observers) expose this endpoint. +::: + +--- + +### REST API overview + +## Introduction + +MultiversX has 2 layers of REST APIs that can be publicly accessed. Both of them can be recreated by anyone that +wants to have the same infrastructure, but self-hosted. + +These 2 layers of REST APIs are: + +- `https://gateway.multiversx.com`: the lower level layer (backed by `MultiversX Proxy`) that handles routing all the requests in accordance to + the sharding mechanism. More details can be found [here](/sdk-and-tools/rest-api/gateway-overview). + +- `https://api.multiversx.com`: the higher level layer (backed by `api.multiversx.com` repository) that uses the gateway level underneath, + but also integrates Elasticsearch (historical) queries, battle-tested caching mechanisms, friendly fields formatting and so on. More details + can be found [here](/sdk-and-tools/rest-api/multiversx-api). + +--- + +### Rest API Transactions + +```mdx-code-block +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; +``` + + +This component of the REST API allows one to send (broadcast) Transactions to the Blockchain and query information about them. + + +## POST Send Transaction {#send-transaction} + +`https://gateway.multiversx.com/transaction/send` + +This endpoint allows one to send a signed Transaction to the Blockchain. + + + + +Body Parameters + +| Param | Required | Type | Description | +| +| ---------------- | ----------------------------------------- | -------- | -------------------------------------------------------------------------------- | +| nonce | REQUIRED | `number` | The Nonce of the Sender. | +| value | REQUIRED | `string` | The Value to transfer, as a string representation of a Big Integer (can be "0"). | +| receiver | REQUIRED | `string` | The Address (bech32) of the Receiver. | +| sender | REQUIRED | `string` | The Address (bech32) of the Sender. | +| guardian | OPTIONAL | `string` | The Address (bech32) of the Guardian. | +| senderUsername | OPTIONAL | `string` | The base64 string representation of the Sender's username. | +| receiverUsername | OPTIONAL | `string` | The base64 string representation of the Receiver's username. | +| gasPrice | REQUIRED | `number` | The desired Gas Price (per Gas Unit). | +| gasLimit | REQUIRED | `number` | The maximum amount of Gas Units to consume. | +| data | OPTIONAL | `string` | The base64 string representation of the Transaction's message (data). | +| signature | REQUIRED | `string` | The Signature (hex-encoded) of the Transaction. | +| guardianSignature| OPTIONAL | `string` | The Guardian's Signature (hex-encoded) of the Transaction. | +| chainID | REQUIRED | `string` | The Chain identifier. | +| version | REQUIRED | `number` | The Version of the Transaction (e.g. 1). | +| options | OPTIONAL | `number` | The Options of the Transaction (e.g. 1). | + + + + +🟢 200: OK + +Transaction sent with success. A Transaction Hash is returned. + +```json +{ + "data": { + "txHash": "6c41c71946b5b428c2cfb560e3ea425f8a00345de4bb2eb1b784387790914277" + }, + "error": "", + "code": "successful" +} +``` + +🔴 400: Bad request + +Invalid Transaction signature. + +```json +{ + "data": null, + "error": "transaction generation failed: ed25519: invalid signature", + "code": "bad_request" +} +``` + + + + +:::caution +For Nodes (Observers or Validators with the HTTP API enabled), this endpoint **only accepts transactions whose sender is in the Node's Shard**. +::: + +Here's an example of a request: + +```bash +POST https://gateway.multiversx.com/transaction/send HTTP/1.1 +Content-Type: application/json +``` + +```json +{ + "nonce": 42, + "value": "100000000000000000", + "receiver": "erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r", + "sender": "erd1njqj2zggfup4nl83x0nfgqjkjserm7mjyxdx5vzkm8k0gkh40ezqtfz9lg", + "gasPrice": 1000000000, + "gasLimit": 70000, + "data": "Zm9vZCBmb3IgY2F0cw==", #base64 representation of "food for cats" + "signature": "93207c579bf57be03add632b0e1624a73576eeda8a1687e0fa286f03eb1a17ffb125ccdb008a264c402f074a360442c7a034e237679322f62268b614e926d10f", + "chainId": "1", + "version": 1 +} +``` +:::info +More information about sending a guarded transaction can be found here: [Guarded Accounts](/developers/guard-accounts#sending-guarded-co-signed-transactions) +::: + + +## POST Send Multiple Transactions {#send-multiple-transactions} + +`https://gateway.multiversx.com/transaction/send-multiple` + +This endpoint allows one to send a bulk of Transactions to the Blockchain. + + + + +Body Parameters + +Array of: + +| Param | Required | Type | Description | +| ---------------- | ----------------------------------------- | -------- | -------------------------------------------------------------------------------- | +| nonce | REQUIRED | `number` | The Nonce of the Sender. | +| value | REQUIRED | `string` | The Value to transfer, as a string representation of a Big Integer (can be "0"). | +| receiver | REQUIRED | `string` | The Address (bech32) of the Receiver. | +| sender | REQUIRED | `string` | The Address (bech32) of the Sender. | +| senderUsername | OPTIONAL | `string` | The base64 string representation of the Sender's username. | +| receiverUsername | OPTIONAL | `string` | The base64 string representation of the Receiver's username. | +| gasPrice | REQUIRED | `number` | The desired Gas Price (per Gas Unit). | +| gasLimit | REQUIRED | `number` | The maximum amount of Gas Units to consume. | +| data | OPTIONAL | `string` | The base64 string representation of the Transaction's message (data). | +| signature | REQUIRED | `string` | The Signature (hex-encoded) of the Transaction. | +| chainID | REQUIRED | `string` | The Chain identifier. | +| version | REQUIRED | `number` | The Version of the Transaction (e.g. 1). | +| options | OPTIONAL | `number` | The Options of the Transaction (e.g. 1). | + + + + +🟢 200: OK + +A bulk of Transactions were successfully sent. + +```json +{ + "data": { + "numOfSentTxs": 2, + "txsHashes": { + "0": "6c41c71946b5b428c2cfb560e3ea425f8a00345de4bb2eb1b784387790914277", + "1": "fa8195bae93d4609a6fc5972a7a6176feece39a6c4821acae2276701aee12fb0" + } + }, + "error": "", + "code": "successful" +} +``` + + + + +:::caution +For Nodes (Observers or Validators with the HTTP API enabled), this endpoint **only accepts transactions whose sender is in the Node's Shard**. +::: + +Here's an example of a request: + +```bash +POST https://gateway.multiversx.com/transaction/send-multiple HTTP/1.1 +``` + +```json + +Content-Type: application/json + +[ + { + "nonce": 42, + "value": "100000000000000000", + "receiver": "erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r", + "sender": "erd1njqj2zggfup4nl83x0nfgqjkjserm7mjyxdx5vzkm8k0gkh40ezqtfz9lg", + "gasPrice": 1000000000, + "gasLimit": 70000, + "data": "Zm9vZCBmb3IgY2F0cw==", #base64 representation of "food for cats" + "signature": "93207c579bf57be03add632b0e1624a73576eeda8a1687e0fa286f03eb1a17ffb125ccdb008a264c402f074a360442c7a034e237679322f62268b614e926d10f", + "chainId": "1", + "version": 1 +} + { + "nonce": 43, + "value": "100000000000000000", + "receiver": "erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r", + "sender": "erd1rhp4q3qlydyrrjt7dgpfzxk8n4f7yrat4wc6hmkmcnmj0vgc543s8h7hyl", + "gasPrice": 1000000000, + "gasLimit": 70000, + "data": "YnVzIHRpY2tldHM=", #base64 representation of "bus tickets" + "signature": "01535fd1d40d98b7178ccfd1729b3f526ee4542482eb9f591d83433f9df97ce7b91db07298b1d14308e020bba80dbe4bba8617a96dd7743f91ee4b03d7f43e00", + "chainID": "1", + "version": 1 + } +] +``` + + +## POST Simulate Transaction {#simulate-transaction} + +**Nodes and observers** + +`https://gateway.multiversx.com/transaction/simulate` + +This endpoint allows one to send a signed Transaction to the Blockchain in order to simulate its execution. +This can be useful in order to check if the transaction will be successfully executed before actually sending it. +It receives the same request as the `/transaction/send` endpoint. + +Move balance successful transaction simulation + + + + +Body Parameters + +| Param | Required | Type | Description | +| ---------------- | ----------------------------------------- | -------- | -------------------------------------------------------------------------------- | +| nonce | REQUIRED | `number` | The Nonce of the Sender. | +| value | REQUIRED | `string` | The Value to transfer, as a string representation of a Big Integer (can be "0"). | +| receiver | REQUIRED | `string` | The Address (bech32) of the Receiver. | +| sender | REQUIRED | `string` | The Address (bech32) of the Sender. | +| senderUsername | OPTIONAL | `string` | The base64 string representation of the Sender's username. | +| receiverUsername | OPTIONAL | `string` | The base64 string representation of the Receiver's username. | +| gasPrice | REQUIRED | `number` | The desired Gas Price (per Gas Unit). | +| gasLimit | REQUIRED | `number` | The maximum amount of Gas Units to consume. | +| data | OPTIONAL | `string` | The base64 string representation of the Transaction's message (data). | +| signature | REQUIRED | `string` | The Signature (hex-encoded) of the Transaction. | +| chainID | REQUIRED | `string` | The Chain identifier. | +| version | REQUIRED | `number` | The Version of the Transaction (e.g. 1). | +| options | OPTIONAL | `number` | The Options of the Transaction (e.g. 1). | + + + + +A full response contains the fields above: +_SimulationResults_ +| Field | Type | Description | +|------------|---------------------------|-------------------| +| status | string | success, fail ... | +| failReason | string | the error message | +| scResults | []ApiSmartContractResult | an array of smart contract results (if any) | +| receipts | []ApiReceipt | an array of the receipts (if any) | +| hash | string | the hash of the transaction | + +❕ Note that fields that are empty won't be included in the response. This can be seen in the examples below + +--- + +🟢 200: OK + +Transaction would be successful. + +```json +{ + "data": { + "status": "success", + "hash": "bb24ccaa2da8cddd6a3a8eb162e6ff62ad4f6e1914d9aa0cacde6772246ca2dd" + }, + "error": "", + "code": "successful" +} +``` + +--- + +🟢 200: Simulation was successful, but the transaction wouldn't be executed. + +Invalid Transaction signature. + +```json +{ + "data": { + "status": "fail", + "failReason": "higher nonce in transaction", + "hash": "bb24ccaa2da8cddd6a3a8eb162e6ff62ad4f6e1914d9aa0cacde6772246ca2dd" + }, + "error": "", + "code": "successful" +} +``` + +--- + +🔴 400: Bad request + +```json +{ + "data": null, + "error": "transaction generation failed: invalid chain ID", + "code": "bad_request" +} +``` + + + + +--- + +**Proxy** + +On the Proxy side, if the transaction to simulate is a cross-shard one, then the response format will contain two elements called `senderShard` and `receiverShard` which are of type `SimulationResults` explained above. + +Example response for cross-shard transactions: + +```json +{ + "data": { + "receiverShard": { + "status": "success", + "hash": "bb24ccaa2da8cddd6a3a8eb162e6ff62ad4f6e1914d9aa0cacde6772246ca2dd" + }, + "senderShard": { + "status": "success", + "hash": "bb24ccaa2da8cddd6a3a8eb162e6ff62ad4f6e1914d9aa0cacde6772246ca2dd" + } + }, + "error": "", + "code": "successful" +} +``` + + +## POST Estimate Cost of Transaction {#estimate-cost-of-transaction} + +`https://gateway.multiversx.com/transaction/cost` + +This endpoint is used to estimate the gas cost of a given transaction. + +It performs a read-only simulation of the transaction against the current on-chain state, returning the number of gas units the transaction would consume if executed in that exact state. + +#### How it works: +- The endpoint takes all transaction input fields (value, sender, receiver, data, chainID, etc.). +- It executes the transaction in a sandboxed, non-persistent environment, meaning it simulates execution without affecting the actual blockchain. +- It uses the current state of the network (including smart contract storage, balances, etc.). +- It returns the estimated gas (txGasUnits) and may also return smart contract results and events triggered by the simulation. + +#### How does it apply to smart contracts? + +For smart contracts, the endpoint simulates the contract call exactly as if it were executed live, including processing all logic, branches, and emitted events. +The gas estimate reflects the computation and storage impact that would occur if the state remained unchanged at the time of actual execution. + +#### How does it approximate the cost if the contract logic has variable gas usage? + +If a smart contract’s logic has branches or conditional execution that result in variable gas usage (e.g., depending on internal storage state, previous executions, etc.), +the estimation will only reflect the gas used by the path taken during this particular simulation. + +#### Why is providing the correct nonce important? + +Because the simulation engine mirrors real transaction behavior, it requires a valid and correct nonce to properly simulate the transaction. Using an outdated or incorrect +nonce may lead to simulation failure. + +#### Can the estimated gas differ from the actual cost? + +Yes. Since the blockchain state may change between the moment you call /transaction/cost and when the transaction is actually sent to the network, the real gas usage can differ. +This is especially true for smart contract calls that depend on dynamic or mutable state. + + + + + +Body Parameters + +| Param | Required | Type | Description | +| -------- | ----------------------------------------- | -------- | -------------------------------------------------------------------------------- | +| value | REQUIRED | `string` | The Value to transfer, as a string representation of a Big Integer (can be "0"). | +| receiver | REQUIRED | `string` | The Address (bech32) of the Receiver. | +| sender | REQUIRED | `string` | The Address (bech32) of the Sender. | +| chainID | REQUIRED | `string` | The Chain identifier. | +| version | REQUIRED | `number` | The Version of the Transaction (e.g. 1). | +| nonce | REQUIRED | `number` | The Sender nonce. | +| options | OPTIONAL | `number` | The Options of the Transaction (e.g. 1). | +| data | OPTIONAL | `string` | The base64 string representation of the Transaction's message (data). | + + + + + +🟢 200: OK + +The cost is estimated successfully. + +```json +{ + "data": { + "txGasUnits": "77000" + }, + "error": "", + "code": "successful" +} +``` + + + + +:::tip +- Use the returned `txGasUnits` value as the `gasLimit` in your actual transaction. +- Make sure to provide the correct `nonce` of the transaction +::: + +:::tip +**Best practice:** when sending the transaction, add ~10% extra gas to the estimated value to avoid underestimation and failure due to insufficient gas. +::: + +Here's an example of a request: + +```json +POST https://gateway.multiversx.com/transaction/cost HTTP/1.1 +Content-Type: application/json + +{ + "value": "100000", + "receiver": "erd188nydpkagtpwvfklkl2tn0w6g40zdxkwfgwpjqc2a2m2n7ne9g8q2t22sr", + "sender": "erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz", + "data": "dGhpcyBpcyBhbiBleGFtcGxl", #base64 representation of "this is an example" + "chainID": "1", + "version": 1, + "nonce": 1 +} +``` + + +## GET **Get Transaction** {#get-transaction} + +`https://gateway.multiversx.com/transaction/:txHash` + +This endpoint allows one to query the details of a Transaction. + + + + +Path Parameters + +| Param | Required | Type | Description | +| ------ | ----------------------------------------- | -------- | ----------------------------------------- | +| txHash | REQUIRED | `string` | The hash (identifier) of the Transaction. | + +Query Parameters + +| Param | Required | Type | Description | +| ----------- | ----------------------------------------- | -------- | -------------------------------------------------------------------------------------------- | +| sender | OPTIONAL | `string` | The Address of the sender - a hint to optimize the request. | +| withResults | OPTIONAL | `bool` | Boolean parameter to specify if smart contract results and other details should be returned. | + + + + +🟢 200: OK + +Transaction details retrieved successfully. + +```json +{ + "data": { + "transaction": { + "type": "normal", + "nonce": 3, + "round": 186580, + "epoch": 12, + "value": "1000000000000000000", + "receiver": "erd1...", + "sender": "erd1...", + "gasPrice": 1000000000, + "gasLimit": 70000, + "data": "Zm9yIHRlc3Rz", + "signature": "1047...", + "sourceShard": 2, + "destinationShard": 1, + "blockNonce": 186535, + "miniblockHash": "e927...", + "blockHash": "50a1...", + "status": "executed" + } + }, + "error": "", + "code": "successful" +} +``` + + + +Request URL: + +`https://gateway.multiversx.com/transaction/:txHash?withResults=true` + +Response: + +The response can contain additional fields such as `smartContractResults`, or `receipt` + +```json +{ + "data": { + "transaction": { + "type": "normal", + "nonce": 3, + "round": 186580, + "epoch": 12, + "value": "1000000000000000000", + "receiver": "erd1...", + "sender": "erd1...", + "gasPrice": 1000000000, + "gasLimit": 70000, + "data": "Zm9yIHRlc3Rz", + "signature": "1047...", + "sourceShard": 2, + "destinationShard": 1, + "blockNonce": 186535, + "miniblockHash": "e927...", + "blockHash": "50a1...", + "status": "executed", + "receipt": { + "value": 100, + "sender": "erd1...", + "data": "...", + "txHash": "b37..." + }, + "smartContractResults": [ + { + "hash": "...", + "nonce": 5, + "value": 1000, + "receiver": "erd1...", + "sender": "erd1...", + "data": "@6f6b", + "prevTxHash": "3638...", + "originalTxHash": "3638...", + "gasLimit": 0, + "gasPrice": 1000000000, + "callType": 0 + } + ] + } + }, + "error": "", + "code": "successful" +} +``` + + + + +:::important +The optional query parameter **`sender`** is only applicable to requests against the Proxy (not against the Observer Nodes). +::: + + +## GET **Get Transaction Shallow Status** {#get-transaction-status} + +`https://gateway.multiversx.com/transaction/:txHash/status` + +This endpoint allows one to query **the shallow status** of a transaction. For more details, see [this](/integrators/querying-the-blockchain). + + + + +Path Parameters + +| Param | Required | Type | Description | +| ------ | ----------------------------------------- | -------- | ----------------------------------------- | +| txHash | REQUIRED | `string` | The hash (identifier) of the Transaction. | + +Query Parameters + +| Param | Required | Type | Description | +| ------ | ----------------------------------------- | -------- | ----------------------------------------------------------- | +| sender | OPTIONAL | `string` | The Address of the sender - a hint to optimize the request. | + + + + +🟢 200: OK + +Transaction status retrieved successfully. + +```json +{ + "data": { + "status": "success" + }, + "error": "", + "code": "successful" +} +``` + + + + +:::important +The optional query parameter **`sender`** is only applicable to requests against the Proxy (not against the Observer Nodes). +::: + + +## GET **Get Transaction Process Status** {#get-transaction-process-status} + +`https://gateway.multiversx.com/transaction/:txHash/process-status` + +This endpoint allows one to query the **process status** of a transaction. For more details, see [this](/integrators/querying-the-blockchain). + + + + +Path Parameters + +| Param | Required | Type | Description | +| ------ | ----------------------------------------- | -------- | ----------------------------------------- | +| txHash | REQUIRED | `string` | The hash (identifier) of the Transaction. | + + + + +🟢 200: OK + +Transaction status retrieved successfully. + +```json +{ + "data": { + "reason": "" + "status": "success" + }, + "error": "", + "code": "successful" +} +``` + + + + + +## GET **Get Transactions Pool** {#get-transactions-pool} + +`http://local-proxy-instance/transaction/pool` + +:::caution +This endpoint isn't available on public gateway. However, it can be used on a local proxy instance, by setting `AllowEntireTxPoolFetch` to `true` +::: + +This endpoint allows one to fetch the entire transactions pool, merging the pools from each shard. + + +### Default + + + + +Example: + +`http://local-proxy-instance/transaction/pool` + + + + +🟢 200: OK + +Transaction status retrieved successfully. + +```json +{ + "data": { + "txPool": { + "regularTransactions": [ + { + "txFields": { + "hash": "84bb8a..." + } + }, + { + "txFields": { + "hash": "4e2c43..." + } + } + ], + "smartContractResults": [], + "rewards": [] + }, + "error": "", + "code": "successful" +} +``` + + + + + +### Using custom fields + + + + +Query Parameters + +| Param | Required | Type | Description | +| -------- | ----------------------------------------- | -------- | ------------------------------------------------------------- | +| fields | OPTIONAL | `string` | A list of the fields to be included. | +| shard-id | OPTIONAL | `string` | A specific shard id(0, 1, 2 etc. or 4294967295 for Metachain) | + +As seen above, if the `fields` item is empty, only the transaction hash will be displayed. + +If the `shard-id` item is used, only the transactions from that specific shard's pool will be displayed. + +Example request with shard id and fields: + +`https://gateway.multiversx.com/transaction/pool?shard-id=0&fields=sender,receiver,value` + +All possible values for fields item are: + +- hash +- nonce +- sender +- receiver +- gaslimit +- gasprice +- receiverusername +- data +- value + + + + +🟢 200: OK + +Transaction status retrieved successfully. + +```json +{ + "data": { + "txPool": { + "regularTransactions": [ + { + "txFields": { + "gasLimit": 10, + "gasPrice": 1000, + "receiver": "erd1...", + "sender": "erd1...", + "value": "10000000000000000000" + } + } + ], + "smartContractResults": [ + { + "txFields": { + "gasLimit": 10, + "gasPrice": 1000, + "receiver": "erd1...", + "sender": "erd1...", + "value": "10000000000000000000" + } + } + ], + "rewards": [ + { + "txFields": { + "gasLimit": 10, + "gasPrice": 1000, + "receiver": "erd1...", + "sender": "erd1...", + "value": "10000000000000000000" + } + } + ] + } + }, + "error": "", + "code": "successful" +} +``` + + + + + +## GET **Get Transactions Pool for a Sender** {#get-transactions-pool-for-a-sender} + +`https://gateway.multiversx.com/transaction/pool?by-sender=:sender:` + +This endpoint allows one to fetch all the transactions of a sender from the transactions pool. + + +### Default + + + + +Query Parameters + +| Param | Required | Type | Description | +| --------- | ----------------------------------------- | -------- | -------------------------- | +| by-sender | REQUIRED | `string` | The Address of the sender. | + +Example: + +`https://gateway.multiversx.com/transaction/pool?by-sender=erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th` + + + + +🟢 200: OK + +Transaction status retrieved successfully. + +```json +{ + "data": { + "txPool": { + "transactions": [ + { + "txFields": { + "hash": "1daea5..." + } + } + ] + } + }, + "error": "", + "code": "successful" +} +``` + + + + + +### Using custom fields + + + + +Query Parameters + +| Param | Required | Type | Description | +| --------- | ----------------------------------------- | -------- | ------------------------------------ | +| by-sender | REQUIRED | `string` | The Address of the sender. | +| fields | OPTIONAL | `string` | A list of the fields to be included. | + +As seen above, if the `fields` item is empty, only the transaction hash will be displayed. + +Example request with fields: + +`https://gateway.multiversx.com/transaction/pool?by-sender=erd1at9...&fields=sender,receiver,value` + +All possible values for fields item are: + +- hash +- nonce +- sender +- receiver +- gaslimit +- gasprice +- receiverusername +- data +- value + + + + +🟢 200: OK + +Transaction status retrieved successfully. + +```json +{ + "data": { + "txPool": { + "transactions": [ + { + "txFields": { + "hash": "1daea...", + "receiver": "erd1932...", + "sender": "erd1at9ke...", + "value": 0 + } + } + ] + } + }, + "error": "", + "code": "successful" +} +``` + + + + + +## GET **Get the latest nonce of a sender from Tx Pool** {#get-the-latest-nonce-of-a-sender-from-tx-pool} + +`https://gateway.multiversx.com/transaction/pool?by-sender=:sender:&last-nonce=true` + +This endpoint allows one to fetch the latest nonce of a sender from the transactions pool. + + + + +Query Parameters + +| Param | Required | Type | Description | +| ---------- | ----------------------------------------- | -------- | ----------------------------------------------- | +| by-sender | REQUIRED | `string` | The Address of the sender. | +| last-nonce | REQUIRED | `bool` | Specifies if the last nonce has to be returned. | + + + + +🟢 200: OK + +Transaction status retrieved successfully. + +```json +{ + "data": { + "nonce": 38 + }, + "error": "", + "code": "successful" +} +``` + + + + + +## GET **Get the nonce gaps of a sender from Tx Pool** {#get-the-nonce-gaps-of-a-sender-from-tx-pool} + +`https://gateway.multiversx.com/transaction/pool?by-sender=:sender:&nonce-gaps=true` + +This endpoint allows one to fetch the nonce gaps of a sender from the transactions pool. + + + + +Query Parameters + +| Param | Required | Type | Description | +| ---------- | ----------------------------------------- | -------- | ----------------------------------------------- | +| by-sender | REQUIRED | `string` | The Address of the sender. | +| nonce-gaps | REQUIRED | `bool` | Specifies if the nonce gaps should be returned. | + + + + +🟢 200: OK + +Transaction status retrieved successfully. + +```json +{ + "data": { + "nonceGaps": { + "gaps": [ + { + "from": 34, + "to": 35 + }, + { + "from": 37, + "to": 37 + } + ] + } + }, + "error": "", + "code": "successful" +} +``` + + + + +--- + +### Rest API Virtual Machine + +```mdx-code-block +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; +``` + + +This component of the REST API allows one to call view functions (pure functions) of Smart Contracts, or, to put it in other words, to query values stored within contracts. + + +## POST Compute Output of Pure Function {#compute-output-of-pure-function} + +`https://gateway.multiversx.com/vm-values/query` + +This endpoint allows one to execute - with no side-effects - a pure function of a Smart Contract and retrieve the execution results (the Virtual Machine Output). + + + + +Body Parameters + +| Param | Required | Type | Description | +| --------- | ----------------------------------------- | -------- | ----------------------------------------------------------------------------------- | +| scAddress | REQUIRED | `string` | The Address (bech32) of the Smart Contract. | +| funcName | REQUIRED | `string` | The name of the Pure Function to execute. | +| args | REQUIRED | `array` | The arguments of the Pure Function, as hex-encoded strings. The array can be empty. | +| caller | OPTIONAL | `string` | The Address (bech32) of the caller. | +| value | OPTIONAL | `string` | The Value to transfer (can be zero). | + + + + +🟢 200: OK + +The VM Output is retrieved successfully. + +```json +{ + "data": { + "data": { + "ReturnData": ["eyJSZ... (base64)"], + "ReturnCode": 0, + "ReturnMessage": "", + "GasRemaining": 1500000000, + "GasRefund": 0, + "OutputAccounts": { + "...": { + "Address": "... (base64)", + "Nonce": 0, + "Balance": null, + "BalanceDelta": 0, + "StorageUpdates": null, + "Code": null, + "CodeMetadata": null, + "Data": null, + "GasLimit": 0, + "CallType": 0 + } + }, + "DeletedAccounts": null, + "TouchedAccounts": null, + "Logs": null + } + }, + "error": "", + "code": "successful" +} +``` + + + + +Here's an example of a request: + +```json +POST https://gateway.multiversx.com/vm-values/query HTTP/1.1 +Content-Type: application/json + +{ + "scAddress": "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqllls0lczs7", + "funcName": "get", + "caller": "erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8", + "value": "0", + "args": ["d98d..."] +} +``` + + +## POST Compute Hex Output of Pure Function {#compute-hex-output-of-pure-function} + +`https://gateway.multiversx.com/vm-values/hex` + +This endpoint allows one to execute - with no side-effects - a pure function of a Smart Contract and retrieve the first output value as a hex-encoded string. + + + + +Body Parameters + +| Param | Required | Type | Description | +| --------- | ----------------------------------------- | -------- | ----------------------------------------------------------------------------------- | +| scAddress | REQUIRED | `string` | The Address (bech32) of the Smart Contract. | +| funcName | REQUIRED | `string` | The name of the Pure Function to execute. | +| args | REQUIRED | `array` | The arguments of the Pure Function, as hex-encoded strings. The array can be empty. | +| caller | OPTIONAL | `string` | The Address (bech32) of the caller. | +| value | OPTIONAL | `string` | The Value to transfer (can be zero). | + + + + +🟢 200: OK + +The output value is retrieved successfully. + +```json +{ + "data": "7b22..." +} +``` + + + + + +## POST Compute String Output of Pure Function {#compute-string-output-of-pure-function} + +`https://gateway.multiversx.com/vm-values/string` + +This endpoint allows one to execute - with no side effects - a pure function of a Smart Contract and retrieve the first output value as a string. + + + + +Body Parameters + +| Param | Required | Type | Description | +| --------- | ----------------------------------------- | -------- | ----------------------------------------------------------------------------------- | +| scAddress | REQUIRED | `string` | The Address (bech32) of the Smart Contract. | +| funcName | REQUIRED | `string` | The name of the Pure Function to execute. | +| args | REQUIRED | `array` | The arguments of the Pure Function, as hex-encoded strings. The array can be empty. | +| caller | OPTIONAL | `string` | The Address (bech32) of the caller. | +| value | OPTIONAL | `string` | The Value to transfer (can be zero). | + + + + +🟢 200: OK + +The output value is retrieved successfully. + +```json +{ + "data": "foobar" +} +``` + + + + + +## POST Get Integer Output of Pure Function {#get-integer-output-of-pure-function} + +`https://gateway.multiversx.com/vm-values/int` + +This endpoint allows one to execute - with no side-effects - a pure function of a Smart Contract and retrieve the first output value as an integer. + + + + +Body Parameters + +| Param | Required | Type | Description | +| --------- | ----------------------------------------- | -------- | ----------------------------------------------------------------------------------- | +| scAddress | REQUIRED | `string` | The Address (bech32) of the Smart Contract. | +| funcName | REQUIRED | `string` | The name of the Pure Function to execute. | +| args | REQUIRED | `array` | The arguments of the Pure Function, as hex-encoded strings. The array can be empty. | +| caller | OPTIONAL | `string` | The Address (bech32) of the caller. | +| value | OPTIONAL | `string` | The Value to transfer (can be zero). | + + + + +🟢 200: OK + +The output value is retrieved successfully. + +```json +{ + "data": "2020" +} +``` + + + + +--- + +### Result Handlers + +## Overview + +Most of the transaction fields are inputs, or work like inputs. The last one of the fields is the one that deals with the outputs. + +There are 3 types of transactions where it comes to outputs: +1. Transactions where we never receive a result, such as those sent via _transfer-execute_. Result handlers are not needed here, in fact, they are inappropriate. +2. Transactions that must finalize before we can move on. Here, various results might be returned, and we can decode them on the spot. Result handlers will determine what gets decoded and how. +3. Transactions that will finalize at an unknown time in the future, such as cross-shard calls from contracts. Here, the result handler is the callback that we register, to be executed as soon as the VM receives the response from that transaction and passes it on to our code. + +We've had callbacks for a long time, whereas decoders are new. A transaction can have either one, or the other, not both. + +We are going to focus on their usage and at the end, also try to explain how they work. + + + +## Diagram + +The result handler diagram is split into two: the callback side, and the decoder side. + +The decoders are considerably more complex, here we have a simplified version. + +```mermaid +graph LR + subgraph Result Handlers + rh-unit("()") + rh-unit -->|original_type| rh-ot("OriginalTypeMarker") + rh-ot -->|callback| CallbackClosure -->|gas_for_callback| CallbackClosureWithGas + dh[Decode Handler] + rh-unit -->|"returns
with_result"| dh + rh-ot -->|"returns
with_result"| dh + dh -->|"returns
with_result"| dh + end +``` + + + +## No result handlers + +A transaction might have no result handlers attached to it, if: +- The transaction does not return any result data, such as the case for _transfer-execute_ or simple _transfer_ transactions. +- The transaction could return some results, but we are not interested in them. + - If no result handlers are specified, no decoding takes place. + - This is similar to using the `IgnoreValue` return type in the legacy contract call syntax. + + + + +## Original result marker + +Type safety is not only important for inputs, but also for outputs. The first step is signaling what the intended result type is in the original contract, where the endpoint is defined. + +Proxies define this type themselves, since they have access to the ABI. + +If set, the marker will be visible in the transaction type, as an `OriginalResultMarker` result handler. + +:::info +The `OriginalResultMarker` does not do anything by itself, it is a zero-size type, with no methods implemented. + +Having only this marker set is no different from having no result handlers specified. It is only a compile-time artifact for ensuring type safety for outputs. +::: + +Even when we are providing raw data to a transaction, without proxies, we are allowed to specify the original intended result type ourselves. We do this by calling `.original_result()`, with no arguments. If the type cannot be inferred, we need to specify it explicitly, `.original_result::()`. + + + + +## Default result handler + +There is a special case of a default result handler in interactors and in tests. + +Without specifying anything, the framework will check that a transaction is successful. This applies to both interactors and tests (in contracts one cannot recover from a failed sync call, so this mechanism is not necessary). + +If, however, the developer expects the transaction to fail, this system can easily be overridden by adding an error-related result handler, such as [ExpectError](#expecterror), or [ReturnsStatus](#returnsstatus). + + + +## Asynchronous callbacks + + + +## Result decoders + +Result decoders come in handy when defining exact return types from smart contract endpoints. Being part of the unified syntax, they are consistent through the various environments (smart contract, interact, test) and can be used in combination with each other as long as it makes sense for the specific transaction. + +There are two ways to add a result decoder: via `with_result` or `returns`. + + +### `with_result` + +The simpler type of result decoder, it doesn't alter the return type of the transaction run method in any way. + +It registers lambdas or similar constructs, which then react to the results as they come. + + + +### `returns` + +Adding a result handler via `result` causes the transaction run function to return various versions of the result, as indicated by the result handler. + +For instance, [ReturnsResult](#returnsresult) causes the deserialized result of the transaction to be returned. + +If we add multiple result handlers, we will get a tuple with all the requested results. + +:::info +The return type is determined at compile time, with no runtime or bytecode size overhead. +::: + +In the examples below, the return type is stated explicitly, for clarity. Please note that, because of type inference, they barely ever need to be specified like this. + +All examples assume the variable `tx` already has all inputs constructed for it. Let's also assume that the original result type is `MyResult`. + +```rust title="No decoder" +let _: () = tx + .run(); +``` + +The return type here is `()` nothing is deserialized. + +```rust title="One decoder" +let r: MyResult = tx + .returns(ReturnsResult) + .run(); +``` + +Here, we get the transaction result returned. You might see this in a contract, interactor, or test. + +```rust title="Two decoders" +let r: (MyResult, BackTransfers) = tx + .returns(ReturnsResult) + .returns(ReturnsBackTransfers) + .run(); +``` + +This time we want two values out. The framework packs them in a pair, behind the scenes. The order of the values in the tuple is always the same as the order the result handlers were passed. If we pass them in reverse order, we also get the output in reverse order: + +```rust title="Same two decoders, reverse order" +let r: (BackTransfers, MyResult) = tx + .returns(ReturnsBackTransfers) + .returns(ReturnsResult) + .run(); +``` + +This mechanism works with any number of result handlers. _(There is a limit of 16 for now, in the unlikely case that anybody will need more, it can easily be increased.)_ + +```rust title="Three decoders" +let r: (ManagedVec, ManagedAddress, BackTransfers) = tx + .returns(ReturnsRawResult) + .returns(ReturnsNewManagedAddress) + .returns(ReturnsBackTransfers) + .run(); +``` + +There is no limitation that the same decoder cannot be used multiple times, although it makes little sense in practice: + +```rust title="Duplicate decoders" +let r: (MyResult, MyResult, Address, MyResult) = tx + .returns(ReturnsResult) + .returns(ReturnsResult) + .returns(ReturnsNewAddress) + .returns(ReturnsResult) + .run(); +``` + +Methods `returns` and `with_result` can be interspersed in any order. Calls to `with_result` will not affect the return type. + +```rust title="returns + with_result" +let r: Address = tx + .returns(ReturnsAddress) + .with_result(WithResultRaw(|raw|) assert!(raw.is_empty())) + .run(); +``` + +Also adding an example with only `with_result`, something you are likely to see in black-box tests. + +```rust title="No return, with_result" +let r: () = tx + .with_result(ExpectError(4, "sample error")) + .run(); +``` + + + +## List of result decoders + +There are various predefined types of result decoders: + + + + +### `ReturnsRawResult` + +Returns: `ManagedVec>`, representing the raw data result from the call. + +```rust title=contract.rs +#[endpoint] +fn deploy_contract( + &self, + code: ManagedBuffer, + code_metadata: CodeMetadata, + args: MultiValueEncoded, +) -> ManagedVec { + self.tx() + .raw_deploy() + .code(code) + .code_metadata(code_metadata) + .arguments_raw(args.to_arg_buffer()) + .returns(ReturnsRawResult) + .sync_call() + .into() +} +``` + + + +### `ReturnsResult` + +Returns: the original type from the function signature. The exact original type is extracted from the return type of the corresponding function from the proxy. + +```rust title=interact.rs +async fn quorum_reached(&mut self, action_id: usize) -> bool { + self.interactor + .query() + .to(self.state.current_multisig_address()) + .typed(multisig_proxy::MultisigProxy) + .quorum_reached(action_id) + .returns(ReturnsResult) // knows from the original type marker that the expected return type is bool + .prepare_async() + .run() + .await +} +``` + + + +### `ReturnsResultUnmanaged` + +Returns: the unmanaged version of the original result type. This relies on the `Unmanaged` associated type in `TypeAbi`. + +For example: + +| Managed type | Unmanaged version | +| --------------------- | ------------------------------------- | +| Managed `BigUint` | Rust `BigUint` (alias: `RustBigUint`) | +| Managed `BigInt` | Rust `BigInt` (alias: `RustBigInt`) | +| `ManagedBuffer` | `Vec` | +| `ManagedAddress` | `Address` | +| `ManagedVec` | `Vec` | +| `ManagedOption` | `Option` | +| `ManagedByteArray` | `[u8; N]` | +| `BigFloat` | `f64` | + +Also, most generic container types (`Option`, `Vec`, etc.) will point to themselves, but with the unmanaged version of their contents. + +For all other types, it returns the original type, same as `ReturnsResult`. + +It is especially useful in interactor and test environments, as it allows us to avoid performing additional conversions. + +```rust title=interact.rs +async fn get_sum(&mut self) -> RustBigUint { + self + .interactor + .query() + .to(self.state.current_adder_address()) + .typed(adder_proxy::AdderProxy) + .sum() // original return type is multiversx_sc::types::BigUint + .returns(ReturnsResultUnmanaged) // converts into num_bigint::BigUint + .prepare_async() + .run() + .await +} +``` + +In this case, the original return type of the endpoint `sum` is `multiversx_sc::types::BigUint` which is a managed type. `ReturnsResultUnmanaged` automatically provides us with `num_bigint::BigUint`, a much more accessible type for the interactor, where we want to avoid constantly having to specify the API. + + + +### `ReturnsStatus` + +Returns: the transaction status as u64. + +Especially useful in the testing and interactor environments. + +```rust title=blackbox_test.rs +#[test] +fn status_test() { + let mut world = setup(); + + let status = world + .tx() + .from(OWNER_ADDRESS) + .to(SC_ADDRESS) + .typed(proxy::ContractProxy) + .some_endpoint() + .returns(ReturnsStatus) + .run(); + + assert_eq!(status, 4); // status 4 - user error +} +``` + + + +### `ReturnsMessage` + +Returns: the transaction error message as String. + +Especially useful in the testing and interactor environments, + +```rust title=blackbox_test.rs +#[test] +fn status_and_message_test() { + let mut world = setup(); + + let (status, message) = world + .tx() + .from(OWNER_ADDRESS) + .to(SC_ADDRESS) + .typed(proxy::ContractProxy) + .some_endpoint() + .returns(ReturnsStatus) + .returns(ReturnsMessage) + .run(); + + assert_eq!(status, 4); // status 4 - user error + assert_eq!(message, "test"); // error message - test +} +``` + + + +### `ReturnsNewBech32Address` + +Returns: the newly deployed address after a deploy, as `Bech32Address`. + +Used in the testing and interactor environments. + +```rust title=interact.rs +async fn deploy(&mut self) -> Bech32Address { + self + .interactor + .tx() + .from(&self.wallet_address) + .typed(adder_proxy::AdderProxy) + .init(0u32) // deploys adder contract + .code(&self.adder_code) + .returns(ReturnsNewBech32Address) // returns newly deployed address as Bech32Address + .prepare_async() + .run() + .await +} +``` + + + +### `ReturnsNewManagedAddress` + +Returns: the newly deployed address after a deploy, as `Bech32Address`. + +Used in the smart contract environments. + +```rust title=contract.rs +#[endpoint] +fn deploy_from_source( + &self, + source_contract_address: ManagedAddress, + args: MultiValueEncoded, +) -> ManagedAddress { + self.tx() + .raw_deploy() // creates a deploy transaction + .from_source(source_contract_address) + .arguments_raw(args.to_arg_buffer()) + .returns(ReturnsNewManagedAddress) // returns newly deployed address as ManagedAddress + .sync_call() +} +``` + + + +### `ReturnsNewAddress` + +Returns: the newly deployed address after a deploy, as `multiversx_sc::types::heap::Address`. + +```rust title=blackbox_test.rs +#[test] +fn returns_address_test() { + let mut world = ScenarioWorld::new(); + + let new_address = world + .tx() + .from(OWNER_ADDRESS) + .typed(scenario_tester_proxy::ScenarioTesterProxy) + .init(5u32) // deploys contract + .code(CODE_PATH) + .returns(ReturnsNewAddress) // returns newly deployed address as Address + .run(); + + assert_eq!(new_address, SC_TEST_ADDRESS.to_address()); +} +``` + + + +### `ReturnsNewTokenIdentifier` + +Returns: a newly issued token identifier, as String. It will search for it in logs. + +Usable in interactor environments. + +```rust title=interact.rs +async fn issue_token(action_id: usize) -> String { + self.interactor + .tx() + .from(&self.wallet_address) + .to(self.state.current_multisig_address()) + .gas(NumExpr("80,000,000")) + .typed(multisig_proxy::MultisigProxy) + .perform_action_endpoint(action_id) // endpoint that issues token + .returns(ReturnsNewTokenIdentifier) // newly issued token identifier returned as String + .prepare_async() + .run() + .await +} +``` + + + +### `ReturnsBackTransfers` + +Returns the back-transfers of the call, as a specialized structure, called `BackTransfers`. + +Usable in a smart contract environment. + +```rust title=contract.rs +#[endpoint] +fn forward_sync_retrieve_funds_bt( + &self, + to: ManagedAddress, + token: EgldOrEsdtTokenIdentifier, + token_nonce: u64, + amount: BigUint, +) { + let back_transfers = self + .tx() + .to(&to) + .typed(vault_proxy::VaultProxy) + .retrieve_funds(token, token_nonce, amount) + .returns(ReturnsBackTransfers) + .sync_call(); + + require!( + back_transfers.esdt_payments.len() == 1 || back_transfers.total_egld_amount != 0, + "Only one ESDT payment expected" + ); +} +``` + + +### `ReturnsHandledOrError` + +Returns the handled result from a SC call as a `Result`. Can be chained with other result handlers. + +Acting as a wrapper over other result handlers, checks the status of the transaction and returns `Some(HandledType)` or `Err(TxResponseStatus)`. Especially useful in external programs that integrate the interactor in their operations such as microservices. This result handler makes sure that the external program keeps running and opens the door for a more elegant error handling. + +It is usable both in the interactor and testing environments. + +In this example, `ReturnsHandledOrError` checks the status of the transaction. If the status is `success`, the other result handler, `ReturnsNewBech32Address`, will try to extract the newly deployed SC address from the transaction on the blockchain. If successful, the new address is returned as a `Bech32Address`. Otherwise, a `TxResponseStatus` struct is returned, containing the error and the message. + +```rust title=microservice.rs +impl ContractInteract { + pub async fn deploy_paint_harvest( + &mut self, + collection_token_id: String, + is_open: bool, + ) -> Result { + let paint_harvest_code = BytesValue::from(self.contract_code.paint_harvest); + + self.interactor + .tx() + .from(&self.wallet_address) + .gas(60_000_000u64) + .typed(PaintHarvestScProxy) + .init(TokenIdentifier::from(&collection_token_id), is_open) + .code(paint_harvest_code) + .code_metadata(CodeMetadata::UPGRADEABLE) + .returns(ReturnsHandledOrError::new().returns(ReturnsNewBech32Address)) + .run() + .await + } +} +``` + + +### `ExpectError` + +Indicates that the expected return type is error and does an assert on the actual return value. Usable in the testing and interactor environments. + +```rust title=blackbox_test.rs + self.world + .tx() // tx with testing environment + .from(BOARD_MEMBER_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .perform_action_endpoint(action_id) + .with_result(ExpectError(4, err_message)) // expects error return type + .run(); +``` + +In this example, we expect the returned value to be an error with error code 4 (user error) and a specific error message. If not true, the execution fails. + +However, because `ExpectError` only receives a status and a message, writing `ExpectError(0, "")` is equivalent to success (code 0 - ok, no error message). + + +### `ExpectValue` + +Indicates the expected return type and does an assert on the actual return value. Usable in the testing and interactor environments. + +```rust title=blackbox_test.rs + world + .query() + .to(ADDER_ADDRESS) + .typed(adder_proxy::AdderProxy) + .sum() + .returns(ExpectValue(5u32)) + .run(); +``` + + +### `ExpectMessage` + +Indicates that the expected return type is error and does an assert on the actual error message. Usable in the testing and interactor environments. + +```rust title=blackbox_test.rs + state + .world + .tx() + .from(USER_ADDRESS) + .to(TRANSFER_ROLE_FEATURES_ADDRESS) + .typed(transfer_role_proxy::TransferRoleFeaturesProxy) + .forward_payments(Address::zero(), "", MultiValueVec::>::new()) + .egld_or_single_esdt( + &EgldOrEsdtTokenIdentifier::esdt(TRANSFER_TOKEN), + 0u64, + &multiversx_sc::proxy_imports::BigUint::from(100u64), + ) + .with_result(ExpectMessage("Destination address not whitelisted")) + .run(); +``` + + +### `ExpectStatus` + +Indicates that the expected return type is u64 and does an assert on the actual transaction. Usable in the testing and interactor environments. + +```rust title=blackbox_test.rs + self.world + .tx() + .from(from) + .to(PRICE_AGGREGATOR_ADDRESS) + .typed(price_aggregator_proxy::PriceAggregatorProxy) + .submit( + EGLD_TICKER, + USD_TICKER, + submission_timestamp, + price, + DECIMALS, + ) + .with_result(ExpectStatus(4)) + .run(); +``` + +--- + +### rounds + +This page describes the structure of the `rounds` index (Elasticsearch), and also depicts a few examples of how to query it. + + +## _id + +The `_id` field of this index is composed in this way: `{shardID}_{round}` (example: `2_10905514`) + + +## Fields + + +| Field | Description | +|------------------|------------------------------------------------------------------------------------------------------------------------| +| round | The round field represents the number of the round. | +| signersIndexes | The signersIndexes field is an array that contains the indices of the validators that should sign the block from this round. | +| blockWasProposed | The blockWasProposed field is true if a block was proposed and executed in this round. | +| shardId | The shardId field represents the shard the round belongs to. | +| epoch | The epoch field represents the epoch the round belongs to. | +| timestamp | The timestamp field represents the timestamp of the round. | + + +## Query examples + + +### Fetch the latest rounds for a shard when block was produced + +``` +curl --request GET \ + --url ${ES_URL}/rounds/_search \ + --header 'Content-Type: application/json' \ + --data '{ + "query": { + "match": { + "shardId": 1 + } + }, + "sort": [ + { + "timestamp": { + "order": "desc" + } + } + ], + "size":10 +}' +``` + +--- + +### Run transactions + +## Overview + +As discussed previously, the transaction syntax is consistent through the various transaction environments. However, when sending the transaction across a specific environment, certain conditions apply, depending on the framework's capability of processing the information and the route of the transaction. + +:::note +The transaction itself is not different and will produce the same result, but the way the framework processes the transaction might differ depending on the environment. +::: + + +## Smart contract + +From the smart contract point of view, the transaction ends when specifying the transaction type (sync/async call). + + +### `async_call_and_exit` + +Executes the transaction asynchronously and exits after execution. + +```rust title=contract.rs + self.tx() // tx with sc environment + .to(&marketplace_address) + .typed(nft_marketplace_proxy::NftMarketplaceProxy) + .claim_tokens(token_id, token_nonce, caller) + .async_call_and_exit(); // async call and stop execution +``` + +In this case, the function `async_call_and_exit` marks the end of the transaction and executes it asynchronously. After the transaction is executed, the `never` type is returned, marking the end of the execution. + + +### `sync_call` + +Sends a transaction synchronously. + +```rust title=contract.rs + self + .tx() // tx with sc environment + .to(&to) + .typed(vault_proxy::VaultProxy) + .retrieve_funds(token, token_nonce, amount) + .sync_call(); // synchronous call +``` + + +### `upgrade_async_call_and_exit` + +Upgrades contract asynchronously and exits after execution. + +```rust title=contract.rs + self.tx() // tx with sc environment + .to(child_sc_address) + .typed(vault_proxy::VaultProxy) + .upgrade(opt_arg) // calling the upgrade function + .from_source(source_address) + .upgrade_async_call_and_exit(); // upgrades async and exits +``` + + +### `sync_call_same_context` + +Executes the transaction synchronously on the same context (in the name of the caller). + +```rust title=contract.rs + self + .tx() // tx with sc environment + .to(&to) + .raw_call(endpoint_name) + .arguments_raw(args.to_arg_buffer()) + .sync_call_same_context(); // sync call in the same context +``` + + +### `sync_call_readonly` + +Executes the transaction synchronously, in readonly mode (target contract cannot have its state altered). + +```rust title=contract.rs + let result = self + .tx() // tx with sc environment + .to(&to) + .raw_call(endpoint_name) + .arguments_raw(args.to_arg_buffer()) + .returns(ReturnsRawResult) // result handler - returns raw result data + .sync_call_readonly(); // sync call in readonly mode +``` + +### `transfer_execute` + +Sends transaction asynchronously, and doesn't wait for callback. + +```rust title=contract.rs + self + .tx() + .to(&caller) + .gas(50_000_000u64) + .raw_call(func_name) + .single_esdt(&token, 0u64, &amount) + .transfer_execute(); +``` + + +### `transfer` + +Same as `transfer_execute`, but only allowed for simple transfers. + +```rust title=contract.rs + self + .tx() + .to(&caller_address) + .egld(&collected_fees) + .transfer(); +``` + + + +### `async_call`, `async_call_promise` + +Backwards compatibility only. + +:::important +For the moment, the functions `async_call` and `async_call_promise` exist for backwards compatibility reasons only. These functions do `NOT` execute a transaction, they just return the current object state. Delete them from existing codebases. +::: + + + +## Integration test + +For the Rust testing environment, the only keyword for sending transactions is `run`. Inside an integration test, a developer can build a transaction, choose the return type and then `run` it, marking the end of the transaction and the start of the execution. + +```rust title=blackbox_test.rs + world // ScenarioWorld instance + .query() // query with test environment + .to(ADDER_ADDRESS) + .typed(adder_proxy::AdderProxy) + .sum() + .returns(ExpectValue(6u32)) // result handler - assert return value + .run(); // runs the query step +``` + +In this case, regarding of the type of the transaction (raw call, deploy, upgrade, query), it eventually turns into a scenario `Step` (`ScQueryStep`, `ScCallStep`, `ScDeployStep`) and it is processed as such. + + + +## Interactor + + +### Async Rust + +In the case of the interactor, the processing is similar to the integration test, with the exception that the interactor is using async Rust. First, the transaction is built, then it is turned into a `Step` using the `prepare_async` function, then we can `run` it and `await` the result. + +```rust title=interact.rs + self.interactor // Interactor instance + .tx() // tx with interactor exec environment + .from(&self.wallet_address) + .to(self.state.current_multisig_address()) + .gas(NumExpr("30,000,000")) + .typed(multisig_proxy::MultisigProxy) + .dns_register(dns_address, name) + .prepare_async() // converts tx into step + .run() // runs the step + .await; // awaits the result - async Rust +``` + + +### Sync Rust + +We also have a plan for adding support for a blocking interactor API, but this is currently not available. + + + + +## Feature table + +This table shows what transaction fields are mandatory, optional, or disallowed, in order to run a transaction. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
EnvironmentRun methodFromToPaymentGasDataResult Handler
SC: callasync_call_and_exitFC or ()callback only
SC: callregister_promiseFCcallbacks only, with gas for callback
SC: calltransfer_executeFC or ()
SC: calltransfer()
SC: callsync_call🟡FC
SC: callsync_call_same_context🟡FC
SC: callsync_call_readonly🟡FC
SC: deploysync_callimg🟡deploy
SC: upgradeupgrade_async_call_and_exitimg🟡upgradecallback only
Test: tx callrun🟡FC or ()
Test: tx deployrunimg🟡deploy
Test: queryrunFC
Interactor: tx callrun🟡FC or ()
Interactor: tx deployrunimg🟡deploy
Interactor: queryrunFC
+ +Legend: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SymbolMeaning
Mandatory, any allowed value type
Not allowed
🟡Optional
imgEGLD only
FCFunction Call
+ +--- + +### Running scenarios + +Most of the MultiversX smart contract testing infrastructure is built with scenarios in mind, so there are lots of ways to execute them. + +```mermaid +graph TD + json["JSON Scenario + *.scen.json"] + json --> test-go-tool["run-scenarios"] --> vm-go["⚙️ Go VM"] + json --> test-go["Generated + *_scenario_go_test.rs"] --> vm-go + json --> test-rs["Generated + *_scenario_rs_test.rs"] --> vm-rust["⚙️ Rust VM (Debugger)"] +``` + +Assume we have a scenario JSON file. The options for running it are: +- using the `run-scenarios` standalone tool +- generating some tests in a Rust project and running the Rust tests. + + +## Standalone tool + +The only standalone tool for running scenarios is `run-scenarios`, part of the VM tooling. + +The binary is build from [here](https://github.com/multiversx/mx-chain-vm-go/blob/master/cmd/scenariostest/scenariosTest.go). +Most of the code lies [here](https://github.com/multiversx/mx-chain-vm-go/tree/master/scenarioexec), if you're curious. + +To call, simply run `run-scenarios `, where the path can be either a speciific scenario file, or a folder containing scenarios. In the case of a folder, the tool will run all files ending in `*.scen.json`. Results are printed to console. + + +## Integration in Rust + +We normally want to integrate scenario tests in the CI. For this, at the very least we should write Rust tests that run the scenarios. + +We have decided to have a separate Rust test for each scenario file. This way, when running a full test suite, it is easy to see exactly what test has failed, at a glance: + +![console screenshot](/developers/testing/scenario-json-rust-console.png "Example of console with failed scenario tests") + +We also want to have a set of such tests for each of the backends: Go and Rust. + + +### Standard project layout + +The standard for organising tests in a Rust crate is a follows: + +``` +├── Cargo.toml +├── meta +├── multiversx.json +├── output +├── scenarios +│ ├── scenario1.scen.json +│ └── scenario2.scen.json +├── src +│ └── my_contract.rs +├── tests +│ ├── my_contract_scenario_go_test.rs +│ ├── my_contract_scenario_rs_test.rs +│ ├── other_tests.rs +│ └── more_tests.rs +└── wasm +``` + +At a minimum, the project needs to have `src`, `meta`, and `wasm` folders. + +Integration tests go into the `tests` folder. Scenario files, if present, should reside in a folder named `scenarios`. It is fine to also have sub-folders under `scenarios`, if needed. + +The Rust tests should go into 2 files ending in `*scenario_go_test.rs` and `*scenario_rs_test.rs`, respectively. It is also acceptable to simply use file names `scenario_go_test.rs` and `scenario_rs_test.rs` directly, with no prefix, but having a prefix can help developers orient themselves easier in projects with many contracts. + + +### Go backend + +The `*scenario_go_file.rs` should look like this: + +```rust title="adder/tests/my_contract_scenario_go_test.rs" +use multiversx_sc_scenario::*; + +fn world() -> ScenarioWorld { + ScenarioWorld::vm_go() +} + +#[test] +fn adder_go() { + world().run("scenarios/adder.scen.json"); +} + +#[test] +#[ignore = "reason to ignore"] +fn ignored_test_go() { + world().run("scenarios/ignored_test.scen.json"); +} +``` + +The `world()` function sets up the environment, which in this case is very straightforward. + +:::caution +Do not comment out tests! + +Use the `#[ignore]` annotation instead (also writing a reason is nice, but optional). + +The code generator for these files has trouble dealing with commented tests. + +It also helps that ignored tests will also show up in console, unlike the ones that are commented out. +::: + +It is customary to add a `_go` suffix to the test functions, to distinguish them from the ones with the Rust backend. The test-gen tool does the same. + + +### Rust backend + +The `*scenario_rs_file.rs` needs more setup to the environment, but other than that it looks the same: + +```rust title="adder/tests/my_contract_scenario_rs_test.rs" +use multiversx_sc_scenario::*; + +fn world() -> ScenarioWorld { + let mut blockchain = ScenarioWorld::new(); + blockchain.register_contract("file:output/adder.wasm", adder::ContractBuilder); + blockchain +} + +#[test] +#[ignore] +fn adder_rs() { + world().run("scenarios/adder.scen.json"); +} + +#[test] +fn interactor_trace_rs() { + world().run("scenarios/interactor_trace.scen.json"); +} +``` + +Note that we can have different tests ignored on the different backends. + +Here it is also customary to add a `_rs` suffix to the test functions, to distinguish them from the ones on the Go backend. The test-gen tool does the same. + + +### Rust backend environment minimal setup + +The example above is a great example of a minimal setup. Other than creating the `world: ScenarioWorld` object, this is the line of interest: + +```rust +blockchain.register_contract("file:", ::ContractBuilder); +``` + +The Rust backend doesn't run compiled contracts, instead, it hooks the actual Rust contract code to its engine. This is where we tell the framework how to do that. +The interpretation of this is: +- whenever the framework is asked to deploy or run a contract whose code would normally lie on disk at `` ... +- it should run the code, a prepared by `::ContractBuilder`. + +The path to binary is given as a scenario value expression. [The file syntax](/developers/testing/scenario/values-simple#file-contents) in the example is simply the most common way of loading a large value from file. It is also possible to provide the compiled contract as bytes, (e.g. `"0x0061736d0100000001661160000060017..."`), but hard-coding that is weird. + +The `ContractBuilder` object is generated automatically for every contract, by the `#[multiversx_sc::contract]` procedural macro. That is why you won't see it in code, but it's always there. + + +### Auto-generating the boilerplate + +If you thought "writing these test functions manually is tedious and repetitive", you would be correct. It is also error prone, since it is easy to accidentally leave scenarios out. + +That is why we've build the [`sc-meta test-gen`](/developers/meta/sc-meta-cli#calling-test-gen) tool to help automate this task. + +The tool works as follows: +- If no `*scenario_go_file.rs` or `*scenario_rs_file.rs` are found, they can be created anew, but only if the `--create` flag is passed to the tool. +- The Go VM tests can be fully generated, but the Rust VM environment setup (the `world()` function) cannot be generated automatically. When creating it for the first time, you will get a stub of this function, and will have to fill in the implementation manually. +- If the `scenarios` folder is missing, the tool won't do anything. +- You will always get a test function for each scenario in the `scenarios` folder. +- The tool can be called any number of times, it is easy to update these tests whenever scenarios change. New scenario tests will be added and missing tests will be removed. +- The tool always preserves: + - `#[ignore]` and `#[ignore = "reason"]` annotations; + - Test comments; + - Any additional code that may be written before the tests (not just the `world()` function). + +The test tool can handle multiple contract crates at once. In fact, it will try to update tests for all contracts it can find under a given folder. + +For reference, the tool parameters are: +- `--path` + - Target directory where to call all contract meta crates. + - _default_: current directory. +- `--ignore` + - Ignore all directories with these names. + - _default_: `target`. +- `--create` + - Creates test files if they don't exist. + +--- + +### Rust SDK + +## Rust Interactors + +The Rust SDK for interacting with the blockchain comes in the form of the so-called [**Rust Interactors**](/developers/meta/interactor/interactors-overview). + +Since they use very similar syntax to smart contracts and smart contract tests, their documentation is grouped under the Rust Development Framework section. + + + +## Quick tutorial + +You can also find a quick tutorial [here](/developers/tutorials/interactors-guide). + +--- + +### Rust Version + +## Required Rust version + +Starting with framework version [v0.50.0](https://crates.io/crates/multiversx-sc/0.50.0), MultiversX smart contracts can be built using stable Rust. + +Before this version, nightly Rust was required. + + +## Recommended compiler versions + +For everything after v0.50.0 we recommend running the latest stable version of Rust. Older versions have had compatibility issues with certain framework dependencies, on certain versions of the compiler. + +Also, everything on versions older than v0.50.0 needs to run on nightly Rust. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Application VersionRequired Rust ChannelVersion Requirements
Prior to `v0.50`Nightly`nightly-2023-12-11` or `nightly-2024-05-22`
`v0.50` to `v0.56`**Stable (recommended)** or Nightly≥`1.78` and ≤`1.86`
`v0.57`**Stable (recommended)** or Nightly≥`1.83` and ≤`1.86`
`v0.58` and Higher**Stable (recommended)** or Nightly≥`1.83`*
+ +\* Starting with Rust version `1.89` and higher, there are known runtime issues when using **wasmer 6.0** (`wasmer-experimental`) exclusively on the **Linux platform**. + +:::note +If you are using **wasmer 6.0** on Linux, we recommend pinning your Rust version below `1.89`. +::: + + +## Why Nightly for the older versions? + +There were several nightly features that the framework was using, which we had hoped to see stabilized sooner. + +These are of little relevance to the average developer, but for the record, let's mention a few of them and how we managed to circumvent their usage: + +- `never_type` - avoided by using slightly different syntax; +- `auto_traits` and `negative_impls` - avoided by redesigning the `CodecFrom`/`TypeAbiFrom` trait systems; +- `generic_const_exprs` - replaced with massive amounts of macros; +- `panic_info_message` - replaced by a different method to retrieve the panic message. + +If any of these get stabilized in the future, we might revert the changes enacted in v0.50.0. + +It is in any case our commitment to keep the framework compatible with stable Rust from here on, no matter what. + +--- + +### SC to SC Calls + +This guide provides an overview of the different types of smart contract calls that originate from other smart contract calls. + + +## Introduction + +Smart contract calls on MultiversX fall into two main categories: synchronous (`sync`) and asynchronous (`async`), each with distinct usage scenarios based on developer needs, and dApp architecture. + + + +## Overview + + +### Sync calls vs. async calls + + +A `sync` call is similar to regular function call in a program: it relies on a call stack, the current execution is paused, the call is executed immediately, and execution of the caller function resumes immediately after. + +An `async` call is similar to an asynchronous function call in a program, just like launching it on a different thread. The async call is not added to the same stack and does not interrupt the execution of the caller function. + +The main differences between the two are in this table: + +| `sync` calls | `async` calls | +| ------------ | ------------- | +| Are executed immediately, inline. | Are executed after the current transaction is completed. | +| Only work in the same shard. | Work both in-shard and cross-shard. | +| Function results are available immediately. | Function results are only available later, in the callback, if a callback exists. | +| A callee crash causes caller to immediately crash as well. | A callee crash does not cause caller to crash, the error can be caught in the callback, if it exists. | +| Reentrancy issues possible. | Reentrancy not possible. | + + + +### Transfer-execute calls + +Transfer-execute calls are basically async calls without callback. You can think of them as a "fire and forget" mechanism. + +They come in two flavors: +- **Transfer-only** - they are used to move EGLD or ESDT balance, and nothing else. Balance transfers can rarely fail, so they are very convenient to use. +- **Transfer-and-execute** - used when the caller does not care what the result of the callee function is. + +They were very important in async v1, because they were the only mechanism to have more than one call leaving a transaction. + + +### Error recovery + +Error recovery is not possible with sync calls, so sometimes contracts might choose to perform async calls to allow themselves to recover from errors, even if the target contract is in the same shard. + +This also means that no error handling is necessary (or effective) around sync calls. + + +### Reentrancy + +Reentrancy is very unlikely on a MultiversX blockchain, but not impossible, so let's dedicate a chapter to it. + +There are 2 main elements that make it less of an issue than on other blockchain architectures: +1. Native tokens are built into the system, so they cannot become malicious. Most reentrancy attacks are performed via malicious tokens that exploit intermediate states in the middle of function execution. +2. Reentrancy can only be performed in sync calls. Async calls do not interrupt the execution, so they are not vectors for an attack. + +Even so, to avoid vulnerabilities it is best to perform sync calls either at the very beginning, or at the very end of a SC endpoint execution. + +A similar problem to reentrancy is the management of the intermediate state between an async call and the processing of its callback. Great care must be taken, to ensure the state of the contract is not vulnerable to attacks in this interval. + + + +### Async callbacks + +Async calls can optionally register callbacks. The callbacks are called by the system, irrespective of whether the caller completed successfully or failed. + +The callback receives the following inputs: +- The call result, which contains several values: + - In case of success: + - The status code, 0 in this case, in the first position. + - One argument for each result returned by the callee, after that. + - In case of error: + - The status code, will be different from 0. For example, error code "4" indicates a failure in the smart contract execution. It is also in the first position. + - One argument, containing the error message as string. + - Note: it is customary to use type `ManagedAsyncCallResult` since it knows how to conveniently decode this structure. +- The callback closure: + - There might be multiple async calls involved in a smart contract interaction, it can sometimes be hard to figure out which callback came from which call. That is why it is almost always the case that some information needs to be passed directly from the call site to the callback. + - This is done by adding arguments + +:::note +Async call functions do not return values, but may include a `callback` function to handle the response from the destination contract. This is because the results are never available immediately. +::: + +This is an example of a callback function that gets triggered after an `issue_fungible` action: + +```rust + #[callback] + fn esdt_issue_callback( + &self, + caller: &ManagedAddress, + #[call_result] result: ManagedAsyncCallResult<()>, + ) { + let (token_identifier, returned_tokens) = + self.call_value().egld_or_single_fungible_esdt(); + // callback is called with ESDTTransfer of the newly issued token, with the amount requested, + // so we can get the token identifier and amount from the call data + match result { + ManagedAsyncCallResult::Ok(()) => { + self.last_issued_token().set(token_identifier.unwrap_esdt()); + self.last_error_message().clear(); + }, + ManagedAsyncCallResult::Err(message) => { + // return issue cost to the caller + if token_identifier.is_egld() && returned_tokens > 0 { + self.tx().to(caller).egld(&returned_tokens).transfer(); + } + + self.last_error_message().set(&message.err_msg); + }, + } + } +``` + +And this is how it gets called: + +```rust + self.send() + .esdt_system_sc_proxy() + .issue_fungible(/* ... arguments ... */) + .with_callback(self.callbacks().esdt_issue_callback(&caller)) + .async_call_and_exit() +``` + +Notice how the caller gets passed from the call site directly to the callback. + +Also notice how the tokens transferred back to the caller are available in the callback, as _call value_. + + + + +### All call types + +Each of these calls further divide in multiple categories, depending on the mechanism they use, different developer use-cases and expected results. + +- Sync calls + - Sync call + - Sync call same context + - Sync call readonly + - Deploy call +- Async calls + - Async call (V1) + - Register promise (V2) + - Transfer execute + - Upgrade call + +We will now explain each of them in greater depth, and provide some syntax examples. + +For the full syntax specification, visit the [unified transaction syntax](/developers/transactions/tx-overview) documentation. + + + +## Sync calls + + + +### Standard sync call + +A standard sync call performs a direct, synchronous transaction to a contract on the same shard. This type of call is launched with `.sync_call()`. + +```rust + /// Executes transaction synchronously. + /// + /// Only works with contracts from the same shard. + pub fn sync_call(self) -> ::Unpacked +``` + +In this example, we are building a `sync call` to a `destination` smart contract address using the adder contract's proxy: + +```rust title=adder.rs + #[endpoint] + fn sync(&self, destination: ManagedAddress, value: BigUint) { + self.tx() + .to(destination) + .typed(adder_proxy::AdderProxy) + .add(value) + .sync_call(); + } +``` + + + +### Sync call, same context + +This call operates in the same execution context as the source contract. This means that the callee code is executed over the caller's storage and context, so it's just like calling third-party code to deal with your storage. + +It's essential that the code called in such a way is _trusted_, since we are granting it direct access to our entire storage. + +It can be useful for having library-like smart contracts or plug-in systems. It is currently not used often. + +To perform this type of call, use `.sync_call_same_context()`. + +```rust + /// Executes transaction synchronously, in the same context (performed in the name of the caller). + /// + /// Only works with contracts from the same shard. + pub fn sync_call_same_context(self) -> ::Unpacked +``` + +In this example, we are building a `sync call` using the `same execution context` to a `destination` smart contract address using the adder contract's proxy: + +```rust title=adder.rs + #[endpoint] + fn sync_same_context(&self, destination: ManagedAddress, value: BigUint) { + self.tx() + .to(destination) + .typed(adder_proxy::AdderProxy) + .add(value) + .sync_call_same_context(); + } +``` + + + +### Sync call, readonly + +This type of call performs a synchronous call in `readonly` mode, meaning the destination contract's state cannot be altered by this action. This type of call is performed with `.sync_call_readonly()`. + +```rust + /// Executes transaction synchronously, in readonly mode (target contract cannot have its state altered). + /// + /// Only works with contracts from the same shard. + pub fn sync_call_readonly(self) -> ::Unpacked +``` + +In this example, we are building a `sync call` in `readonly mode` to a `destination` smart contract address using the adder contract's proxy: + +```rust title=adder.rs + #[endpoint] + fn sync_readonly(&self, destination: ManagedAddress, _value: BigUint) { + self.tx() + .to(destination) + .typed(adder_proxy::AdderProxy) + .sum() + .sync_call_readonly(); + } +``` + + + +### Contract Deploy + +On MultiversX contracts can currently only be deployed in the same shard as their deployer. The new address will always be generated in such a way that it always lands in the same shard, no matter the shard configuration. + +It therefore makes sense that deploy calls are always synchronous. + +During the deploy, the constructor of the new contract , `init`, is always called. All contracts must have this endpoint. + +There are 2 types of deploy call: +- Deploy with explicit byte code (provided explicitly by the caller contract); +- Deploy from source (using bytecode from an existing source address). This is usually cheaper, since byte codes can be large, and processing or storing this code can incur significant gas costs. + + +Both of these calls are executed using `.sync_call()`, but the transaction setup differs for each type. + +For a simple `raw deploy`, initiate a raw deploy transaction using the `.raw_deploy()` function, as shown below: + +```rust title=adder.rs + #[endpoint] + fn raw_deploy(&self, code: ManagedBuffer) -> ManagedAddress { + self.tx() + .raw_deploy() + .code(code) + .code_metadata(CodeMetadata::UPGRADEABLE) + .returns(ReturnsNewManagedAddress) + .sync_call() + } +``` + +In the example above, only `.code()` is mandatory for a deploy sync call. We need to either pass the code to the transaction as an argument, or to receive it from a specified location on the blockchain (from source). + +In the case of a `deploy from source` transaction, we would use the specific function `.from_source()` instead of `.code()` and pass the source address as a parameter, as such: + +```rust title=adder.rs + #[endpoint] + fn raw_deploy_from_source(&self, source: ManagedAddress) -> ManagedAddress { + self.tx() + .raw_deploy() + .from_source(source) + .code_metadata(CodeMetadata::UPGRADEABLE) + .returns(ReturnsNewManagedAddress) + .sync_call() + } +``` + + + +## Async calls + + + + +### Async call (V1) + +The most common type of async call. This type of call can be executed with `.async_call_and_exit()`. + +:::important +Async call uses the `async V1` mechanism. +::: + +```rust +pub fn async_call_and_exit(self) -> ! +``` + +This type of call always terminates the current transaction immediately. Any code coming after it will not be executed. + +It is therefore only possible to have **one** such call per transaction. + +In this example, we are building an `async V1 call` to a `destination` smart contract address using the adder contract's proxy: + +```rust title=adder.rs + #[endpoint] + fn async_call(&self, destination: ManagedAddress, value: BigUint) { + self.tx() + .to(destination) + .typed(adder_proxy::AdderProxy) + .add(value) + .async_call_and_exit(); + } +``` + + + +### Register promise (V2) + +Register promise performs an asynchronous promise call and allows multiple calls as such in a single transaction. To perform this type of call, use `.register_promise()`. + +:::important +Register promise uses the `async V2` mechanism. +::: + +```rust + /// Launches a transaction as an asynchronous promise (async v2 mechanism). + /// + /// Several such transactions can be launched from a single transaction. + /// + /// Must set: + /// - to + /// - gas + /// - a function call, ideally via a proxy. + /// + /// Value-only promises are not supported. + /// + /// Optionally, can add: + /// - any payment + /// - a promise callback, which also needs explicit gas for callback. + pub fn register_promise(self) +``` + +Unlike the old async call, it is possible to have more than one `register_promise` call in a transaction. Execution is not terminated. + +In this example, we are building an `async V2 call` to a `destination` smart contract address using the adder contract's proxy: + +```rust title=adder.rs + #[endpoint] + fn register_promise(&self, destination: ManagedAddress, value: BigUint) { + self.tx() + .to(destination) + .gas(30_000_000u64) + .typed(adder_proxy::AdderProxy) + .add(value) + .register_promise(); + } +``` + +Just like the old async call, promises allow callbacks. + +:::important +Promises callbacks must be annotated with `#[promises_callback]` instead of `#[callback]`. +::: + + + +### Transfer execute + +This call executes a transaction asynchronously without waiting for a callback. In order to perform this type of call use `.transfer_execute()`. + +```rust + /// Sends transaction asynchronously, and doesn't wait for callback ("fire and forget".) + pub fn transfer_execute(self) +``` + +In this example, we are building an async call that **does not wait for a callback** (fire and forget) to a `destination` smart contract address using the adder contract's proxy: + +```rust title=adder.rs + #[endpoint] + fn transfer_execute(&self, destination: ManagedAddress, value: BigUint) { + self.tx() + .to(destination) + .gas(30_000_000u64) + .typed(adder_proxy::AdderProxy) + .add(value) + .transfer_execute(); + } +``` + + + +### Upgrade call + +If a smart contract is marked as **upgradeable**, its owner is allowed to upgrade the smart contract code to a newer version. + +The upgrade call changes the code and causes the special endpoint `upgrade` to be called, analogous to how a deploy will call the `init` constructor. + +Unlike deploy calls, it is possible to upgrade a contract from another shard. This is because, even though the original owner deployer will always be in the same shard as the contract, contract ownership can be transferred. + +Similar to deploy calls, there are two types of expressing upgrade calls: +- Upgrade with explicit byte code (provided explicitly by the caller contract); +- Upgrade from source (using bytecode from an existing source address). This is usually cheaper, since byte codes can be large, and processing or storing this code can incur significant gas costs. + +Since the upgrade call is an async call (v1), it also terminates execution immediately. It also accepts a callback. + +```rust + /// Launches the upgrade from source async call. + pub fn upgrade_async_call_and_exit(self) +``` + +Syntax-wise, both of these calls are executed using `.upgrade_async_call_and_exit()`, but the transaction setup differs for each type. + +For a simple `raw upgrade` transaction, we could write: +```rust title=adder.rs + #[endpoint] + fn raw_upgrade(&self, address: ManagedAddress, code: ManagedBuffer) { + self.tx() + .to(address) + .raw_upgrade() + .code(code) + .code_metadata(CodeMetadata::UPGRADEABLE) + .upgrade_async_call_and_exit(); + } +``` + +Similar to deploy calls, `.code()` is mandatory. We must either pass the code to the transaction, or to receive it from a specified location on the blockchain (from source). + +In order to change this call into an `upgrade from source` call, replace the provided code with the source address, using `.from_source()`: + +```rust title=adder.rs + #[endpoint] + fn raw_upgrade_from_source(&self, address: ManagedAddress, source: ManagedAddress) { + self.tx() + .to(address) + .raw_upgrade() + .from_source(source) + .code_metadata(CodeMetadata::UPGRADEABLE) + .upgrade_async_call_and_exit(); + } +``` + +--- + +### scdeploys + +This page describes the structure of the `sc-deploys` index (Elasticsearch), and also depicts a few examples of how to query it. + + +## _id + +The `_id` field of this index is represented by a bech32 encoded smart contract address. + + +## Fields + + +| Field | Description | +|---------------|-----------------------------------------------------------------------------------------------------| +| deployTxHash | The deployTxHash holds the hex encoded hash of the transaction that deployed the smart contract. | +| deployer | The address field holds the address in bech32 encoding of the smart contract deployer. | +| timestamp | The timestamp field represents the timestamp of the block in which the smart contract was deployed. | +| upgrades | The upgrades field holds a list with details about the upgrades of the smart contract. | + +The `upgrades` field is populated with the fields below: + + +| upgrades fields | Description | +|-----------------|--------------------------------------------------------------------------------------------------------| +| upgrader | The upgrader field holds the bech32 encoded address of the sender of the contract upgrade transaction. | +| upgradeTxHash | The upgradeTxHash field holds the hex encoded hash of the contract upgrade transaction. | +| timestamp | The timestamp field represents the timestamp of the block in which the smart contract was upgraded. | + + +## Query examples + + +### Fetch details about a smart contract + +``` +curl --request GET \ + --url ${ES_URL}/scdeploys/_search \ + --header 'Content-Type: application/json' \ + --data '{ + "query": { + "match": { + "_id":"erd..." + } + } +}' +``` + +--- + +### Scenario Complex Values + +We already covered representations of simple types [here](/developers/testing/scenario/values-simple). This is enough for arguments of types like `usize`, `BigUint` or `&[u8]`, but we need to also somehow specify complex types like custom structs or lists of items. + + +## **Concatenation** + +It is possible to concatenate multiple expressions using the pipe operator (`|`). The pipe operator takes precedence over everything, so it is not currently possible to concatenate and then apply a function to the whole result. + +This is ideal for short lists or small structs. + +:::note Example + +- a `Vec` can be expressed as `"u32:1|u32:2|u32:3"`. +- a `(BigUint, BigUint)` tuple can be expressed as `"biguint:1|biguint:2"` +- a `SimpleStruct { a: u8, b: BoxedBytes }` can be expressed as `"u8:4|nested:str:value-b"` + ::: + +Please note that the pipe operator only takes care of the concatenation itself. You are responsible for making sure that [nested encoding](/developers/data/serialization-overview/#the-concept-of-top-level-vs-nested-objects) is used where appropriate. + + +## **Using JSON lists as values** + +Scenarios allow using JSON lists to express longer values. This especially makes sense when the value being represented is itself a list in the smart contract. + +:::note Example + +- a `Vec` can also be expressed as `["u32:1", "u32:2", "u32:3"]`. +- a `(BigUint, BigUint)` tuple can also be expressed as `["biguint:1", "biguint:2"]` +- a `SimpleStruct { a: u8, b: BoxedBytes }` can also be expressed as `["u8:4", "nested:str:value-b"]`, although in this case a [JSON map](#using-json-maps-as-values) might be more appropriate. + ::: + +Make sure not to confuse values expressed as JSON lists with other elements of scenario syntax. + +:::note Example + +```json +{ + "step": "scCall", + "txId": "echo_managed_vec_of_managed_vec", + "tx": { + "from": "address:an_account", + "to": "sc:basic-features", + "value": "0", + "function": "echo_managed_vec_of_managed_vec", + "arguments": [ + [ + "u32:3", + [ + "u32:1", + "u32:2", + "u32:3" + ], + "u32:0", + "u32:2", + [ + "u32:5", + "u32:6" + ] + ] + ], + "gasLimit": "50,000,000", + "gasPrice": "0" + } +} +``` + +In the example above, there is in fact a single argument that we are passing to the endpoint. The outer brackets in `"arguments": [ ... ]` are scenario syntax for the list of arguments. The brackets immediately nested signal a JSON list value. Notice how the list itself contains some more lists inside it. They all get concatenated in the end into a single value. + +In this example the only argument is `0x0000000300000001000000020000000300000000000000020000000500000006`. + +::: + +:::tip + +We mentioned above how the developer needs to take care of the serialization of the nested items. This is actually a good example of that. The endpoint `echo_managed_vec_of_managed_vec` takes a list of lists, so we need to serialize the lengths of the lists on the second level. Notice how the lengths are given as JSON strings and the contents as JSON lists; the first `"u32:3"` is the serialized length of the first item, which is `["u32:1", "u32:2", "u32:3"]`, and so forth. + +::: + + +## **Using JSON maps as values** + +JSON lists make sense for representing series of items, but for structs JSON maps are more expressive. + +The rules are as follows: + +- The interpreter will concatenate all JSON map values and leave the keys out. +- The keys need to be in alphanumerical order, so we customarily prefix them with numbers. Map keys in JSON are fundamentally unordered and this is the easiest way to enforce a deterministic order for the values. +- Map values can be either JSON strings, lists or other maps, all scenario value rules apply the same way all the way down. + +:::note Example + +This is an abridged section of the actual lottery contract in the examples. + +```json +{ + "step": "checkState", + "accounts": { + "sc:lottery": { + "storage": { + "str:lotteryInfo|nested:str:lottery_name": { + "0-token_identifier": "nested:str:LOTTERY-123456", + "1-ticket_price": "biguint:100", + "2-tickets-left": "u32:0", + "3-deadline": "u64:123,456", + "4-max_entries_per_user": "u32:1", + "5-prize_distribution": ["u32:2", "u8:75", "u8:25"], + "6-whitelist": [ + "u32:3", + "address:acc1", + "address:acc2", + "address:acc3" + ], + "7-prize_pool": "biguint:500" + } + }, + "code": "file:../output/lottery-esdt.wasm" + } + } +} +``` + +The Rust struct this translates to is: + +```rust +#[derive(NestedEncode, NestedDecode, TopEncode, TopDecode, TypeAbi)] +pub struct LotteryInfo { + pub token_identifier: TokenIdentifier, + pub ticket_price: BigUint, + pub tickets_left: u32, + pub deadline: u64, + pub max_entries_per_user: u32, + pub prize_distribution: Vec, + pub whitelist: Vec
, + pub prize_pool: BigUint, +} + +``` + +::: + +:::tip + +Once again, note that all contained values are in [nested encoding format](/developers/data/serialization-overview/#the-concept-of-top-level-vs-nested-objects): + +- the token identifier has its length automatically prepended by the `nested:` prefix, +- big ints are given with the `biguint:` syntax, which prepends their byte length, +- small ints are given in full length, +- lists have their length explicitly encoded at the start, always on 4 bytes (as `u32`). + +::: + + +## **A note about enums** + +There are 2 types of enums that we use in Rust: + +- simple enums are simply encoded as `u8` +- complex enums are encoded just like structures, with an added `u8` discriminant at the beginning. + +Discriminants are not explicit in the Rust code, they get generated automatically. Discriminats start from `0`. + +:::note Example + +This is an abridged section of a Multisig contract test. + +```json +{ + "step": "checkState", + "accounts": { + "sc:multisig": { + "storage": { + "str:action_data.item|u32:3": { + "1-discriminant": "0x06", + "2-amount": "u32:0", + "3-code": "nested:file:../test-contracts/adder.wasm", + "4-code_metadata": "0x0000", + "5-arguments": ["u32:1", "u32:2|1234"] + }, + "+": "" + }, + "code": "file:../output/multisig.wasm" + }, + "+": "" + } +} +``` + +In this example we have a `SCDeploy`: + +```rust +#[derive(NestedEncode, NestedDecode, TopEncode, TopDecode, TypeAbi)] +pub enum Action { + Nothing, + AddBoardMember(ManagedAddress), + AddProposer(ManagedAddress), + RemoveUser(ManagedAddress), + ChangeQuorum(usize), + SendEgld { + to: ManagedAddress, + amount: BigUint, + data: BoxedBytes, + }, + SCDeploy { + amount: BigUint, + code: ManagedBuffer, + code_metadata: CodeMetadata, + arguments: Vec, + }, + SCCall { + to: ManagedAddress, + egld_payment: BigUint, + endpoint_name: BoxedBytes, + arguments: Vec, + }, +} +``` + +::: + +--- + +### Scenario Simple Values + +We went through the structure of a scenario, and you might have noticed that in a lot of places values are expressed in diverse ways. + +The VM imposes very few restrictions on its inputs and outputs, most fields are processed as raw bytes. The most straightforward way to write a test that one could think of would be to have the actual raw bytes always expressed in a simple format (e.g. like hexadecimal encoding). Indeed, our first contract tests were like this, but we soon discovered that it took painfully long prepare them and even longer to refactor. So, we gradually came up with increasingly complex formats to represent values in an intuitive human-readable way. + +We chose to create a single universal format to be used everywhere in a scenario file. The same format is used for expressing: + +- addresses, +- balances, +- transaction and block nonces, +- contract code, +- storage keys and values, +- log identifiers, topics and data, +- gas limits, gas costs, +- ESDT metadata, etc. + +The advantage of this unique value format is that it is enough to understand it once to then use it everywhere. + +The scenario value format is closely related to the [MultiversX serialization format](/developers/data/serialization-overview). This is not by accident, Scenarios are designed to make it easy to interact MultiversX contracts and their data. + +Exceptions: `txId`, `comment` and `asyncCallData` are simple strings. `asyncCallData` might be changed to the default value format in the future and/or reworked. + +:::important + +It must be emphasized that no matter how values are expressed in scenarios, the communication with the VM is always done via raw bytes. Of course it is best when the value expression and the types in the smart contract match, but this is not enforced. + +::: + +A note on error messages: whenever we write a test that fails, the test runner tries its best to transform the actual value it found from raw bytes to a more human-readable form. It doesn't really know what format to use, so it tries its best to find something plausible. However, all it has are some heuristics, so it doesn't always get it right. It also displays the raw bytes so that the developer can investigate the proper value. + + +## **A note about the value parser and the use of prefixes** + +The value interpreter is not very complex and uses simple prefixes for most functions. Examples of prefixes are `"str:"` and `"u32:"`. + +The `|` (pipe) operator, which we use for concatenation has the highest priority. More about it [here](/developers/testing/scenario/values-complex#concatenation). + +The arguments of functions start after the prefix (no whitespace) and end either at the first pipe (`|`) or at the end of the string. + +Multiple prefixes evaluated right to left, for instance `"keccak256:keccak256:str:abcd"` will first convert `"abcd"` to bytes, then apply the hashing function on it twice. + +With that being said, the following sections will describe how to express different value types in scenarios. A full list of the prefixes is [at the end of this page](#the-full-list-of-scenario-value-prefixes). + + +## **Empty value** + +Empty strings (`""`) mean empty byte arrays. The number zero can also be represented as an empty byte array. +Other values that translate to an empty byte array are `"0"` and `"0x"`. + + +## **Hexadecimal representation** + +To provide raw hexadecimal representations of the values, use the prefix `0x` and follow it with the base-16 bytes. E.g. `"0x1234567890"`. +After the `0x` prefix an even number of digits is expected, since 2 digits = 1 byte. + +:::note Examples + +- `"0x"` +- `"0x1234567890abcdef"` +- `"0x0000000000000000"` +::: + + +## **Standalone number representations** + +Unprefixed numbers are interpreted as base 10, unsigned. +Unsigned numbers will be represented in the minimum amount of bytes in which they can fit. + +:::note Examples + +- `"0"` +- `"1"` +- `"1000000".` +- `"255"` is the same as `"0xff"` +- `"256"` is the same as `"0x0100"` +- `"0"` is the same as `""` +::: + +:::tip +Digit separators are allowed anywhere, for readability, e.g. `"1,000,000"`. +::: + + +## **Standalone signed numbers** + +:::caution +Only use signed numbers if you absolutely need to. Big signed integer representation has some pitfalls that can lead to subtle and unexpected issues when interacting with the contract. +::: + +Sometimes contract arguments are expected to be signed. These arguments will be transmitted as two’s complement representation. Prefixing any number (base 10 or hex) with a minus sign will convert them to two’s complement. Two’s complement is interpreted as positive or negative based on the first bit. + +Sometimes positive numbers can start with a "1" bit and get accidentally interpreted as negative. To prevent this, we can prefix them with a plus. A few examples should make this clearer: + +:::note Examples + +- `"1"` is represented as `"0x01"`, signed interpretation: `1`, everything OK. +- `"255"` is represented as `"0xff"`, signed interpretation: `"-1",` this might not be what we expected. +- `"+255"` is represented as `"0x00ff"`, signed interpretation: `"255".` The prepended zero byte makes sure the contract interprets it as positive. The `+` makes sure those leading zeroes are added if necessary. +- `"+1"` is still represented as `"0x01"`, here the leading 0 is not necessary. Still, it is good practice adding the `+` if we know the argument is expected to be signed. +- `"-1"` is represented as `"0xff"`. Negative numbers are also represented in the minimum number of bytes possible. +::: + +For more about signed number encoding, see [the big number serialization format](/developers/data/simple-values#arbitrary-width-big-numbers). + + +## **Nested numbers** + +Whenever we nest numbers in larger structures, we need to somehow encode their length. Otherwise, it would become impossible for them to be deserialized. + +The format helps developers to also easily represent nested numbers. These are as follows: + +- `biguint:` is useful for representing a nested BigUint. It outputs the length of the byte representation, followed by the big endian byte representation itself. +- `u64:` `u32:` `u16:` `u8:` interpret the argument as an unsigned int and convert to big endian bytes of respective length (8/4/2/1 bytes) +- `i64:` `i32:` `i16:` `i8:` interpret the argument as a signed int and convert to 2's complement big endian bytes of respective length (8/4/2/1 bytes) + +:::note Examples + +- `"biguint:0"` equals `0x00000000` +- `"biguint:1"` equals `0x0000000101` +- `"biguint:256"` equals `0x00000020100` +- `u64:1` equals `0x0000000000000001` +- `i64:-1` equals `0xFFFFFFFFFFFFFFFF` +- `u32:1` equals `0x00000001` +- `u16:1` equals `0x0001` +- `u8:1` equals `0x01` +::: + + +## **Nested items** + +The `nested:` prefix prepends the length of the argument. It is similar to `biguint:`, but does not expect a number. + +:::note Examples + +- `"nested:str:abc"` equals `0x00000003|str:abc` +- `"nested:0x01020304"` equals `0x0000000401020304` +::: + + +## **Booleans** + +The format offers these 2 constants, for convenience: + +- `"true"` = `"1"` = `"0x01"` +- `"false"` = `"0"` = `""`. + +:::caution +This is the standalone representation. If your boolean is embedded in a structure or list, use `u8:0` instead of `false`. +::: + + +## **ASCII strings** + +The preferred way of representing ASCII strings is with the `str:` prefix. + +:::important +The `''` and ` `` ` prefixes are common in older examples. They are equivalent to `str:`, but considered legacy. We recommend avoiding them because they clash with the syntax of languages where we might want to embed scenario code (Go and Markdown in particular). +::: + + +## **User Addresses** + +`address:` constructs a dummy user address from a word. + +Addresses need to be 32 bytes long, so + +- if the word is longer, it gets chopped off at the end +- if the word is shorter, it gets extended to the right with `0x5f` bytes (the `"_"` character). + +:::note Example +`"address:my_address"` is the same as: + +- `"str:my_address______________________"` or +- `"0x6d795f616464726573735f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f"`. +::: + + +## **Smart Contract Addresses** + +`sc:` constructs a dummy smart contract address. + +On MultiversX, smart contract addresses have a different format than user address - they start with 8 bytes of zero. + +:::important +The format requires that all accounts with addresses in SC format must have non-empty code. + +It is forbidden to have accounts with code but an address that doesn't obey the SC format. +::: + +:::note Example +`"sc:my_address"` is the same as: + +- `"0x0000000000000000|str:my_address______________"` or +- `"0x00000000000000006d795f616464726573735f5f5f5f5f5f5f5f5f5f5f5f5f5f"`. +::: + +Sometimes the last byte of a a SC address is relevant, since it affects which shard the contract will end up in. It can be specified with a hash character `#`, followed by the final byte as hex. + +:::note Example +`"sc:my_address#a3"` is the same as: + +- `"0x0000000000000000|str:my_address_____________|0xa3"` and +- `"0x00000000000000006d795f616464726573735f5f5f5f5f5f5f5f5f5f5f5f5fa3"`. +::: + + +## **File contents** + +`file:` loads an entire file and uses the contents of the entire file as value. + +The path of the file is given relative to the current scenario file. + +Used in the first place for specifying smart contract code. It can, however, be used for specifying any value, anywhere. + +:::note Example +`"file:../output/my-contract.wasm"` +::: + +Example usage: + +- initializing contract code, +- contract code to deploy, +- contract code to be passed to another contract as argument for indirect deploy, +- checking some contract code in storage, +- any large argument + + +## **Hash function** + +`keccak256:` computes the Keccak256 hash of the argument. The result is always 32 bytes in length. + + +## **The full list of scenario value prefixes** + +The prefixes are: + +- `str:` converts from ASCII strings to bytes. +- `address:` dummy user address +- `sc:` dummy smart contract address +- `file:` loads the entire contents of a file +- `keccak256:` computes the hash of the argument +- `u64:` `u32:` `u16:` `u8:` interpret the argument as an unsigned int and convert to big endian bytes of respective length (8/4/2/1 bytes) +- `i64:` `i32:` `i16:` `i8:` interpret the argument as a signed int and convert to 2's complement big endian bytes of respective length (8/4/2/1 bytes) +- `biguint:` big number unsigned byte length followed by big number unsigned bytes themselves +- `nested:` prepends the length of the argument + +--- + +### scresults + +This page describes the structure of the `sc-results` index (Elasticsearch), and also depicts a few examples of how to query it. + + +## _id + +:::warning Important + +**The `scresults` index will be deprecated and removed in the near future.** +We recommend using the [operations](/sdk-and-tools/indices/es-index-operations) index, which contains all the smart contract results data. +The only change required in your queries is to include the `type` field with the value `unsigned` to fetch all smart contract results. + +Please make the necessary updates to ensure a smooth transition. +If you need further assistance, feel free to reach out. +::: + +The `_id` field for this index is composed of hex-encoded smart contract result hash. +(example: `cbd4692a092226d68fde24840586bdf36b30e02dc4bf2a73516730867545d53c`) + + +## Fields + + +| Field | Description | +|-------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| miniBlockHash | The miniBlockHash field represents the hash of the miniblock in which the smart contract result was included. | +| nonce | The nonce field represents the transaction sequence number. | +| gasLimit | The gasLimit field represents the maximum gas units the sender is willing to pay for. | +| gasPrice | The gasPrice field represents the amount to be paid for each gas unit. | +| value | The value field represents the amount of EGLD to be sent from the sender to the receiver. | +| sender | The sender field represents the address of the smart contract result sender. | +| receiver | The receiver field represents the destination address of the smart contract result. | +| senderShard | The senderShard field represents the shard ID of the sender address. | +| receiverShard | The receiverShard field represents the shard ID of the receiver address. | +| relayerAddr | The relayerAddr field represents the address of the relayer. | +| relayedValue | This relayedValue field represents the amount of EGLD to be transferred via the inner transaction's sender. | +| code | The code holds the code of the smart contract result. | +| data | The data field holds additional information for a smart contract result. It can contain a simple message, a function call, an ESDT transfer payload, and so on. | +| prevTxHash | The prevTxHash holds the hex encoded hash of the previous transaction. | +| originalTxHash | The originalTxHash holds the hex encoded hash of the transaction that generated the smart contract result. | +| callType | The callType field holds the type of smart contract call that is done through the smart contract result. | +| codeMetaData | The codeMetaData field holds the code metadata. | +| returnMessage | The returnMessage field holds the message that is returned by a smart contract in case of an error. | +| timestamp | The timestamp field represents the timestamp of the block in which the smart contract result was executed. | +| status | The status field holds the execution state of the smart contract result. The execution state can be `pending` or `success`. | +| tokens | The tokens field contains a list of ESDT tokens that are transferred based on the data field. The indices from the `tokens` list are linked to the indices from `esdtValues` list. | +| esdtValues | The esdtValues field contains a list of ESDT values that are transferred based on the data field. | +| receivers | The receivers field contains a list of receiver addresses in case of ESDTNFTTransfer or MultiESDTTransfer. | +| receiversShardIDs | The receiversShardIDs field contains a list of receiver addresses' shard IDs. | +| operation | The operation field represents the operation of the smart contract result based on the data field. | +| function | The function field holds the name of the function that is called in case of a smart contract call. | +| originalSender | The originalSender field holds the sender's address of the original transaction. | +| hasLogs | The hasLogs field is true if the transaction has logs. | + + +## Query examples + + +### Fetch all the smart contract results generated by a transaction + +``` +curl --request GET \ + --url ${ES_URL}/scresults/_search \ + --header 'Content-Type: application/json' \ + --data '{ + "query": { + "match": { + "originalTxHash":"d6.." + } + } +}' +``` + +--- + +### sdk-dapp + +Library used to build React dApps on MultiversX Network. + +:::important +The following documentation is for `sdk-dapp v5.0.0` and above. +::: + + +## Introduction + +`sdk-dapp` is a library that holds core functional logic that can be used to create a dApp on MultiversX Network. + +It is built for applications that use any of the following technologies: + +- React (example: [React Template Dapp](https://github.com/multiversx/mx-template-dapp)) +- Angular (example: [Angular Template Dapp](https://github.com/multiversx/mx-template-dapp-angular)) +- Vue (example: [Vue Template Dapp](https://github.com/multiversx/mx-template-dapp-vue)) +- Any other JavaScript framework (e.g. Solid.js etc.) (example: [Solid.js Dapp](https://github.com/multiversx/mx-solidjs-template-dapp)) +- React Native +- Next.js (example: [Next.js Dapp](https://github.com/multiversx/mx-template-dapp-nextjs)) + + +### GitHub project + +The GitHub repository can be found here: [https://github.com/multiversx/mx-sdk-dapp](https://github.com/multiversx/mx-sdk-dapp) + + +### Live demo: template-dapp + +See [Template dApp](https://template-dapp.multiversx.com/) for live demo or checkout usage in the [Github repo](https://github.com/multiversx/mx-template-dapp) + + +### Requirements + +- Node.js version 20.13.1+ +- Npm version 10.5.2+ + + +### Distribution + +[npm](https://www.npmjs.com/package/@multiversx/sdk-dapp) + + +## Installation + +The library can be installed via npm or yarn. + +```bash +npm install @multiversx/sdk-dapp +``` + +or + +```bash +yarn add @multiversx/sdk-dapp +``` + +> **Note:** Make sure you run your app on `https`, not `http`, otherwise some providers will not work. + +If you're transitioning from `@multiversx/sdk-dapp@4.x`, you can check out the [Migration guide](https://github.com/multiversx/mx-template-dapp/blob/main/MIGRATION_GUIDE.md) and [migration PR](https://github.com/multiversx/mx-template-dapp/pull/343) of Template Dapp + + +## Usage + +sdk-dapp aims to abstract and simplify the process of interacting with users' wallets and with the MultiversX blockchain, allowing developers to easily get started with new applications. + +```mermaid +flowchart LR + A["Signing Providers & APIs"] <--> B["sdk-dapp"] <--> C["dApp"] +``` + +The basic concepts you need to understand are configuration, provider interaction, transactions, and presenting data. These are the building blocks of any dApp, and they are abstracted in the `sdk-dapp` library. + +Having this knowledge, we can consider several steps needed to put a dApp together: + +**Table 1.** Steps to build a dApp + +| # | Step | Description | +|----|---------------------|-------------| +| 1 | Configuration | storage configuration (e.g. sessionStorage, localStorage etc.); chain configuration; custom provider configuration (adding / disabling / changing providers) | +| 2 | Provider interaction| logging in and out; signing transactions / messages | +| 3 | Presenting data | get store data (e.g. account balance, account address etc.); use components to display data (e.g. balance, address, transactions list) | +| 4 | Transactions | sending transactions; tracking transactions; displaying transactions history | + +Each of these steps will be explained in more detail in the following sections. + + +### 1. Configuration + +Before your application bootstraps, you need to configure the storage, the network, and the signing providers. This is done by calling the `initApp` method from the `methods` folder. It is recommended to call this method in the `index.tsx` to ensure the sdk-dapp internal functions are initialized before rendering the app. + +```typescript +// index.tsx +import { initApp } from '@multiversx/sdk-dapp/out/methods/initApp/initApp'; +import type { InitAppType } from '@multiversx/sdk-dapp/out/methods/initApp/initApp.types'; +import { EnvironmentsEnum } from '@multiversx/sdk-dapp/out/types/enums.types'; +import { App } from "./App"; + +const config: InitAppType = { + storage: { getStorageCallback: () => sessionStorage }, + dAppConfig: { + // nativeAuth: true, // optional + environment: EnvironmentsEnum.devnet, + // network: { // optional + // walletAddress: 'https://devnet-wallet.multiversx.com' // or other props you want to override + // }, + // transactionTracking: { // optional + // successfulToastLifetime: 5000, + // onSuccess: async (sessionId) => { + // console.log('Transaction session successful', sessionId); + // }, + // onFail: async (sessionId) => { + // console.log('Transaction session failed', sessionId); + // } + } + // customProviders: [myCustomProvider] // optional +}; + +initApp(config).then(() => { + render(() => , root!); // render your app +}); +``` + + +### 2. Provider interaction + +Once your dApp has loaded, the first user action is logging in with a chosen provider. There are two ways to perform a login, namely using the `UnlockPanelManager` and programmatic login using the `ProviderFactory`. + +#### 2.1 Using the `UnlockPanelManager` +By using the provided UI, you get the benefit of having all supported providers ready for login in a side panel. You simply need to link the `unlockPanelManager.openUnlockPanel` to a user action. + +```typescript +import { UnlockPanelManager } from '@multiversx/sdk-dapp/out/managers/UnlockPanelManager'; +import { ProviderFactory } from '@multiversx/sdk-dapp/out/providers/ProviderFactory'; + +export const ConnectButton = () => { + const unlockPanelManager = UnlockPanelManager.init({ + loginHandler: () => { + navigate('/dashboard'); + }, + onClose: () => { // optional action to be performed when the user closes the Unlock Panel + navigate('/'); + }, + // allowedProviders: [ProviderTypeEnum.walletConnect, 'myCustomProvider'] // optionally, only show specific providers + }); + const handleOpenUnlockPanel = () => { + unlockPanelManager.openUnlockPanel(); + }; + return ; +}; + +``` +Once the user has logged in, if `nativeAuth` is configured in the `initApp` method, an automatic logout will be performed upon native auth expiration. Before the actual logout is performed, the `LogoutManager` will show a warning toast to the user. This toast can be customized by passing a `tokenExpirationToastWarningSeconds` to the `nativeAuth` config. + +```typescript +// in initAoo config +const config: InitAppType = { + // ... + nativeAuth: { + expirySeconds: 30, // test auto logout after 30 seconds + tokenExpirationToastWarningSeconds: 10 // show warning toast 10 seconds before auto logout + }, +} +``` + +You have the option to stop this behavior by calling `LogoutManager.getInstance().stop()` after the user has logged in. + +```typescript +import { LogoutManager } from '@multiversx/sdk-dapp/out/managers/LogoutManager/LogoutManager'; + + loginHandler: () => { + navigate('/dashboard'); + // optional, to stop the automatic logout upon native auth expiration + LogoutManager.getInstance().stop(); + }, +``` + +If you want to perform some actions as soon as the user has logged in, you will need to call `ProviderFactory.create` inside a handler accepting arguments. + +```typescript +export const AdvancedConnectButton = () => { + const unlockPanelManager = UnlockPanelManager.init({ + loginHandler: async ({ type, anchor }) => { + const provider = await ProviderFactory.create({ + type, + anchor + }); + const { address, signature } = await provider.login(); + navigate(`/dashboard?address=${address}`; + }, + }); + const handleOpenUnlockPanel = () => { + unlockPanelManager.openUnlockPanel(); + }; + return ; +}; + +``` + +#### 2.2 Programmatic login using the `ProviderFactory` +If you want to login using your custom UI, you can link user actions to specific providers by calling the `ProviderFactory`. + +```typescript +import { ProviderTypeEnum } from '@multiversx/sdk-dapp/out/providers/types/providerFactory.types'; + +const provider = await ProviderFactory.create({ + type: ProviderTypeEnum.extension +}); +await provider.login(); +``` + +> **Note:** Extension and Ledger login will only work if dApp is served over HTTPS protocol + + +### 3. Displaying app data + +Depending on the framework, you can either use hooks or selectors to get the user details: + +#### 3.1 React hooks + +If you are using React, all hooks can be found under the `/out/react` folder. All store information can be accessed via different hooks but below you will find the main hook related to most common use cases + +```bash +out/react/ +├── account/useGetAccount ### access account data like address, balance and nonce +├── loginInfo/useGetLoginInfo ### access login data like accessToken and provider type +├── network/useGetNetworkConfig ### access network information like chainId and egldLabel +├── store/useSelector ### make use of the useSelector hook for querying the store via selectors +└── transactions/useGetTransactionSessions ### access all current and historic transaction sessions +``` + +Below is an example of using the hooks to display the user address and balance. + +```typescript +import { useGetAccount } from '@multiversx/sdk-dapp/out/react/account/useGetAccount'; +import { useGetNetworkConfig } from '@multiversx/sdk-dapp/out/react/network/useGetNetworkConfig'; + +const account = useGetAccount(); +const { + network: { egldLabel } +} = useGetNetworkConfig(); + +console.log(account.address); +console.log(`${account.balance} ${egldLabel}`); +``` + +#### 3.2 Store selector functions: + +If you are not using the React ecosystem, you can use store selectors to get the store data, but note that information will not be reactive unless you subscribe to store changes. + +```typescript +import { getAccount } from '@multiversx/sdk-dapp/out/methods/account/getAccount'; +import { getNetworkConfig } from '@multiversx/sdk-dapp/out/methods/network/getNetworkConfig'; + +const account = getAccount(); +const { egldLabel } = getNetworkConfig(); +``` + +In order to get live updates you may need to subscribe to the store like this: + +```typescript +import { createSignal, onCleanup } from 'solid-js'; +import { getStore } from '@multiversx/sdk-dapp/out/store/store'; + +export function useStore() { + const store = getStore(); + const [state, setState] = createSignal(store.getState()); + + const unsubscribe = store.subscribe((newState) => { + setState(newState); + }); + + onCleanup(() => unsubscribe()); + + return state; +} +``` + + +### 4. Transactions + +#### 4.1 Signing transactions + +To sign transactions, you first need to create the `Transaction` object, then pass it to the initialized provider. + +```typescript +import { Address, Transaction } from '@multiversx/sdk-core'; +import { + GAS_PRICE, + GAS_LIMIT +} from '@multiversx/sdk-dapp/out/constants/mvx.constants'; +import { getAccountProvider } from '@multiversx/sdk-dapp/out/providers/helpers/accountProvider'; +import { refreshAccount } from '@multiversx/sdk-dapp/out/utils/account/refreshAccount'; + +const pongTransaction = new Transaction({ + value: BigInt(0), + data: Buffer.from('pong'), + receiver: Address.newFromBech32(contractAddress), + gasLimit: BigInt(GAS_LIMIT), + gasPrice: BigInt(GAS_PRICE), + chainID: network.chainId, + nonce: BigInt(account.nonce), + sender: Address.newFromBech32(account.address), + version: 1 +}); + +await refreshAccount(); // optionally, to get the latest nonce +const provider = getAccountProvider(); +const signedTransactions = await provider.signTransactions(transactions); +``` + +#### 4.2 Sending and tracking transactions + +Then, to send the transactions, you need to use the `TransactionManager` class and pass in the `signedTransactions` to the `send` method. You can then track the transactions by using the `track` method. This will create a toast notification with the transaction hash and its status. + +> **Note:** You can set callbacks for the transaction manager to handle the session status, by using the `setCallbacks` method on the `TransactionManager` class. This can also be configured globally in the `initApp` method. + +```typescript +import { TransactionManager } from '@multiversx/sdk-dapp/out/managers/TransactionManager'; +import type { TransactionsDisplayInfoType } from '@multiversx/sdk-dapp/out/types/transactions.types'; + + +const txManager = TransactionManager.getInstance(); + +const sentTransactions = await txManager.send(signedTransactions); + +const toastInformation: TransactionsDisplayInfoType = { + processingMessage: 'Processing transactions', + errorMessage: 'An error has occurred during transaction execution', + successMessage: 'Transactions executed' +} + +const sessionId = await txManager.track(sentTransactions, { + transactionsDisplayInfo: toastInformation, +}); +``` + +#### 4.3 Using the Notifications Feed + +The Notifications Feed is a component that displays **session transactions** in a list. Internally it gets initialized in the `initApp` method. It can be accessed via the `NotificationManager.getInstance()` method. +Once the user logs out of the dApp, all transactions displayed by the Notifications Feed are removed from the store. Note that you can switch between toast notifications and Notifications Feed by pressing the View All button above the current pending transaction toast in the UI. + +```typescript +const notificationManager = NotificationManager.getInstance(); +await notificationManager.openNotificationsFeed(); +``` + +#### 4.4 Inspecting transactions + +You can find both methods and hooks to access transactions data, as seen in the table below. + +**Table 2**. Inspectig transactions +| # | Helper | Description | React hook equivalent | +|---|------|-------------|----| +| | `methods/transactions` | path | `react/transactions` | +| 1 | `getTransactionSessions()` | returns all trabsaction sessions |`useGetTransactionSessions()` | +| 2 | `getPendingTransactionsSessions()` | returns an record of pending sessions | `useGetPendingTransactionsSessions()`| +| 3 | `getPendingTransactions()` | returns an array of signed transactions | `useGetPendingTransactions()` | +| 4 | `getFailedTransactionsSessions()` | returns an record of failed sessions | `useGetFailedTransactionsSessions()`| +| 5 | `getFailedTransactions()` | returns an array of failed transactions | `useGetFailedTransactions()`| +| 6 | `getSuccessfulTransactionsSessions()` | returns an record of successful sessions | `useGetSuccessfulTransactionsSessions()`| +| 7 | `getSuccessfulTransactions()` | returns an array of successful transactions | `useGetSuccessfulTransactions()`| + +There is a way to inspect store information regarding a specific transaction, using the `transactionsSliceSelector`. An example is shown below: + +```typescript +import { + pendingTransactionsSessionsSelector, + transactionsSliceSelector +} from '@multiversx/sdk-dapp/out/store/selectors/transactionsSelector'; +import { getStore } from '@multiversx/sdk-dapp/out/store/store'; + +const store = getStore(); // or use useStore hook for reactivity +const pendingSessions = pendingTransactionsSessionsSelector(store.getState()); +const allTransactionSessions = transactionsSliceSelector(store.getState()); + +const isSessionIdPending = + Object.keys(pendingSessions).includes(sessionId); +const currentSession = allTransactionSessions[sessionId]; +const currentSessionStatus = currentSession?.status; +const currentTransaction = currentSession?.transactions?.[0]; +const currentTransactionStatus = currentTransaction?.status; +``` + +#### 4.5 Logging out +The user journey ends with calling the `provider.logout()` method. + +```typescript +import { getAccountProvider } from '@multiversx/sdk-dapp/out/providers/helpers/accountProvider'; +const provider = getAccountProvider(); +await provider.logout(); +``` + + +## Internal structure + +We have seen in the previous chapter what are the minimal steps to get up and running with a blockchain interaction using sdk-dapp. Next we will detail each element mentioned above + +**Table 3**. Elements needed to build a dApp +| # | Type | Description | +|---|------|-------------| +| 1 | Network | Chain configuration | +| 2 | Provider | The signing provider for logging in and singing transactions | +| 3 | Account | Inspecting user address and balance | +| 4 | Transactions Manager | Sending and tracking transactions | +| 5 | UI Components | Displaying UI information like balance, public keys etc. | + +Since these are mixtures of business logic and UI components, the library is split into several folders to make it easier to navigate. +When inspecting the package, there is more content under `src`, but the folders of interest are: + + +```bash +src/ +├── apiCalls/ ### methods for interacting with the API +├── constants/ ### useful constants from the ecosystem like ledger error codes, default gas limits for transactions etc. +├── controllers/ ### business logic for UI elements that are not bound to the store +├── managers/ ### business logic for UI elements that are bound to the store +├── providers/ ### signing providers +├── methods/ ### utility functions to query and update the store +├── react/ ### react hooks to query the store +└── store/ ### store initialization, middleware, slices, selectors and actions +``` + +Conceptually, these can be split into 3 main parts: + +- First is the business logic in `apiCalls`, `constants`, `providers` and `methods` +- Then comes the persistence layer hosted in the `store` folder, using [Zustand](https://zustand.docs.pmnd.rs/) under the hood. +- Last are the UI components hosted in [@multiversx/sdk-dapp-ui](https://github.com/multiversx/mx-sdk-dapp-ui) with some components controlled on demand by classes defined in `controlles` and `managers` + +Next, we will take the elements from Table 3 and detail them in the following sections. + + +### 1. Network + +The network configuration is done in the `initApp` method, where you can make several configurations like: + +- specifying the environment (`devnet`, `testnet`, `mainnet`) +- overriding certain network parameters like wallet address, explorer address etc. + +Once the network is configured, the `network` slice in the store will hold the network configuration. + +To query different network parameters, you can use the `getNetworkConfig` method from the `methods/network` folder. + + +### 2. Provider + +The provider is the main class that handles the signing of transactions and messages. It is initialized in the `initApp` method and can be accessed via the `getAccountProvider` method from the `providers/helpers` folder. + +#### Initialization + +An existing provider is initialized on app load (this is take care of by `initApp`), since it restores the session from the store and allows signing transactions without the need of making a new login. + +#### Creating a custom provider + +If you need to create a custom signing provider, make sure to extend the `IProvider` interface and implement all required methods (see example [here](https://github.com/multiversx/mx-template-dapp/tree/main/src/provider)). Next step would be to include it in the `customProviders` array in the `initApp` method or add it to the [window object](https://github.com/multiversx/mx-template-dapp/tree/main/src/initConfig). Last step is to login using the custom provider. + +```typescript +const provider = await ProviderFactory.create({ + type: 'custom-provider' +}); +await provider?.login(); +``` + +#### Accessing provider methods + +Once the provider is initialized, you can get a reference to it using the `getAccountProvider` method. Then you can call the `login`, `logout`, `signTransactions`, `signMessage` methods, or other custom methods depending on the initialized provider (see ledger for example). + + +### 3. Account + +#### Getting account data + +Once the user logs in, a call is made to the API for fetching the account data. This data is persisted in the store and is accessible through helpers found in `methods/account`. These functions are: + +**Table 4**. Getting account data +| # | Helper | Description | React hook equivalent | +|---|------|-------------|----| +| | `methods/account` | path | `react/account` | +| 1 | `getAccount()` | returns all account data |`useGetAccount()` | +| 2 | `getAddress()` | returns just the user's public key | `useGetAddress()`| +| 3 | `getIsLoggedIn()` | returns a login status boolean | `useGetIsLoggedIn()` | +| 4 | `getLatestNonce()` | returns the account nonce | `useGetLatestNonce()` + +#### Nonce management + +`sdk-dapp` has a mechanism that does its best to manage the account nonce. For example, if the user sends a transaction, the nonce gets incremented on the client so that if a new transaction is sent, it will have the correct increased nonce. If you want to make sure the nonce is in sync with the API account, you can call `refreshAccount()` as shown above in the **Signing transactions** section. + + +### 4. Transactions Manager + +#### Overview + +The `TransactionManager` is a class that handles sending and tracking transactions in the MultiversX ecosystem. It provides methods to send either single or batch transactions. It also handles tracking, error management, and toast notifications for user feedback. It is initialized in the `initApp` method and can be accessed via `TransactionManager.getInstance()`. + +#### Features + +- **Supports Single and Batch Transactions:** Handles individual transactions as well as grouped batch transactions. +- **Automatic Tracking:** Monitors transaction status and updates accordingly through a webhook or polling fallback mechanism. +- **Toast Notifications:** Displays status updates for user feedback, with options to disable notifications and customize toast titles. +- **Error Handling:** Catches and processes errors during transaction submission + +#### Transactions Lifecycle + +The transaction lifecycle consists of the following steps: + +1. **Creating** a `Transaction` object from `@multiversx/sdk-core` +2. **Signing** the transaction with the initialized provider and receiving a `SignedTransactionType` object +3. **Sending** the signed transaction using TransactionManager's `send()` function. Signed transactions can be sent in 2 ways: + +**Table 5**. Sending signed transactions +| # | Signature | Method | Description | +|---|------|-------------|-------------| +| 1 | `send([tx1, tx2])` | `POST` to `/transactions` | Transactions are executed in parallel +| 2 | `send([[tx1, tx2], [tx3]])` | `POST` to `/batch` | **a)** 1st batch of two transactions is executed, **b)** the 2nd batch of one transaction waits for the finished results, **c)** and once the 1st batch is finished, the 2nd batch is executed + +4. **Tracking** transactions is made by using `transactionManager.track()`. Since the `send()` function returns the same arguments it has received, the same array payload can be passed into the `track()` method. Under the hood, status updates are received via a WebSocket or polling mechanism. + Once a transaction array is tracked, it gets associated with a `sessionId`, returned by the `track()` method and stored in the `transactions` slice. Depending on the array's type (plain/batch), the session's status varies from initial (`pending`/`invalid`/`sent`) to final (`successful`/`failed`/`timedOut`). + +**Table 6**. Inspecting transaction sessions +| # | Helper | Description | React hook equivalent | +|---|------|-------------|----| +| | `methods/transactions` | path | `react/transactions` | +| 1 | `getTransactionSessions()` | returns all trabsaction sessions |`useGetTransactionSessions()` | +| 2 | `getPendingTransactionsSessions()` | returns an record of pending sessions | `useGetPendingTransactionsSessions()`| +| 3 | `getPendingTransactions()` | returns an array of signed transactions | `useGetPendingTransactions()` | +| 4 | `getFailedTransactionsSessions()` | returns an record of failed sessions | `useGetFailedTransactionsSessions()`| +| 5 | `getFailedTransactions()` | returns an array of failed transactions | `useGetFailedTransactions()`| +| 6 | `getSuccessfulTransactionsSessions()` | returns an record of successful sessions | `useGetSuccessfulTransactionsSessions()`| +| 7 | `getSuccessfulTransactions()` | returns an array of successful transactions | `useGetSuccessfulTransactions()`| + +5. **User feedback** is provided through toast notifications, which are triggered to inform about transactions' progress. Additional tracking details can be optionally displayed in the toast UI. +There is an option to add custom toast messages by using the `createCustomToast` helper. + +```ts +import { createRoot } from 'react-dom/client'; +import { createCustomToast } from '@multiversx/sdk-dapp/out/store/actions/toasts/toastsActions'; + +// by creating a custom toast element containing a component +createCustomToast({ + toastId: 'username-toast', + instantiateToastElement: () => { + const toastBody = document.createElement('div'); + const root = createRoot(toastBody); + root.render(); + return toastBody; + } +}); + +// or by creating a simple custom toast +createCustomToast({ + toastId: 'custom-toast', + icon: 'times', + iconClassName: 'warning', + message: 'This is a custom toast', + title: 'My custom toast' +}); + + +``` + +6. **Error Handling & Recovery** is done through a custom toast that prompts the user to take appropriate action. + +#### Methods +___ + +#### 1. Sending Transactions + +In this way, all transactions are sent simultaneously. There is no limit to the number of transactions contained in the array. + +```typescript +const transactionManager = TransactionManager.getInstance(); +const parallelTransactions: SigendTransactionType[] = [tx1, tx2, tx3, tx4]; +const sentTransactions = await transactionManager.send(parallelTransactions); +``` + +#### 2. Sending Batch Transactions + +In this sequential case, each batch waits for the previous one to complete. + +```typescript +const transactionManager = TransactionManager.getInstance(); +const batchTransactions: SignedTransactionType[][] = [ + [tx1, tx2], + [tx3, tx4] +]; +const sentTransactions = await transactionManager.send(batchTransactions); +``` + +#### 3. Tracking Transactions + +The basic option is to use the built-in tracking, which displays toast notifications with default messages. + +```typescript +import { TransactionManagerTrackOptionsType } from '@multiversx/sdk-dapp/out/managers/TransactionManager/TransactionManager.types'; + +const options: TransactionManagerTrackOptionsType = { + disableToasts: false, // `false` by default + transactionsDisplayInfo: { // `undefined` by default + errorMessage: 'Failed adding stake', + successMessage: 'Stake successfully added', + processingMessage: 'Staking in progress' + }, + sessionInformation: { // `undefined` by default. Use to perform additional actions based on the session information + stakeAmount: '1000000000000000000000000' + } +}; + +const sessionId = await transactionManager.track( + sentTransactions, + options // optional +); +``` + +If you want to provide more human-friendly messages to your users, you can enable tracking with custom toast messages: + +```typescript +const sessionId = await transactionManager.track(sentTransactions, { + transactionsDisplayInfo: { + errorMessage: 'Failed adding stake', + successMessage: 'Stake successfully added', + processingMessage: 'Staking in progress' + } +}); +``` + +#### 3.1 Tracking transactions without being logged in + +If your application needs to track transactions sent by a server and the user does not need to login to see the outcome of these transactions, there are several steps that you need to do to enable this process. + +Step 1. Enabling the tracking mechanism + +By default the tracking mechanism is enabled only after the user logs in. That is the moment when the WebSocket connection is established. If you want to enable tracking before the user logs in, you need to call the `trackTransactions` method from the `methods/trackTransactions` folder. This method will enable a polling mechanism. + +```typescript +import { trackTransactions } from '@multiversx/sdk-dapp/out/methods/trackTransactions/trackTransactions'; + +initApp(config).then(async () => { + await trackTransactions(); // enable here since by default tracking will be enabled only after login + render(() => , root!); +}); +``` + +Step 2. Tracking transactions + +Then, you can track transactions by calling the `track` method from the `TransactionManager` class with a plain transaction containing the transaction hash. + +```typescript +import { Transaction, TransactionsConverter } from '@multiversx/sdk-core'; + +const tManager = TransactionManager.getInstance(); +const txConverter = new TransactionsConverter(); +const transaction = txConverter.plainObjectToTransaction(signedTx); + +const hash = transaction.getHash().toString(); // get the transaction hash + +const plainTransaction = { ...transaction.toPlainObject(), hash }; +await tManager.track([plainTransaction]); +``` + +#### 3.2 Advanced Usage + +If you need to check the status of the signed transactions, you can query the store directly using the `sessionId` returned by the `track()` method. + +```typescript +import { getStore } from '@multiversx/sdk-dapp/out/store/store'; +import { transactionsSliceSelector } from '@multiversx/sdk-dapp/out/store/selectors/transactionsSelector'; + +const state = transactionsSliceSelector(getStore()); +Object.entries(state).forEach(([sessionKey, data]) => { + if (sessionKey === sessionId) { + console.log(data.status); + } +}); +``` + + +### 5. UI Components + +`sdk-dapp` needs to make use of visual elements for allowing the user to interact with some providers (like the ledger), or to display messages to the user (like idle states or toasts). These visual elements consitst of webcomponents hosted in the `@multiversx/sdk-dapp-ui` package. Thus, `sdk-dapp` does not hold any UI elements, just business logic that controls external components. We can consider two types of UI components: internal and public. They are differentiated by the way they are controlled: internal components are controlled by `sdk-dapp`'s signing or logging in flows, while public components should be controlled by the dApp. + +#### 5.1 Public components + +The business logic for these components is served by a controller. The components are: + +- `MvxTransactionsTable` - used to display the user's transactions + +```tsx +import { TransactionsTableController } from '@multiversx/sdk-dapp/out/controllers/TransactionsTableController'; +import { MvxTransactionsTable } from '@multiversx/sdk-dapp-ui/react'; + +const processedTransactions = await TransactionsTableController.processTransactions({ + address, + egldLabel: network.egldLabel, + explorerAddress: network.explorerAddress, + transactions + }); + +// and use like this: +; + +``` + +- `MvxFormatAmount` - used to format the amount of the user's balance + + +```tsx +import { MvxFormatAmount } from '@multiversx/sdk-dapp-ui/react'; +import { MvxFormatAmountPropsType } from '@multiversx/sdk-dapp-ui/types'; +export { DECIMALS, DIGITS } from '@multiversx/sdk-dapp-utils/out/constants'; +import { FormatAmountController } from '@multiversx/sdk-dapp/out/controllers/FormatAmountController'; +export { useGetNetworkConfig } from '@multiversx/sdk-dapp/out/react/network/useGetNetworkConfig'; + + +interface IFormatAmountProps + extends Partial { + value: string; + className?: string; +} + +export const FormatAmount = (props: IFormatAmountProps) => { + const { + network: { egldLabel } + } = useGetNetworkConfig(); + + const { isValid, valueDecimal, valueInteger, label } = + FormatAmountController.getData({ + digits: DIGITS, + decimals: DECIMALS, + egldLabel, + ...props, + input: props.value + }); + + return ( + + ); +}; +``` + + +#### 5.2 Internal components (advanced usage) + +The way internal components are controlled is through a [pub-sub pattern](https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern) called EventBus. Each webcomponent has a method of exposing its EventBus, thus allowing `sdk-dapp` to get a reference to it and use it for communication. + +```mermaid +flowchart LR + A["Controller"] <--> B["Event Bus"] <--> C["webcomponent"] +``` + +```typescript +import { ComponentFactory } from '@multiversx/sdk-dapp/out/utils/ComponentFactory'; + +const modalElement = await ComponentFactory.create( + 'mvx-ledger-connect-panel' +); +const eventBus = await modalElement.getEventBus(); +eventBus.publish('TRANSACTION_TOAST_DATA_UPDATE', someData); +``` + +If you want to override private components and create your own, you can implement a similar strategy by respecting each webcomponent's API (see an interface example [here](https://github.com/multiversx/mx-sdk-dapp/blob/main/src/providers/strategies/LedgerProviderStrategy/types/ledger.types.ts)). + + +## Debugging your dApp + +> **Note:** For an advanced documentation on how internal flows are implemented, you can check out the [deepwiki](https://deepwiki.com/multiversx/mx-sdk-dapp) diagrams. + +The recommended way to debug your application is by using [lerna](https://lerna.js.org/). Make sure you have the same package version in sdk-dapp's package.json and in your project's package.json. + +If you prefer to use [npm link](https://docs.npmjs.com/cli/v11/commands/npm-link), make sure to use the `preserveSymlinks` option in the server configuration: + +```js + resolve: { + preserveSymlinks: true, // 👈 + alias: { + src: "/src", + }, + }, +``` + +Crome Redux DevTools are by default enabled to help you debug your application. You can find the extension in the Chrome Extensions store. + +To build the library, run: + +```bash +npm run build +``` + +To run the unit tests, run: + +```bash +npm test +``` + +--- + +### sdk-js + +MultiversX SDK for TypeScript and JavaScript + +This SDK consists of TypeScript / JavaScript helpers and utilities for interacting with the Blockchain (in general) and with Smart Contracts (in particular). + + +## Packages + +Base libraries: + +| Package | Source code | Description | +|------------------------------------------------------------------------------------------|---------------------------------------------------------------------|--------------------------------------------------------------------------------| +| [sdk-core](https://www.npmjs.com/package/@multiversx/sdk-core) | [Github](https://github.com/multiversx/mx-sdk-js-core) | The `sdk-core` package is a unification of the previous packages (`multiversx/sdk-wallet` and `multiversx/sdk-network-providers` into `multiversx/sdk-core`). It has basic components for interacting with the blockchain and with smart contracts. | +| [sdk-exchange](https://www.npmjs.com/package/@multiversx/sdk-exchange) | [Github](https://github.com/multiversx/mx-sdk-js-exchange) | Utilities modules for xExchange interactions. | + +Signing providers for dApps: + +| Package | Docs | Source code | Description | +|--------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------| +| [sdk-hw-provider](https://www.npmjs.com/package/@multiversx/sdk-hw-provider) | [documentation](/sdk-and-tools/sdk-js/sdk-js-signing-providers#the-hardware-wallet-provider) | [Github](https://github.com/multiversx/mx-sdk-js-hw-provider) | Sign using the hardware wallet (Ledger). | +| [sdk-wallet-connect-provider](https://www.npmjs.com/package/@multiversx/sdk-wallet-connect-provider) | [documentation](/sdk-and-tools/sdk-js/sdk-js-signing-providers#the-walletconnect-provider) | [Github](https://github.com/multiversx/mx-sdk-js-wallet-connect-provider) | Sign using WalletConnect. | +| [sdk-extension-provider](https://www.npmjs.com/package/@multiversx/sdk-extension-provider) | [documentation](/sdk-and-tools/sdk-js/sdk-js-signing-providers#the-browser-extension-provider) \| [interactive documentation](https://interactive-tutorials.multiversx.com/dashboard/extension-provider) | [Github](https://github.com/multiversx/mx-sdk-js-extension-provider) | Sign using the MultiversX DeFi Wallet (browser extension). | +| [sdk-web-wallet-provider](https://www.npmjs.com/package/@multiversx/sdk-web-wallet-provider) | [documentation](/sdk-and-tools/sdk-js/sdk-js-signing-providers#the-web-wallet-provider) | [Github](https://github.com/multiversx/mx-sdk-js-web-wallet-provider) | Sign using the MultiversX web wallet, using webhooks **(DEPRECATED)**. | +| [mx-sdk-js-web-wallet-cross-window-provider](https://www.npmjs.com/package/@multiversx/sdk-web-wallet-cross-window-provider) | [documentation](/sdk-and-tools/sdk-js/sdk-js-signing-providers#the-web-wallet-cross-window-provider) \| [interactive documentation](https://interactive-tutorials.multiversx.com/dashboard/cross-window-provider) | [Github](https://github.com/multiversx/mx-sdk-js-web-wallet-cross-window-provider) | Sign using the MultiversX web wallet, by opening the wallet in a new tab. | +| [mx-sdk-js-metamask-proxy-provider](https://www.npmjs.com/package/@multiversx/sdk-metamask-proxy-provider) | [documentation](/sdk-and-tools/sdk-js/sdk-js-signing-providers#the-metamask-proxy-provider) \| [interactive documentation](https://interactive-tutorials.multiversx.com/dashboard/iframe-provider) | [Github](https://github.com/multiversx/mx-sdk-js-metamask-proxy-provider) | Sign using the Metamask wallet, by using web wallet as a proxy widget in iframe. | + +For more details about integrating a signing provider into your dApp, please follow [this guide](/sdk-and-tools/sdk-js/sdk-js-signing-providers) or the [mx-sdk-js-examples repository](https://github.com/multiversx/mx-sdk-js-examples). + +Signing SDKs: + +| Package | Source code | Description | +|--------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------| +| [sdk-dapp](https://www.npmjs.com/package/@multiversx/sdk-dapp) | [Github](https://github.com/multiversx/mx-sdk-dapp) | A library that holds the core functional & signing logic of a dapp on the MultiversX Network. | +| [sdk-guardians-provider](https://www.npmjs.com/package/@multiversx/sdk-guardians-provider) | [Github](https://github.com/multiversx/mx-sdk-js-guardians-provider) | Helper library for integrating a co-signing provider (Guardian) into dApps. | + + +:::important +For all purposes, **we recommend using [sdk-dapp](/sdk-and-tools/sdk-dapp)** instead of integrating the signing providers on your own. +::: + +Native Authenticator libraries: + +| Package | Source code | Description | +|-------------------------------------------------------------------------------------------|----------------------------------------------------------------------|--------------------------------------------------------| +| [sdk-native-auth-client](https://www.npmjs.com/package/@multiversx/sdk-native-auth-client) | [Github](https://github.com/multiversx/mx-sdk-js-native-auth-client) | Native Authenticator - client-side components. | +| [sdk-native-auth-server](https://www.npmjs.com/package/@multiversx/sdk-native-auth-server) | [Github](https://github.com/multiversx/mx-sdk-js-native-auth-server) | Native Authenticator - server-side components. | + +Additional utility packages: + +| Package | Source code | Description | +|------------------------------------------------------------------------------------------|--------------------------------------------------------------------|--------------------------------------------------------| +| [transaction-decoder](https://www.npmjs.com/package/@multiversx/sdk-transaction-decoder) | [Github](https://github.com/multiversx/mx-sdk-transaction-decoder) | Decodes transaction metadata from a given transaction. | + +--- + +### SDKs and Tools - Overview + +## Introduction + +One can (programmatically) interact with the MultiversX Network by leveraging the following SDKs, tools and APIs: + + +### sdk-rs - Rust SDK + +:::important +Note that Rust is also the recommended programming language for writing Smart Contracts on MultiversX. That is, Rust can be used to write both _on-chain software_ (Smart Contracts) and _off-chain software_ (e.g. desktop applications, web applications, microservices). For the on-chain part, please follow [Smart Contracts](/developers/smart-contracts). Here, we refer to the off-chain part. +::: + +| Name | Description | +| ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [sdk-rs](https://github.com/multiversx/mx-sdk-rs) | Rust SDK used to interact with the MultiversX Blockchain.
This is the parent repository, also home to the Rust Framework for Smart Contracts. | +| [sdk-rs/core](https://github.com/multiversx/mx-sdk-rs/tree/master/sdk/core) | Core components, accompanied by a set of usage examples. | +| [sdk-rs/snippets](https://github.com/multiversx/mx-sdk-rs/tree/master/framework/snippets) | Smart Contract interaction snippets - base components. Examples of usage: [adder](https://github.com/multiversx/mx-sdk-rs/tree/master/contracts/examples/adder/interact), [multisig](https://github.com/multiversx/mx-sdk-rs/tree/master/contracts/examples/multisig/interact). | + + +### sdk-js - Javascript SDK + +| 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) | 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 signing providers](/sdk-and-tools/sdk-js/sdk-js-signing-providers) | Integrate sdk-js signing providers. | + +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). + + +### sdk-dapp - core functional logic of a dApp + +| Name | Description | +| ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [sdk-dapp](/sdk-and-tools/sdk-dapp) | React library aimed to help developers create dApps based on MultiversX Network.

It abstracts away all the boilerplate for logging in, signing transactions or messages, and also offers helper functions for common tasks. | + + +### sdk-py - Python SDK + +| Name | Description | +| ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ | +| [sdk-py](/sdk-and-tools/sdk-py/#sdk-py-the-python-libraries) | Python SDK that can be used to create wallets, create and send transactions, interact with Smart Contracts and with the MultiversX Network in general. | + + +### mxpy - Python SDK (CLI) + +| Name | Description | +| -------------------------------------------------------------------------------- | ----------------------------------------- | +| [Installing mxpy](/sdk-and-tools/mxpy/installing-mxpy) | How to install and get started with mxpy. | +| [mxpy cli](/sdk-and-tools/mxpy/mxpy-cli) | How to use the Command Line Interface. | + + +### sdk-nestjs - NestJS SDK + +| Name | Description | +| --------------------------------------- | ------------------------------------------------------------------ | +| [sdk-nestjs](/sdk-and-tools/sdk-nestjs) | NestJS SDK commonly used in the MultiversX Microservice ecosystem. | + + +### mx-sdk-go - Golang SDK + +| Name | Description | +| ------------------------------- | -------------------------------------------------------------- | +| [sdk-go](/sdk-and-tools/sdk-go) | Go/Golang SDK used to interact with the MultiversX Blockchain. | + + +### mx-sdk-java - Java SDK + +| Name | Description | +| ------------------------------- | --------------------------------------------------------- | +| [mxjava](/sdk-and-tools/mxjava) | Java SDK used to interact with the MultiversX Blockchain. | + + +### erdcpp - C++ SDK + +| Name | Description | +| ------------------------------- | -------------------------------------------------------- | +| [erdcpp](/sdk-and-tools/erdcpp) | C++ SDK used to interact with the MultiversX Blockchain. | + + +### erdkotlin - Kotlin SDK + +| Name | Description | +| ------------------------------------- | ----------------------------------------------------------- | +| [erdkotlin](/sdk-and-tools/erdkotlin) | Kotlin SDK used to interact with the MultiversX Blockchain. | + + +### nesdtjs-sdk - NestJS SDK + +| Name | Description | +| --------------------------------------- | ----------------------------------------------------------- | +| [sdk-nestjs](/sdk-and-tools/sdk-nestjs) | NestJS SDK used to interact with the MultiversX Blockchain. | + + +### Node Rest API + +| Name | Description | +| ------------------------------------------------------------------------ | ----------------------------------------------------------------- | +| [Rest API](/sdk-and-tools/rest-api/) | High level overview over the MultiversX's Rest API. | +| [api.multiversx.com](/sdk-and-tools/rest-api/multiversx-api) | MultiversX's main API instance. | +| [Gateway overview](/sdk-and-tools/rest-api/gateway-overview) | Gateway overview - public proxy instance. | +| [Addresses](/sdk-and-tools/rest-api/addresses) | Rest API endpoints dedicated to addresses. | +| [Transactions](/sdk-and-tools/rest-api/transactions) | Rest API endpoints dedicated to transactions. | +| [Network](/sdk-and-tools/rest-api/network) | Rest API endpoints dedicated to network status and configuration. | +| [Nodes](/sdk-and-tools/rest-api/nodes) | Rest API endpoints dedicated to nodes. | +| [Blocks](/sdk-and-tools/rest-api/blocks) | Rest API endpoints dedicated to blocks. | +| [Virtual machine](/sdk-and-tools/rest-api/virtual-machine) | Rest API endpoints dedicated to the SC execution VM. | +| [Versions and changelog](/sdk-and-tools/rest-api/versions-and-changelog) | What's new in different versions. | + + +### Proxy + +Proxy is an abstraction layer over the MultiversX Network's sharding. It routes the API request to the desired shard and +merges results when needed. + +| Name | Description | +| ---------------------------------------- | ---------------------------------------------------- | +| [MultiversX Proxy](/sdk-and-tools/proxy) | A Rest API requests handler that abstracts sharding. | + + +### Elasticsearch + +MultiversX Network uses Elasticsearch to index historical data. Find out more about how it can be configured. + +| Name | Description | +| ---------------------------------------------- | --------------------------------------------------------------------------- | +| [Elasticsearch](/sdk-and-tools/elastic-search) | Make use of Elasticsearch near your nodes in order to keep historical data. | + + +### Events notifier + +Events notifier is an external service that can be used to fetch block events and push them to subscribers. + +| Name | Description | +| ------------------------------------------ | ------------------------------------ | +| [Events notifier](/sdk-and-tools/notifier) | A notifier service for block events. | + + +### Chain simulator + +Chain simulator is designed to replicate the behavior of a local testnet. +It can also be pre-initialized / initialized with blockchain state from other networks, such as mainnet or something similar. + +| Name | Description | +| ------------------------------------------------- | ---------------------------- | +| [Chain simulator](/sdk-and-tools/chain-simulator) | A service for local testing. | + + +### Devcontainers (for VSCode or GitHub Codespaces) + +| Name | Description | +| --------------------------------------------- | ----------------------------------------------------------------------- | +| [Devcontainers](/sdk-and-tools/devcontainers) | Overview of MultiversX devcontainers (for VSCode or GitHub Codespaces). | + +--- + +### Sender + +## Overview + +Within the context of a transaction that comprises seven distinct generics, `From` represents the **second** generic field — **the entity that initiates the transaction**. It is required in three environments: the integration test, the parametric test, and the interactor. + +A transaction originating from a contract environment cannot have a sender in the contract environment. The reason is that the current contract is always the same: the contract that starts the transaction. + + + +## Diagram + +The sender is being set using the `.from(...)` method. Several types can be specified: + +```mermaid +graph LR + subgraph From + from-unit["()"] + from-unit -->|from| from-man-address[ManagedAddress] + from-unit -->|from| from-address[Address] + from-unit -->|from| from-bech32[Bech32Address] + end +``` + + +## No sender + +Transactions initiated in the **contract environment** have no sender specified. There is no need for sender identification because it always refers to the executing contract itself. + +```rust title=contract.rs +#[payable("EGLD")] +#[endpoint] +fn transfer_egld(&self, to: ManagedAddress, amount: BigUint) { + self.tx().to(&to).egld(&amount).transfer(); +} +``` + + +## Explicit sender + +For transactions launched in any other environment than the contract one, it is required to specify the sender. Meaning that, in most cases, no tx can be created without defining the sender. + +In the following section, we will outline the various types of entities that can be designated as the sender in a transaction. + +### ManagedAddress + +The function below is a snippet from an interactor whose purpose is to deploy a contract. The `.from` call, which sets the sender, is the main focus of this example. The sender is a hardcoded ManagedAddress, which illustrates a wallet. + +```rust title=interact.rs +async fn deploy(&mut self) { + let wallet: ManagedAddress = ManagedAddress::new_from_bytes(&[7u8; 32]); + + self.interactor + .tx() + .from(wallet) + .typed(proxy::Proxy) + .init(0u32) + .code(&self.code) + .gas(NumExpr("70,000,000")) + .returns(ReturnsNewBech32Address) +} +``` + +### Address + +The example beneath is a fragment for a blackbox test that runs the add function from a proxy. The sender is received as a parameter in this function. +```rust title=blackbox_test.rs +fn add_one(&mut self, from: &AddressValue) { + self.world + .tx() + .from(from) + .to(self.receiver) + .typed(proxy::Proxy) + .add(1u32) + .run(); +} +``` + +For parametric testing, there is particular address type name: +- **TestAddress** + - encodes a dummy address, equivalent to `"address:{}"`; for the example below it is equivalent to `"address:owner"`; + - contains two functions: + - **`.eval_to_array()`** parse the address into an array of u8 + - **`.eval_to_expr()`** return the address as a String object +```rust title=blackbox_test.rs +const OWNER_ADDRESS: TestAddress = TestAddress::new("owner"); + +fn add_one(&mut self) { + self.world + .tx() + .from(OWNER_ADDRESS) + .to(self.receiver) + .typed(proxy::Proxy) + .add(1u32) + .run(); +} +``` +### Bech32Address +In order to avoid repeated conversions, it keeps the **bech32** representation **inside**. It wraps the address and presents it as a bech32 expression. +```rust title=interact.rs +fn add_one(&mut self, wallet_address: Bech32Address) { + self.world + .tx() + .from(wallet_address) + .to(self.receiver) + .typed(proxy::Proxy) + .add(1u32) + .run(); +} +``` + +## Automatic wallet selection in interactors + +Wallets are registered beforehand when it comes to interactor operations. An automated signature is applied to the transaction. More details about interactors here. + +--- + +### Set up a Localnet (mxpy) + +This guide describes how to set up a local mini-testnet - also known as **localnet** - using **mxpy**. The purpose of a localnet is to allow developers experiment with and test their Smart Contracts, in addition to writing unit and integration tests. + +The localnet contains: + +- **Validator Nodes** (two, by default) +- **Observer Nodes** (zero, by default) +- A **Seednode** +- A **MultiversX Proxy** + +If not specified otherwise, the localnet starts with two shards plus the metachain (each with one validator). + + +## Prerequisites: mxpy + +In order to install **mxpy**, follow [these instructions](/sdk-and-tools/mxpy/installing-mxpy). + +:::note +This guide assumes you are using `mxpy v9.7.1` or newer. +::: + + +## The easy way to start a localnet + +You can simply setup and start a localnet in a workspace (folder) of your choice by following the steps below. + +Create a new folder (workspace) and navigate to it: + +```bash +mkdir -p ~/my-first-localnet && cd ~/my-first-localnet +``` + +Create, build and configure a localnet (in one go): + +```bash +mxpy localnet setup +``` + +Then, start the localnet: + +```bash +mxpy localnet start +``` + +:::tip +Above, the command `mxpy localnet setup` performs the following sub-commands under the hood, in one go (so you don't have to run them individually): + +```bash +mxpy localnet new +mxpy localnet prerequisites +mxpy localnet build +mxpy localnet config +``` +::: + +If everything goes well, in the terminal you should see logs coming from the nodes and proxy: + +``` +INFO:cli.localnet:Starting localnet... +... +INFO:localnet:Starting process ['./seednode', ... +... +INFO:localnet:Starting process ['./node', ... +... +INFO:localnet:Starting process ['./proxy', ... +[PID=...] DEBUG[...] [process/block] started committing block ... +``` + +:::tip +The logs from all processes of the localnet can also be found in `~/my-first-localnet/localnet`. Simply look for `*.log` files. +::: + +:::important +Note that the proxy starts with a delay of about 30 seconds. +::: + + +## Halting and resuming the localnet + +In order to **halt the localnet**, press `Ctrl+C` in the terminal. This will stop all the processes (nodes, proxy etc.). The localnet can be **resumed at any time** by running again the command `mxpy localnet start` (from within your workspace). + + +## Removing the localnet + +In order to remove the localnet, run the command (from within your workspace): + +```bash +mxpy localnet clean +``` + +This will delete the `~/my-first-localnet/localnet` folder. Note that the configuration file (e.g. `localnet.toml`) will not be deleted automatically. + + +## Gaining more control over the localnet + + +### Perform setup steps individually + +If you want to have more control over the localnet, you can run the setup sub-commands individually, as described below. + +First, let's create a separate workspace (for the sake of this guide): + +```bash +mkdir -p ~/my-second-localnet && cd ~/my-second-localnet +``` + +Then, create a new configuration file for the localnet, as follows: + +```bash +mxpy localnet new +``` + +Upon running this command, a new file called `localnet.toml` will be added in the current directory. This file contains the default configuration of the localnet. **Make sure to open the file and inspect its content**. You should see something like this: + +``` +[general] +... +rounds_per_epoch = 100 +round_duration_milliseconds = 6000 + +[metashard] +... +num_validators = 1 + +[shards] +num_shards = 2 +... + +[networking] +... +port_proxy = 7950 +port_first_validator_rest_api = 10200 + +[software.mx_chain_go] +resolution = "remote" +archive_url = "https://github.com/multiversx/mx-chain-go/archive/refs/heads/master.zip" +... + +[software.mx_chain_proxy_go] +resolution = "remote" +archive_url = "https://github.com/multiversx/mx-chain-proxy-go/archive/refs/heads/master.zip" +... +``` + +:::tip +Generally speaking, it's a good idea to only alter the `localnet.toml` **before first starting a localnet**. Once the localnet is started, the configuration file should not be modified anymore (e.g. when halting and resuming the localnet). +::: + +Now, the following command will fetch the software prerequisites - **mx-chain-go**, **mx-chain-proxy-go** etc. - into `~/multiversx-sdk`: + +```bash +mxpy localnet prerequisites +``` + +:::tip +The `prerequisites` step is only necessary when the localnet depends on remote source code archives - i.e. at least one software prerequisite has `resolution = remote` in `localnet.toml`. **This is actually the default, and it's the easiest way to get started with the localnet.** Later on, we'll see how to create the localnet from local source code (for advanced use-cases). +::: + +Once the software prerequisites (source code) are fetched, we can build them: + +```bash +mxpy localnet build +``` + +:::tip +The actual build takes place within the download folders of the software prerequisites which, by default, are children of `~/multiversx-sdk/localnet_software_remote`. +::: + +Now let's configure (prepare) the localnet: + +```bash +mxpy localnet config +``` + +It is only upon running this command that the localnet subfolders are created. Make sure to inspect them: + +```bash +tree -L 3 ~/my-second-localnet +``` + +Example output: + +``` +├── localnet +│   ├── proxy +│   │   ├── config +│   │   └── proxy +│   ├── seednode +│   │   ├── config +│   │   ├── libwasmer_linux_amd64.so +│   | ├── ... +│   │   └── seednode +│   ├── validator00 +│   │   ├── config +│   │   ├── libwasmer_linux_amd64.so +│   | ├── ... +│   │   └── node +│   ├── validator01 +│   │   ├── config +│   │   ├── libwasmer_linux_amd64.so +│   | ├── ... +│   │   └── node +│   └── validator02 +│   ├── config +│   ├── libwasmer_linux_amd64.so +│   ├── ... +│   └── node +└── localnet.toml +``` + +We can then start, halt and resume the localnet as previously described. + + +### Altering chronology parameters + +Let's create a new localnet workspace: + +```bash +mkdir -p ~/my-localnet-with-altered-chronology && cd ~/my-localnet-with-altered-chronology +mxpy localnet new +``` + +**Before first starting a localnet**, you can alter the chronology parameters in `localnet.toml`. For example, let's have shorter epochs and shorter rounds: + +``` +[general] +rounds_per_epoch = 50 +round_duration_milliseconds = 4000 +``` + +Then, setup and start the localnet as previously described. + +```bash +mxpy localnet setup +mxpy localnet start +``` + + +### Altering sharding configuration + +Let's create a new localnet workspace: + +```bash +mkdir -p ~/my-localnet-with-altered-sharding && cd ~/my-localnet-with-altered-sharding +mxpy localnet new +``` + +**Before first starting a localnet**, you can alter the sharding configuration in `localnet.toml`. For example, let's have 3 shards, each with 2 validators and 1 observer (thus, 9 nodes, without the metachain ones): + +``` +[shards] +num_shards = 3 +consensus_size = 2 +num_observers_per_shard = 1 +num_validators_per_shard = 2 +``` + +Then, setup and start the localnet as previously described. + +```bash +mxpy localnet setup +mxpy localnet start +``` + + +### Building from local source code + +Let's create a new localnet workspace: + +```bash +mkdir -p ~/my-localnet-from-local-src && cd ~/my-localnet-from-local-src +mxpy localnet new +``` + +In order to build the **node** or the **proxy** from local source code (instead of fetching the source code from a remote archive), you can set `resolution = local` in `localnet.toml`. For example, let's build both the node and the proxy from local source code: + +``` +[software.mx_chain_go] +resolution = "local" +local_path = "~/Desktop/workspace/mx-chain-go" + +[software.mx_chain_proxy_go] +resolution = "local" +local_path = "~/Desktop/workspace/mx-chain-proxy-go" +``` + +Then, setup and start the localnet as previously described. + +```bash +mxpy localnet setup +mxpy localnet start +``` + + +## Test (development) wallets + +The development wallets **are minted at the genesis of the localnet** and their keys (both PEM files and Wallet JSON files) can be found in `~/multiversx-sdk/testwallets/latest/users`. + +:::caution +These wallets (Alice, Bob, Carol, ..., Mike) **are publicly known** - they should only be used for development and testing purpose. +::: + + +## Interacting with our localnet + + +### Sending transactions + +Let's send a simple transaction using **mxpy**: + +```bash +mxpy tx new --data="Hello, World" --gas-limit=70000 \ + --receiver=erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx \ + --pem=~/multiversx-sdk/testwallets/latest/users/alice.pem \ + --chain=localnet --proxy=http://localhost:7950 \ + --send +``` + +You should see the prepared transaction and the **transaction hash** in the `stdout` (or in the `--outfile` of your choice). Using the transaction hash, you can query the status of the transaction against the Proxy: + +```bash +curl http://localhost:7950/transaction/8b2cd8e61c12d6f02148537bdef40579c6cbff7ff0aba996f294d34a31992ba4 | jq +``` + + +### Deploying and interacting with Smart Contracts + +Let's deploy a Smart Contract using **mxpy**. + +```bash +mxpy --verbose contract deploy --bytecode=./contract.wasm \ + --gas-limit=5000000 \ + --pem=~/multiversx-sdk/testwallets/latest/users/alice.pem \ + --outfile=contract.json \ + --chain=localnet --proxy=http://localhost:7950 \ + --send +``` + +Upon deployment, you can check the status of the transaction and the existence of the Smart Contract: + +```bash +curl http://localhost:7950/transaction/4c5bd51ca0a051397bd6b0c89add2a5375106562b005c839f7e9bb113e2a8ce4 | jq +curl http://localhost:7950/address/erd1qqqqqqqqqqqqqpgqj5zftf3ef3gqm3gklcetpmxwg43rh8z2d8ss2e49aq | jq +``` + +If everything is fine (transaction status is `executed` and the `code` property of the address is set), you can interact with the deployed contract: + +```bash +mxpy --verbose contract call erd1qqqqqqqqqqqqqpgqj5zftf3ef3gqm3gklcetpmxwg43rh8z2d8ss2e49aq \ + --gas-limit=1000000 --function=increment \ + --pem=~/multiversx-sdk/testwallets/latest/users/alice.pem --outfile=myCall.json \ + --chain=localnet --proxy=http://localhost:7950 \ + --send +``` + +In order to perform queries against the contract using `mxpy`, do as follows: + +``` +mxpy --verbose contract query erd1qqqqqqqqqqqqqpgqj5zftf3ef3gqm3gklcetpmxwg43rh8z2d8ss2e49aq --function=get --proxy=http://localhost:7950 +``` + + +### Simulating transactions + +At times, you can simulate transactions instead of broadcasting them, by replacing the flag `--send` with the flag `--simulate`. For example: + +```bash +# Simulate: Call Contract +mxpy contract call erd1qqqqqqqqqqqqqpgqj5zftf3ef3gqm3gklcetpmxwg43rh8z2d8ss2e49aq \ + --gas-limit=1000000 --function=increment \ + --pem=~/multiversx-sdk/testwallets/latest/users/alice.pem \ + --chain=localnet --proxy=http://localhost:7950 \ + --simulate + +# Simulate: Simple Transfer +mxpy tx new --data="Hello, World" --gas-limit=70000 \ + --receiver=erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx \ + --pem=~/multiversx-sdk/testwallets/latest/users/alice.pem \ + --chain=localnet --proxy=http://localhost:7950 \ + --simulate +``` + +--- + +### Set up a Localnet (raw) + +How to set up a local MultiversX Testnet on a workstation. + + +## **Prerequisites** + +First, clone [mx-chain-go](https://github.com/multiversx/mx-chain-go) and [mx-chain-proxy-go](https://github.com/multiversx/mx-chain-proxy-go) in a directory of your choice. + +```bash +$ mkdir mytestnet && cd mytestnet +$ git clone git@github.com:multiversx/mx-chain-go.git +$ git clone git@github.com:multiversx/mx-chain-proxy-go.git +``` + +Then, run the `prerequisites` command. + +```bash +$ cd mx-chain-go/scripts/testnet +$ ./prerequisites.sh +``` + +This will install some packages and also clone the [mx-chain-deploy-go](https://github.com/multiversx/mx-chain-deploy-go) repository, as a sibling of the previously cloned `mx-chain-go`. + +Depending on your Linux distribution, you may need to run the following commands as well: + +```bash +sudo apt install tmux +sudo apt install gnome-terminal +``` + + +## **Configure the Testnet** + +The variables that dictate the structure of the Testnet are located in the file `scripts/testnet/variables.sh`. For example: + +```bash +export TESTNETDIR="$HOME/MultiversX/testnet" +export SHARDCOUNT=2 +... +``` + +You can override the default variables by creating a new file called `local.sh`, as a sibling of `variables.sh`. For example, in order to use a different directory than the default one: + +```bash +local.sh +export TESTNETDIR="$HOME/Desktop/mytestnet/sandbox" +export USETMUX=1 +export NODETERMUI=0 +``` + +Once ready with overriding the desired parameters, run the `config` command. + +```bash +$ ./config.sh +``` + +After that, you can inspect the generated configuration files in the specified folder: + +``` +$HOME/Desktop/mytestnet/sandbox +├── filegen +│ ├── filegen +│ └── output +│ ├── delegationWalletKey.pem +│ ├── delegators.pem +│ ├── genesis.json +│ ├── genesisSmartContracts.json +│ ├── nodesSetup.json +│ ├── validatorKey.pem +│ └── walletKey.pem +├── node +│ └── config +│ ├── api.toml +│ ├── config_observer.toml +│ ├── config_validator.toml +│ ├── delegationWalletKey.pem +│ ├── delegators.pem +│ ├── economics.toml +│ ├── external.toml +│ ├── gasSchedule.toml +│ ├── genesisContracts +│ │ ├── delegation.wasm +│ │ └── dns.wasm +│ ├── genesis.json +│ ├── genesisSmartContracts.json +│ ├── nodesSetup.json +│ ├── p2p.toml +│ ├── prefs.toml +│ ├── ratings.toml +│ ├── systemSmartContractsConfig.toml +│ ├── validatorKey.pem +│ └── walletKey.pem +├── node_working_dirs +├── proxy +│ └── config +│ ├── config.toml +│ ├── economics.toml +│ ├── external.toml +│ └── walletKey.pem +└── seednode + └── config + ├── config.toml + └── p2p.toml +``` + + +## **Starting and stopping the Testnet** + +In order to start the Testnet, run the `start` command. + +```bash +$ ./start.sh debug +``` + +After waiting about 1 minute, you can inspect the logs of the running nodes in folder `mytestnet/sandbox/node_working_dirs`. + +In order to stop the Testnet, run the `stop` command. + +```bash +$ ./stop.sh +``` + +If desired, you can also `pause` and `resume` the Testnet (without actually stopping the running nodes): + +```bash +$ ./pause.sh +$ ./resume.sh +``` + + +## **Recreating the Testnet** + +In order to destroy the Testnet, run the `clean` command: + +```bash +./stop.sh +./clean.sh +``` + +:::note Run config after clean +After running **clean,** you need to run **config** before **start**, in order to start the Testnet again. +::: + +If you need to recreate a Testnet from scratch, use the `reset` command (which also executes `clean` under the hood): + +```bash +$ ./reset.sh +``` + + +## **Inspecting the Proxy** + +By default, the local Testnet also includes a local MultiversX Proxy instance, listening on port **7950**. You can query in a browser or directly in the command line. Also see [REST API](/sdk-and-tools/rest-api/). + +```bash +$ curl http://localhost:7950/network/config +``` + +Given the request above, extract and save the fields `erd_chain_id` and `erd_min_transaction_version` from the response. You will need them in order to send transactions against your local Testnet. + + +## **Sending transactions** + +Let's send a simple transaction using **mxpy:** + +```bash +$ mxpy tx new --data="Hello, World" --gas-limit=70000 \ + --receiver=erd1... \ + --pem=./sandbox/node/config/walletKey.pem --pem-index=0 \ + --proxy=http://localhost:7950 \ + --send +``` + +You should see the prepared transaction and the **transaction hash** in the `stdout` (or in the `--outfile` of your choice). Using the transaction hash, you can query the status of the transaction against the Proxy: + +```bash +$ curl http://localhost:7950/transaction/1363... +``` + + +## **Deploying and interacting with Smart Contracts** + +Let's deploy a Smart Contract using **mxpy**. + +```bash +Deploy +mxpy --verbose contract deploy --bytecode=./mycontract/output/contract.wasm \ + --gas-limit=5000000 \ + --pem=./sandbox/node/config/walletKey.pem --pem-index=0 \ + --outfile=contract.json \ + --proxy=http://localhost:7950 \ + --send +``` + +Upon deployment, you can check the status of the transaction and the existence of the Smart Contract: + +```bash +$ curl http://localhost:7950/transaction/daf2... +$ curl http://localhost:7950/address/erd1qqqqqqqqqqqqqpgql... +``` +If everything is fine (transaction status is `executed` and the `code` property of the address is set), you can interact with or perform queries against the deployed contract: + +```bash +Call +mxpy --verbose contract call erd1qqqqqqqqqqqqqpgql... \ + --gas-limit=1000000 --function=increment \ + --pem=./sandbox/node/config/walletKey.pem --pem-index=0 --outfile=myCall.json \ + --proxy=http://localhost:7950 \ + --send + +``` + +```bash +Query +mxpy --verbose contract query erd1qqqqqqqqqqqqqpgqlq... \ + --function=get \ + --proxy=http://localhost:7950 +``` + +--- + +### Setup & Basics + +Write, build and deploy a simple smart contract written in Rust. + +This tutorial will guide you through the process of writing, building and deploying a simple smart contract for the MultiversX Network, written in Rust. + +:::important +The MultiversX Network supports smart contracts written in any programming language compiled into WebAssembly. +::: + + +## Scenario + +Let's say you need to raise EGLD for a cause you believe in. They will obviously be well spent, but you need to get the EGLD first. For this reason, you decided to run a crowdfunding campaign on the MultiversX Network, which naturally means that you will use a smart contract for the campaign. This tutorial will teach you how to do just that: **write a crowdfunding smart contract, deploy it, and use it**. + +The idea is simple: the smart contract will accept transfers until a deadline is reached, tracking all contributors. + +If the deadline is reached and the smart contract has gathered an amount of EGLD above the desired funds, then the smart contract will consider the crowdfunding a success, and it will consequently send all the EGLD to a predetermined account (yours!). + +However, if the donations fall short of the target, the contract will return all the all EGLD tokens to the donors. + + +## Design + +Here is how the smart contract methods are designed: + +- `init`: automatically triggered when the contract is deployed. It takes two inputs from you: + 1. The target amount of EGLD you want to raise; + 2. The crowdfunding deadline, which is expressed as a block nonce. +- `fund`: used by donors to contribute EGLD to the campaign. It will receive EGLD and save the necessary details so the contract can return funds if the campaign doesn't reach its goal; +- `claim`: if called before the deadline, it does nothing and returns an error. If called after the deadline: + - By you (the campaign creator), it sends all the raised EGLDs to your account if the target amount is met. Otherwise, it returns an error; + - By donor, it refunds their contribution if the target amount is not reached. If the target is met, it does nothing and returns an error; + - By anyone else, it does nothing and returns an error. +- `status`: Provides information about the campaign, such as whether it is still active or completed and how much EGLD has been raised so far. You will likely use this frequently to monitor progress. + +In this part of the tutorial, we will start with the `init` method to familiarize you with the development process and tools. You will not only implement the init method but also **tests** to ensure it works as expected. + +:::important testing +Automated testing is exceptionally important for the development of smart contracts, due to the sensitive nature of the information they must handle. +::: + + +## Prerequisites + +:::important +Before starting this tutorial, make sure you have the following: + +- `stable` **Rust** version `≥ 1.85.0` (install via [rustup](/docs/developers/toolchain-setup.md#installing-rust-and-sc-meta)) +- `sc-meta` (install [multiversx-sc-meta](/docs/developers/meta/sc-meta-cli.md)) + +::: + +For contract developers, we generally recommend [**VSCode**](https://code.visualstudio.com) with the following extensions: + +- [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer) +- [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb) + + +## Step 1: prepare the workspace + +The source code of each smart contract requires its own folder. We will start the development of the **crowdfunding** contract from the **empty** template. To get the development environment ready, simply run the following commands in your terminal: + +```bash +sc-meta new --name crowdfunding --template empty +``` + +You may choose any location you want for your smart contract. Either way, now that you are in the `crowdfunding` folder we can begin. + +[sc-meta](/docs/developers/meta/sc-meta.md) created your project out of a template. These templates are contracts written and tested by MultiversX, which can be used by anybody as starting points. + +```toml title=Cargo.toml +[package] +name = "crowdfunding" +version = "0.0.0" +publish = false +edition = "2024" +authors = ["you"] + +[lib] +path = "src/crowdfunding.rs" + +[dependencies.multiversx-sc] +version = "0.64.1" + +[dev-dependencies.multiversx-sc-scenario] +version = "0.64.1" + +[workspace] +members = [ + ".", + "meta", +] +``` + +Let's inspect the file found at path `crowdfunding/Cargo.toml`: + +- `[package]` represents the **project** which is unsurprisingly named `crowdfunding`, and has version `0.0.0`. You can set any version you like, just make sure it has 3 numbers separated by dots. It is a requirement. The `publish` is set to **false** to prevent the package from being published to Rust’s central package registry. It's useful for private or experimental projects; +- `[lib]` declares the source code of the smart contracts, which in our case is `src/crowdfunding.rs`. You can name this file anything you want. The default Rust naming is `lib.rs`, but it can be easier to organize your code when the main code files bear the names of the contracts; +- This project has `dependencies` and `dev-dependencies`. You'll need a few special and very helpful packages: + - `multiversx-sc`: developed by MultiversX, it is the interface that the smart contract sees and can use; + - `multiversx-sc-scenario`: developed by MultiversX, it is the interface that defines and runs blockchain scenarios involving smart contracts; + - `num-bigint`: for working with arbitrarily large integers. +- `[workspace]` is a group of related Rust projects that share common dependencies or build settings; +- The resulting binary will be the name of the project, which in our case is `crowdfunding` (actually, `crowdfunding.wasm`, but the compiler will add the `.wasm` part). + + +## Step 2: develop + +With the structure in place, you can now write the code and build it. + +Open `src/crowdfunding.rs`: + +```rust +#![no_std] // [1] + +use multiversx_sc::imports::*; // [2] +#[allow(unused_imports)] // [3] + +/// An empty contract. To be used as a template when starting a new contract from scratch. +#[multiversx_sc::contract] // [4] +pub trait Crowdfunding { // [5] + #[init] // [6] + fn init(&self) {} // [7] + + #[upgrade] // [8] + fn upgrade(&self) {} // [9] +} +``` + +Let's take a look at the code: + +- **[1]**: means that the smart contract **has no access to standard libraries**. That will make the code lean and very light. +- **[2]**: brings imports module from the [multiversx_sc](https://crates.io/crates/multiversx-sc) crate into **Crowdfunding** contract. It effectively grants you access to the [MultiversX framework for Rust smart contracts](https://github.com/multiversx/mx-sdk-rs), which is designed to simplify the code **enormously**. +- **[3]**: since the contract is still in an early stage of development, clippy (Rust's linter) will flag some imports as unused. For now, we will ignore this kind of error. +- **[4]**: processes the **Crowdfunding** trait definition as a **smart contract** that can be deployed on the MultiversX blockchain. +- **[5]**: the contract [trait](https://doc.rust-lang.org/book/ch10-02-traits.html) where all the endpoints will be developed. +- **[6]**: marks the following method (`init`) as the constructor function for the contract. +- **[7]**: this is the constructor itself. It receives the contract's instance as a parameter (_&self_). The method is called once the contract is deployed on the MultiversX blockchain. You can name it any way you wish, but it must be annotated with `#[init]`. For the moment, no initialization logic is defined. +- **[8]**: marks the following method (`upgrade`) as the upgrade function for the contract. It is called when the contract is re-deployed to the same address. +- **[9]**: this is the upgrade method itself. Similar to [7], it takes a reference to the contract instance (_&self_) and performs no specific logic here. + + +## Step 3: build + +Now go back to the terminal, make sure the current folder is the one containing the Crowdfunding smart contract (`crowdfunding/`), then trigger the **build** command: + +```bash +sc-meta all build +``` + +If this is the first time you build a Rust smart contract with the `sc-meta` command, it will take a little while before it's done. Subsequent builds will be much faster. + +When the command completes, a new folder will appear: `crowdfunding/output/`. This folder contains: + +1. `crowdfunding.abi.json` +2. `crowdfunding.imports.json` +3. `crowdfunding.mxsc.json` +4. `crowdfunding.wasm` + +We won't be doing anything with these files just yet - wait until we get to the deployment part. Along with `crowdfunding/output/`, there are a few other folders and files generated. You can safely ignore them for now, but do not delete the `/crowdfunding/wasm/` folder - it's what makes the build command faster after the initial run. + +The following can be safely deleted, as they are not important for this contract: + +- The `scenarios/` folder; +- The `crowdfunding/tests/crowdfunding_scenario_go_test.rs` file; +- The `crowdfunding/tests/crowdfunding_scenario_rs_test.rs` file. + +The structure of your folder should be like this (output printed using command `tree -L 2`): + +```bash +. +├── Cargo.lock +├── Cargo.toml +├── meta +│ ├── Cargo.toml +│ └── src +├── multiversx.json +├── output +│ ├── crowdfunding.abi.json +│ ├── crowdfunding.imports.json +│ ├── crowdfunding.mxsc.json +│ └── crowdfunding.wasm +├── src +│ └── crowdfunding.rs +├── target +│ ├── CACHEDIR.TAG +│ ├── debug +│ ├── release +│ ├── tmp +│ └── wasm32-unknown-unknown +├── tests +└── wasm + ├── Cargo.lock + ├── Cargo.toml + └── src +``` + +It's time to add some functionality to the `init` function now. + + +## Step 4: persisting values + +In this step, you will use the `init` method to persist some values in the storage of the Crowdfunding smart contract. + + +### Storage mappers + +Every smart contract can store key-value pairs in a persistent structure, created for the smart contract at its deployment on the MultiversX Network. + +The storage of a smart contract is, for all intents and purposes, **a generic hash map or dictionary**. When you want to store some arbitrary value, you store it under a specific key. To get the value back, you need to know the key you stored it under. + +To help you keep the code clean, the framework enables you to write **setter** and **getter** methods for individual key-value pairs. There are several ways to interact with storage from a contract, but the simplest one is by using [**storage mappers**](/docs/developers/developer-reference/storage-mappers.md). + +Next, you will declare a [_SingleValueMapper_](/docs/developers/developer-reference/storage-mappers.md#singlevaluemapper) that has the purpose of storing a [_BigUint_](/docs/developers/best-practices/biguint-operations.md) number. This storage mapper is dedicated to storing/retrieving the value stored under the key `target`: + +```rust +#[storage_mapper("target")] +fn target(&self) -> SingleValueMapper; +``` + +:::important +`BigUint` **type** is a big unsigned number, handled by the VM. There is no need to import any library, big number arithmetic is provided for all contracts out of the box. +::: + +Normally, smart contract developers are used to dealing with raw bytes when storing or loading values from storage. The MultiversX framework for Rust smart contracts makes it far easier to manage the storage because it can handle typed values automatically. + + +### Extend init + +You will now instruct the `init` method to store the amount of tokens that should be gathered upon deployment. + +The owner of a smart contract is the account that deployed it (you). By design, your Crowdfunding smart contract will send all the donated EGLD to its owner (you), assuming the target amount was reached. Nobody else has this privilege, because there is only one single owner of any given smart contract. + +Here's how the `init` method looks, with the code that saves the target: + +```Rust +#[init] +fn init(&self, target: BigUint) { + self.target().set(&target); +} +``` + +We have added an argument to the constructor method. It is called `target` and will need to be supplied when we deploy the contract. The argument then promptly gets saved to storage. + +Now note the `self.target()` invocation. This gives us an object that acts like a proxy for a part of the storage. Calling the `.set()` method on it causes the value to be saved to the contract storage. + +:::important +All of the stored values end up in the storage if the transaction completes successfully. Smart contracts cannot access the protocol directly, it is the VM that intermediates everything. +::: + +Whenever you want to make sure your code is in order, run the build command: + +```bash +sc-meta all build +``` + +There's one more thing: by default, none of the `fn` statements declare smart contract methods that are _externally callable_. All the data in the contract is publicly available, but it can be cumbersome to search through the contract storage manually. That is why it is often nice to make getters public, so people can call them to get specific data out. + +Public methods are annotated with either `#[endpoint]` or `#[view]`. There is currently no difference in functionality between them (but there might be at some point in the future). Semantically, `#[view]` indicates readonly methods, while `#[endpoint]` suggests that the method also changes the contract state. + +```rust + #[view] + #[storage_mapper("target")] + fn target(&self) -> SingleValueMapper; +``` + +You can also think of `#[init]` as a special type of endpoint. + + +## Step 5: testing + +You must always make sure that the code you write functions as intended. That's what **automated testing** is for. + +For now, this is how your contract looks: + +```rust +#![no_std] + +#[allow(unused_imports)] +use multiversx_sc::imports::*; + +#[multiversx_sc::contract] +pub trait Crowdfunding { + #[init] + fn init(&self, target: BigUint) { + self.target().set(&target); + } + + #[upgrade] + fn upgrade(&self) {} + + #[view] + #[storage_mapper("target")] + fn target(&self) -> SingleValueMapper; +} +``` + +There are several ways to write [smart contract tests in Rust](/docs/developers/testing/rust/sc-test-overview.md). Now, we will focus on developing a test using [black-box calls](/docs/developers/testing/rust/sc-blackbox-calls.md). + +:::important +Blackbox tests execution imitates the blockchain with no access to private contract functions. +::: + +Let's write a test against the `init` method to make sure that it definitely stores the address of the owner under the `target` key at deployment. + + +### Set up + +In the folder of the Crowdfunding smart contract, there is a folder called `tests/`. Inside it, create a new Rust file called `crowdfunding_blackbox_test.rs`. + +Your folder should look like this (output from the command `tree -L 2`): + +```bash +. +├── Cargo.lock +├── Cargo.toml +├── meta +│ ├── Cargo.toml +│ └── src +├── multiversx.json +├── output +│ ├── crowdfunding.abi.json +│ ├── crowdfunding.imports.json +│ ├── crowdfunding.mxsc.json +│ └── crowdfunding.wasm +├── src +│ └── crowdfunding.rs +├── target +│ ├── CACHEDIR.TAG +│ ├── debug +│ ├── release +│ ├── tmp +│ └── wasm32-unknown-unknown +├── tests +│ └── crowdfunding_blackbox_test.rs +└── wasm + ├── Cargo.lock + ├── Cargo.toml + └── src +``` + +Before creating the first test, we need to [set up the environment](/docs/developers/testing/rust/sc-test-setup.md). We will: + +1. Generate the smart contract's proxy; +2. Register the contract; +3. Set up accounts. + + +### Generate Proxy + +A smart contract's [proxy](/docs/developers/transactions/tx-proxies.md) is an object that mimics the contract. We will use the proxy to call the endpoints of the Crowdfunding contracts. + +The proxy contains entirely autogenerated code. However, before running the command to generate the proxy, we need to set up a configuration file. + +In the root of the contract, at the path `crowdfunding/`, we will create the configuration file `sc-config.toml`, where we will specify the path to generate the proxy: + +```toml title=sc-config.toml +[settings] + +[[proxy]] +path = "src/crowdfunding_proxy.rs" +``` + +In the terminal, in the root of the contract, we will run the next command that will generate the proxy for the Crowdfunding smart contract: + +```bash +sc-meta all proxy +``` + +Once the proxy is generated, our work is not over yet. The next thing to do is to import the module in the Crowdfunding smart contract's code: + +```rust +#![no_std] + +#[allow(unused_imports)] +use multiversx_sc::imports::*; + +pub mod crowdfunding_proxy; + +#[multiversx_sc::contract] +pub trait Crowdfunding { + // Here is the implementation of the crowdfunding contract +} +``` + +With each build of the contract executed by the developer, the proxy will be automatically updated with the changes made to the contract. + + +### Register + +The Rust backend does not run compiled contracts, instead, it hooks the actual Rust contract code to its engine. You can find more [here](/docs/developers/testing/rust/sc-test-setup.md#registering-contracts). + +In order to link the smart contract code to the test you are developing, you need to call `register_contract()` in the setup function of the blackbox test. + +```rust +use crowdfunding::crowdfunding_proxy; +use multiversx_sc_scenario::imports::*; + +const CODE_PATH: MxscPath = MxscPath::new("output/crowdfunding.mxsc.json"); + +fn world() -> ScenarioWorld { + let mut blockchain = ScenarioWorld::new(); + + blockchain.set_current_dir_from_workspace("crowdfunding"); + blockchain.register_contract(CODE_PATH, crowdfunding::ContractBuilder); + blockchain +} +``` + +### Account + +The environment you're working in is a mocked blockchain. This means you have to create and manage accounts, allowing you to test and verify the behavior of your functions without deploying to a real blockchain. + +Here's an example to get started in `crowdfunding_blackbox_test.rs`: + +```rust +const OWNER: TestAddress = TestAddress::new("owner"); + +#[test] +fn crowdfunding_deploy_test() { + let mut world = world(); + + world.account(OWNER).nonce(0).balance(1000000); +} +``` + +In the snippet above, we've added only one account to the fictional universe of Crowdfunding smart contract. It is an account with the address `owner`, which the testing environment will use to pretend it's you. Note that in this fictional universe, your account nonce is `0` (meaning you've never used this account yet) and your `balance` is `1,000,000`. + +:::important +No transaction can start if that account does not exist in the mocked blockchain. More explanations can be found [here](/docs/developers/testing/rust/sc-test-setup.md#setting-accounts). +::: + +### Deploy + +The purpose of the account created previously is to act as the owner of the Crowdfunding smart contract. To make this happen, the **OWNER** constant will serve as the transaction **sender**. + +```rust +const CROWDFUNDING_ADDRESS: TestSCAddress = TestSCAddress::new("crowdfunding"); + +#[test] +fn crowdfunding_deploy_test() { + /* + Set up account + */ + + let crowdfunding_address = world + .tx() + .from(OWNER) + .typed(crowdfunding_proxy::CrowdfundingProxy) + .init(500_000_000_000u64) + .code(CODE_PATH) + .new_address(CROWDFUNDING_ADDRESS) + .returns(ReturnsNewAddress) + .run(); +} +``` + +The transaction above is a deploy call that stores in `target` value `500,000,000,000`. It was fictionally submitted by "you", using your account with the address `owner`. + +`.new_address(CROWDFUNDING_ADDRESS)` marks that the address of the deployed contracts will be the value stored in the **CROWDFUNDING_ADDRESS** constant. + +`.code(CODE_PATH)` explicitly sets the deployment Crowdfunding's code source as bytes. + +:::note +Deploy calls are specified by the code source. You can find more details about what data needs a transaction [here](/docs/developers/transactions/tx-data.md). +::: + +:::important +Remember to run `sc-meta all build` before running the test, especially if you made recent changes to the smart contract source code! Code source will be read directly from the file you specify through the **MxscPath** constant, without rebuilding it automatically. +::: + +### Checks + +What's the purpose of testing if we do not validate the behavior of the entities interacting with the blockchain? Let's take the next step by enhancing the `crowdfunding_deploy_test()` function to include verification operations. + +Once the deployment is executed, we will verify if: + +- The **contract address** is **CROWDFUNDING_ADDRESS**; +- The **owner** has no less EGLD than the value with which it was initialized: `1,000,000`; +- `target` contains the value set at deployment: `500,000,000,000`. + +```rust +#[test] +fn crowdfunding_deploy_test() { + /* + Set up account + Deploy + */ + + assert_eq!(crowdfunding_address, CROWDFUNDING_ADDRESS.to_address()); + + world.check_account(OWNER).balance(1_000_000); + + world + .query() + .to(CROWDFUNDING_ADDRESS) + .typed(crowdfunding_proxy::CrowdfundingProxy) + .target() + .returns(ExpectValue(500_000_000_000u64)) + .run(); +} +``` + +Notice that there are two accounts now, not just one. There's evidently the account `owner` and the new account `crowdfunding`, as a result of the deployment transaction. + +:::important +Smart contracts _are_ accounts in the MultiversX Network, accounts with associated code, which can be executed when transactions are sent to them. +::: + +The **owner's balance** remains unchanged - the deployment transaction did not cost anything, because the gas price is set to `0` in the **testing environment**. + +:::note +The `.check_account(OWNER)` method verifies whether an account exists at the specified address and checks its ownership details. Details available [here](/docs/developers/testing/rust/sc-blackbox-example.md#check-accounts). +::: + +:::note +The `.query()` method is used to interact with the smart contract's view functions via the proxy, retrieving information without modifying the blockchain state. + +There is no caller, no payment, and gas price/gas limit. On the real blockchain, a smart contract query does not create a transaction on the blockchain, so no account is needed. Details available [here](/docs/developers/testing/rust/sc-blackbox-calls.md#query). +::: + +## Run test + +Do you want to try it out first? Go ahead and issue this command on your terminal at path `/crowdfunding`: + +```bash +cargo test +``` + +If everything went well, you should see the following being printed: + +```rust +running 1 test +test crowdfunding_deploy_test ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.05s +``` + +You need to understand the contents of this blackbox test - again, the importance of testing your smart contracts cannot be overstated. + + +## Next up + +The tutorial will continue with defining of the `fund`, `claim` and `status` function, and will guide you through writing [blackbox tests](/docs/developers//testing/rust/sc-blackbox-calls.md) for them. + +--- + +### 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#signing-objects) +- [Signing objects using **sdk-py**](/sdk-and-tools/sdk-py#signing-objects) + +--- + +### Signing Providers for dApps + +This page will guide you through the process of integrating the **sdk-js signing providers** in a dApp which isn't based on `sdk-dapp`. + +:::important +Note that for most purposes, **we recommend using [sdk-dapp](https://github.com/multiversx/mx-sdk-dapp)** instead of integrating the signing providers on your own. +::: + + +## Anatomy of a signing provider + +Generally speaking, a signing provider is a component that supports the following use-cases: + +- **Log in (trivial flow, not recommended)**: the user of a dApp is asked her MultiversX identity. The user reaches the wallet, unlocks it, and confirms the login. The flow continues back to the dApp, which is now informed about the user's blockchain address. Note, though, that this piece of information is not authenticated: the dApp receives a _hint_ about the user's address, not a _guarantee_ (proof). Sometimes (though rarely), this is enough. If in doubt, always have your users login using the **native authentication** flow (see below). +- **Log in using native authentication (recommended)**: once the user decides to log in, the dApp crafts a special piece of data called _the native authentication initial part_ - a shortly-lived artifact that contains, among others, a marker of the originating dApp and a marker of the target Network. The user is given this piece of data and she is asked to sign it, to prove her MultiversX identity. The user then reaches the wallet, which unwraps and (partly) displays the payload of _the native authentication initial part_. The user unlocks the wallet and confirms the login - under the hood, the _part_ is signed with the user's secret key. The flow continues back to the dApp, which now receives the user's blockchain address, along with a proof (signature). Then, the dApp (e.g. maybe a server-side component) can verify the signature to make sure that the user is indeed the owner of the address. +- **Log out**: once the user decides to log out from the dApp, the latter should ask the wallet to do so. Once the user is signed out, the flow continues back to the dApp. +- **Sign transactions**: while interacting with the dApp, the user might be asked to sign one or more transactions. The user reaches the wallet, unlocks it again if necessary, and confirms the signing. The flow continues back to the dApp, which receives the signed transactions, ready to be broadcasted to the Network. +- **Sign messages**: while interacting with the dApp, the user might be asked to sign an arbitrary message. The user reaches the wallet, unlocks it again if necessary, and confirms the signing. The flow continues back to the dApp, which receives the signed message. + + +## Implementations (available providers) + +For MultiversX dApps, the following signing providers are available: + +- [Web wallet provider](https://github.com/multiversx/mx-sdk-js-web-wallet-provider), compatible with the [Web Wallet](/wallet/web-wallet) and [xAlias](/wallet/xalias) +- [Browser extension provider](https://github.com/multiversx/mx-sdk-js-extension-provider), compatible with the [MultiversX DeFi Wallet](/wallet/wallet-extension) +- [WalletConnect provider](https://github.com/multiversx/mx-sdk-js-wallet-connect-provider), compatible with [xPortal App](/wallet/xportal) +- [Hardware wallet provider](https://github.com/multiversx/mx-sdk-js-hw-provider), compatible with [Ledger](/wallet/ledger) + +:::important +The code samples depicted on this page are written in JavaScript, and can also be found [on GitHub](https://github.com/multiversx/mx-sdk-js-examples/tree/main/signing-providers). There, the signing providers are integrated into a basic web page (for example purposes). +::: + + +## The Web Wallet Provider + +:::note +Make sure you have a look over the [webhooks](/wallet/webhooks), in advance. +::: + +[`@multiversx/sdk-web-wallet-provider`](https://github.com/multiversx/mx-sdk-js-web-wallet-provider) allows the users of a dApp to log in and sign data using the [Web Wallet](/wallet/web-wallet) or [xAlias](/wallet/xalias). + +:::important +Remember that [xAlias](/wallet/xalias) exposes **the same [URL hooks and callbacks](/wallet/webhooks)** as the [Web Wallet](/wallet/web-wallet). Therefore, integrating xAlias is **identical to integrating the Web Wallet** - with one trivial exception: the configuration of the URL base (see below). +::: + +In order to create an instance of the provider that talks to the **Web Wallet**, do as follows: + +```js +import { WalletProvider, WALLET_PROVIDER_DEVNET } from "@multiversx/sdk-web-wallet-provider"; + +const provider = new WalletProvider(WALLET_PROVIDER_DEVNET); +``` + +Or, for **xAlias**: + +```js +import { WalletProvider, XALIAS_PROVIDER_DEVNET } from "@multiversx/sdk-web-wallet-provider"; + +const provider = new WalletProvider(XALIAS_PROVIDER_DEVNET); +``` + +The following provider URLs [are defined](https://github.com/multiversx/mx-sdk-js-web-wallet-provider/blob/main/src/constants.ts) by the package: + +- `WALLET_PROVIDER_TESTNET`, `WALLET_PROVIDER_DEVNET`, `WALLET_PROVIDER_MAINNET` +- `XALIAS_PROVIDER_MAINNET`, `XALIAS_PROVIDER_DEVNET`, `XALIAS_PROVIDER_TESTNET` + + +### Login and logout {#wallet-login-and-logout} + +Then, ask the user to log in: + +```js +const callbackUrl = encodeURIComponent("http://my-dapp"); +await provider.login({ callbackUrl }); +``` + +Once the user opens her wallet, the web wallet issues a redirected back to `callbackUrl`, along with the **address** of the user. You can get the address as follows: + +```js +import qs from "qs"; + +const queryString = window.location.search.slice(1); +const queryStringParams = qs.parse(queryString); +console.log(queryStringParams.address); +``` + +In order to log out, do as follows: + +```js +const callbackUrl = window.location.href.split("?")[0]; +await provider.logout({ callbackUrl: callbackUrl }); +``` + +Though, most often, the dApps (or their server-side components) want to **reliably assign an off-chain user identity to a MultiversX address**. For this, the signing providers support an extra parameter to the `login()` method: **the initial part of the native authentication token**. That piece of data, generally crafted with the aid of [`sdk-native-auth-client`](https://www.npmjs.com/package/@multiversx/sdk-native-auth-client), is signed with the user's wallet at login-time, and the signature is made available to the dApp, which, in turn, packs it into the actual **native authentication token**. + +:::important +We always recommend using the **native authentication** flow, instead of the trivial one. That is, always pass the `token` parameter to the `login()` method. This is applicable to all signing providers. +::: + +```js +import { NativeAuthClient } from "@multiversx/sdk-native-auth-client"; + +const nativeAuthClient = new NativeAuthClient({ ... }); +const nativeAuthInitialPart = await nativeAuthClient.initialize(); + +const callbackUrl = encodeURIComponent("https://my-dapp/on-wallet-login"); +await provider.login({ callbackUrl, token: nativeAuthInitialPart }); +``` + +Once the flow returns to the dApp, the `address` and `signature` parameters are available. The actual **native authentication token** is obtained upon an additional processing step - a re-packing, by means of `NativeAuthClient.getToken()`: + +```js +const address = queryStringParams.address; +const signature = queryStringParams.signature; +const nativeAuthToken = nativeAuthClient.getToken(address, nativeAuthInitialPart, signature); +``` + + +### Signing transactions {#wallet-signing-transactions} + +Transactions can be signed as follows: + +```js +import { Transaction } from "@multiversx/sdk-core"; + +const firstTransaction = new Transaction({ ... }); +const secondTransaction = new Transaction({ ... }); + +await provider.signTransactions( + [firstTransaction, secondTransaction], + { callbackUrl: callbackUrl } +); +``` + +Upon signing the transactions, the user is redirected back to `callbackUrl`, while the _query string_ contains information about the transactions, including their signatures. The information can be used to reconstruct `Transaction` objects using `getTransactionsFromWalletUrl()`: + +```js +const plainSignedTransactions = provider.getTransactionsFromWalletUrl(); +``` + +:::important +The following workaround is subject to change. +::: + +As of January 2024, this signing provider returns the data field as a plain string. However, sdk-js' `Transaction.fromPlainObject()` expects it to be base64-encoded. Therefore, we need to apply a workaround (an additional conversion) on the results of `getTransactionsFromWalletUrl()`. + +```js +for (const plainTransaction of plainSignedTransactions) { + const plainTransactionClone = structuredClone(plainTransaction); + plainTransactionClone.data = Buffer.from(plainTransactionClone.data).toString("base64"); + const transaction = Transaction.fromPlainObject(plainTransactionClone); + + // "transaction" can now be broadcasted. +} +``` + + +### Signing messages {#wallet-signing-messages} + +Messages can be signed as follows: + +```js +import { SignableMessage } from "@multiversx/sdk-core"; + +const message = new SignableMessage({ message: "hello" }); +await provider.signMessage(message, { callbackUrl }); +``` + +Upon signing the transactions, the user is redirected back to `callbackUrl`, while the _query string_ includes the signature of the message. The signature can be retrieved as follows: + +```js +const signature = provider.getMessageSignatureFromWalletUrl(); +``` + + +## The browser extension provider + +:::note +Make sure you have a look over [this page](/wallet/wallet-extension), in advance. +::: + +[`@multiversx/sdk-js-extension-provider`](https://github.com/multiversx/mx-sdk-sdk-js-extension-provider) allows the users of a dApp to log in and sign transactions using the [MultiversX DeFi Wallet](/wallet/wallet-extension). + +In order to acquire the instance (singleton) of the provider, do as follows: + +```js +import { ExtensionProvider } from "@multiversx/sdk-extension-provider"; + +const provider = ExtensionProvider.getInstance(); +``` + +Before performing any operation, make sure to initialize the provider: + +```js +await provider.init(); +``` + + +### Login and logout {#extension-login-and-logout} + +Then, ask the user to log in: + +```js +const address = await provider.login(); + +console.log(address); +console.log(provider.account); +``` + +In order to log out, do as follows: + +```js +await provider.logout(); +``` + +The `login()` method supports the `token` parameter, for **the native authentication flow** (always recommended, see above): + +```js +const nativeAuthInitialPart = await nativeAuthClient.initialize(); +await provider.login({ token: nativeAuthInitialPart }); + +const address = provider.account.address; +const signature = provider.account.signature; +const nativeAuthToken = nativeAuthClient.getToken(address, nativeAuthInitialPart, signature); +``` + + +### Signing transactions {#extension-signing-transactions} + +Transactions can be signed as follows: + +```js +import { Transaction } from "@multiversx/sdk-core"; + +const firstTransaction = new Transaction({ ... }); +const secondTransaction = new Transaction({ ... }); + +await provider.signTransactions([firstTransaction, secondTransaction]); + +// "firstTransaction" and "secondTransaction" can now be broadcasted. +``` + + +### Signing messages {#extension-signing-messages} + +Arbitrary messages can be signed as follows: + +```js +import { SignableMessage } from "@multiversx/sdk-core"; + +const message = new SignableMessage({ + message: Buffer.from("hello") +}); + +await provider.signMessage(message); + +console.log(message.toJSON()); +``` + + +## The WalletConnect provider + +[`@multiversx/sdk-js-wallet-connect-provider`](https://github.com/multiversx/mx-sdk-js-wallet-connect-provider) allows the users of a dApp to log in and sign transactions using [xPortal](https://xportal.com/) (the mobile application). + +For this example we will use the WalletConnect 2.0 provider since 1.0 is no longer maintained and it is [deprecated](https://medium.com/walletconnect/weve-reset-the-clock-on-the-walletconnect-v1-0-shutdown-now-scheduled-for-june-28-2023-ead2d953b595) + +First, let's see a (simple) way to build a QR dialog using [`qrcode`](https://www.npmjs.com/package/qrcode) (and bootstrap): + +```js +import QRCode from "qrcode"; + +async function openModal(connectorUri) { + const svg = await QRCode.toString(connectorUri, { type: "svg" }); + + // The referenced elements must be added to your page, in advance + $("#MyWalletConnectQRContainer").html(svg); + $("#MyWalletConnectModal").modal("show"); +} + +function closeModal() { + $("#MyWalletConnectModal").modal("hide"); +} +``` + +In order to create an instance of the provider, do as follows: + +```js +import { WalletConnectV2Provider } from "@multiversx/sdk-wallet-connect-provider"; + +// Generate your own WalletConnect 2 ProjectId here: +// https://cloud.walletconnect.com/app +const projectId = "9b1a9564f91cb659ffe21b73d5c4e2d8"; +// The default WalletConnect V2 Cloud Relay +const relayUrl = "wss://relay.walletconnect.com"; +// T for Testnet, D for Devnet and 1 for Mainnet +const chainId = "T" + +const callbacks = { + onClientLogin: async function () { + // closeModal() is defined above + closeModal(); + const address = await provider.getAddress(); + console.log("Address:", address); + }, + onClientLogout: async function () { + console.log("onClientLogout()"); + }, + onClientEvent: async function (event) { + console.log("onClientEvent()", event); + } +}; + +const provider = new WalletConnectProvider(callbacks, chainId, relayUrl, projectId); +``` + +:::note +You can customize the Core WalletConnect functionality by passing `WalletConnectProvider` an optional 5th parameter: `options` +For example `metadata` and `storage` for [React Native](https://docs.walletconnect.com/2.0/javascript/guides/react-native) or `{ logger: 'debug' }` for a detailed under the hood logging +::: + +Before performing any operation, make sure to initialize the provider: + +```js +await provider.init(); +``` + + +### Login and logout {#walletconnect-login-and-logout} + +Then, ask the user to log in using xPortal on her phone: + +```js +const { uri, approval } = await provider.connect(); +// connect will provide the uri required for the qr code display +// and an approval Promise that will return the connection session +// once the user confirms the login + +// openModal() is defined above +openModal(uri); + +// pass the approval Promise +await provider.login({ approval }); +``` + +The `login()` method supports the `token` parameter, for **the native authentication flow** (always recommended, see above): + +```js +const nativeAuthInitialPart = await nativeAuthClient.initialize(); +await provider.login({ approval, token: nativeAuthInitialPart }); + +const address = await provider.getAddress(); +const signature = await provider.getSignature(); +const nativeAuthToken = nativeAuthClient.getToken(address, nativeAuthInitialPart, signature); +``` + +:::important +The pairing proposal between a wallet and a dapp is made using an [URI](https://docs.walletconnect.com/2.0/specs/clients/core/pairing/pairing-uri). In WalletConnect v2.0 the session and pairing are decoupled from each other. This means that a URI is shared to construct a pairing proposal, and only after settling the pairing the dapp can propose a session using that pairing. In simpler words, the dapp generates an URI that can be used by the wallet for pairing. +::: + +Once the user confirms the login, the `onClientLogin()` callback (declared above) is executed. + +In order to log out, do as follows: + +```js +await provider.logout(); +``` + + +### Signing transactions {#walletconnect-signing-transactions} + +Transactions can be signed as follows: + +```js +import { Transaction } from "@multiversx/sdk-core"; + +const firstTransaction = new Transaction({ ... }); +const secondTransaction = new Transaction({ ... }); + +await provider.signTransactions([firstTransaction, secondTransaction]); + +// "firstTransaction" and "secondTransaction" can now be broadcasted. +``` + +Alternatively, one can sign a single transaction using the method `signTransaction()`. + + +### Signing messages {#walletconnect-signing-messages} + +Arbitrary messages can be signed as follows: + +```js +import { SignableMessage } from "@multiversx/sdk-core"; + +const message = new SignableMessage({ + message: Buffer.from("hello") +}); + +await provider.signMessage(message); + +console.log(message.toJSON()); +``` + + +## The hardware wallet provider + +:::note +Make sure you have a look over [this page](/wallet/ledger), in advance. +::: + +[`@multiversx/sdk-hw-provider`](https://github.com/multiversx/mx-sdk-js-hw-provider) allows the users of a dApp to log in and sign transactions using a [Ledger device](/wallet/ledger). + +In order to create an instance of the provider, do as follows: + +```js +import { HWProvider } from "@multiversx/sdk-hw-provider"; + +const provider = new HWProvider(); +``` + +Before performing any operation, make sure to initialize the provider (also, the MultiversX application has to be open on the device): + +```js +await provider.init(); +``` + + +### Login + +Before asking the user to log in using the Ledger, you may want to get all the available addresses on the device, display them, and let the user choose one of them: + +```js +const addresses = await provider.getAccounts(); +console.log(addresses); +``` + +The login looks like this: + +```js +const chosenAddressIndex = 3; +await provider.login({ addressIndex: chosenAddressIndex }); +alert(`Logged in. Address: ${await provider.getAddress()}`); +``` + +Alternatively, in order to select a specific address on the device after login, call `setAddressIndex()`: + +```js +const addressIndex = 3; +await provider.setAddressIndex(addressIndex); +console.log(`Address has been set: ${await provider.getAddress()}.`); +``` + +The Ledger provider does not support a _logout_ operation per se (not applicable in this context). + +The **the native authentication flow** (always recommended, see above) is supported using the `tokenLogin()` method: + +```js +const nativeAuthInitialPart = await nativeAuthClient.initialize(); +const nativeAuthInitialPartAsBuffer = Buffer.from(nativeAuthInitialPart); +const { address, signature } = await provider.tokenLogin({ addressIndex: 0, token: nativeAuthInitialPartAsBuffer }); + +const nativeAuthToken = nativeAuthClient.getToken(address, nativeAuthInitialPart, signature.toString("hex")); +``` + + +### Signing transactions {#hw-signing-transactions} + +Transactions can be signed as follows: + +```js +import { Transaction } from "@multiversx/sdk-core"; + +const firstTransaction = new Transaction({ ... }); +const secondTransaction = new Transaction({ ... }); + +await provider.signTransactions([firstTransaction, secondTransaction]); + +// "firstTransaction" and "secondTransaction" can now be broadcasted. +``` + +Alternatively, one can sign a single transaction using the method `signTransaction()`. + + +### Signing messages {#hw-signing-messages} + +Arbitrary messages can be signed as follows: + +```js +import { SignableMessage } from "@multiversx/sdk-core"; + +const message = new SignableMessage({ + message: Buffer.from("hello") +}); + +await provider.signMessage(message); + +console.log(message.toJSON()); +``` + + +## The Web Wallet Cross Window Provider + +[`@multiversx/sdk-web-wallet-cross-window-provider`](https://github.com/multiversx/mx-sdk-js-web-wallet-cross-window-provider) allows the users of a dApp to log in and sign transactions using the MultiversX Web Wallet. + +In order to acquire the instance (singleton) of the provider, do as follows: + +```js +import { CrossWindowProvider } from "@multiversx/sdk-web-wallet-cross-window-provider"; + +const provider = CrossWindowProvider.getInstance(); +``` + +Before performing any operation, make sure to initialize the provider and web wallet address: + +```js +await provider.init(); +provider.setWalletUrl("https://wallet.multiversx.com"); +``` + + +### Login and logout {#cross-window-login-and-logout} + +Then, ask the user to log in: + +```js +const address = await provider.login(); + +console.log(address); +console.log(provider.account); +``` + +In order to log out, do as follows: + +```js +await provider.logout(); +``` + +The `login()` method supports the `token` parameter, for **the native authentication flow** (always recommended, see above): + +```js +const nativeAuthInitialPart = await nativeAuthClient.initialize(); +await provider.login({ token: nativeAuthInitialPart }); + +const address = provider.account.address; +const signature = provider.account.signature; +const nativeAuthToken = nativeAuthClient.getToken(address, nativeAuthInitialPart, signature); +``` + + +### Signing transactions {#cross-window-signing-transactions} + +Transactions can be signed as follows: + +```js +import { Transaction } from "@multiversx/sdk-core"; + +const firstTransaction = new Transaction({ ... }); +const secondTransaction = new Transaction({ ... }); + +await provider.signTransactions([firstTransaction, secondTransaction]); + +// "firstTransaction" and "secondTransaction" can now be broadcasted. +``` + + +### Signing messages {#cross-window-signing-messages} + +Arbitrary messages can be signed as follows: + +```js +import { SignableMessage } from "@multiversx/sdk-core"; + +const message = new SignableMessage({ + message: Buffer.from("hello") +}); + +await provider.signMessage(message); + +console.log(message.toJSON()); +``` + + +## The Metamask Proxy Provider + +[`@multiversx/sdk-metamask-proxy-provider`](https://github.com/multiversx/mx-sdk-js-metamask-proxy-provider) allows the users of a dApp to log in and sign transactions using the Metamask wallet by using MultiversX Web Wallet as a proxy widget in iframe. + +In order to acquire the instance (singleton) of the provider, do as follows: + +```js +import { MetamaskProxyProvider } from "@multiversx/sdk-metamask-proxy-provider"; + +const provider = MetamaskProxyProvider.getInstance(); +``` + +Before performing any operation, make sure to initialize the provider and metamask snap web wallet address: + +```js +await provider.init(); +provider.setWalletUrl("https://snap-wallet.multiversx.com"); +``` + + +### Login and logout {#metamask-proxy-login-and-logout} + +Then, ask the user to log in: + +```js +const address = await provider.login(); + +console.log(address); +console.log(provider.account); +``` + +In order to log out, do as follows: + +```js +await provider.logout(); +``` + +The `login()` method supports the `token` parameter, for **the native authentication flow** (always recommended, see above): + +```js +const nativeAuthInitialPart = await nativeAuthClient.initialize(); +await provider.login({ token: nativeAuthInitialPart }); + +const address = provider.account.address; +const signature = provider.account.signature; +const nativeAuthToken = nativeAuthClient.getToken(address, nativeAuthInitialPart, signature); +``` + + +### Signing transactions {#metamask-proxy-signing-transactions} + +Transactions can be signed as follows: + +```js +import { Transaction } from "@multiversx/sdk-core"; + +const firstTransaction = new Transaction({ ... }); +const secondTransaction = new Transaction({ ... }); + +await provider.signTransactions([firstTransaction, secondTransaction]); + +// "firstTransaction" and "secondTransaction" can now be broadcasted. +``` + + +### Signing messages {#metamask-proxy-signing-messages} + +Arbitrary messages can be signed as follows: + +```js +import { SignableMessage } from "@multiversx/sdk-core"; + +const message = new SignableMessage({ + message: Buffer.from("hello") +}); + +await provider.signMessage(message); + +console.log(message.toJSON()); +``` + + +## Verifying the signature of a login token + +:::note +It's recommended to use the libraries [`sdk-native-auth-client`](https://www.npmjs.com/package/@multiversx/sdk-native-auth-client) and [`sdk-native-auth-server`](https://www.npmjs.com/package/@multiversx/sdk-native-auth-server) to handle the **native authentication** flow. +::: + +Please follow [this](https://github.com/multiversx/mx-sdk-js-native-auth-server) for more details about verifying the signature of a login token (i.e. within a server-side component of the dApp). + +--- + +### Signing Transactions + +By reading this page you will find out how to serialize and sign the Transaction payload. + +Transactions must be **signed** with the Sender's Private Key before submitting them to the MultiversX Network. Signing is performed with the [Ed25519](https://ed25519.cr.yp.to/) algorithm. + + +## **General structure** + +An _unsigned transaction_ has the following fields: + +| Field | Type | Required | Description | +| ---------- | ------ | ------------------ | ------------------------------------------------------------------------------ | +| `nonce` | number | Yes | The account sequence number | +| `value` | string | Yes (can be `"0"`) | The value to transfer, represented in atomic units:`EGLD` times `denomination` | +| `receiver` | string | Yes | The address of the receiver (bech32 format) | +| `sender` | string | Yes | The address of the sender (bech32 format) | +| `gasPrice` | number | Yes | The gas price to be used in the scope of the transaction | +| `gasLimit` | number | Yes | The maximum number of gas units allocated for the transaction | +| `data` | string | No | Arbitrary information about the transaction, **base64-encoded**. | +| `chainID` | string | Yes | The chain identifier. | +| `version` | number | Yes | The version of the transaction (e.g. `1`). | + +A signed transaction has the additional **`signature`** field: + +| Field | Type | Description | +| --------- | ------ | ---------------------------------------------------------------------------------------------- | +| signature | string | The digital signature consisting of 128 hex-characters (thus 64 bytes in a raw representation) | + + +## **Serialization for signing** + +Before signing a transaction, one has to **serialize** it, that is, to obtain its raw binary representation - as a sequence of bytes. This is achieved through the following steps: + +1. order the fields of the transaction with respect to their appearance order in the table above (`nonce` is first, `version` is last). +2. discard the `data` field if it's empty. +3. convert the `data` payload to its **base64** representation. +4. obtain a JSON representation (UTF-8 string) of the transaction, maintaining the order of the fields. This JSON representation must contain **no indentation** and **no separating spaces**. +5. encode the resulted JSON (UTF-8) string as a sequence of bytes. + +For example, given the transaction: + +```js +nonce = 7 +value = "10000000000000000000" # 10 EGLD +receiver = "erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r" +sender = "erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz" +gasPrice = 1000000000 +gasLimit = 70000 +data = "for the book" +chainID = "1" +version = 1 +``` + +By applying steps 1-3 (step 4 is omitted in this example), one obtains: + +```js +{"nonce":7,"value":"10000000000000000000","receiver":"erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r","sender":"erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz","gasPrice":1000000000,"gasLimit":70000,"data":"Zm9yIHRoZSBib29r","chainID":"1","version":1} +``` + +If the transaction has an empty **no data field**: + +```js +nonce = 8 +value = "10000000000000000000" # 10 ERD +receiver = "erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r" +sender = "erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz" +gasPrice = 1000000000 +gasLimit = 50000 +data = "" +chainID = "1" +version = 1 +``` + +Then it's serialized form (step 5 is omitted in this example) is as follows: + +```js +{"nonce":8,"value":"10000000000000000000","receiver":"erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r","sender":"erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz","gasPrice":1000000000,"gasLimit":50000,"chainID":"1","version":1} +``` + + +## **Ed25519 signature** + +MultiversX uses the [Ed25519](https://ed25519.cr.yp.to/) algorithm to sign transactions. In order to obtain the signature, one can use generic software libraries such as [PyNaCl](https://pynacl.readthedocs.io/en/stable/signing/), [tweetnacl-js](https://github.com/dchest/tweetnacl-js#signatures) or components of MultiversX SDK such as [mx-sdk-js-core](https://github.com/multiversx/mx-sdk-js-core), [mx-sdk-py](https://github.com/multiversx/mx-sdk-py), [sdk-go](https://github.com/multiversx/mx-sdk-go), [mxjava](https://github.com/multiversx/mx-sdk-java) etc. + +The raw signature consisting of 64 bytes has to be **hex-encoded** afterwards and placed in the transaction object. + + +## **Ready to broadcast** + +Once the `signature` field is set as well, the transaction is ready to be broadcasted. Following the examples above, their ready-to-broadcast form is as follows: + +```js +# With data field +nonce = 7 +value = "10000000000000000000" # 10 EGLD +receiver = "erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r" +sender = "erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz" +gasPrice = 1000000000 +gasLimit = 70000 +data = "Zm9yIHRoZSBib29r" +chainID = "1" +version = 1 +signature = "1702bb7696f992525fb77597956dd74059b5b01e88c813066ad1f6053c6afca97d6eaf7039b2a21cccc7d73b3e5959be4f4c16f862438c7d61a30c91e3d16c01" +``` + +```js +# Without data field +nonce = 8 +value = "10000000000000000000" # 10 EGLD +receiver = "erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r" +sender = "erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz" +gasPrice = 1000000000 +gasLimit = 50000 +data = "" +chainID = "1" +version = 1 +signature = "4a6d8186eae110894e7417af82c9bf9592696c0600faf110972e0e5310d8485efc656b867a2336acec2b4c1e5f76c9cc70ba1803c6a46455ed7f1e2989a90105" +``` + +--- + +### Simple Values + +We will start by going through the basic types used in smart contracts: + +- Fixed-width numbers +- Arbitrary width (big) numbers +- Boolean values + + +### Fixed-width numbers + +Small numbers can be stored in variables of up to 64 bits. We use big endian encoding for all numbers in our projects. + +**Rust types**: `u8`, `u16`, `u32`, `usize`, `u64`, `i8`, `i16`, `i32`, `isize`, `i64`. + +**Top-encoding**: The same as for all numerical types, the minimum number of bytes that +can fit their 2's complement, big endian representation. + +**Nested encoding**: Fixed width big endian encoding of the type, using 2's complement. + +:::important +A note about the types `usize` and `isize`: these Rust-specific types have the width of the underlying architecture, +i.e. 32 on 32-bit systems and 64 on 64-bit systems. However, smart contracts always run on a wasm32 architecture, so +these types will always be identical to `u32` and `i32` respectively. +Even when simulating smart contract execution on 64-bit systems, they must still be serialized on 32 bits. +::: + +**Examples** + +| Type | Number | Top-level encoding | Nested encoding | +| +| ------- | --------------------- | -------------------- | -------------------- | +| `u8` | `0` | `0x` | `0x00` | +| `u8` | `1` | `0x01` | `0x01` | +| `u8` | `0x11` | `0x11` | `0x11` | +| `u8` | `255` | `0xFF` | `0xFF` | +| `u16` | `0` | `0x` | `0x0000` | +| `u16` | `0x11` | `0x11` | `0x0011` | +| `u16` | `0x1122` | `0x1122` | `0x1122` | +| `u32` | `0` | `0x` | `0x00000000` | +| `u32` | `0x11` | `0x11` | `0x00000011` | +| `u32` | `0x1122` | `0x1122` | `0x00001122` | +| `u32` | `0x112233` | `0x112233` | `0x00112233` | +| `u32` | `0x11223344` | `0x11223344` | `0x11223344` | +| `u64` | `0` | `0x` | `0x0000000000000000` | +| `u64` | `0x11` | `0x11` | `0x0000000000000011` | +| `u64` | `0x1122` | `0x1122` | `0x0000000000001122` | +| `u64` | `0x112233` | `0x112233` | `0x0000000000112233` | +| `u64` | `0x11223344` | `0x11223344` | `0x0000000011223344` | +| `u64` | `0x1122334455` | `0x1122334455` | `0x0000001122334455` | +| `u64` | `0x112233445566` | `0x112233445566` | `0x0000112233445566` | +| `u64` | `0x11223344556677` | `0x11223344556677` | `0x0011223344556677` | +| `u64` | `0x1122334455667788` | `0x1122334455667788` | `0x1122334455667788` | +| `usize` | `0` | `0x` | `0x00000000` | +| `usize` | `0x11` | `0x11` | `0x00000011` | +| `usize` | `0x1122` | `0x1122` | `0x00001122` | +| `usize` | `0x112233` | `0x112233` | `0x00112233` | +| `usize` | `0x11223344` | `0x11223344` | `0x11223344` | +| `i8` | `0` | `0x` | `0x00` | +| `i8` | `1` | `0x01` | `0x01` | +| `i8` | `-1` | `0xFF` | `0xFF` | +| `i8` | `127` | `0x7F` | `0x7F` | +| `i8` | `-0x11` | `0xEF` | `0xEF` | +| `i8` | `-128` | `0x80` | `0x80` | +| `i16` | `-1` | `0xFF` | `0xFFFF` | +| `i16` | `-0x11` | `0xEF` | `0xFFEF` | +| `i16` | `-0x1122` | `0xEEDE` | `0xEEDE` | +| `i32` | `-1` | `0xFF` | `0xFFFFFFFF` | +| `i32` | `-0x11` | `0xEF` | `0xFFFFFFEF` | +| `i32` | `-0x1122` | `0xEEDE` | `0xFFFFEEDE` | +| `i32` | `-0x112233` | `0xEEDDCD` | `0xFFEEDDCD` | +| `i32` | `-0x11223344` | `0xEEDDCCBC` | `0xEEDDCCBC` | +| `i64` | `-1` | `0xFF` | `0xFFFFFFFFFFFFFFFF` | +| `i64` | `-0x11` | `0xEF` | `0xFFFFFFFFFFFFFFEF` | +| `i64` | `-0x1122` | `0xEEDE` | `0xFFFFFFFFFFFFEEDE` | +| `i64` | `-0x112233` | `0xEEDDCD` | `0xFFFFFFFFFFEEDDCD` | +| `i64` | `-0x11223344` | `0xEEDDCCBC` | `0xFFFFFFFFEEDDCCBC` | +| `i64` | `-0x1122334455` | `0xEEDDCCBBAB` | `0xFFFFFFEEDDCCBBAB` | +| `i64` | `-0x112233445566` | `0xEEDDCCBBAA9A` | `0xFFFFEEDDCCBBAA9A` | +| `i64` | `-0x11223344556677` | `0xEEDDCCBBAA9989` | `0xFFEEDDCCBBAA9989` | +| `i64` | `-0x1122334455667788` | `0xEEDDCCBBAA998878` | `0xEEDDCCBBAA998878` | +| `isize` | `0` | `0x` | `0x00000000` | +| `isize` | `-1` | `0xFF` | `0xFFFFFFFF` | +| `isize` | `-0x11` | `0xEF` | `0xFFFFFFEF` | +| `isize` | `-0x1122` | `0xEEDE` | `0xFFFFEEDE` | +| `isize` | `-0x112233` | `0xEEDDCD` | `0xFFEEDDCD` | +| `isize` | `-0x11223344` | `0xEEDDCCBC` | `0xEEDDCCBC` | + +--- + + +### Arbitrary width (big) numbers + +For most smart contracts applications, number larger than the maximum uint64 value are needed. +EGLD balances for instance are represented as fixed-point decimal numbers with 18 decimals. +This means that to represent even just 100 EGLD we use the number 100*1018, which already exceeds the capacity of a regular 64-bit integer. + +**Rust types**: `BigUint`, `BigInt`, + +:::important +These types are managed by MultiversX VM, in many cases the contract never sees the data, only a handle. +This is to reduce the burden on the smart contract. +::: + +**Top-encoding**: The same as for all numerical types, the minimum number of bytes that +can fit their 2's complement, big endian representation. + +**Nested encoding**: Since these types are variable length, we need to encode their length, so that the decodes knows when to stop decoding. +The length of the encoded number always comes first, on 4 bytes (`usize`/`u32`). +Next we encode: + +- For `BigUint` the big endian bytes +- For `BigInt` the shortest 2's complement number that can unambiguously represent the number. Positive numbers must always have the most significant bit `0`, while the negative ones `1`. See examples below. + +**Examples** + +| Type | Number | Top-level encoding | Nested encoding | Explanation | +| --------- | ------ | ------------------ | ---------------- | ------------------------------------------------------------------------------------------------------------ | +| `BigUint` | `0` | `0x` | `0x00000000` | The length of `0` is considered `0`. | +| `BigUint` | `1` | `0x01` | `0x0000000101` | `1` can be represented on 1 byte, so the length is 1. | +| `BigUint` | `256` | `0x0100` | `0x000000020100` | `256` is the smallest number that takes 2 bytes. | +| `BigInt` | `0` | `0x` | `0x00000000` | Signed `0` is also represented as zero-length bytes. | +| `BigInt` | `1` | `0x01` | `0x0000000101` | Signed `1` is also represented as 1 byte. | +| `BigInt` | `-1` | `0xFF` | `0x00000001FF` | The shortest 2's complement representation of `-1` is `FF`. The most significant bit is 1. | +| `BigUint` | `127` | `0x7F` | `0x000000017F` | | +| `BigInt` | `127` | `0x7F` | `0x000000017F` | | +| `BigUint` | `128` | `0x80` | `0x0000000180` | | +| `BigInt` | `128` | `0x0080` | `0x000000020080` | The most significant bit of this number is 1, so to avoid ambiguity an extra `0` byte needs to be prepended. | +| `BigInt` | `255` | `0x00FF` | `0x0000000200FF` | Same as above. | +| `BigInt` | `256` | `0x0100` | `0x000000020100` | `256` requires 2 bytes to represent, of which the MSB is 0, no more need to prepend a `0` byte. | + +--- + + +### Boolean values + +Booleans are serialized the same as a byte (`u8`) that can take values `1` or `0`. + +**Rust type**: `bool` + +**Values** + +| Type | Value | Top-level encoding | Nested encoding | +| ------ | ------- | ------------------ | --------------- | +| `bool` | `true` | `0x01` | `0x01` | +| `bool` | `false` | `0x` | `0x00` | + +--- + + +### Byte slices and ASCII strings + +Byte slices are technically a special case of the [list types](/developers/data/composite-values#lists-of-items), but they are usually thought of as basic types. Their encoding is, in any case, consistent with the rules for lists of "byte items". + +:::important +Strings are treated from the point of view of serialization as series of bytes. Using Unicode strings, while often a good practice in programming, tends to add unnecessary overhead to smart contracts. The difference is that Unicode strings get validated on input and concatenation. + +We consider best practice to use Unicode on the frontend, but keep all messages and error messages in ASCII format on smart contract level. +::: + +**Rust types**: `ManagedBuffer`, `BoxedBytes`, `&[u8]`, `Vec`, `String`, `&str`. + +**Top-encoding**: The byte slice, as-is. + +**Nested encoding**: The length of the byte slice on 4 bytes, followed by the byte slice as-is. + +**Examples** + +| Type | Value | Top-level encoding | Nested encoding | Explanation | +| --------------- | --------------------------- | ------------------ | ------------------ | ----------------------------------------------------------------- | +| `&'static [u8]` | `b"abc"` | `0x616263` | `0x00000003616263` | ASCII strings are regular byte slices of buffers. | +| `ManagedBuffer` | `ManagedBuffer::from("abc")`| `0x616263` | `0x00000003616263` | Use `Vec` for a buffer that can grow. | +| `BoxedBytes` | `BoxedBytes::from( b"abc")` | `0x616263` | `0x00000003616263` | BoxedBytes are just optimized owned byte slices that cannot grow. | +| `Vec` | `b"abc".to_vec()` | `0x616263` | `0x00000003616263` | Use `Vec` for a buffer that can grow. | +| `&'static str` | `"abc"` | `0x616263` | `0x00000003616263` | Unicode string (slice). | +| `String` | `"abc".to_string()` | `0x616263` | `0x00000003616263` | Unicode string (owned). | + +:::info Note +Inside contracts, `ManagedBuffer` is [the only recommended type for generic bytes](/developers/best-practices/the-dynamic-allocation-problem). +::: + +--- + + +### Address + +MultiversX addresses are 32 byte arrays, so they get serialized as such, both in top and nested encodings. + +--- + + +### Token identifiers + +MultiversX ESDT token identifiers are of the form `XXXXXX-123456`, where the first part is the token ticker, 3 to 20 characters in length, and the last is a random generated number. + +They are top-encoded as is, the exact bytes and nothing else. + +Because of their variable length, they need to be serialized like variable length byte slices when nested, so the length is explicitly encoded at the start. + +| Type | Value | Top-level encoding | Nested encoding | +| --------------- | --------------------------- | ------------------ | ------------------ | +| `TokenIdentifier` | `ABC-123456` | `0x4142432d313233343536` | `0x0000000A4142432d313233343536` | + +--- + +--- + +### Smart contract annotations + +## Introduction + +Annotations (also known as Rust "attributes") are the bread and butter of the `multiversx-sc` smart contract development framework. While contracts can in principle be written without any annotations or code generation macros in place, it is infinitely more difficult to do so. + +One of the main purposes of the framework is to make the code as readable and concise as possible, and annotations are the path to get there. + +For an introduction, check out [the Crowdfunding tutorial](/developers/tutorials/crowdfunding-p1). This page is supposed to be a complete index of all annotations that can be encountered in smart contracts. + + +## Trait annotations + + +### `#[multiversx_sc::contract]` + +The `contract` annotation must always be placed on a trait and will automatically make that trait the main container for the smart contract endpoints and logic. There should be only one such trait defined per crate. + +Note that the annotation takes no additional arguments. + +--- + + +### `#[multiversx_sc::module]` + +The `module` annotation must always be placed on a trait and will automatically make that trait a smart contract module. + +Note that the annotation takes no additional arguments. + +:::caution +Only one contract, module or proxy annotation is allowed per Rust module. If they are in separate files there is no problem, but if several share a file, explicit `mod module_name { ... }` must enclose the module. +::: + +--- + + +### `#[multiversx_sc::proxy]` + +The `proxy` annotation must always be placed on a trait and will automatically make that trait a smart contract call proxy. More about smart contract proxies in [the contract calls reference](/developers/transactions/tx-legacy-calls). + +In short, contracts always get an auto-generated proxy. However, if such an auto-generated proxy of another contract is not available, it is possible to define such a "contract interface" by hand, using the `proxy` attribute. + +Note that the annotation takes no additional arguments. + +:::caution +Only one contract, module or proxy annotation is allowed per Rust module. If they are in separate files there is no problem, but if several share a file, explicit `mod proxy_name { ... }` must enclose the module. +::: + + +## Method annotations + + +### `#[init]` + +Every smart contract needs one constructor that only gets called once when the contract is deployed. The method annotated with init is the constructor. + +```rust +#[multiversx_sc::contract] +pub trait Example { + #[init] + fn this_is_the_constructor( + constructor_arg_1: u32, + constructor_arg_2: BigUint) { + // ... + } +} +``` + +:::note +When upgrading a smart contract, the constructor in the new code is called. It is also called only once, and it can also never be called again. +::: + + +### `#[endpoint]` and `#[view]` + +Endpoints are the public methods of contracts, which can be called in transactions. A contract can define any number of methods, but only those annotated with `#[endpoint]` or `#[view]` are visible to the outside world. + +`#[view]` is meant to indicate readonly methods, but this is currently not enforced in any way. Functionally, `#[view]` and `#[endpoint]` are currently perfectly synonymous. However, there are plans for the future to enforce views to be verified at compile time to be readonly. When that happens, smart contracts that will already have been correctly annotated will be easier to migrate. Until then, there is still value in having 2 annotations, since they indicate intent. + +If no arguments are provided to the attribute, the name of the Rust method will be the name of the endpoint. Alternatively, an explicit endpoint name can be provided in brackets. + +Example: + +```rust +#[multiversx_sc::contract] +pub trait Example { + #[endpoint] + fn example(&self) { + } + + #[endpoint(camelCaseEndpointName)] + fn snake_case_method_name(&self, value: BigUint) { + } + + fn private_method(&self, value: &BigUint) { + } + + #[view(getData)] + fn get_data(&self) -> u32 { + 0 + } +} +``` + +In this example, 3 methods are public endpoints. They are named `example`, `camelCaseEndpointName` and `getData`. All other names are internal and do not show up in the resulting contract. + +:::note +All endpoint arguments and results must be either serializable or special endpoint argument types such as `MultiValueEncoded`. They must also all implement the `TypeAbi` trait. There is no such restriction for private methods. +::: + + +### Callbacks + +There are 2 annotations for callbacks: `#[callback]` and `#[callback_raw]`. The second is only used in extreme cases. + +Callbacks are special methods that get called automatically when the response comes after an asynchronous contract call. They give the contract the possibility to react to the result of a cross-shard call, but for consistency they get called the same way if the asynchronous call happens in the same shard. + +They also act as closures, since they can retain some of the context of the transaction that performed the asynchronous call in the first place. + +A more detailed explanation on how they work in [the contract calls reference](/developers/transactions/tx-legacy-calls). + + +### Storage + +It is possible for a developer to access storage manually in a contract, but this is error-prone and involves a lot of boilerplate code. For this reason, `multiversx-sc` offers storage annotations that manage and serialize the keys and values behind the scenes. + +Each contract has a storage where arbitrary data can be stored on-chain. This storage is organized as a map of arbitrary length keys and values. The blockchain has no concept of storage key or value types, they are all stored as raw bytes. It is the job of the contract to interpret these values. + +All trait methods annotated for storage handling must have no implementation. + + +#### `#[storage_get("key")]` + +This is the simplest way to retrieve data from the storage. Let's start with an example of usage: + +```rust +#[multiversx_sc::contract] +pub trait Adder { + #[view(getSum)] + #[storage_get("sum")] + fn get_sum(&self) -> BigUint; + + #[storage_get("example_map")] + fn get_value(&self, key_1: u32, key_2: u32) -> SerializableType; +} +``` + +First off, please note that a storage method can also be annotated with `#[view]` or `#[endpoint]`. The endpoint annotations refer to the role of the method in the contract, while the storage annotation refers to its implementation, so there is no overlap. + +Then, also note that there are 2 ways to use this annotation. In the first example, we simply specify the key in the annotation and from here on the method will always read from the same storage key, `"sum"` in this case. + +In the second example the get method also takes some arguments. Any number of arguments is allowed. These get concatenated to the base key to form a composite key, effectively turning a section of the contract storage into a dictionary or map. + +For instance calling `self.get_value(1, 2)` will retrieve from the storage key `"example_map\x00\x00\x00\x01\x00\x00\x00\x02"` or `0x6578616d706c655f6d61700000000100000002`. `self.get_value(1, 3)` will read from a different place in storage, and so on. + +This is the easiest way to get the equivalent of a HashMap in a smart contract. + +Lastly, storage getters must always return a deserializable type. The framework will automatically deserialize the object from whatever bytes it finds in the storage value. + + +#### `#[storage_set("key")]` + +This is the simplest way to write data to storage. Example: + +```rust +#[multiversx_sc::contract] +pub trait Adder { + #[storage_set("sum")] + fn set_sum(&self, sum: &BigUint); + + #[storage_set("example_map")] + fn set_value(&self, key_1: u32, key_2: u32, value: &SerializableType); +} +``` + +It works very similarly to `storage_get`, with the notable difference that instead of returning a value, the value must be provided as an argument. The value to store is always the last argument. + +Again, just like for the getter, an arbitrary number of additional map keys can be specified, as for `set_value` in the example. This is how we can write values to a section of our storage that behaves like a map. + +:::caution +There is no mechanism in place to ensure that there is no overlap between storage keys. Nothing prevents a developer from writing: + +```rust +#[storage_set("sum")] +fn set_sum(&self, sum: &BigUint); + +#[storage_set("sum")] +fn set_another_sum(&self, another_sum: &BigUint); + +#[storage_set("s")] +fn set_value(&self, key: u16, value: &SerializableType); +``` + +The first problem is easy to spot: we have 2 setters with the same key. + +The second is harder to notice. Calling `self.set_value(0x756d, value)` or `self.set_value(30061, value)` will also overwrite `"sum"`. This is because `"um"` = `"\x75\6d"`, which gets concatenated to the `"s"`, forming `"sum"`. + +To avoid this vulnerability, **never have a key that is the prefix of another key!** + +::: + + +#### `#[storage_mapper("key")]` + +Storage mappers are objects that can manage multiple storage keys at once. They are in charge with both reading and writing values. Some of them read and write values to multiple storage keys at once. + +There are many storage mappers in the framework and more can be custom-defined. + +Example: + +```rust +#[storage_mapper("user_status")] +fn user_status(&self) -> SingleValueMapper; + +#[storage_mapper("list_mapper")] +fn list_mapper(&self, sub_key: usize) -> LinkedListMapper; +``` + +The `SingleValueMapper` is the simplest of them all, since it only manages one storage key. Even though it only works with one storage entry, its syntax is more compact than `storage_get`/`storage_set` so it is used quite a lot. + +In the `LinkedListMapper` we are dealing with a list of items, each with its own key. + +Also note that additional sub-keys are also allowed for storage mappers, the same as for `storage_get` and `storage_set`. + + +#### `#[storage_is_empty("key")]` + +This is very similar to `storage_get`, but instead of retrieving the value, it returns a boolean indicating whether the serialized value is empty or not. It does not attempt to deserialize the value, so it can be faster and more resilient than `storage_get`, depending on type. + +```rust +#[storage_is_empty("opt_addr")] +fn is_empty_opt_addr(&self) -> bool; +``` + +Nowadays, it is more common to use storage mappers. The `SingleValueMapper` has an `is_empty()` method that does the same. + + +#### `#[storage_clear("key")]` + +This is very similar to `storage_set`, but instead of serializing and writing the storage value, it simply clears the raw bytes. +It does not do any serializing, so it can be faster than `storage_set`, depending on type. + +```rust +#[storage_clear("field_to_clear")] +fn clear_storage_value(&self); +``` + +Nowadays, it is more common to use storage mappers. The `SingleValueMapper` has an `clear()` method that does the same. + + +### Events + +Events are a way of returning data from smart contract, by leaving a trace of what happened during the execution. Event logs are not saved on the blockchain, but a hash of them is. This means that we can always check whether certain events were emitted by a transactions or not. + +Because they are not saved on the chain in full, they are also a lot cheaper than storage. + +In smart contracts we define them as trait methods with no implementation, as follows: + +```rust +#[event("transfer")] +fn transfer_event( + &self, + #[indexed] from: &ManagedAddress, + #[indexed] to: &ManagedAddress, + #[indexed] token_id: u32, + data: ManagedBuffer, +); +``` + +The annotation always requires the name of the event to be specified explicitly in brackets. + +Events have 2 types of arguments: + +- "Topics" are annotated with `#[indexed]`. When saving event logs to a database, indexes will be created for all these fields, so they can be searched for efficiently. +- The "data" argument has no annotation. There can be only one data field in an event, and it cannot be indexed later. + +Event arguments (fields) can be of any serializable type. There is no return value for events. + + +### Events (legacy) + +There is a legacy annotation, `#[legacy_event]` still used by some older contracts. It is deprecated and should no longer be used. + + +### `#[proxy]` + +This is a simple getter, which provides a convenient instance of a contract proxy. It is used when wanting to call another contract. + +```rust +#[multiversx_sc::module] +pub trait ForwarderAsyncCallModule { + #[proxy] + fn vault_proxy(&self, to: Address) -> vault::Proxy; + + // ... +} +``` + +There is no need for arguments, the annotation will figure out the contract to call by the provided return type. + +:::important +Proxy types need to be specified with an explicit module. In the example `vault::` is compulsory. +::: + + +### `#[output_names]` + +This one is used for ABI result names. In Rust, it is impossible to write Rust Docs for method returns, so we are using this annotation to optionally name the outputs of an endpoint. + +--- + +### Smart Contract API Functions + +## Introduction + +The Rust framework provides a wrapper over the MultiversX VM API functions and over account-level built-in functions. They are split into multiple modules, grouped by category: + +- BlockchainApi: Provides general blockchain information, which ranges from account balances, NFT metadata/roles to information about the current and previous block (nonce, epoch, etc.) +- CallValueApi: Used in payable endpoints, providing information about the tokens received as payment (token type, nonce, amount) +- CryptoApi: Provides support for cryptographic functions like hashing and signature checking +- SendApi: Handles all types of transfers to accounts and smart contract calls/deploys/upgrades, as well as support for ESDT local built-in functions + +The base trait for the APi is: https://docs.rs/multiversx-sc/0.39.0/multiversx_sc/api/trait.VMApi.html + +The source code for the APIs can be found here: https://github.com/multiversx/mx-sdk-rs/tree/master/framework/base/src/api + + +## Blockchain API + +This API is accessible through `self.blockchain()`. Available functions: + + +### get_sc_address + +```rust +get_sc_address() -> ManagedAddress +``` + +Returns the smart contract's own address. + + +### get_owner_address + +```rust +get_owner_address() -> ManagedAddress +``` + +Returns the owner's address. + + +### check_caller_is_owner + +```rust +check_caller_is_owner() +``` + +Terminates the execution and signals an error if the caller is not the owner. + +Use `#[only_owner]` endpoint annotation instead of directly calling this function. + + +### get_shard_of_address + +```rust +get_shard_of_address(address: &ManagedAddress) -> u32 +``` + +Returns the shard of the address passed as argument. + + +### is_smart_contract + +```rust +is_smart_contract(address: &ManagedAddress) -> bool +``` + +Returns `true` if the address passed as parameter is a Smart Contract address, `false` for simple accounts. + + +### get_caller + +```rust +get_caller() -> ManagedAddress +``` + +Returns the current caller. + +Keep in mind that for SC Queries, this function will return the SC's own address, so having a view function that uses this API function will not have the expected behaviour. + + +### get_balance + +```rust +get_balance(address: &ManagedAddress) -> BigUint +``` + +Returns the EGLD balance of the given address. + +This only works for addresses that are in the same shard as the smart contract. + + +### get_sc_balance + +```rust +get_sc_balance(token: &EgldOrEsdtTokenIdentifier, nonce: u64) -> BigUint +``` + +Returns the EGLD/ESDT/NFT balance of the smart contract. + +For fungible ESDT, nonce should be 0. To get the EGLD balance, you can simply pass `EgldOrEsdtTokenIdentifier::egld()` as parameter. + + +### get_tx_hash + +```rust +get_tx_hash() -> ManagedByteArray +``` + +Returns the current tx hash. + +In case of asynchronous calls, the tx hash is the same both in the original call and in the associated callback. + + +### get_gas_left + +```rust +get_gas_left() - > u64 +``` + +Returns the remaining gas, at the time of the call. + +This is useful for expensive operations, like iterating over an array of users in storage and sending rewards. + +A smart contract call that runs out of gas will revert all operations, so this function can be used to return _before_ running out of gas, saving a checkpoint, and continuing on a second call. + + +### get_block_timestamp_seconds + +```rust +get_block_timestamp_millis() -> TimestampSeconds +``` + +Returns the timestamp of the current block, in seconds (UNIX timestamp). + + + +### get_block_timestamp_millis + +```rust +get_block_timestamp_millis() -> TimestampMillis +``` + +Returns the timestamp of the current block, in milliseconds (UNIX timestamp). + + + +### get_block_nonce + +```rust +get_block_nonce() -> u64 +``` + +Returns the unique nonce of the block that includes the current transaction. + + +### get_block_round + +```rust +get_block_round() -> u64 +``` + +Returns the round number of the current block. Each epoch consists of a fixed number of rounds. The round number resets to 1 at the start of every new epoch. + + +### get_block_epoch + +```rust +get_block_epoch() -> u64 +``` + +Returns the epoch of the current block. + +These functions are mostly used for setting up deadlines, so they've been grouped together. + + +### get_block_random_seed + +```rust +get_block_random_seed() -> ManagedByteArray +``` + +Returns the block random seed, which can be used for generating random numbers. + +This will be the same for all the calls in the current block, so it can be predicted by someone calling this at the start of the round and only then calling your contract. + + +### get_prev_block_timestamp_seconds + +```rust +get_prev_block_timestamp_seconds() -> TimestampSeconds +``` + +Returns the timestamp of the previous block, in seconds (UNIX timestamp). + + + +### get_prev_block_timestamp_millis + +```rust +get_prev_block_timestamp_millis() -> TimestampMillis +``` + +Returns the timestamp of the previous block, in milliseconds (UNIX timestamp). + + + +### get_prev_block_nonce + +```rust +get_prev_block_nonce() -> u64 +``` + + +### get_prev_block_round + +```rust +get_prev_block_round() -> u64 +``` + + +### get_prev_block_epoch + +```rust +get_prev_block_epoch() -> u64 +``` + + +### get_prev_block_random_seed + +```rust +get_prev_block_random_seed() -> ManagedByteArray +``` + + +### get_block_round_time_millis + +```rust +get_block_round_time_millis(&self) -> DurationMillis +``` + +The block round time, in milliseconds, i.e the time between consecutive blocks. + + + +### get_current_esdt_nft_nonce + +```rust +get_current_esdt_nft_nonce(address: &ManagedAddress, token_id: &TokenIdentifier) -> u64 +``` + +Gets the last nonce for an SFT/NFT. Nonces are incremented after every ESDTNFTCreate operation. + +This only works for accounts that have the ESDTNFTCreateRole set and only for accounts in the same shard as the smart contract. + +This function is usually used with `self.blockchain().get_sc_address()` for smart contracts that create SFT/NFTs themselves. + + +### get_esdt_balance + +```rust +get_esdt_balance(address: &ManagedAddress, token_id: &TokenIdentifier, nonce: u64) -> BigUint +``` + +Gets the ESDT/SFT/NFT balance for the specified address. + +This only works for addresses that are in the same shard as the smart contract. + +For fungible ESDT, nonce should be 0. For EGLD balance, use the `get_balance` instead. + + +### get_esdt_token_data + +```rust +get_esdt_token_data(address: &ManagedAddress, token_id: &TokenIdentifier, nonce: u64) -> EsdtTokenData +``` + +Gets the ESDT token properties for the specific token type, owned by the specified address. + +`EsdtTokenData` has the following format: + +```rust +pub struct EsdtTokenData { + pub token_type: EsdtTokenType, + pub amount: BigUint, + pub frozen: bool, + pub hash: ManagedBuffer, + pub name: ManagedBuffer, + pub attributes: ManagedBuffer, + pub creator: ManagedAddress, + pub royalties: BigUint, + pub uris: ManagedVec>, +} +``` + +`token_type` is an enum, which can have one of the following values: + +```rust +pub enum EsdtTokenType { + Fungible, + NonFungible, + SemiFungible, + Meta, + Invalid, +} +``` + +You will only receive basic distinctions for the token type, i.e. only `Fungible` and `NonFungible` (The smart contract has no way of telling the difference between non-fungible, semi-fungible and meta tokens). + +`amount` is the current owned balance of the account. + +`frozen` is a boolean indicating if the account is frozen or not. + +`hash` is the hash of the NFT. Generally, this will be the hash of the `attributes`, but this is not enforced in any way. Also, the hash length is not fixed either. + +`name` is the name of the NFT, often used as display name in front-end applications. + +`attributes` can contain any user-defined data. If you know the format, you can use the `EsdtTokenData::decode_attributes` method to deserialize them. + +`creator` is the creator's address. + +`royalties` a number between 0 and 10,000, meaning a percentage of any selling price the creator receives. This is used in the ESDT NFT marketplace, but is not enforced in any other way. (The way these percentages work is 5,444 would be 54.44%, which you would calculate: price \* 5,444 / 10,000. This convention is used to grant some additional precision) + +`uris` list of URIs to an image/audio/video, which represents the given NFT. + +This only works for addresses that are in the same shard as the smart contract. + +Most of the time, this function is used with `self.blockchain().get_sc_address()` as address to get the properties of a token that is owned by the smart contract, or was transferred to the smart contract in the current executing call. + + +### get_esdt_local_roles + +```rust +get_esdt_local_roles(token_id: &TokenIdentifier) -> EsdtLocalRoleFlags +``` + +Gets the ESDTLocalRoles set for the smart contract, as a bitflag. The returned type contains methods of checking if a role exists and iterating over all the roles. + +This is done by simply reading protected storage, but this is a convenient function to use. + + +## Call Value API + +This API is accessible through `self.call_value()`. The alternative is to use the `#[payment]` annotations, but we no longer recommend them. They have a history of creating confusion, especially for new users. + +Available functions: + + +### egld_value + +```rust +egld_value() -> BigUint +``` + +Returns the amount of EGLD transferred in the current transaction. Will return 0 for ESDT transfers. + + +### all_esdt_transfers + +```rust +all_esdt_transfers() -> ManagedVec +``` + +Returns all ESDT transfers. Useful when you're expecting a variable number of transfers. + +Returns the payments into a `ManagedVec` of structs, that contain the token type, token ID, token nonce and the amount being transferred: + +```rust +pub struct EsdtTokenPayment { + pub token_identifier: TokenIdentifier, + pub token_nonce: u64, + pub amount: BigUint, +} +``` + + +### multi_esdt + +```rust +multi_esdt() -> [EsdtTokenPayment; N] +``` + +Returns a fixed number of ESDT transfers as an array. Will signal an error if the number of ESDT transfers differs from `N`. + +For example, if you always expect exactly 3 payments in your endpoint, you can use this function like so: +`let [payment_a, payment_b, payment_c] = self.call_value().multi_esdt();` + + +### single_esdt + +```rust +single_esdt() -> EsdtTokenPayment +``` + +Returns the received ESDT token payment if exactly one was received. Will signal an error in case of multi-transfer or no transfer. + + +### single_fungible_esdt + +```rust +single_fungible_esdt(&self) -> (TokenIdentifier, BigUint) +``` + +Similar to the function above, but also enforces the payment to be a fungible ESDT. + + +### egld_or_single_fungible_esdt + +```rust +egld_or_single_fungible_esdt(&self) -> (EgldOrEsdtTokenIdentifier, BigUint) +``` + +Same as the function above, but also allows EGLD to be received. + + +### egld_or_single_esdt + +```rust +egld_or_single_esdt() -> EgldOrEsdtTokenPayment +``` + +Allows EGLD or any single ESDT token to be received. + + +## Crypto API + +This API is accessible through `self.crypto()`. It provides hashing functions and signature verification. Since those functions are widely known and have their own pages of documentation, we will not go into too much detail in this section. + +Hashing functions: + + +### sha256 + +```rust +sha256(data: &ManagedBuffer) -> ManagedByteArray +``` + + +### keccak256 + +```rust +keccak256(data: &ManagedBuffer) -> ManagedByteArray +``` + + +### ripemd160 + +```rust +ripemd160(data: &ManagedBuffer) -> ManagedByteArray +``` + +Signature verification functions: + + +### verify_ed25519_legacy_managed + +```rust +verify_ed25519_legacy_managed(key: &ManagedByteArray, message: &ManagedBuffer, signature: &ManagedByteArray) -> bool +``` + + +### verify_bls + +```rust +verify_bls(key: &[u8], message: &[u8], signature: &[u8]) -> bool +``` + + +### verify_secp256k1 + +```rust +verify_secp256k1(key: &[u8], message: &[u8], signature: &[u8]) -> bool +``` + + +### verify_custom_secp256k1 + +```rust +verify_custom_secp256k1(key: &[u8], message: &[u8], signature: &[u8], hash_type: MessageHashType) -> bool +``` + +`MessageHashType` is an enum, representing the hashing algorithm that was used to create the `message` argument. Use `ECDSAPlainMsg` if the message is in "plain text". + +```rust +pub enum MessageHashType { + ECDSAPlainMsg, + ECDSASha256, + ECDSADoubleSha256, + ECDSAKeccak256, + ECDSARipemd160, +} +``` + +To be able to use the hashing functions without dynamic allocations, we use a concept in Rust known as `const generics`. This allows the function to have a constant value as a generic instead of the usual trait types you'd see in generics. The value is used to allocate a static buffer in which the data is copied temporarily, to then be passed to the legacy API. + +To call such a function, the call would look like this: + +```rust +let hash = self.crypto().sha256_legacy_managed::<200>(&data); +``` + +Where `200` is the max expected byte length of `data`. + + +### encode_secp256k1_der_signature + +```rust +encode_secp256k1_der_signature(r: &[u8], s: &[u8]) -> BoxedBytes +``` + +Creates a signature from the corresponding elliptic curve parameters provided. + + +## Send API + +This API is accessible through `self.send()`. It provides functionalities like sending tokens, performing smart contract calls, calling built-in functions and much more. + +We will not describe every single function in the API, as that would create confusion. We will only describe those that are recommended to be used (as they're mostly wrappers around more complicated low-level functions). + +For Smart Contract to Smart Contract calls, use the Proxies, as described in the [contract calls](/developers/transactions/tx-legacy-calls) section. + +Without further ado, let's take a look at the available functions: + + +### direct + +```rust +direct(to: &ManagedAddress, token: &EgldOrEsdtTokenIdentifier, nonce: u64, amount: &BigUint) +``` + +Performs a simple EGLD/ESDT/NFT transfer to the target address, with some optional additional data. If you want to send EGLD, simply pass `EgldOrEsdtTokenIdentifier::egld()`. For both EGLD and fungible ESDT, `nonce` should be 0. + +This will fail if the destination is a non-payable smart contract, but the current executing transaction will only fail if the destination SC is in the same shard, and as such, any changes done to the storage will persist. The tokens will not be lost though, as they will be automatically returned. + +Even though an invalid destination will not revert, an illegal transfer will return an error and revert. An illegal transfer is any transfer that would leave the SC with a negative balance for the specific token. + +If you're unsure about the destination's account type, you can use the `is_smart_contract` function from `Blockchain API`. + +If you need a bit more control, use the `direct_with_gas_limit` function instead. + + +### direct_egld + +```rust +direct_egld(to: &ManagedAddress, amount: &BigUint) +``` + +The EGLD-transfer version for the `direct` function. + + +### direct_esdt + +```rust +direct_esdt(to: &ManagedAddress, token_id: &TokenIdentifier, token_nonce: u64, amount: &BigUint) +``` + +The ESDT-only version for the `direct` function. Used so you don't have to wrap `TokenIdentifier` into an `EgldOrEsdtTokenIdentifier`. + + +### direct_multi + +```rust +direct_multi(to: &ManagedAddress, payments: &ManagedVec) +``` + +The multi-transfer version for the `direct_esdt` function. Keep in mind you cannot transfer EGLD with this function, only ESDTs. + + +### change_owner_address + +```rust +change_owner_address(child_sc_address: &ManagedAddress, new_owner: &ManagedAddress) +``` + +Changes the ownership of target child contract to another address. This will fail if the current contract is not the owner of the `child_sc_address` contract. + +This also has the implication that the current contract will not be able to call `#[only_owner]` functions of the child contract, upgrade, or change owner again. + + +### esdt_local_mint + +```rust +esdt_local_mint(token: &TokenIdentifier, nonce: u64, amount: &BigUint) +``` + +Allows synchronous minting of ESDT/SFT (depending on nonce). Execution is resumed afterwards. Note that the SC must have the `ESDTLocalMint` or `ESDTNftAddQuantity` roles set, or this will fail with "action is not allowed". + +For SFTs, you must use `esdt_nft_create` before adding additional quantity. + +This function cannot be used for NFTs. + + +### esdt_local_burn + +```rust +esdt_local_burn(token: &TokenIdentifier, nonce: u64, amount: &BigUint) +``` + +The inverse operation of `esdt_local_mint`, which permanently removes the tokens. Note that the SC must have the `ESDTLocalBurn` or `ESDTNftBurn` roles set, or this will fail with "action is not allowed". + +Unlike the mint function, this can be used for NFTs. + + +### esdt_nft_create + +```rust +esdt_nft_create(token: &TokenIdentifier, amount: &BigUint, name: &ManagedBuffer, royalties: &BigUint, hash: &ManagedBuffer, attributes: &T, uris: &ManagedVec< ManagedBuffer>) -> u64 +``` + +Creates a new SFT/NFT, and returns its nonce. + +Must have `ESDTNftCreate` role set, or this will fail with "action is not allowed". + +`token` is identifier of the SFT/NFT brand. + +`amount` is the amount of tokens to be minted. For NFTs, this should be "1". + +`name` is the display name of the token, which will be used in explorers, marketplaces, etc. + +`royalties` is a number between 0 and 10,000, which represents the percentage of any selling amount the creator receives. This representation is used to be able to have more precision. For example, a percentage like `55.66%` is stored as `5566`. These royalties are not enforced, and will mostly be used in "official" NFT marketplaces. + +`hash` is a user-defined hash for the token. Recommended value is sha256(attributes), but it can be anything. + +`attributes` can be any serializable user-defined struct, more specifically, any type that implements the `TopEncode` trait. There is no real standard for attributes format at the point of writing this document, but that might change in the future. + +`uris` is a list of links to the NFTs visual/audio representation, most of the time, these will be links to images, videos or songs. If empty, the framework will automatically add an "empty" URI. + + +### esdt_nft_create_compact + +```rust +esdt_nft_create_compact(token: &TokenIdentifier, amount: &BigUint, attributes: &T) -> u64 +``` + +Same as `esdt_nft_create`, but fills most arguments with default values. Mostly used in contracts that use NFTs as a means of information rather than for display purposes. + + +### sell_nft + +```rust +sell_nft(nft_id: &TokenIdentifier, nft_nonce: u64, nft_amount: &BigUint, buyer: &ManagedAddress, payment_token: &EgldOrEsdtTokenIdentifier, payment_nonce: u64, payment_amount: &BigUint) -> BigUint +``` + +Sends the SFTs/NFTs to target address, while also automatically calculating and sending NFT royalties to the creator. Returns the amount left after deducting royalties. + +`(nft_id, nft_nonce, nft_amount)` are the SFTs/NFTs that are going to be sent to the `buyer` address. + +`(payment_token, payment_nonce, payment_amount)` are the tokens that are used to pay the creator royalties. + +This function's purpose is mostly to be used in marketplace-like smart contracts, where the contract sells NFTs to users. + + +### nft_add_uri + +```rust +nft_add_uri(token_id: &TokenIdentifier, nft_nonce: u64, new_uri: ManagedBuffer) +``` + +Adds an URI to the selected NFT. The SC must own the NFT and have the `ESDTRoleNFTAddURI` to be able to use this function. + +If you need to add multiple URIs at once, you can use `nft_add_multiple_uri` function, which takes a `ManagedVec` as argument instead. + + +### nft_update_attributes + +```rust +nft_update_attributes(token_id: &TokenIdentifier, nft_nonce: u64, new_attributes: &T) +``` + +Updates the attributes of the selected NFT to the provided value. The SC must own the NFT and have the `ESDTRoleNFTUpdateAttributes` to be able to update the attributes. + + +## Conclusion + +While there are still other various APIs in multiversx-sc, they are mostly hidden from the user. These are the ones you're going to be using in your day-to-day smart contract development. + +--- + +### Smart Contract Call Events + + +--- + +### Smart Contract Calls Data Format + +This page provides an in-depth examination of the Smart Contract Calls Data Format. + + +## Introduction + +Besides regular move-balance transactions (address A sends the amount X to address B, while optionally including a note in the `data` field), +MultiversX transactions can trigger a Smart Contract call, or a [built-in function call](/developers/built-in-functions). + +This can happen in the following situations: + +- the receiver of the transaction is a Smart Contract Address and the data field begins with a valid function of the contract. +- the data field of the transaction begins with a valid built-in function name. + +Calls to Smart Contracts functions (or built-in functions) on MultiversX have the following format: + +```rust +ScCallTransaction { + Sender: + Receiver: # can be a SC, or other address in case of built in functions + Value: X # to be determined for each case + GasLimit: Y # to be determined for each case + Data: "functionName" + + "@" + + + "@" + + + ... +} +``` + +The number of arguments is specific to each function. + +_Example_. We have a smart contract A with the address `erd1qqqqqqqqqqqqqpgqrchxzx5uu8sv3ceg8nx8cxc0gesezure5awqn46gtd`. The contract +has a function `add(numberToAdd numeric)` which adds the `numberToAdd` to an internally managed sum. If we want to call the +function and add `15` to the internal sum, the transaction would look like: + +```rust +ExampleScCallTransaction { + Sender: + Receiver: erd1qqqqqqqqqqqqqpgqrchxzx5uu8sv3ceg8nx8cxc0gesezure5awqn46gtd + Value: 0 # no value needed for this call + GasLimit: 1_000_000 # let's suppose we need this much gas for calling the function + Data: "add@0f" # call the function add with the argument 15, hex-encoded +} +``` + + +### Constraints + +Focusing only on the data field of a Smart Contract call / Built-In Function Call, there are some limitation for the function name and the arguments: + +- `function name` has to be the plain text name of the function to be called. +- `arguments` must be hexadecimal encoded with an **even number of characters** (eq: `7` - invalid, `07` - valid; `f6f` - invalid, `6f6b` - valid). +- the `function name` and the `arguments` must be separated by the `@` character. + +The next section of this page will focus on how different data types have to be encoded in order to be compliant with the desired format. + + +## How to convert arguments for Smart Contract calls + +There are multiple ways of converting arguments from their original format to the hexadecimal encoding. + +For manually created transactions, arguments can be encoded by using tools that can be found online. For example, `hex to string`, `hex to decimal` and so on. + +For programmatically created transactions, arguments can be encoded by using one of our SDKs (`sdk-js`, `mxpy`, `sdk-go`, `sdk-java`, and so on) or by using built-in components or other libraries +of the language the transaction is created in. + +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/#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 + + +## Converting bech32 addresses (erd1) + +MultiversX uses `bech32` addresses with the HRP `erd`. Therefore, an address would look like: + +`erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th` + +:::caution +Converting a bech32 address into hexadecimal encoding _is not_ a simple `string to hex` operation, but requires specialized +tools or helpers. +::: + +There are many smart contract calls (or built-in function calls) that receive an address as one of their arguments. Obviously, +they have to be hexadecimal encoded. + + +### Examples + +bech32 --> hex + +``` +erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th +--> +0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1 + +erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r +--> +c70cf50b238372fffaf7b7c5723b06b57859d424a2da621bcc1b2f317543aa36 +``` + + +### Converting addresses using online tools + +There are multiple tools that one can use in order to convert an address into hexadecimal encoding: + +- [https://utils.multiversx.com/converters#addresses-bech32-to-hexadecimal](https://utils.multiversx.com/converters#addresses-bech32-to-hexadecimal) + +- [https://slowli.github.io/bech32-buffer](https://slowli.github.io/bech32-buffer) (go to `Data`, select `erd` as Tag and `Bech32` as Encoding) + +- [http://207.244.241.38/elrond-converters/#bech32-to-hex](http://207.244.241.38/elrond-converters/#bech32-to-hex) + + +### Converting addresses using mxpy + +Make sure you have `mxpy` [installed](/sdk-and-tools/mxpy/installing-mxpy). + +```bash +mxpy wallet bech32 --decode erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th +``` + +will output `0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1`. + +Additionally, hex addresses can be converted to bech32 as follows: + +```bash +mxpy wallet bech32 --encode 0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1 +``` + +will output `erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th`. + +The encoding algorithm that handles these conversions can be found [here](https://github.com/multiversx/mx-sdk-py-core/blob/main/multiversx_sdk_core/bech32.py). + + +### Converting addresses using sdk-js + +Find more about `sdk-js` [here](/sdk-and-tools/sdk-js/). + +```js +import { Address } from "@multiversx/sdk-core"; +... + +const address = Address.fromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); +console.log(address.hex()); +``` + +will output `0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1`. + +Additionally, hex addresses can be converted to bech32 as follows: + +```js +import { Address } from "@multiversx/sdk-core"; +... + +const address = Address.fromHex("0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1"); +console.log(address.bech32()); +``` + +will output `erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th`. + +The encoding algorithm that handles these conversions can be found [here](https://github.com/multiversx/mx-sdk-js-core/blob/main/src/address.ts). + + +### Converting addresses using sdk-go + +Find more about `sdk-go` [here](/sdk-and-tools/sdk-go/). + +```js +import ( + ... + "github.com/multiversx/mx-sdk-go/data" + ... +) + +addressObj, err := data.NewAddressFromBech32String("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th") +if err != nil { + return err +} + +fmt.Println(hex.EncodeToString(addressObj.AddressBytes())) +``` + +will output `0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1`. + +Additionally, hex addresses can be converted to bech32 as follows: + +```js +import ( + ... + "ggithub.com/multiversx/mx-sdk-go/data" + ... +) + +addressBytes, err := hex.DecodeString("0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1") +if err != nil { + return err +} +addressObj := data.NewAddressFromBytes(addressBytes) + +fmt.Println(addressObj.AddressAsBech32String()) +``` + +will output `erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th`. + +The encoding algorithm that handles these conversions can be found [here](https://github.com/multiversx/mx-chain-core-go/blob/main/core/pubkeyConverter/bech32PubkeyConverter.go). + + +### Converting addresses using sdk-java + +Find more about `sdk-java` [here](/sdk-and-tools/mxjava). + +```java +System.out.println(Address.fromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th").hex()); +``` + +will output `0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1`. + +Additionally, hex addresses can be converted to bech32 as follows: + +```java +System.out.println(Address.fromHex("0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1").bech32()); +``` + +will output `erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th`. + +The encoding algorithm that handles these conversions can be found [here](https://github.com/multiversx/mx-sdk-java/blob/main/src/main/java/multiversx/Address.java). + + +## Converting string values + +For situations when a string argument is desired for a smart contract call, it can be simply obtained by using +built-in libraries to convert them into hexadecimal format. + +:::important +Make sure that the result has an even number of characters. +::: + +Below you can find some examples: + +:::note +By no means, these code snippets provide a coding guideline; they are more of simple examples on how to perform the necessary actions. +::: + + +### Examples + +string --> hex + +``` +ok --> 6f6b +MEX-455c57 --> 4d45582d343535633537 +``` + + +### Converting string values in javascript + +```js +console.log(Buffer.from("ok").toString("hex")); // 6f6b +``` + +for converting hex-encoded string to regular string: + +```js +console.log(Buffer.from("6f6b", "hex").toString()); // ok +``` + + +### Converting string values in java + +```java +String inputHex = Hex.encodeHexString("ok".getBytes(StandardCharsets.UTF_8)); +if (inputHex.length() % 2 != 0) { + inputHex = "0" + inputex; +} + +System.out.println(inputHex); // 6f6b +``` + +for converting hex-encoded string to regular string: + +```java +byte[] bytes = Hex.decodeHex("6f6b".toCharArray()); + +String result = new String(bytes, StandardCharsets.UTF_8); // ok +``` + + +### Converting string values in go + +```go +fmt.Println(hex.EncodeToString([]byte("ok"))) // 6f6b +``` + +for converting hex-encoded string to regular string: + +```go +decodedInput, err := hex.DecodeString("6f6b") +if err != nil { + return err +} + +fmt.Println(string(decodedInput)) // ok +``` + + +## Converting numeric values + +For situations when a numeric argument is desired for a smart contract call, it can be simply obtained by using +built-in libraries to convert them into hexadecimal format. + +:::important +Make sure that the result has an even number of characters. +::: + +Below you can find some examples. They use big integer / number libraries to ensure the code works for large values as well: + +:::note +By no means, these code snippets provide a coding guideline; they are more of simple examples on how to perform the necessary actions. +::: + + +### Examples + +numeric --> hex + +``` +7 --> 07 +10 --> 0a +35 --> 23 +``` + + +### Converting numeric values in javascript + +```js +const intValue = 37; +const bn = new BigNumber(intValue, 10); +let bnStr = bn.toString(16); +if (bnStr.length % 2 != 0) { + bnStr = "0" + bnStr; +} +console.log(bnStr); // 25 +``` + +for converting hex-encoded string to regular number: + +```js +const hexValue = "25"; +let bn = new BigNumber(hexValue, 16); +console.log(bn.toString()); // 37 +``` + +Also, `sdk-js` includes some [utility functions](https://github.com/multiversx/mx-sdk-js-core/blob/main/src/utils.codec.ts) for padding the results. + + +### Converting numeric values in go + +```go +inputNum := int64(37) + +bi := big.NewInt(inputNum) +fmt.Println(hex.EncodeToString(bi.Bytes())) // 25 +``` + +for converting hex-encoded number to regular number: + +```go +hexString := "25" + +decodedHex, err := hex.DecodeString(hexString) +if err != nil { + return err +} +bi := big.NewInt(0).SetBytes(decodedHex) +fmt.Println(bi.String()) // 37 +``` + +--- + +### Smart Contract Debugging + +## Introduction + +Debugging smart contracts is possible with the integrated debugger in Visual Studio Code. You will be able to debug your contract just like you would debug a regular program. + + +## Prerequisites + +For this tutorial, you will need: +- Visual Studio Code +- the [rust-analyser](https://marketplace.visualstudio.com/items?itemName=matklad.rust-analyzer) extension. +- the [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb) extension. +- A [Rust test](rust/sc-blackbox-example) + +If you want to follow along, you can clone the [mx-sdk-rs](https://github.com/multiversx/mx-sdk-rs) repository and use the [crowdfunding](https://github.com/multiversx/mx-sdk-rs/tree/master/contracts/examples/crowdfunding) example. + + +## Step by step debugging + +In VSCode, you can put breakpoints anywhere in your code, by simply clicking to the left of the line number. A red dot should appear to mark the breakpoint as registered by the environment: + +![img](/developers/sc-debugging/breakpoint_setup.png) + +Once you've done that, you can debug your test function by pressing the `Debug` button above the test function name: + +![img](/developers/sc-debugging/start_test.png) + +If it doesn't appear, you might have to wait a bit for rust-analyser to load, or you might've forgotten the `#[test]` annotation. + +Once you've started the test, it should stop at the breakpoint and highlight the current line for you: + +![img](/developers/sc-debugging/first_step_debugging.png) + +Then, you can use VSCode's step by step debugging (usually F10 to step over, F11 to step into, or shift + F11 to step out). + + +## Inspecting variables + +For base Rust types, like u64 and such, you will be able to simply hover over them and see the value. + +You might however, try to hover over the `target` variable for instance, and will be immediately disappointed, since all you'll see is something like this: + +```rust +handle:0 +_phantom:{...} +``` + +This is not very helpful. Unfortunately, for managed types you don't have the actual data in the type itself, you only have a handle (i.e. an index) in a stack somewhere. + +For that reason, we have the `sc_print!` macro: + +```rust +sc_print!("{}", target); +``` + +Adding this line to the beginning of the `#[init]` function will print `2000` in the console. + + +## Printing formatted messages + +If you want to print other data types, maybe even with a message, you can use the `sc_print!` macro all the same. + +For example, if you were to add this to the start of the `#[init]` function: +```rust +sc_print!( + "I accept {}, a number of {}, and only until {}", + token_identifier, + target, + deadline +); +``` + +This macro would print the following: + +`"I accept CROWD-123456, a number of 2000, and only until 604800"` + +Note: For ASCII or decimal representation, use `{}`, and for hex, use `{:x}`. + +--- + +### Smart Contract Deploy Events + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + + +Contract deploy events are generated when a transaction involves either the deployment of a +smart contract or an upgrade to an existing contract. + +### Contract deploy event + +The contract deploy event is generated upon the successful execution of a transaction that includes +the deployment of a smart contract, without encountering any errors. + + + + + +| Field | Value | +|------------|------------------------------------------------------------------------------------------------------------------------| +| identifier | SCDeploy | +| address | the address of the deployed contract | +| topics | `topics[0]` - the address bytes of the deployed contract base64 encoded
`topics[1]` - the address bytes of the deployer of the smart contract base64 encoded
`topics[2]` - the code hash bytes of the deployer smart contract base64 encoded | +| data | empty | + +
+ + +```json +{ + { + "address": "erd1qqqqqqqqqqqqqpgqnnl9nn0kuuckhg24g02hq2745n4jk2hp327qcay4nm", + "identifier": "SCDeploy", + "topics": [ + "AAAAAAAAAAAFAJz+Wc325zFroVVD1XAr1aTrKyrhirw=", + "NRl7AwoM3hEPC0t9RTDy7gdJUSJvKC5dpJwLYaHLirw=", + "bJtNdzjeaYecInf/NpHzSjHJEZ2l6hR/uJh0NkLIe+k=" + ], + "data": null + } +} +``` + + +
+ + +### Contract upgrade event + +The contract upgrade event is generated when a transaction, involving an upgrade, is successfully executed without any errors. + + + + + +| Field | Value | +|------------|------------------------------------------------------------------------------------------------------------------------| +| identifier | SCUpgrade | +| address | the address of the deployed contract | +| topics | `topics[0]` - the address bytes of the upgraded contract base64 encoded
`topics[1]` - the address bytes of the upgrader of the smart contract base64 encoded
`topics[2]` - the code hash bytes of the upgraded smart contract base64 encoded | +| data | empty | + +
+ + +```json +{ + "address": "erd1qqqqqqqqqqqqqpgqnnl9nn0kuuckhg24g02hq2745n4jk2hp327qcay4nm", + "identifier": "SCUpgrade", + "topics": [ + "AAAAAAAAAAAFAJz+Wc325zFroVVD1XAr1aTrKyrhirw=", + "NRl7AwoM3hEPC0t9RTDy7gdJUSJvKC5dpJwLYaHLirw=", + "kUVJtdwvHG2sCTi9l2uneSONUVonWfgHCK69gdB+52o=" + ], +} +``` + + +
+ + +### Change owner event + +The `ChangeOwnerAddress` event is generated upon the successful execution of a transaction that specifically involves +a `ChangeOwnerAddress` built-in function call, and this execution must occur without encountering any errors. + + + + + +| Field | Value | +|------------|------------------------------------------------------------------------------------------------------------------------| +| identifier | ChangeOwnerAddress | +| address | the address of the contract | +| topics | `topics[0]` - the address bytes of the new contract owner base64 encoded | +| data | empty | + + + + +```json +{ + "address": "erd1qqqqqqqqqqqqqpgqnnl9nn0kuuckhg24g02hq2745n4jk2hp327qcay4nm", + "identifier": "ChangeOwnerAddress", + "topics": [ + "UKAg0hORMjk0oT6RalZp1w0Xulvvj0Wa/SSYstBepao=" + ], + "data": null +} +``` + + + + +--- + +### Smart contract interactions + +Let's dive deeper into smart contract interactions and what you need to know to interact with a contract. If you followed the [previous `mxpy`](/docs/sdk-and-tools/mxpy/mxpy-cli.md) related documentation, you should be able to set up your prerequisites like proxy URL, the chain ID and the PEM file. + +For this, we need a file inside the contract's folder, with a suggestive name. For example: `devnet.snippets.sh`. + +:::important +In order to be able to call methods from the file, we need to assign the shell file as a source file in the terminal. We can do this by running the next command: + +```shell +source devnet.snippets.sh +``` + +After each change to the interactions file, we need to repeat the source command. +::: + +Let's take the following example: + +1. We want to **deploy** a new smart contract on the Devnet. +2. We then need to **upgrade** the contract, to make it payable. +3. We **call** an endpoint without transferring any assets. +4. We **transfer** ESDT, in order to call a payable endpoint. +5. We call a **view** function. + + +## Prerequisites + +Before starting this tutorial, make sure you have the following: + +- [`mxpy`](/sdk-and-tools/mxpy/mxpy-cli). Follow the [installation guide](/sdk-and-tools/mxpy/installing-mxpy) - make sure to use the latest version available. +- `stable` **Rust** version `≥ 1.83.0`. Follow the [installation guide](/docs/developers/toolchain-setup.md#installing-rust-and-sc-meta). +- `sc-meta`. Follow the [installation guide](/docs/developers/toolchain-setup.md#installing-rust-and-sc-meta). + + +## Deploy + +First things first. In order to deploy a new contract, we need to use `sc-meta` to build it, in the contract root, by invoking the next command: + +```shell +sc-meta all build +``` + +This will output the WASM bytecode, to be used within the interactions file: + +```shell +WASM_PATH="~/my-contract/output/my-contract.wasm" +``` + +Now, in order to deploy the contract, we use the special **deploy** function of `mxpy`, that deploys the contract on the appointed chain, and runs the **init** function of the contract. + +```shell +WALLET_PEM="~/my-wallet/my-wallet.pem" +PROXY="https://devnet-gateway.multiversx.com" + +deploySC() { + mxpy --verbose contract deploy \ + --bytecode=${WASM_PATH} \ + --pem=${WALLET_PEM} \ + --proxy=${PROXY} \ + --arguments $1 $2 \ + --send || return +} +``` + +Run in terminal the following command to deploy the smart contract on Devnet. Replace `arg1` and `arg2` with your desired deployment values. + +```shell +source devnet.snippets.sh +deploySC arg1 arg2 +``` + +Now let's look at the structure of the interaction. It receives the path of the **wasm file**, where we previously built the contract. It also receives the path of the **wallet** (the PEM file) and the **proxy URL** where the contract will be deployed. + +Other than this, we also have the **arguments** keyword, that allows us to pass in the required parameters. As we previously said, deploying a smart contract means that we run the **init** function, which may or may not request some parameters. In our case, the **init** function has two different arguments, and we pass them when calling the **deploy** function. We'll come back later in this section at how we can pass parameters in function calls. + +After the transaction is sent, `mxpy` will output information like the **transaction hash**, **data** and any other important information, based on the type of transaction. In case of a contract deployment, it will also output the **newly deployed contract address**. + + +## Upgrade + +Let's now suppose we need to make the contract **payable**, in case it needs to receive funds. We could redeploy the contract but that will mean two different contracts, and not to mention that we will lose any existing storage. For that, we can use the **upgrade** command, that replaces the existing smart contract bytecode with the newly built contract version. + +:::caution +It is import to handle data storage with caution when upgrading a smart contract. Data structure, especially for complex data types, must be preserved, otherwise the data may become corrupt. +::: + +The upgrade function would look like this: + +```shell +CONTRACT_ADDRESS="erd1qqqqqqqqqqqqqpgqspymnxmfjve0vxhmep5vr3tf6sj8e80dd8ss2eyn3p" + +upgradeSC() { + mxpy --verbose contract upgrade ${CONTRACT_ADDRESS} --metadata-payable \ + --bytecode=${WASM_PATH} \ + --pem=${WALLET_PEM} \ + --proxy=${PROXY} \ + --arguments $1 $2 \ + --send || return +} +``` + +`CONTRACT_ADDRESS` is a placeholder value which value needs to be replaced with the address previously generated in the deploy action. + +Here we have 2 new different elements that we need to observe: + +1. We changed the **deploy** function with the **upgrade** function. This new function requires the address of the previously deployed smart contract so the system can identify which contract to update. It is important to note that this function can only be called by the smart contract's owner. +2. The **metadata-payable** keyword, which represents a [code metadata](/docs/developers/data/code-metadata.md) flag that allows the smart contract to receive payments. + + +## Non payable endpoint interaction + +Let's suppose we want to call the following endpoint, that receives an address and three different `BigUint` arguments, in this specific order. + +```shell +###PARAMS +# $1 = FirstBigUintArgument +# $2 = SecondBigUintArgument +THIRD_BIGUINT_ARGUMENT=0x0f4240 +ADDRESS_ARGUMENT=erd14nw9pukqyqu75gj0shm8upsegjft8l0awjefp877phfx74775dsq49swp3 + +myNonPayableEndpoint() { + address_argument="0x$(mxpy wallet bech32 --decode ${ADDRESS_ARGUMENT})" + mxpy --verbose contract call ${CONTRACT_ADDRESS} \ + --pem=${WALLET_PEM} \ + --proxy=${PROXY} \ + --function="myNonPayableEndpoint" \ + --arguments $address_argument $1 $2 ${THIRD_BIGUINT_ARGUMENT}\ + --send || return +} +``` + +So, what happens in this interaction and how do we call it? + +Besides the function and arguments parts, the snippet is more or less the same as when deploying or upgrading a contract. When calling a **non payable** function, we need to provide the **endpoint's name** as the function argument. As for the arguments, they have to be in the **same order** as in the endpoint's signature. Now, for the sake of example, we provided the arguments in multiple ways. + +It is up to each developer to choose the layout he prefers, but a few points need to be underlined: + +- Most of the supplied **arguments** need to be in the **hexadecimal format**: `0x...`. +- When converting a value to a hexadecimal format, we need to make sure it has an **even number** of characters. If not, we need to provide an extra `0` in order to make it even: + - Example: the number `911` -> in hexadecimal encoding, it is equal to: `38f` -> so we need to provide the argument `0x038f`. +- Arguments can be provided both as a fixed arguments (usually for unchangeable arguments like the contract's address or a fixed number) or can be provided as an input in the terminal, when interacting with the snippet (mostly used for arguments that change often like numbers). + +In our example we provide the address argument as a fixed argument. We then convert it to hexadecimal format (as it is in the bech32 format by default) and only after that we pass it as a parameter. As for the `BigUint` parameters, we provide the first two parameters directly in the terminal and the last one as a fixed argument, hexadecimal encoded. + +:::tip +`mxpy` provides the following encoding conventions: + +- We can use `str:` for encoding strings. For example: `str:MYTOKEN-123456`. +- Blockchain addresses that start with `erd1` are automatically encoded, so there is no need to further hex encode them. +- The values **true** or **false** are automatically converted to **boolean** values. +- Values that are identified as **numbers** are hex encoded as `BigUint` values. +- Arguments like `0x...` are left unchanged, as they are interpreted as already encoded hex values. + +::: + +So, in case of our **myNonPayableEndpoint** interaction, we can write it like so: + +```shell +###PARAMS +# $1 = FirstBigUintArgument +# $2 = SecondBigUintArgument +THIRD_BIGUINT_ARGUMENT=1000000 +ADDRESS_ARGUMENT=addr:erd14nw9pukqyqu75gj0shm8upsegjft8l0awjefp877phfx74775dsq49swp3 + +myNonPayableEndpoint() { + mxpy --verbose contract call ${CONTRACT_ADDRESS} \ + --pem=${WALLET_PEM} \ + --proxy=${PROXY} \ + --function="myNonPayableEndpoint" \ + --arguments ${ADDRESS_ARGUMENT} $1 $2 ${THIRD_BIGUINT_ARGUMENT}\ + --send || return +} +``` + +A call example for this endpoint would look like: + +```shell +source devnet.snippets.sh +myNonPayableEndpoint 10000 100000 +``` + +Using unencoded values (for easier reading) would translate into: + +```shell +myNonPayableEndpoint addr:erd14nw9pukqyqu75gj0shm8upsegjft8l0awjefp877phfx74775dsq49swp3 10000 100000 1000000 +``` + +:::caution +It is import to make sure all arguments have the correct encoding. Otherwise, the transaction will fail. +::: + + +## Payable endpoint interaction + + +### Fungible ESDT transfer + +Now let's take a look at the following example, where we want to call a payable endpoint. + +```shell +myPayableEndpoint() { + token_identifier=$1 + token_amount=$2 + mxpy --verbose contract call ${CONTRACT_ADDRESS} \ + --pem=${WALLET_PEM} \ + --proxy=${PROXY} \ + --token-transfers $token_identifier $token_amount \ + --function="myPayableEndpoint" \ + --send || return +} +``` + +To call a **payable endpoint**, we use the `--token-transfer` argument, which requires two values: + +1. The token identifier. +2. The amount. + +In our case, we specify in the terminal the **token identifier** and **the amount of tokens** we want to transfer. + +:::info +The format for the token identifier changes based on the type of asset you are sending: + +- **ESDTs Tokens**: Use the **standard** Token Identifier. +- **NFTs and SFTs**: Use the **extended** Token identifier format, which includes the token's nonce. The nonce must be hex-encoded. + - Example: `NFT-123456-0a` (where `0a` is the hex-encoded nonce). + +::: + +:::info +When specifying the amount of tokens to transfer, the value must include the token's decimal precision. + +For example EGLD uses 18 decimals. This means that if you want to transfer 1.5 EGLD, the amount value will be $1.5 \times 10^{18}$. +::: + + +### Non-fungible ESDT transfer (NFT, SFT and META ESDT) + +Now let's suppose we want to call an endpoint that accepts an NFT or an SFT as payment. + +```shell +###PARAMS +# $1 = NFT/SFT Token Identifier, +# $2 = NFT/SFT Token Amount, +FIRST_BIGUINT_ARGUMENT=1000 +SECOND_BIGUINT_ARGUMENT=10000 + +myESDTNFTPayableEndpoint() { + sft_token_identifier=$1 + sft_token_amount=$2 + mxpy --verbose contract call ${CONTRACT_ADDRESS} \ + --pem=${WALLET_PEM} \ + --proxy=${PROXY} \ + --token-transfers $sft_token_identifier $sft_token_amount \ + --function="myESDTNFTPayableEndpoint" \ + --arguments ${FIRST_BIGUINT_ARGUMENT} ${SECOND_BIGUINT_ARGUMENT} \ + --send || return +} +``` + + +### Multi-ESDT transfer + +In case we need to call an endpoint that accepts multiple tokens (let's say for example 2 fungible tokens and an NFT). Let's take a look at the following example: + +```shell +###PARAMS +# $1 = First Token Identifier, +# $2 = First Token Amount, +# $3 = Second Token Identifier, +# $4 = Second Token Amount, +# $5 = Third Token Identifier, +# $6 = Third Token Amount, +FIRST_BIGUINT_ARGUMENT=1000 +SECOND_BIGUINT_ARGUMENT=10000 + +myMultiESDTNFTPayableEndpoint() { + first_token_identifier=$1 + first_token_amount=$2 + second_token_identifier=$3 + second_token_amount=$4 + third_token_identifier=$5 + third_token_amount=$6 + + mxpy --verbose contract call ${CONTRACT_ADDRESS} \ + --pem=${WALLET_PEM} \ + --proxy=${PROXY} \ + --token-transfers $first_token_identifier $first_token_amount \ + $second_token_identifier $second_token_amount \ + $third_token_identifier $third_token_amount \ + --function="payable_nft_with_args" \ + --arguments ${FIRST_BIGUINT_ARGUMENT} ${SECOND_BIGUINT_ARGUMENT} \ + --send || return +``` + +In this example, we call `myMultiESDTPayableEndpoint` endpoint, by transferring **3 different tokens**: the first two are fungible tokens and the last one is an NFT. + +:::tip +More information about ESDT Transfers [here](/tokens/fungible-tokens/#transfers). +::: + + +## View interaction + +In case we want to call a view function, we can use the **query** keyword. + +```shell +###PARAMS +# $1 = First argument +# $2 = Second argument + +myView() { + mxpy --verbose contract query ${CONTRACT_ADDRESS} \ + --proxy=${PROXY} \ + --function="myView" \ + --arguments $1 $2 +} +``` + +When calling a **view** function, `mxpy` will output the standard information in the terminal, along with the results, formatted based on the requested data type. The arguments are specified in the same way as with endpoints. + +--- + +### Smart contract modules + +## Introduction + +Smart contract modules are a handy way of dividing a contract into smaller components. Modules also reduce code duplication, since they can be reused across multiple contracts. + + +## Declaration + +Modules can be defined both in the same crate as the main contract, or even in their own standalone crate. The latter is used when you want to use the same module in multiple contracts. + +A module is trait declared with the `#[multiversx_sc::module]` macro. Inside the trait, you can write any code you would usually write in a smart contract, even endpoints, events, storage mappers, etc. + +For example, let's say you want to have your storage mappers in a separate module. The implementation would look like this: + +```rust +#[multiversx_sc::module] +pub trait StorageModule { + #[view(getQuorum)] + #[storage_mapper("firstStorage")] + fn first_storage(&self) -> SingleValueMapper; + + #[view] + #[storage_mapper("secondStorage")] + fn second_storage(&self) -> SingleValueMapper; +} +``` + +Then, in your main file (usually named `lib.rs`), you have to define the module. If the file for the above module is named `storage.rs`, then in the main file you'd declare it like this: + +```rust +pub mod storage; +``` + + +## Importing a module + +A module can be imported both by other modules and contracts: + +```rust +pub trait SetupModule: + crate::storage::StorageModule + + crate::util::UtilModule { + +} +``` + +```rust +#[multiversx_sc::contract] +pub trait MainContract: + setup::SetupModule + + storage::StorageModule + + util::UtilModule { + +} +``` + +Keep in mind your main contract has to implement all modules that any sub-module might use. In this example, even if the `MainContract` does not use anything from the `UtilModule`, it still has to implement it if it wants to use `SetupModule`. + + +## Conclusion + +We hope this module system will make it a lot easier to write maintainable smart contract code, and even reusable modules. + +More modules and examples can be found here: https://github.com/multiversx/mx-sdk-rs/tree/master/contracts/modules + +--- + +### Smart contract payments + +## Some general points + +We want to offer an overview on how smart contracts process payments. This includes two complementary parts: receiving tokens and sending them. + +:::important important +On MultiversX it is possible to send one or more tokens with any transaction. This includes EGLD, and it is also possible (though impractical) to send several payments of the same token at once. +::: + + +:::note note +Historically, it used to be impossible to send EGLD and ESDT at the same time, this is why some of the legacy APIs have this restriction. This restriction no longer applies since the [Spica release](https://multiversx.com/release/release-spica-patch-4-v1-8-12). +::: + +--- + + +## Receiving payments + +There are two ways in which a smart contract can receive payments: +1. Like any regular account, directly, without any contract code being called; +2. Via an endpoint. + + +### Receiving payments directly + +Sending EGLD and ESDT tokens directly to accounts works the same way for EOAs (externally owned accounts) as for smart contracts: the tokens are transferred from one account to the other without firing up the VM. + +However, not all smart contracts are allowed to receive tokens directly. There is a flag that controls this, called "payable". This flag is part of the [code metadata](/developers/data/code-metadata), and is specified in the transaction that deploys or upgrades the smart contract. + +The rationale for this is as follows: the MultiversX blockchain doesn't offer any mechanism to allow contracts to react to direct calls. This is because we wanted to keep direct calls simple, consistent, and with a predictable gas cost, in all contexts. Most contracts, however, will likely want to keep track of all the funds that are fed into them, so they do not want to accept payments without an opportunity to also change their internal state. + + +### Receiving payments via endpoints + +The most common way for contracts to accept payments is by having endpoints annotated with the `#[payable]` annotation (or `#[payable("*")]`). + +:::important important +The "payable" flag in the code metadata only refers to direct transfers. Transferring tokens via contract endpoint calls is not affected by it in any way. +::: + +To accept any kind of payment, annotate the endpoints with `#[payable]`: + +```rust +#[endpoint] +#[payable] +fn accept_any_payment(&self) { + // ... +} +``` + +Usually on the first line there will be an instruction that processes, interprets, and validates the received payment ([see below](#call-value-methods)) + + + +If an endpoint only accepts EGLD, it can be annotated with `#[payable("EGLD")]`, although this is slowly falling out of favor. + +```rust +#[endpoint] +#[payable("EGLD")] +fn accept_egld(&self) { + // ... +} +``` + + +:::note Multi-transfer note +Note that it is currently possible to send two or more EGLD payments in the same transaction. The `#[payable("EGLD")]` annotation rejects that. +::: + +This snippet is equivalent to: + +```rust +#[endpoint] +#[payable] +fn accept_egld(&self) { + let payment_amount = self.call_value().egld(); + // ... +} +``` + + + +:::note Hard-coded token identifier +It is also possible to hard-code a token identifier in the `payable`, e.g. `#[payable("MYTOKEN-123456")]`. It is rarely, if ever, used, tokens should normally be configured in storage, or at runtime. +::: + + +## Payment Types + +The framework provides a unified approach to handling payments using the `Payment` type that treats EGLD and ESDT tokens uniformly. EGLD is represented as `EGLD-000000` token identifier, making all payment handling consistent. + +**`Payment
`** - The primary payment type that combines: +- `token_identifier`: `TokenId` - unified token identifier (EGLD serialized as "EGLD-000000") +- `token_nonce`: `u64` - token nonce for NFTs/SFTs, which is zero for all fungible tokens (incl. EGLD) +- `amount`: `NonZeroBigUint` - guaranteed non-zero amount + +**`PaymentVec`** - A managed vector of `Payment` objects, representing multiple payments in a single transaction. + + +## Call Value Methods + +Additional restrictions on the incoming tokens can be imposed in the body of the endpoint, by calling the call value API. Most of these functions retrieve data about the received payment, while also stopping execution if the payment is not of the expected type. + + +### `all()` - Complete Payment Collection + +`self.call_value().all()` retrieves all payments sent with the transaction as a `PaymentVec`. It handles all tokens uniformly, including EGLD (represented as "EGLD-000000"). Never stops execution. + +```rust +#[payable] +#[endpoint] +pub fn process_all_payments(&self) { + let payments = self.call_value().all(); + for payment in payments.iter() { + // Handle each payment uniformly + self.process_payment(&payment.token_identifier, payment.token_nonce, &payment.amount); + } +} +``` + + +### `single()` - Strict Single Payment + +`self.call_value().single()` expects exactly one payment and returns it. Will halt execution if zero or multiple payments are received. Returns a `Payment` object. + +```rust +#[payable] +#[endpoint] +pub fn deposit(&self) { + let payment = self.call_value().single(); + // Guaranteed to be exactly one payment + let token_id = &payment.token_identifier; + let amount = payment.amount; + + self.deposits(&self.blockchain().get_caller()).set(&amount); +} +``` + + +### `single_optional()` - Flexible Single Payment + +`self.call_value().single_optional()` accepts either zero or one payment. Returns `Option>` for graceful handling. Will halt execution if multiple payments are received. + +```rust +#[payable] +#[endpoint] +pub fn execute_with_optional_fee(&self) { + match self.call_value().single_optional() { + Some(payment) => { + // Process the payment as fee + self.execute_premium_service(payment); + }, + None => { + // Handle no payment scenario + self.execute_basic_service(); + } + } +} +``` + + +### `array()` - Fixed-Size Payment Array + +`self.call_value().array()` expects exactly N payments and returns them as a fixed-size array. Will halt execution if the number of payments doesn't match exactly. + +```rust +#[payable] +#[endpoint] +pub fn swap(&self) { + // Expect exactly 2 payments for the swap + let [input_payment, fee_payment] = self.call_value().array(); + + require!( + input_payment.token_identifier != fee_payment.token_identifier, + "Input and fee must be different tokens" + ); + + self.execute_swap(input_payment, fee_payment); +} +``` + + +## Legacy Call Value Methods + +The following methods are available for backwards compatibility but may be deprecated in future versions: + +- `self.call_value().egld_value()` retrieves the EGLD value transferred, or zero. Never stops execution. +- `self.call_value().all_esdt_transfers()` retrieves all the ESDT transfers received, or an empty list. Never stops execution. +- `self.call_value().multi_esdt()` is ideal when we know exactly how many ESDT transfers we expect. It returns an array of `EsdtTokenPayment`. It knows exactly how many transfers to expect based on the return type (it is polymorphic in the length of the array). Will fail execution if the number of ESDT transfers does not match. +- `self.call_value().single_esdt()` expects a single ESDT transfer, fails otherwise. Will return the received `EsdtTokenPayment`. It is a special case of `multi_esdt`, where `N` is 1. +- `self.call_value().single_fungible_esdt()` further restricts `single_esdt` to only fungible tokens, so those with their nonce zero. Returns the token identifier and amount, as pair. +- `self.call_value().egld_or_single_esdt()` retrieves an object of type `EgldOrEsdtTokenPayment`. Will halt execution in case of ESDT multi-transfer. +- `self.call_value().egld_or_single_fungible_esdt()` further restricts `egld_or_single_esdt` to fungible ESDT tokens. It will return a pair of `EgldOrEsdtTokenIdentifier` and an amount. +- `self.call_value().any_payment()` is the most general payment retriever. Never stops execution. Returns an object of type `EgldOrMultiEsdtPayment`. *(Deprecated since 0.64.1 - use `all()` instead)* + +--- + + +## Sending payments + +We have seen how contracts can accommodate receiving tokens. Sending them is, in principle, even more straightforward, as it only involves specializing the `Payment` generic of the transaction using specific methods, essentially attaching a payload to a regular transaction. Read more about payments [here](../transactions/tx-payment.md). + +--- + +### Staking smart contract + +## Introduction + +This tutorial aims to teach you how to write a simple staking contract, and to illustrate and correct the common pitfalls new smart contract developers might fall into. + +:::tip +If you find anything not answered here, feel free to ask further questions on the MultiversX Developers Telegram channel: [https://t.me/MultiversXDevelopers](https://t.me/MultiversXDevelopers) +::: + + +## Prerequisites + +:::important +Before starting this tutorial, make sure you have the following: + +- `stable` **Rust** version `≥ 1.85.0` (install via [rustup](/docs/developers/toolchain-setup.md#installing-rust-and-sc-meta)) +- `sc-meta` (install [multiversx-sc-meta](/docs/developers/meta/sc-meta-cli.md)) + +::: + +For contract developers, we generally recommend [**VSCode**](https://code.visualstudio.com) with the following extensions: + +- [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer) +- [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb) + + +## Creating the contract + +Run the following command in the folder in which you want your smart contract to be created: + +```bash +sc-meta new --name staking-contract --template empty +``` + +Open VSCode, select **File > Open Folder**, and open the newly created `staking-contract` folder. + + +## Building the contract + +In the terminal, run the following command to build the contract: + +```bash +sc-meta all build +``` + +After the building has completed, our folder should look like this: + +```bash +├── Cargo.lock +├── Cargo.toml +├── meta +│ ├── Cargo.toml +│ └── src +├── multiversx.json +├── output +│ ├── staking-contract.abi.json +│ ├── staking-contract.imports.json +│ ├── staking-contract.mxsc.json +│ └── staking-contract.wasm +├── scenarios +│ └── staking_contract.scen.json +├── src +│ └── staking_contract.rs +├── target +│ ├── CACHEDIR.TAG +│ ├── debug +│ ├── release +│ ├── tmp +│ └── wasm32-unknown-unknown +├── tests +│ ├── staking_contract_scenario_go_test.rs +│ └── staking_contract_scenario_rs_test.rs +└── wasm + ├── Cargo.lock + ├── Cargo.toml + └── src +``` + +A new folder, called `output` was created, which contains the compiled contract code. More on this is used later. + + +## First lines of Rust + +Currently, we just have an empty contract. Not very useful, is it? So let's add some simple code for it. Since this is a staking contract, we would expect to have a `stake` function, right? + +First, remove all the code in the `staking-contract/src/staking_contract.rs` file and replace it with this: + +```rust +#![no_std] + +use multiversx_sc::imports::*; + +#[multiversx_sc::contract] +pub trait StakingContract { + #[init] + fn init(&self) {} + + #[upgrade] + fn upgrade(&self) {} + + #[payable("EGLD")] + #[endpoint] + fn stake(&self) {} +} +``` + +Since we want this function to be callable by users, we have to annotate it with `#[endpoint]`. Also, since we want to be able to [receive a payment](/docs/developers/developer-reference/sc-payments.md#receiving-payments), we mark it also as `#[payable("EGLD)]`. For now, we'll use EGLD as our staking token. + +:::important +The contract **does NOT** need to be payable for it to receive payments on endpoint calls. The payable flag at contract level is only for receiving payments without endpoint invocation. +::: + +We need to see how much a user paid, and save their staking information in storage. + +```rust +#![no_std] + +use multiversx_sc::imports::*; + +#[multiversx_sc::contract] +pub trait StakingContract { + #[init] + fn init(&self) {} + + #[upgrade] + fn upgrade(&self) {} + + #[payable("EGLD")] + #[endpoint] + fn stake(&self) { + let payment_amount = self.call_value().egld().clone(); + require!(payment_amount > 0, "Must pay more than 0"); + + let caller = self.blockchain().get_caller(); + self.staking_position(caller.clone()).set(&payment_amount); + self.staked_addresses().insert(caller.clone()); + } + + #[view(getStakedAddresses)] + #[storage_mapper("stakedAddresses")] + fn staked_addresses(&self) -> UnorderedSetMapper; + + #[view(getStakingPosition)] + #[storage_mapper("stakingPosition")] + fn staking_position(&self, addr: ManagedAddress) -> SingleValueMapper; +} +``` + +[`require!`](/docs/developers/developer-reference/sc-messages.md#require) is a macro that is a shortcut for `if !condition { signal_error(msg) }`. Signalling an error will terminate the execution and revert any changes made to the internal state, including token transfers from and to the smart contract. In this case, there is no reason to continue if the user did not pay anything. + +We've also added [`#[view]`](/docs/developers/developer-reference/sc-annotations.md#endpoint-and-view) annotation for the storage mappers, allowing us to later perform queries on those storage entries. You can read more about annotations [here](/developers/developer-reference/sc-annotations/). + +If you're confused about some of the functions used or the storage mappers, you can read more here: + +- [Smart Contract API Functions](/developers/developer-reference/sc-api-functions) +- [Storage Mappers](/developers/developer-reference/storage-mappers) + +Now, there is intentionally written some bad code here. Can you spot any improvements we could make? + +1. The last `clone()` from `stake()` function is unnecessary. If you're cloning variables all the time,s then you need to take some time to read the [ownership](https://doc.rust-lang.org/book/ch04-00-understanding-ownership.html) chapter of the Rust book and also about the [implications of cloning](/developers/best-practices/biguint-operations) types from the Rust framework. + +2. The `staking_position` does not need an owned value of the `addr` argument. We can pass a reference instead. + +3. There is a logic error: what happens if an user stakes twice? Their position will be overwritten with the new value. Instead, we should add the new stake amount to their existing amount, using the [`update`](/docs/developers/developer-reference/storage-mappers.md#update) method. + +After fixing these issues, we end up with the following code: + +```rust +#![no_std] + +use multiversx_sc::imports::*; + +#[multiversx_sc::contract] +pub trait StakingContract { + #[init] + fn init(&self) {} + + #[upgrade] + fn upgrade(&self) {} + + #[payable("EGLD")] + #[endpoint] + fn stake(&self) { + let payment_amount = self.call_value().egld().clone(); + require!(payment_amount > 0, "Must pay more than 0"); + + let caller = self.blockchain().get_caller(); + self.staking_position(&caller) + .update(|current_amount| *current_amount += payment_amount); + self.staked_addresses().insert(caller); + } + + #[view(getStakedAddresses)] + #[storage_mapper("stakedAddresses")] + fn staked_addresses(&self) -> UnorderedSetMapper; + + #[view(getStakingPosition)] + #[storage_mapper("stakingPosition")] + fn staking_position(&self, addr: &ManagedAddress) -> SingleValueMapper; +} +``` + + +### What's with the empty init and upgrade function? + +Every smart contract must include a function annotated with [`#[init]`](/docs/developers/developer-reference/sc-annotations.md#init) and another with `#[upgrade]`. + +The `init()` function is called when the contract is first deployed, while `upgrade()` is triggered during an upgrade. For now, we need no logic inside it, but we still need to have those functions. + + +## Creating a wallet + +:::note +You can skip this section if you already have a Devnet wallet setup. +::: + +Open the terminal and run the following commands: + +```sh +mkdir -p ~/MyTestWallets +sc-meta wallet new --format pem --outfile ~/MyTestWallets/tutorialKey.pem +``` + + +## Deploy the contract + +Now that we've created a wallet, it's time to deploy our contract. + +:::important +Make sure you build the contract before deploying it. Open the terminal and run the following command from the contract root directory: + +```bash +sc-meta all build +``` + +::: + +Once the contract is built, generate the interactor: + +```bash +sc-meta all snippets +``` + +Add the interactor to your project. In `staking-contract/Cargo.toml`, add `interactor` as a member of the workspace: + +```toml +[workspace] +members = [ + ".", + "meta", + "interactor" # <- new member added +] +``` + +Next, update the wallet path for sending transactions. In `staking-contract/interactor/src/interact.rs` locate the function `new(config: Config)` and **modify** the `wallet_address` variable with the [absolute path](https://www.redhat.com/en/blog/linux-path-absolute-relative) to your wallet: + +```rust +let wallet_address = interactor + .register_wallet( + Wallet::from_pem_file("/MyTestWallets/tutorialKey.pem").expect( + "Unable to load wallet. Please ensure the file exists.", + ), + ) + .await; +``` + +Finally, deploy the contract to Devnet: + +```bash +cd interactor/ +cargo run deploy +``` + +:::note +To use Testnet instead, update `gateway_uri` in `staking-contract/interactor/config.toml` to: `https://testnet-gateway.multiversx.com`. + +For mainnet, use: `https://gateway.multiversx.com`. + +More details can be found [here](/developers/constants/). +::: + +You're going to see an error like the following: + +```bash +error sending tx (possible API failure): transaction generation failed: insufficient funds for address erd1... +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +``` + +This is because your account has no EGLD in it. + + +### Getting EGLD on Devnet + +There are many ways of getting EGLD on Devnet: + +- Through the Devnet wallet; +- Using an external faucet; +- From the [MultiversX Builders Discord Server faucet](https://discord.gg/multiversxbuilders); +- By asking a team member on [Telegram](https://t.me/MultiversXDevelopers). + + +#### Getting EGLD through devnet wallet + +Go to [Devnet Wallet](https://devnet-wallet.multiversx.com) and login to your account using your PEM file. From the left side menu, select *Faucet*: + +![img](/developers/staking-contract-tutorial-img/wallet_faucet.png) + +Request the tokens. After a few seconds you should have **5 xEGLD** in your wallet. + + +#### Getting EGLD through external faucet + +Go to [https://r3d4.fr/faucet](https://r3d4.fr/faucet) and submit a request: +![img](/developers/staking-contract-tutorial-img/external_faucet.png) + +Make sure you selected `Devnet` and input **your** address! + +It might take a little while, depending on how "busy" the faucet is. + + +### Deploying the contract, second try + +Run the `deploy` command again and let's see the results: + +```bash +sender's recalled nonce: 0 +-- tx nonce: 0 +sc deploy tx hash: 8a007... +deploy address: erd1qqqqqqqqqqqqq... +new address: erd1qqqqqqqqqqqqq... +``` + +Alternatively, you can check the address in the logs tab on [Devnet Explorer](https://devnet-explorer.multiversx.com/transactions), namely the `SCDeploy` method. + + +#### Too much gas error? + +Everything should work just fine, but you'll see this message: +![img](/developers/staking-contract-tutorial-img/too_much_gas.png) + +This is **not** an error. This simply means you provided way more gas than needed, so all the gas was consumed instead of the leftover being returned to you. + +This is done to protect the network against certain attacks. For instance, one could always provide the maximum gas limit and only use very little, decreasing the network's throughput significantly. + + +## The first stake + +Let's update the `stake` function from `staking-contract/interactor/src/interact.rs` to do the first stake. + +Initialize the `egld_amount` variable with `1` instead of `0`: + +```rust +let egld_amount = BigUint::::from(1u128); +``` + +This variable represents the amount of EGLD to be sent with the transaction. + +Let's stake! At path `staking-contract/interactor` run the following command: + +```bash +cargo run stake +``` + +We've now successfully staked 1 EGLD... or have we? + +If we look at the transaction, that's not quite the case: + +![img](/developers/staking-contract-tutorial-img/first_stake.png) + + +### Why was a smaller amount of EGLD sent? + +This happens because EGLD uses **18 decimals**. So, to send 1 EGLD, you actually need to send the value `1000000000000000000` (i.e. 1018). + +The blockchain works only with unsigned integers. Floating point numbers are not allowed. The only reason the explorer displays the balances with a floating point is because it's much more user-friendly to tell someone they have 1 EGLD instead of 1000000000000000000 EGLD, but internally, only the integer value is used. + + +### But how do I send 0.5 EGLD? + +Since we know EGLD has 18 decimals, we have to simply multiply 0.5 by 1018, which yields 500000000000000000. + + +### Actually staking 1 EGLD + +To stake 1 EGLD, simply update the `egld_amount` in the `stake` function from `staking-contract/interactor/src/interact.rs` with the following: + +```rust +let egld_amount = BigUint::::from(1000000000000000000u128); +``` + +Now let's try staking again: + +![img](/developers/staking-contract-tutorial-img/second_stake.png) + + +## Performing contract queries + +To perform smart contract queries for the `getStakingPosition` view, update the `addr` variable in the `staking_position` function from `staking-contract/interactor/src/interact.rs` with the address of the wallet you used for the staking transaction: + +```rust +let addr = bech32::decode("erd1vx..."); +``` + +Query the staking position by running the following command in the terminal, inside the `staking-contract/interactor` directory: + +```bash +cargo run getStakingPosition +``` + +This will show the staking amount according to the smart contract's internal state: + +```bash +Result: 1000000000000000001 +``` + +The result includes 1 EGLD plus the initial 10-18 EGLD that was sent. + +Next, query the **stakers list**. Replace the next line from `staked_addresses` **function** in `staking-contract/interactor/src/interact.rs`: + +```rust +println!("Result: {result_value:?}"); +``` + +with the following: + +```rust +for result in result_value.iter() { + println!("Result: {}", Bech32Address::from(result).to_bech32_string()); +} +``` + +It is necessary to iterate through `result_value` because it is a [`MultiValueVec`](/docs/developers/data/multi-values.md#standard-multi-values) of `Address`. Each address is converted to `Bech32Address` to ensure it’s printed in Bech32 format, not as raw ASCII. + +Run in terminal at path `staking-contract/interactor`: + +```bash +cargo run getStakedAddresses +``` + +Running this function should yield a result like this: + +```bash +Result: erd1vx... +``` + + +## Adding unstake functionality + +Currently, users can only stake, but they cannot actually get their EGLD back... at all. Let's add the unstake functionality in our smart contract: + +```rust +#[endpoint] +fn unstake(&self) { + let caller = self.blockchain().get_caller(); + let stake_mapper = self.staking_position(&caller); + + let caller_stake = stake_mapper.get(); + if caller_stake == 0 { + return; + } + + self.staked_addresses().swap_remove(&caller); + stake_mapper.clear(); + + self.tx().to(caller).egld(caller_stake).transfer(); +} +``` + +You might notice the `stake_mapper` variable. Just to remind you, the mapper's definition looks like this: + +```rust +#[storage_mapper("stakingPosition")] +fn staking_position(&self, addr: &ManagedAddress) -> SingleValueMapper; +``` + +In Rust terms, this is a method of our contract trait, with one argument, that returns a [`SingleValueMapper`](/docs/developers/developer-reference/storage-mappers.md#singlevaluemapper). All [mappers](/docs/developers/developer-reference/storage-mappers.md) are nothing more than structure types that provide an interface to the storage API. + +So then, why save the mapper in a variable? + + +### Better usage of storage mapper types + +Each time you access `self.staking_position(&addr)`, the storage key has to be constructed again, by concatenating the static string `stakingPosition` with the given `addr` argument. The mapper saves its key internally, so if we reuse the same mapper, the key is only constructed once. + +This saves us the following operations: + +```rust +let mut key = ManagedBuffer::new_from_bytes(b"stakingPosition"); +key.append(addr.as_managed_buffer()); +``` + +Instead, we just reuse the key we built previously. This can be a great performance enhancement, especially for mappers with multiple arguments. For mappers with no arguments, the improvement is minimal, but might still be worth thinking about. + + +### Partial unstake + +Some users might only want to unstake a part of their tokens, so we could simply add an `unstake_amount` argument: + +```rust +#[endpoint] +fn unstake(&self, unstake_amount: BigUint) { + let caller = self.blockchain().get_caller(); + let remaining_stake = self.staking_position(&caller).update(|staked_amount| { + require!( + unstake_amount > 0 && unstake_amount <= *staked_amount, + "Invalid unstake amount" + ); + *staked_amount -= &unstake_amount; + + staked_amount.clone() + }); + if remaining_stake == 0 { + self.staked_addresses().swap_remove(&caller); + } + + self.tx().to(caller).egld(unstake_amount).transfer(); +} +``` + +As you might notice, the code changed quite a bit: + +1. To handle invalid user input, we used the `require!` statement; +2. Previously, we used `clear` to reset the staking position. However, now that we need to modify the stored value, we use the `update` method, which allows us to change the currently stored value through a mutable reference. + +[`update`](/docs/developers/developer-reference/storage-mappers.md#update) is the same as doing [`get`](/docs/developers/developer-reference/storage-mappers.md#get), followed by computation, and then [`set`](/docs/developers/developer-reference/storage-mappers.md#set), but it's just a lot more compact. Additionally, it also allows us to return anything we want from the given closure, so we use that to detect if this was a full unstake. + + +### Optional arguments + +For a bit of performance enhancement, we could have the `unstake_amount` as an optional argument, with the default being full unstake. + +```rust +#[endpoint] +fn unstake(&self, opt_unstake_amount: OptionalValue) { + let caller = self.blockchain().get_caller(); + let stake_mapper = self.staking_position(&caller); + let unstake_amount = match opt_unstake_amount { + OptionalValue::Some(amt) => amt, + OptionalValue::None => stake_mapper.get(), + }; + + let remaining_stake = stake_mapper.update(|staked_amount| { + require!( + unstake_amount > 0 && unstake_amount <= *staked_amount, + "Invalid unstake amount" + ); + *staked_amount -= &unstake_amount; + + staked_amount.clone() + }); + if remaining_stake == 0 { + self.staked_addresses().swap_remove(&caller); + } + + self.tx().to(caller).egld(unstake_amount).transfer(); +} +``` + +This makes it so if someone wants to perform a full unstake, they can simply not give the argument at all. + + +### Unstaking our Devnet tokens + +Now that the unstake function has been added, let's test it out on Devnet. Build the smart contract again. In the contract root (`staking-contract/`), run the next command: + +```bash +sc-meta all build +``` + +After building the contract, regenerate the interactor by running the following command in the terminal, also in the contract root: + +```bash +sc-meta all snippets +``` + +:::warning +Make sure `wallet_address` stores the wallet that has to execute the transactions. +::: + +Let's unstake some EGLD! Replace variable `opt_unstake_amount` from `unstake` function in `staking-contract/interactor/src/interact.rs` with: + +```rust +let opt_unstake_amount = OptionalValue::Some(BigUint::::from(500000000000000000u128)); +``` + +Then, run in terminal at path `staking-contract/interactor`: + +```bash +cargo run unstake +``` + +Now run this function, and you'll get this result: + +![img](/developers/staking-contract-tutorial-img/first_unstake.png) + +...but why? We just added the function! + +Well, we might've added it to our code, but the contract on the Devnet still has our old code. So, how do we upload our new code? + + +## Upgrading smart contracts + +Since we've added some new functionality, we also want to update the currently deployed implementation. **Build the contract** and then run the following command at path `staking-contract/interactor`: + +```bash +cargo run upgrade +``` + +:::note Attention required +All the storage is kept on upgrade, so make sure any storage changes you make to storage mapping are **backwards compatible**! +::: + + +## Try unstaking again + +Run the `unstake` snippet again. This time, it should work just fine. Afterwards, let's query our staked amount through `getStakingPosition`, to see if it updated our amount properly. + +:::warning +Make sure that function `staking_position` has the changes previously made. +::: + +```bash +cargo run getStakingPosition +``` + +```bash +Result: 500000000000000001 +``` + +We had 1 EGLD, and we unstaked 0.5 EGLD. Now, we have 0.5 EGLD staked. (with the extra 1 fraction of EGLD we've staked initially). + + +### Unstake with no arguments + +Now let’s test how the contract behaves when no amount is explicitly provided. This will confirm that the `OptionalValue::None` path works as expected. + +In `staking-contract/interactor/src/interact.rs`, update the `unstake` function. Replace the `opt_unstake_amount` variable with: + +```rust +let opt_unstake_amount: OptionalValue> = OptionalValue::None; +``` + +And then unstake: + +```bash +cargo run unstake +``` + +After unstaking, query `stakingPosition` and `stakedAddresses` to see if the state was cleaned up properly: + +These should show that: + +- Your staking position is now empty: 0 EGLD; +- Your address has been removed from the stakers list. + + +## Writing tests + +As you might've noticed, it can be quite a chore to keep upgrading the contract after every little change, especially if all we want to do is test a new feature. So, let's recap what we've done until now: + +- deploy our contract +- stake +- partial unstake +- full unstake + +:::tip +A more detailed explanation of Rust tests can be found [here](https://docs.multiversx.com/developers/testing/rust/sc-test-overview/). +::: + +Before developing the tests, you will have to generate the contract's [proxy](/docs/developers/transactions/tx-proxies.md). + +You will add to `staking-contract/sc-config.toml`: + +```toml +[[proxy]] +path = "tests/staking_contract_proxy.rs" +``` + +Then run in terminal at path `staking-contract/`: + +```bash +sc-meta all proxy +``` + +You’ll find the contract’s proxy in `staking-contract/tests`, inside the file `staking_contract_proxy.rs`. This proxy will be used to help us write and run tests for the smart contract. + +To test the previously described scenario, we're going to need a user address, and a new test function. + +**Create** file `staking_contract_blackbox_test.rs` in `staking-contract/tests` with the following: + +```rust +mod staking_contract_proxy; +use multiversx_sc::{ + imports::OptionalValue, + types::{TestAddress, TestSCAddress}, +}; +use multiversx_sc_scenario::imports::*; + +const OWNER_ADDRESS: TestAddress = TestAddress::new("owner"); +const STAKING_CONTRACT_ADDRESS: TestSCAddress = TestSCAddress::new("staking-contract"); +const USER_ADDRESS: TestAddress = TestAddress::new("user"); +const WASM_PATH: MxscPath = MxscPath::new("output/staking-contract.mxsc.json"); +const USER_BALANCE: u64 = 1_000_000_000_000_000_000; + +struct ContractSetup { + pub world: ScenarioWorld, +} + +impl ContractSetup { + pub fn new() -> Self { + let mut world = ScenarioWorld::new(); + world.set_current_dir_from_workspace("staking-contract"); + world.register_contract(WASM_PATH, staking_contract::ContractBuilder); + + world.account(OWNER_ADDRESS).nonce(1).balance(0); + world.account(USER_ADDRESS).nonce(1).balance(USER_BALANCE); + + // simulate deploy + world + .tx() + .from(OWNER_ADDRESS) + .typed(staking_contract_proxy::StakingContractProxy) + .init() + .code(WASM_PATH) + .new_address(STAKING_CONTRACT_ADDRESS) + .run(); + + ContractSetup { world } + } +} + +#[test] +fn stake_unstake_test() { + let mut setup = ContractSetup::new(); + + setup + .world + .check_account(USER_ADDRESS) + .balance(USER_BALANCE); + setup.world.check_account(OWNER_ADDRESS).balance(0); + + // stake full + setup + .world + .tx() + .from(USER_ADDRESS) + .to(STAKING_CONTRACT_ADDRESS) + .typed(staking_contract_proxy::StakingContractProxy) + .stake() + .egld(USER_BALANCE) + .run(); + + setup + .world + .query() + .to(STAKING_CONTRACT_ADDRESS) + .typed(staking_contract_proxy::StakingContractProxy) + .staking_position(USER_ADDRESS) + .returns(ExpectValue(USER_BALANCE)) + .run(); + + setup.world.check_account(USER_ADDRESS).balance(0); + setup + .world + .check_account(STAKING_CONTRACT_ADDRESS) + .balance(USER_BALANCE); + + // unstake partial + setup + .world + .tx() + .from(USER_ADDRESS) + .to(STAKING_CONTRACT_ADDRESS) + .typed(staking_contract_proxy::StakingContractProxy) + .unstake(OptionalValue::Some(USER_BALANCE / 2)) + .run(); + + setup + .world + .query() + .to(STAKING_CONTRACT_ADDRESS) + .typed(staking_contract_proxy::StakingContractProxy) + .staking_position(USER_ADDRESS) + .returns(ExpectValue(USER_BALANCE / 2)) + .run(); + + setup + .world + .check_account(USER_ADDRESS) + .balance(USER_BALANCE / 2); + setup + .world + .check_account(STAKING_CONTRACT_ADDRESS) + .balance(USER_BALANCE / 2); + + // unstake full + setup + .world + .tx() + .from(USER_ADDRESS) + .to(STAKING_CONTRACT_ADDRESS) + .typed(staking_contract_proxy::StakingContractProxy) + .unstake(OptionalValue::None::) + .run(); + + setup + .world + .query() + .to(STAKING_CONTRACT_ADDRESS) + .typed(staking_contract_proxy::StakingContractProxy) + .staking_position(USER_ADDRESS) + .returns(ExpectValue(0u8)) + .run(); + + setup + .world + .check_account(USER_ADDRESS) + .balance(USER_BALANCE); + setup + .world + .check_account(STAKING_CONTRACT_ADDRESS) + .balance(0); +} +``` + +We've added a `USER_ADDRESS` constant, which is initialized with `USER_BALANCE` EGLD in their account. + +:::note +For the test we're going to use small numbers for balances, since there is no reason to work with big numbers. For this test, we're using 1 EGLD for user balance. +::: + +Then, we've staked the user's entire balance, unstaked half, then unstaked fully. After each transaction, we've checked the smart contract's internal staking storage, and also the balance of the user and the smart contract respectively. + + +### Running the test + +To run a test, you can use click on the `Run Test` button from under the test name. + +![img](/developers/staking-contract-tutorial-img/running_rust_test.png) + +There is also a `Debug` button, which can be used to debug smart contracts. More details on that [here](/developers/testing/sc-debugging/). + +Alternatively, you can run all the tests in the file by running the following command in the terminal, in the `staking-contract/` folder: + +```bash +sc-meta test +``` + + +## Staking Rewards + +Right now, there is no incentive to stake EGLD in this smart contract. Let's say we want to give every staker 10% APY (Annual Percentage Yield). For example, if someone staked 100 EGLD, they will receive a total of 10 EGLD per year. + +For this, we're also going to need to save the time at which each user staked. Also, we can't simply make each user wait one year to get their rewards. We need a more fine-tuned solution, so we're going to calculate rewards per block instead of per year. + +:::tip +You can also use rounds, timestamp, epochs etc. for time keeping in smart contracts, but number of blocks is the recommended approach. +::: + + +### User-defined struct types + +A single `BigUint` for each user is not enough anymore. As stated before, we need to also store the stake block, and we need to update this block number on every action. + +So, we're going to use a struct: + +```rust +pub struct StakingPosition { + pub stake_amount: BigUint, + pub last_action_block: u64, +} +``` + +:::note +Every managed type from the SpaceCraft needs a `ManagedTypeApi` implementation, which allows it to access the VM functions for performing operations. For example, adding two `BigUint` numbers, concatenating two `ManagedBuffers`, etc. Inside smart contract code, the `ManagedTypeApi` associated type is automatically added, but outside of it, we have to manually specify it. +::: + + +Additionally, since we need to store this in storage, we need to tell the Rust framework how to encode and decode this type. This can be done automatically by deriving (i.e. auto-implementing) these traits, via the `#[derive]` annotation: + +```rust +use multiversx_sc::derive_imports::*; + +#[type_abi] +#[derive(TopEncode, TopDecode, PartialEq, Debug)] +pub struct StakingPosition { + pub stake_amount: BigUint, + pub last_action_block: u64, +} +``` + +We've also added `#[type_abi]`, since this is required for ABI generation. ABIs are used by decentralized applications and such to decode custom smart contract types, but this is out of scope of this tutorial. + +Additionally, we've added `PartialEq` and `Debug` derives, for easier use within tests. This will not affect performance in any way, as the code for these is only used during testing/debugging. `PartialEq` allows us to use `==` for comparing instances, while `Debug` will pretty-print the struct, field by field, in case of errors. + +If you want to learn more about how such a structure is encoded, and the difference between top and nested encoding/decoding, you can read more [here](/developers/data/serialization-overview). + + +### Rewards formula + +A block is produced about every 6 seconds, so total blocks in a year would be seconds in year, divided by 6: + +```rust +pub const BLOCKS_IN_YEAR: u64 = 60 * 60 * 24 * 365 / 6; +``` + +More specifically: *60 seconds per minute* x *60 minutes per hour* x *24 hours per day* x *365 days*, divided by the 6-second block duration. + +:::note +This is calculated and replaced with the exact value at compile time, so there is no performance penalty of having a constant with mathematical operations in its value definition. +::: + + +Having defined this constant, rewards formula should look like this: + +```rust +let reward_amt = apy / 100 * user_stake * blocks_since_last_claim / BLOCKS_IN_YEAR; +``` + +Using 10% as the APY, and assuming exactly one year has passed since last claim. + +In this case, the reward should be calculated as: `10/100 * user_stake`, which is exactly 10% APY. + +However, there is something wrong with the current formula. We will always get `reward_amt` = 0. + + +### BigUint division + +BigUint division works the same as unsigned integer division. If you divide `x` by `y`, where `x < y`, you will always get `0` as result. So in our previous example, 10/100 is **NOT** `0.1`, but `0`. + +To fix this, we need to take care of our operation order: + +```rust +let reward_amt = user_stake * apy / 100 * blocks_since_last_claim / BLOCKS_IN_YEAR; +``` + + +### How to express percentages like 50.45%? + +In this case, we need to extend our precision by using fixed point precision. Instead of having `100` as the maximum percentage, we will extend it to `100_00`, and give `50.45%` as `50_45`. Updating our above formula results in this: + +```rust +pub const MAX_PERCENTAGE: u64 = 100_00; + +let reward_amt = user_stake * apy / MAX_PERCENTAGE * blocks_since_last_claim / BLOCKS_IN_YEAR; +``` + +For example, let's assume the user stake is 100, and 1 year has passed. Using `50_45` as APY value, the formula would become: + +```rust +reward_amt = 100 * 50_45 / 100_00 = 5045_00 / 100_00 = 50 +``` + +:::note +Since we're still using the BigUint division, we don't get `50.45`, but `50`. This precision can be increased by using more zeroes for the `MAX_PERCENTAGE` and the respective APY, but this is also "inherently fixed" on the blockchain because we work with very big numbers for `user_stake`. +::: + + +## Rewards functionality + + +### Implementation + +Now let's see how this would look in our Rust smart contract code. The smart contract looks like this after doing all the specified changes: + +```rust +#![no_std] + +use multiversx_sc::derive_imports::*; +use multiversx_sc::imports::*; + +pub const BLOCKS_IN_YEAR: u64 = 60 * 60 * 24 * 365 / 6; +pub const MAX_PERCENTAGE: u64 = 10_000; + +#[type_abi] +#[derive(TopEncode, TopDecode, PartialEq, Debug)] +pub struct StakingPosition { + pub stake_amount: BigUint, + pub last_action_block: u64, +} + +#[multiversx_sc::contract] +pub trait StakingContract { + #[init] + fn init(&self, apy: u64) { + self.apy().set(apy); + } + + #[upgrade] + fn upgrade(&self) {} + + #[payable("EGLD")] + #[endpoint] + fn stake(&self) { + let payment_amount = self.call_value().egld().clone(); + require!(payment_amount > 0, "Must pay more than 0"); + + let caller = self.blockchain().get_caller(); + self.staking_position(&caller).update(|staking_pos| { + self.claim_rewards_for_user(&caller, staking_pos); + + staking_pos.stake_amount += payment_amount + }); + self.staked_addresses().insert(caller); + } + + #[endpoint] + fn unstake(&self, opt_unstake_amount: OptionalValue) { + let caller = self.blockchain().get_caller(); + let stake_mapper = self.staking_position(&caller); + let mut staking_pos = stake_mapper.get(); + + let unstake_amount = match opt_unstake_amount { + OptionalValue::Some(amt) => amt, + OptionalValue::None => staking_pos.stake_amount.clone(), + }; + require!( + unstake_amount > 0 && unstake_amount <= staking_pos.stake_amount, + "Invalid unstake amount" + ); + + self.claim_rewards_for_user(&caller, &mut staking_pos); + staking_pos.stake_amount -= &unstake_amount; + + if staking_pos.stake_amount > 0 { + stake_mapper.set(&staking_pos); + } else { + stake_mapper.clear(); + self.staked_addresses().swap_remove(&caller); + } + + self.tx().to(caller).egld(unstake_amount).transfer(); + } + + #[endpoint(claimRewards)] + fn claim_rewards(&self) { + let caller = self.blockchain().get_caller(); + let stake_mapper = self.staking_position(&caller); + + let mut staking_pos = stake_mapper.get(); + self.claim_rewards_for_user(&caller, &mut staking_pos); + stake_mapper.set(&staking_pos); + } + + fn claim_rewards_for_user( + &self, + user: &ManagedAddress, + staking_pos: &mut StakingPosition, + ) { + let reward_amount = self.calculate_rewards(staking_pos); + let current_block = self.blockchain().get_block_nonce(); + staking_pos.last_action_block = current_block; + + if reward_amount > 0 { + self.tx().to(user).egld(&reward_amount).transfer(); + } + } + + fn calculate_rewards(&self, staking_position: &StakingPosition) -> BigUint { + let current_block = self.blockchain().get_block_nonce(); + if current_block <= staking_position.last_action_block { + return BigUint::zero(); + } + + let apy = self.apy().get(); + let block_diff = current_block - staking_position.last_action_block; + + &staking_position.stake_amount * apy / MAX_PERCENTAGE * block_diff / BLOCKS_IN_YEAR + } + + #[view(calculateRewardsForUser)] + fn calculate_rewards_for_user(&self, addr: ManagedAddress) -> BigUint { + let staking_pos = self.staking_position(&addr).get(); + self.calculate_rewards(&staking_pos) + } + + #[view(getStakedAddresses)] + #[storage_mapper("stakedAddresses")] + fn staked_addresses(&self) -> UnorderedSetMapper; + + #[view(getStakingPosition)] + #[storage_mapper("stakingPosition")] + fn staking_position( + &self, + addr: &ManagedAddress, + ) -> SingleValueMapper>; + + #[view(getApy)] + #[storage_mapper("apy")] + fn apy(&self) -> SingleValueMapper; +} +``` + +Now, rebuild the contract and regenerate the proxy: + +```bash +sc-meta all build +sc-meta all proxy +``` + +Let's update our test, to use our new `StakingPosition` structure, and also provide the `APY` as an argument for the `init` function. + +```rust +mod staking_contract_proxy; +use multiversx_sc::{ + imports::OptionalValue, + types::{BigUint, TestAddress, TestSCAddress}, +}; +use multiversx_sc_scenario::imports::*; +use staking_contract_proxy::StakingPosition; + +const OWNER_ADDRESS: TestAddress = TestAddress::new("owner"); +const USER_ADDRESS: TestAddress = TestAddress::new("user"); +const STAKING_CONTRACT_ADDRESS: TestSCAddress = TestSCAddress::new("staking-contract"); +const WASM_PATH: MxscPath = MxscPath::new("output/staking-contract.mxsc.json"); +const USER_BALANCE: u64 = 1_000_000_000_000_000_000; +const APY: u64 = 10_00; // 10% + +struct ContractSetup { + pub world: ScenarioWorld, +} + +impl ContractSetup { + pub fn new() -> Self { + let mut world = ScenarioWorld::new(); + world.set_current_dir_from_workspace("staking-contract"); + world.register_contract(WASM_PATH, staking_contract::ContractBuilder); + + world.account(OWNER_ADDRESS).nonce(1).balance(0); + world.account(USER_ADDRESS).nonce(1).balance(USER_BALANCE); + + // simulate deploy + world + .tx() + .from(OWNER_ADDRESS) + .typed(staking_contract_proxy::StakingContractProxy) + .init(APY) + .code(WASM_PATH) + .new_address(STAKING_CONTRACT_ADDRESS) + .run(); + + ContractSetup { world } + } +} + +#[test] +fn stake_unstake_test() { + let mut setup = ContractSetup::new(); + + setup + .world + .check_account(USER_ADDRESS) + .balance(USER_BALANCE); + setup.world.check_account(OWNER_ADDRESS).balance(0); + + // stake full + setup + .world + .tx() + .from(USER_ADDRESS) + .to(STAKING_CONTRACT_ADDRESS) + .typed(staking_contract_proxy::StakingContractProxy) + .stake() + .egld(USER_BALANCE) + .run(); + + let expected_result_1 = StakingPosition { + stake_amount: BigUint::from(USER_BALANCE), + last_action_block: 0, + }; + + setup + .world + .query() + .to(STAKING_CONTRACT_ADDRESS) + .typed(staking_contract_proxy::StakingContractProxy) + .staking_position(USER_ADDRESS) + .returns(ExpectValue(expected_result_1)) + .run(); + + setup.world.check_account(USER_ADDRESS).balance(0); + setup + .world + .check_account(STAKING_CONTRACT_ADDRESS) + .balance(USER_BALANCE); + + // unstake partial + setup + .world + .tx() + .from(USER_ADDRESS) + .to(STAKING_CONTRACT_ADDRESS) + .typed(staking_contract_proxy::StakingContractProxy) + .unstake(OptionalValue::Some(USER_BALANCE / 2)) + .run(); + + let expected_result_2 = StakingPosition { + stake_amount: BigUint::from(USER_BALANCE / 2), + last_action_block: 0, + }; + setup + .world + .query() + .to(STAKING_CONTRACT_ADDRESS) + .typed(staking_contract_proxy::StakingContractProxy) + .staking_position(USER_ADDRESS) + .returns(ExpectValue(expected_result_2)) + .run(); + + let staked_addresses_1 = setup + .world + .query() + .to(STAKING_CONTRACT_ADDRESS) + .typed(staking_contract_proxy::StakingContractProxy) + .staked_addresses() + .returns(ReturnsResultUnmanaged) + .run(); + + assert!(staked_addresses_1 + .into_vec() + .contains(&USER_ADDRESS.to_address())); + + setup + .world + .check_account(USER_ADDRESS) + .balance(USER_BALANCE / 2); + setup + .world + .check_account(STAKING_CONTRACT_ADDRESS) + .balance(USER_BALANCE / 2); + + // unstake full + setup + .world + .tx() + .from(USER_ADDRESS) + .to(STAKING_CONTRACT_ADDRESS) + .typed(staking_contract_proxy::StakingContractProxy) + .unstake(OptionalValue::None::) + .run(); + + let staked_addresses_2 = setup + .world + .query() + .to(STAKING_CONTRACT_ADDRESS) + .typed(staking_contract_proxy::StakingContractProxy) + .staked_addresses() + .returns(ReturnsResultUnmanaged) + .run(); + assert!(staked_addresses_2.is_empty()); + + setup + .world + .check_account(USER_ADDRESS) + .balance(USER_BALANCE); + setup + .world + .check_account(STAKING_CONTRACT_ADDRESS) + .balance(0); +} +``` + +Now let's run the test... **it didn't work**. You should see the following error: + +```bash +Error: result code mismatch. +Tx id: '' +Want: "0" +Have: 4 +Message: storage decode error (key: stakingPositionuser____________________________): input too short +``` + +But why? Everything worked fine before. + +This is because instead of using a simple `BigUint` for staking positions, we now use the `StakingPosition` structure. If you follow the error trace, you will see exactly where it failed: + +```bash +28: staking_contract::StakingContract::stake + at ./src/staking_contract.rs:33:9 +``` + +Which leads to the following line: + +```rust +self.staking_position(&caller).update(|staking_pos| { + self.claim_rewards_for_user(&caller, staking_pos); + + staking_pos.stake_amount += payment_amount +}); +``` + +Because we're trying to add a new user, which has no staking entry yet, the decoding fails. + +For a simple `BigUint`, decoding from an empty storage yields the `0` value, which is exactly what we want, but for a struct type, it cannot give us any default value. + +For this reason, we have to add some additional checks. The endpoint implementations will have to be changed to the following (the rest of the code remains the same): + +```rust +#[payable("EGLD")] +#[endpoint] +fn stake(&self) { + let payment_amount = self.call_value().egld().clone(); + require!(payment_amount > 0, "Must pay more than 0"); + + let caller = self.blockchain().get_caller(); + let stake_mapper = self.staking_position(&caller); + + let new_user = self.staked_addresses().insert(caller.clone()); + let mut staking_pos = if !new_user { + stake_mapper.get() + } else { + let current_block = self.blockchain().get_block_epoch(); + StakingPosition { + stake_amount: BigUint::zero(), + last_action_block: current_block, + } + }; + + self.claim_rewards_for_user(&caller, &mut staking_pos); + staking_pos.stake_amount += payment_amount; + + stake_mapper.set(&staking_pos); +} + +#[endpoint] +fn unstake(&self, opt_unstake_amount: OptionalValue) { + let caller = self.blockchain().get_caller(); + self.require_user_staked(&caller); + + let stake_mapper = self.staking_position(&caller); + let mut staking_pos = stake_mapper.get(); + + let unstake_amount = match opt_unstake_amount { + OptionalValue::Some(amt) => amt, + OptionalValue::None => staking_pos.stake_amount.clone(), + }; + require!( + unstake_amount > 0 && unstake_amount <= staking_pos.stake_amount, + "Invalid unstake amount" + ); + + self.claim_rewards_for_user(&caller, &mut staking_pos); + staking_pos.stake_amount -= &unstake_amount; + + if staking_pos.stake_amount > 0 { + stake_mapper.set(&staking_pos); + } else { + stake_mapper.clear(); + self.staked_addresses().swap_remove(&caller); + } + + self.tx().to(caller).egld(unstake_amount).transfer(); +} + +#[endpoint(claimRewards)] +fn claim_rewards(&self) { + let caller = self.blockchain().get_caller(); + self.require_user_staked(&caller); + + let stake_mapper = self.staking_position(&caller); + + let mut staking_pos = stake_mapper.get(); + self.claim_rewards_for_user(&caller, &mut staking_pos); + + stake_mapper.set(&staking_pos); +} + +fn require_user_staked(&self, user: &ManagedAddress) { + require!(self.staked_addresses().contains(user), "Must stake first"); +} +``` + +For the `stake` endpoint, if the user was not previously staked, we provide a default entry. The `insert` method of `UnorderedSetMapper` returns `true` if the entry is new and `false` if the user already exists in the list. This means we can use that result directly, instead of checking for `stake_mapper.is_empty()`. + +For the `unstake` and `claimRewards` endpoints, we have to check if the user was already staked and return an error otherwise (as they'd have nothing to unstake/claim anyway). + +Once you've applied all the suggested changes, **rebuilt the contract** and **regenerate the proxy**, running the test should work just fine now: + +```bash +running 1 test +test stake_unstake_test ... ok +``` + +In order to apply these changes on devnet, you should build the contract, regenerate the interactor and then upgrade it. + +:::warning +Whenever you regenerate the interactor, make sure that wallet_address points to the wallet you intend to use for executing transactions. + +By default, the interactor registers Alice's wallet, so you’ll need to update wallet_address manually if you’re using a different one. +::: + +```bash +sc-meta all build +sc-meta all snippets +cd interactor/ +``` + +Set the `apy` variable inside the `upgrade` function from `staking-contract/interactor/src/interact.rs` to `100`, then run: + +```bash +cargo run upgrade +``` + +To verify the change, query the `apy` storage mapper by running: + +```bash +cargo run getApy +``` + +You should see the following output: + +```bash +Result: 100 +``` + + +### Testing + +Now that we've implemented rewards logic, let's add the following test to `staking-contract/tests/staking_contract_blackbox_test.rs`: + +```rust +#[test] +fn rewards_test() { + let mut setup = ContractSetup::new(); + + // stake full + setup + .world + .tx() + .from(USER_ADDRESS) + .to(STAKING_CONTRACT_ADDRESS) + .typed(staking_contract_proxy::StakingContractProxy) + .stake() + .egld(USER_BALANCE) + .run(); + + let expected_result_1 = StakingPosition { + stake_amount: BigUint::from(USER_BALANCE), + last_action_block: 0, + }; + + setup + .world + .query() + .to(STAKING_CONTRACT_ADDRESS) + .typed(staking_contract_proxy::StakingContractProxy) + .staking_position(USER_ADDRESS) + .returns(ExpectValue(&expected_result_1)) + .run(); + + setup.world.current_block().block_nonce(BLOCKS_IN_YEAR); + + // query rewards + setup + .world + .query() + .to(STAKING_CONTRACT_ADDRESS) + .typed(staking_contract_proxy::StakingContractProxy) + .calculate_rewards_for_user(USER_ADDRESS) + .returns(ExpectValue( + BigUint::from(USER_BALANCE) * APY / MAX_PERCENTAGE, + )) + .run(); + + // claim rewards + setup + .world + .query() + .to(STAKING_CONTRACT_ADDRESS) + .typed(staking_contract_proxy::StakingContractProxy) + .staking_position(USER_ADDRESS) + .returns(ExpectValue(&expected_result_1)) + .run(); + + setup + .world + .tx() + .from(USER_ADDRESS) + .to(STAKING_CONTRACT_ADDRESS) + .typed(staking_contract_proxy::StakingContractProxy) + .claim_rewards() + .run(); + + let expected_result_2 = StakingPosition { + stake_amount: BigUint::from(USER_BALANCE), + last_action_block: BLOCKS_IN_YEAR, + }; + setup + .world + .query() + .to(STAKING_CONTRACT_ADDRESS) + .typed(staking_contract_proxy::StakingContractProxy) + .staking_position(USER_ADDRESS) + .returns(ExpectValue(expected_result_2)) + .run(); + + setup + .world + .check_account(USER_ADDRESS) + .balance(BigUint::from(USER_BALANCE) * APY / MAX_PERCENTAGE); + + // query rewards after claim + setup + .world + .query() + .to(STAKING_CONTRACT_ADDRESS) + .typed(staking_contract_proxy::StakingContractProxy) + .calculate_rewards_for_user(USER_ADDRESS) + .returns(ExpectValue(0u32)) + .run(); +} +``` + +In the test, we perform the following steps: + +- Stake 1 EGLD; +- Set block nonce after 1 year (i.e. simulating 1 year worth of blocks passing); +- Querying rewards, which should give use 10% of 1 EGLD = 0.1 EGLD; +- Claiming said rewards and checking the internal state and user balance; +- Querying again after claim, to check that double-claim is not possible. + +This test should work without any errors. + + +## Conclusion + +Currently, there is no way to deposit rewards into the smart contract, unless the owner makes it payable, which is generally bad practice, and not recommended. + +As this is a fairly simple task compared to what we've done already, we'll leave this as an exercise to the reader. You'll have to add a `payable("EGLD")` endpoint, and additionally, a storage mapper that keeps track of the remaining rewards. + +Good luck! + +--- + +### Storage Mappers + +The Rust framework provides various storage mappers you can use. Deciding which one to use for every situation is critical for performance. There will be a comparison section after each mapper is described. + +Note: All the storage mappers support additional key arguments. + + +# General purpose mappers + + +## SingleValueMapper + +Stores a single value. Examples: + +```rust +fn single_value(&self) -> SingleValueMapper; +fn single_value_with_single_key_arg(&self, key_arg: Type1) -> SingleValueMapper; +fn single_value_with_multi_key_arg(&self, key_arg1: Type1, key_arg2: Type2) -> SingleValueMapper; +``` + +Keep in mind there is no way of iterating over all `key_arg`s, so if you need to do that, consider using another mapper. + +Available methods: + + +### get +```rust +fn get() -> Type +``` + +Reads the value from storage and deserializes it to the given `Type`. For numerical types and vector types, this will return the default value for empty storage, i.e. 0 and empty vec respectively. For custom structs, this will signal an error if trying to read from empty storage. + + +### set +```rust +fn set(value: &Type) +``` + +Sets the stored value to the provided `value` argument. For base Rust numerical types, the reference is not needed. + + +### is_empty +```rust +fn is_empty() -> bool +``` + +Returns `true` is the storage entry is empty. Usually used when storing struct types to prevent crashes on `get()`. + + +### set_if_empty +```rust +fn set_if_empty(value: &Type) +``` + +Sets the value only if the storage for that value is currently empty. Usually used in #init functions to not overwrite values on contract upgrade. + + +### clear +```rust +fn clear() +``` + +Clears the entry. + + +### update +```rust +fn update R>(f: F) -> R +``` + +Takes a closure as argument, applies that closure to the currently stored value, saves the new value, and returns any value the given closure might return. Examples: + +Incrementing a value: +```rust +fn my_value(&self) -> SingleValueMapper; + +self.my_value().update(|val| *val += 1); +``` + +Modifying a struct's field: +```rust +pub struct MyStruct { + pub field_1: u64, + pub field_2: u32 +} + +fn my_value(&self) -> SingleValueMapper; + +self.my_value().update(|val| val.field1 = 5); +``` + +Returning a value from the closure: +```rust +fn my_value(&self) -> SingleValueMapper; + +let new_val = self.my_value().update(|val| { + *val += 1; + *val +}); +``` + + +### raw_byte_length +```rust +fn raw_byte_length() -> usize +``` + +Returns the raw byte length of the stored value. This should be rarely used. + + +## VecMapper + +Stores elements of the same type, each under their own storage key. Allows access by index for said items. Keep in mind indexes start at 1 for VecMapper. Examples: + +```rust +fn my_vec(&self) -> VecMapper; +fn my_vec_with_args(&self, arg: Type1) -> VecMapper; +``` + +Available methods: + + +### push +```rust +fn push(elem: &T) +``` + +Stores the element at index `len` and increments `len` afterwards. + + +### get +```rust +fn get(index: usize) -> Type +``` + +Gets the element at the specific index. Valid indexes are 1 to `len`, both ends included. Attempting to read from an invalid index will signal an error. + + +### set +```rust +fn set(index: usize, value: &Type) +``` + +Sets the element at the given index. Index must be in inclusive range 1 to `len`. + + +### clear_entry +```rust +fn clear_entry(index: usize) +``` + +Clears the entry at the given index. This does not decrease the length. + + +### is_empty +```rust +fn is_empty() -> bool +``` + +Returns `true` if the mapper has no elements stored. + + +### len +```rust +fn len() -> usize +``` + +Returns the number of items stored in the mapper. + + +### extend_from_slice +```rust +fn extend_from_slice(slice: &[Type]) +``` + +Pushes all elements from the given slice at the end of the mapper. More efficient than manual `for` of `push`, as the internal length is only read and updated once. + + +### swap_remove +```rust +fn swap_remove(index: usize) +``` + +Removes the element at `index`, moves the last element to `index` and decreases the `len` by 1. There is no way of removing an element and preserving the order. + + +### clear +```rust +fn clear() +``` + +Clears all the elements from the mapper. This function can run out of gas for big collections. + + +### iter +```rust +fn iter() -> Iter +``` + +Provides an iterator over all the elements. + + +## SetMapper + +Stores a set of values, with no duplicates being allowed. It also provides methods for checking if a value already exists in the set. Values order is given by their order of insertion. + +Unless you need to maintain the order of the elements, consider using `UnorderedSetMapper` or `WhitelistMapper` instead, as they're more efficient. + +Examples: +```rust +fn my_set(&self) -> SetMapper; +``` + +Available methods: + + +### insert +```rust +fn insert(value: Type) -> bool +``` + +Insers the value into the set. Returns `false` if the item was already present. + + +### remove +```rust +fn remove(value: &Type) +``` + +Removes the value from the set. Returns `false` if the set did not contain the value. + + +### contains +```rust +fn contains(value: &Type) -> bool +``` + +Returns `true` if the mapper contains the given value. + + +### is_empty +```rust +fn is_empty() -> bool +``` + +Returns `true` if the mapper has no elements stored. + + +### len +```rust +fn len() -> usize +``` + +Returns the number of items stored in the mapper. + + +### clear +```rust +fn clear() +``` + +Clears all the elements from the mapper. This function can run out of gas for big collections. + + +### iter +```rust +fn iter() -> Iter +``` + +Returns an iterator over all the stored elements. + + +## UnorderedSetMapper + +Same as SetMapper, but does not guarantee the order of the items. More efficient than `SetMapper`, and should be used instead unless you need to maintain the order. Internally, `UnorderedSetMapper` uses a `VecMapper` to store the elements, and additionally, it stores each element's index to provide O(1) `contains`. + +Examples: +```rust +fn my_set(&self) -> UnorderedSetMapper; +``` + +Available methods: + +`UnorderedSetMapper` contains the same methods as `SetMapper`, the only difference being item removal. Instead of `remove`, we only have `swap_remove` available. + + +### swap_remove +```rust +fn swap_remove(value: &Type) -> bool +``` + +Uses the internal `VecMapper`'s swap_remove method to remove the element. Additionally, it overwrites the last element's stored index with the removed value's index. Returns `false` if the element was not present in the set. + + +## WhitelistMapper + +Stores a whitelist of items. Does not provide any means of iterating over the elements, so if you need to iterate over the elements, use `UnorderedSetMapper` instead. Internally, this mapper simply stores a flag in storage for each item if they're whitelisted. + +Examples: +```rust +fn my_whitelist(&self) -> WhitelistMapper +``` + +Available methods: + + +### add +```rust +fn add(value: &Type) +``` + +Adds the value to the whitelist. + + +### remove +```rust +fn remove(value: &Type) +``` + +Removes the value from the whitelist. + + +### contains +```rust +fn contains(value: &Type) -> bool +``` + +Returns `true` if the mapper contains the given value. + + +### require_whitelisted +```rust +fn require_whitelisted(value: &Type) +``` + +Will signal an error if the item is not whitelisted. Does nothing otherwise. + + +## LinkedListMapper + +Stores a linked list, which allows fast insertion/removal of elements, as well as possibility to iterate over the whole list. + +Examples: +```rust +fn my_linked_list(&self) -> LinkedListMapper +``` + +Available methods: + + +### is_empty +```rust +fn is_empty() -> bool +``` + +Returns `true` if the mapper has no elements stored. + + +### len +```rust +fn len() -> usize +``` + +Returns the number of items stored in the mapper. + + +### clear +```rust +fn clear() +``` + +Clears all the elements from the mapper. This function can run out of gas for big collections. + + +### iter +```rust +fn iter() -> Iter +``` + +Returns an iterator over all the stored elements. + + +### iter_from_node_id +```rust +fn iter_from_node_id(node_id: u32) -> Iter +``` + +Returns an iterator starting from the given `node_id`. Useful when splitting iteration over multiple SC calls. + + +### front +```rust +fn front() -> Option> +fn back() -> Option> +``` + +Returns the first/last element if the list is not empty, `None` otherwise. A `LinkedListNode` has the following format: +```rust +pub struct LinkedListNode { + value: Type, + node_id: u32, + next_id: u32, + prev_id: u32, +} + +impl LinkedListNode { + pub fn get_value_cloned(&self) -> Type { + self.value.clone() + } + + pub fn get_value_as_ref(&self) -> &Type { + &self.value + } + + pub fn into_value(self) -> Type { + self.value + } + + pub fn get_node_id(&self) -> u32 { + self.node_id + } + + pub fn get_next_node_id(&self) -> u32 { + self.next_id + } + + pub fn get_prev_node_id(&self) -> u32 { + self.prev_id + } +} +``` + + +### pop_front/pop_back +```rust +fn pop_front(&mut self) -> Option> +fn pop_back(&mut self) -> Option> +``` + +Removes and returns the first/last element from the list. + + +### push_after/push_before +```rust +pub fn push_after(node: &mut LinkedListNode, element: Type) -> Option> +pub fn push_before(node: &mut LinkedListNode, element: Type) -> Option> +``` + +Inserts the given `element` into the list after/before the given `node`. Returns the newly inserted node if the insertion was successful, `None` otherwise. + + +### push_after_node_id/push_before_node_id +```rust +pub fn push_after_node_id(node_id: usize, element: Type) -> Option> +pub fn push_before_node_id(node_id: usize, element: Type) -> Option> +``` + +Same as the methods above, but uses node_id instead of a full node struct. + + +### push_front/push_back +```rust +fn push_front(element: Type) +fn push_back(element: Type) +``` + +Pushes the given `element` at the front/back of the list. Can be seen as specialized versions of `push_before_node_id` and `push_after_node_id`. + + +### set_node_value +```rust +fn set_node_value(mut node: LinkedListNode, new_value: Type) +``` + +Sets a node's value, if the node exists in the list. + + +### set_node_value_by_id +```rust +fn set_node_value_by_id(node_id: usize, new_value: Type) +``` + +Same as the method above, but uses node_id instead of a full node struct. + + +### remove_node +```rust +fn remove_node(node: &LinkedListNode) +``` + +Removes the node from the list, if it exists. + + +### remove_node_by_id +```rust +fn remove_node(node_id: usize) +``` + +Same as the method above, but uses node_id instead of a full node struct. + + +### iter +```rust +fn iter() -> Iter +``` + +Returns an iterator over all the stored elements. + + +### iter_from_node_id +```rust +fn iter_from_node_id(node_id: u32) -> Iter +``` + +Returns an iterator starting from the given `node_id`. Useful when splitting iteration over multiple SC calls. + + +## MapMapper + +Stores (key, value) pairs, while also allowing iteration over keys. This is the most expensive mapper to use, so make sure you really need to use it. Keys order is given by their order of insertion (same as `SetMapper`). + +Examples: +```rust +fn my_map(&self) -> MapMapper +``` + +Available methods: + + +### is_empty +```rust +fn is_empty() -> bool +``` + +Returns `true` if the mapper has no elements stored. + + +### len +```rust +fn len() -> usize +``` + +Returns the number of items stored in the mapper. + + +### contains_key +```rust +fn contains_key(k: &KeyType) -> bool +``` + +Returns `true` if the mapper contains the given key. + + +### get +```rust +fn get(k: &KeyType) -> Option +``` + +Returns `Some(value)` if the key exists. Returns `None` if the key does not exist in the map. + + +### insert +```rust +fn insert(k: KeyType, v: ValueType) -> Option +``` + +Inserts the given key, value pair into the map, and returns `Some(old_value)` if the key was already present. + + +### remove +```rust +fn remove(k: &KeyType) -> Option +``` + +Removes the key and the corresponding value from the map, and returns the value. If the key was not present in the map, `None` is returned. + + +### keys/values/iter +```rust +fn keys() -> Keys +fn values() -> Values +fn iter() -> Iter +``` + +Provides an iterator over all keys, values, and (key, value) pairs respectively. + + +# Specialized mappers + + +## FungibleTokenMapper + +Stores a token identifier (like a `SingleValueMapper`) and provides methods for using this token ID directly with the most common API functions. Note that most method calls will fail if the token was not issued previously. + +Examples: +```rust +fn my_token_id(&self) -> FungibleTokenMapper +``` + +Available methods: + + +### issue/issue_and_set_all_roles +```rust +fn issue(issue_cost: BigUint, token_display_name: ManagedBuffer, token_ticker: ManagedBuffer,initial_supply: BigUint, num_decimals: usize, opt_callback: Option>) -> ! + +fn issue_and_set_all_roles(issue_cost: BigUint, token_display_name: ManagedBuffer, token_ticker: ManagedBuffer,initial_supply: BigUint, num_decimals: usize, opt_callback: Option>) -> ! +``` + +Issues a new fungible token. `issue_cost` is 0.05 EGLD (5000000000000000) at the time of writing this, but since this changed in the past, we've let it as an argument it case it changes again in the future. + +This mapper allows only one issue, so trying to issue multiple types will signal an error. + +`opt_callback` is an optional custom callback you can use for your issue call. We recommend using the default callback. To do so, you need to import multiversx-sc-modules in your Cargo.toml: +```toml +[dependencies.multiversx-sc-modules] +version = "0.39.0" +``` + +Note: current released multiversx-sc version at the time of writing this was 0.39.0, upgrade if necessary. + +Then you should import the `DefaultCallbacksModule` in your contract: +```rust +#[multiversx_sc::contract] +pub trait MyContract: multiversx_sc_modules::default_issue_callbacks::DefaultIssueCallbacksModule { + /* ... */ +} +``` + +Additionally, pass `None` for `opt_callback`. + +Note the "never" type `-> !` as return type for this function. This means this function will terminate the execution and launch the issue async call, so any code after this call will not be executed. + +Alternatively, if you want to issue and also have all roles set for the SC, you can use the `issue_and_set_all_roles` method instead. + + +### mint +```rust +fn mint(amount: BigUint) -> EsdtTokenPayment +``` + +Mints `amount` tokens for the stored token ID, using the `ESDTLocalMint` built-in function. Returns a payment struct, containing the token ID and the given amount. + + +### mint_and_send +```rust +fn mint_and_send(to: &ManagedAddress, amount: BigUint) -> EsdtTokenPayment +``` + +Same as the method above, but also sends the minted tokens to the given address. + + +### burn +```rust +fn burn(amount: &BigUint) +``` + +Burns `amount` tokens, using the `ESDTLocalBurn` built-in function. + + +### get_balance +```rust +fn get_balance() -> BigUint +``` + +Gets the current balance the SC has for the token. + + +## NonFungibleTokenMapper + +Similar to the `FungibleTokenMapper`, but is used for NFT, SFT and META-ESDT tokens. + + +### issue/issue_and_set_all_roles +```rust +fn issue(token_type: EsdtTokenType, issue_cost: BigUint, token_display_name: ManagedBuffer, token_ticker: ManagedBuffer,initial_supply: BigUint, num_decimals: usize, opt_callback: Option) -> ! + +fn issue_and_set_all_roles(token_type: EsdtTokenType, issue_cost: BigUint, token_display_name: ManagedBuffer, token_ticker: ManagedBuffer,initial_supply: BigUint, num_decimals: usize, opt_callback: Option) -> ! +``` + +Same as the previous issue function, but also takes an `EsdtTokenType` enum as argument, to decide which type of token to issue. Accepted values are `EsdtTokenType::NonFungible`, `EsdtTokenType::SemiFungible` and `EsdtTokenType::Meta`. + + +### nft_create/nft_create_named +```rust +fn nft_create(amount: BigUint, attributes: &T) -> EsdtTokenPayment + +fn nft_create_named(amount: BigUint, name: &ManagedBuffer, attributes: &T) -> EsdtTokenPayment +``` + +Creates an NFT (optionally with a display `name`) and returns the token ID, the created token's nonce, and the given amount in a payment struct. + + +### nft_create_and_send/nft_create_and_send_named +```rust +fn nft_create_and_send(to: &ManagedAddress, amount: BigUint, attributes: &T,) -> EsdtTokenPayment + +fn nft_create_and_send_named(to: &ManagedAddress, amount: BigUint, name: &ManagedBuffer, attributes: &T,) -> EsdtTokenPayment +``` + +Same as the methods above, but also sends the created token to the provided address. + + +### nft_add_quantity +```rust +fn nft_add_quantity(token_nonce: u64, amount: BigUint) -> EsdtTokenPayment +``` + +Adds quantity for the given token nonce. This can only be used if one of the `nft_create` functions was used before AND the SC holds at least 1 token for the given nonce. + + +### nft_add_quantity_and_send +```rust +fn nft_add_quantity_and_send(to: &ManagedAddress, token_nonce: u64, amount: BigUint) -> EsdtTokenPayment +``` + +Same as the method above, but also sends the tokens to the provided address. + + +### nft_burn +```rust +fn nft_burn(token_nonce: u64, amount: &BigUint) +``` + +Burns `amount` tokens for the given nonce. + + +### get_all_token_data +```rust +fn get_all_token_data(token_nonce: u64) -> EsdtTokenData +``` + +Gets all the token data for the given nonce. The SC must own the given nonce for this function to work. + +`EsdtTokenData` contains the following fields: +```rust +pub struct EsdtTokenData { + pub token_type: EsdtTokenType, + pub amount: BigUint, + pub frozen: bool, + pub hash: ManagedBuffer, + pub name: ManagedBuffer, + pub attributes: ManagedBuffer, + pub creator: ManagedAddress, + pub royalties: BigUint, + pub uris: ManagedVec>, +} +``` + + +### get_balance +```rust +fn get_balance(token_nonce: u64) -> BigUint +``` + +Gets the SC's balance for the given token nonce. + + +### get_token_attributes +```rust +fn get_token_attributes(token_nonce: u64) -> T +``` + +Gets the attributes for the given token nonce. The SC must own the given nonce for this function to work. + + +## Common functions for FungibleTokenMapper and NonFungibleTokenMapper + +Both mappers work similarly, so some functions have the same implementation for both. + + +### is_empty +```rust +fn is_empty() -> bool +``` + +Returns `true` if the token ID is not set yet. + + +### get_token_id +```rust +fn get_token_id() -> TokenIdentifier +``` + +Gets the stored token ID. + + +### set_token_id +```rust +fn set_token_id(token_id: &TokenIdentifier) +``` + +Manually sets the token ID for this mapper. This can only be used once, and can not be overwritten afterwards. This will fail if the token was issue previously, as the token ID was automatically set. + + +### require_same_token/require_all_same_token +```rust +fn require_same_token(expected_token_id: &TokenIdentifier) +fn require_all_same_token(payments: &ManagedVec) +``` + +Will signal an error if the provided token ID argument(s) differs from the stored token. Useful in `#[payable]` methods when you only want to this token as payment. + + +### set_local_roles +```rust +fn set_local_roles(roles: &[EsdtLocalRole], opt_callback: Option) -> ! +``` + +Sets the provided local roles for the token. By default, no callback is used for this call, but you may provide a custom callback if you want to. + +You don't need to call this function if you use `issue_and_set_all_roles` for issuing. + +Same as the issue function, this will terminate execution when called. + + +### set_local_roles_for_address +```rust +fn set_local_roles_for_address(address: &ManagedAddress, roles: &[EsdtLocalRole], opt_callback: Option) -> ! +``` + +Similar to the previous function, but sets the roles for a specific address instead of the SC address. + + +## UniqueIdMapper + +A special mapper that holds the values from 1 to N, with the following property: if `mapper[i] == i`, then nothing is actually stored. + +This makes it so the mapper initialization is O(1) instead of O(N). Very useful when you want to have a list of available IDs, as its name suggests. + +Both the IDs and the indexes are `usize`. + +Note: If you want an in-memory version of this, you can use the `SparseArray` type provided by the framework. + +Examples: +```rust +fn my_id_mapper(&self) -> UniqueIdMapper +``` + +Available methods: + + +### set_initial_len +```rust +fn set_initial_len(&mut self, len: usize) +``` + +Sets the initial mapper length, i.e. the `N`. The length may only be set once. + + +### is_empty +```rust +fn is_empty() -> bool +``` + +Returns `true` if the mapper has no elements stored. + + +### len +```rust +fn len() -> usize +``` + +Returns the number of items stored in the mapper. + + +### get +```rust +fn get(index: usize) -> usize +``` + +Gets the value for the given index. If the entry is empty, then `index` is returned, as per the mapper's property. + + +### set +```rust +fn set(&mut self, index: usize, id: usize) +``` + +Sets the value at the given index. The mapper's internal property of `mapper[i] == i` if empty entry is maintained. + + +### swap_remove +```rust +fn swap_remove(index: usize) -> usize +``` + +Removes the ID at the given `index` and returns it. Also, the value at `index` is now set the value of the last entry in the map. Length is decreased by 1. + + +### iter +```rust +fn iter() -> Iter +``` + +Provides an iterator over all the IDs. + + +## Comparisons between the different mappers + + +### SingleValueMapper vs old storage_set/storage_get pairs + +There is no difference between `SingleValueMapper` and the old-school setters/getters. In fact, `SingleValueMapper` is basically a combination between `storage_set`, `storage_get`, `storage_is_empty` and `storage_clear`. Use of `SingleValueMapper` is encouraged, as it's a lot more compact, and has no performance penalty (if, for example, you never use `is_empty()`, that code will be removed by the compiler). + + +### SingleValueMapper vs VecMapper + +Storing a `ManagedVec` can be done in two ways: + +```rust +#[storage_mapper("my_vec_single")] +fn my_vec_single(&self) -> SingleValueMapper> + +#[storage_mapper("my_vec_mapper")] +fn my_vec_mapper(&self) -> VecMapper; +``` + +Both of those approaches have their merits. The `SingleValueMapper` concatenates all elements and stores them under a single key, while the `VecMapper` stores each element under a different key. This also means that `SingleValueMapper` uses nested-encoding for each element, while `VecMapper` uses top-encoding. + +Use `SingleValueMapper` when: +- you need to read the whole array on every use +- the array is expected to be of small length + +Use `VecMapper` when: +- you only require reading a part of the array +- `T`'s top-encoding is vastly more efficient than `T`'s nested-encoding (for example: `u64`) + + +### VecMapper vs SetMapper + +The primary use for `SetMapper` is storing a whitelist of addresses, token ids, etc. A token ID whitelist can be stored in these two ways: + +```rust +#[storage_mapper("my_vec_whitelist")] +fn my_vec_whitelist(&self) -> VecMapper + +#[storage_mapper("my_set_mapper")] +fn my_set_mapper(&self) -> SetMapper; +``` + +This might look very similar, but the implications of using `VecMapper` for this are very damaging to the potential gas costs. Checking for an item's existence in `VecMapper` is done in O(n), with each iteration requiring a new storage read! Worst case scenario is the Token ID is not in the whitelist and the whole Vec is read. + +`SetMapper` is vastly more efficient than this, as it provides checking for a value in O(1). However, this does not come without a cost. This is how the storage looks for a `SetMapper` with two elements (this snippet is taken from a scenario test): + +```rust +"str:tokenWhitelist.info": "u32:2|u32:1|u32:2|u32:2", +"str:tokenWhitelist.node_idEGLD-123456": "2", +"str:tokenWhitelist.node_idETH-123456": "1", +"str:tokenWhitelist.node_links|u32:1": "u32:0|u32:2", +"str:tokenWhitelist.node_links|u32:2": "u32:1|u32:0", +"str:tokenWhitelist.value|u32:2": "str:EGLD-123456", +"str:tokenWhitelist.value|u32:1": "str:ETH-123456" +``` + +A `SetMapper` uses 3 * N + 1 storage entries, where N is the number of elements. Checking for an element is very easy, as the only thing the mapper has to do is check the `node_id` entry for the provided token ID. + +Even so, for this particular case, `SetMapper` is way better than `VecMapper`. + + +### VecMapper vs LinkedListMapper + +`LinkedListMapper` can be seen as a specialization for the `VecMapper`. It allows insertion/removal only at either end of the list, known as pushing/popping. It's also storage-efficient, as it only requires 2 * N + 1 storage entries. The storage for such a mapper looks like this: + +```rust +"str:list_mapper.node_links|u32:1": "u32:0|u32:2", +"str:list_mapper.node_links|u32:2": "u32:1|u32:0", +"str:list_mapper.value|u32:1": "123", +"str:list_mapper.value|u32:2": "111", +"str:list_mapper.info": "u32:2|u32:1|u32:2|u32:2" +``` + +This is one of the lesser used mappers, as its purpose is very specific, but it's very useful if you ever need to store a queue. + + +### SingleValueMapper vs MapMapper + +Believe it or not, most of the time, `MapMapper` is not even needed, and can simply be replaced by a `SingleValueMapper`. For example, let's say you want to store an ID for every Address. It might be tempting to use `MapMapper`, which would look like this: + +```rust +#[storage_mapper("address_id_mapper")] +fn address_id_mapper(&self) -> MapMapper; +``` + +This can be replaced with the following `SingleValueMapper`: +```rust +#[storage_mapper("address_id_mapper")] +fn address_id_mapper(&self, address: &ManagedAddress) -> SingleValueMapper; +``` + +Both of them provide (almost) the same functionality. The difference is that the `SingleValueMapper` does not provide a way to iterate over all the keys, i.e. Addresses in this case, but it's also 4-5 times more efficient. + +Unless you need to iterate over all the entries, `MapMapper` should be avoided, as this is the most expensive mapper. It uses 4 * N + 1 storage entries. The storage for a `MapMapper` looks like this: + +```rust +"str:map_mapper.node_links|u32:1": "u32:0|u32:2", +"str:map_mapper.node_links|u32:2": "u32:1|u32:0", +"str:map_mapper.value|u32:1": "123", +"str:map_mapper.value|u32:2": "111", +"str:map_mapper.node_id|u32:123": "1", +"str:map_mapper.node_id|u32:111": "2", +"str:map_mapper.mapped|u32:123": "456", +"str:map_mapper.mapped|u32:111": "222", +"str:map_mapper.info": "u32:2|u32:1|u32:2|u32:2" +``` + +Keep in mind that all the mappers can have as many additional arguments for the main key. For example, you can have a `VecMapper` for every user pair, like this: +```rust +#[storage_mapper("list_per_user_pair")] +fn list_per_user_pair(&self, first_addr: &ManagedAddress, second_addr: &ManagedAddress) -> VecMapper; +``` + +Using the correct mapper for your situation can greatly decrease gas costs and complexity, so always remember to carefully evaluate your use-case. + + +## Accessing a value at an address + +Because of the way the storage mappers are structured, it is very easy to access a "remote" value, meaning a value stored under a key at a different address than the current one. + +```rust +#[storage_mapper("key_example")] +fn content(&self) -> MapMapper; + +#[storage_mapper_from_address("key_example")] +fn content_from_address(&self, address: ManagedAddress, ...) -> SetMapper; +``` + +The `content_from_address` function is used to create a **new map** for accessing the storage of another contract, identified by its address. + +The function can have any name, but it is necessary to be tagged with `#[storage_mapper_from_address("key_example")]`, where **"key_example"** is the **exact key** used by the storage they wish to access. + + +### Parameters + +- `&self`: reference to the current instance of the contract. +- `address: ManagedAddress`: required parameter; it specifies the address of the contract whose storage mapper you want to access. +- **optional** extra keys of any type. + + +### Return type + +The function will return the desired mapper that will store the data. In addition, `ManagedAddress` will always be added to the end of the list of generics in the storage mapper. + +:::important important +This feature only works `intra-shard`. + +Also note that a remote value found under a key at an address can only be `read`, not modified. +::: + + +### Example + +If a developer wanted, for example, to iterate over another contract's `SetMapper`, instead of retrieving the values through a call to an endpoint and then iterating, one could simply create a new `SetMapper` with a specific `address` parameter. Afterwards, the `iter` function can be called easily to accomplish the task. + +```rust title=contract_to_be_called/lib.rs +#[storage_mapper("my_remote_mapper")] +fn my_set_mapper(&self) -> SetMapper +``` + +This is a simple `SetMapper` registered under the address of `contract_to_be_called`, and the value stored will be registered under the key provided, `my_remote_mapper`. If we wanted to iterate over the values of `my_set_mapper` intra-shard, we could write: + +```rust title=caller_contract/lib.rs +// the address of the contract containing the storage (contract_to_be_called) +#[storage_mapper("contract_address")] +fn contract_address(&self) -> SingleValueMapper; + +// by creating the mapper with the address of the sc and exact storage key +// we get access to the value stored under that key +#[storage_mapper_from_address("contract_address")] +fn contract_from_address(&self, address: ManagedAddress) -> SetMapper; + +#[endpoint] +fn my_endpoint(&self) -> u32 { + let mut sum = 0u32; + let address = self.contract_address().get(); + + for number in self.contract_from_address(address).iter() { + sum += number + } + + sum +} +``` + +Calling `my_endpoint` will return the sum of the values stored in `contract_to_be_called`'s SetMapper. + +By specifying the expected type, storage key and address, the value can be read and used inside our logic. + +--- + +### System Delegation Events + + +--- + +### System Smart Contracts + +For transactions which call System Smart Contracts, the **actual gas cost** of processing contains the two previously mentioned cost components - and they are easily computable. + +For more details, please follow: + + - [The Staking Smart Contract](/validators/staking/staking-smart-contract) + - [The Delegation Manager](/validators/delegation-manager) + - [ESDT tokens](/tokens/fungible-tokens) + - [NFT tokens](/tokens/nft-tokens) + +--- + +### tags + +This page describes the structure of the `tags` index (Elasticsearch), and also depicts a few examples of how to query it. + + +## _id + +The `_id` field of this index is represented by the tag name in a base64 encoding. + + +## Fields + + +| Field | Description | +|-------|---------------------------------------------------------------------| +| count | The count field represents the number of NFTs with the current tag. | +| tag | This field represents the tag in an alphanumeric format. | + + +## Query examples + + +### Fetch NFTs count with a given tag + +``` +curl --request GET \ + --url ${ES_URL}/tags/_search \ + --header 'Content-Type: application/json' \ + --data '{ + "query": { + "match": { + "tag":"sport" + } + } +}' +``` + +--- + +### Test setup + +[comment]: # "mx-abstract" + +## Overview + + +### Registering contracts + +Since we don't have native execution in the Rust backend yet, the only way to run contracts is to register the contract implementation for the given contract code identifier. In simpler words, we tell the environment "whenever you encounter this contract code, run this code that I've written instead". + +Since this operation is specific to only the Rust debugger, it doesn't go through the mandos pipeline. + + + +### Setting accounts + +Setting accounts in blackbox tests can be easily done by using a `SetStateBuilder`. In order to create an instance of the builder, we have to call the `.account(...)` method from `ScenarioWorld`. + +```rust title=blackbox_test.rs +world // ScenarioWorld struct + .account(USER_ADDRESS) // SetStateBuilder with USER_ADDRESS account + .nonce(1) // custom nonce + .balance(50) // egld balance + .esdt_balance(TRANSFER_TOKEN, 1000) // esdt balance + .esdt_nft_balance(NFT_TOKEN_ID, 1u64, 1u64, ManagedBuffer::new()); // nft balance +``` + +There are no mandatory fields, so we can only add the fields that we actually need. For example, if we only need to create the account and we don't care about other fields, `world.account(ADDRESS)` will compile. + +However, there is no possibility to upgrade and existing account, so there can only be one set block per account. + +We can also chain the set state declarations (if useful) as such: + +```rust title=blackbox_test.rs + world + .account(first) // SetStateBuilder for account `first` + .nonce(1) + .balance(100) + .account(second) // SetStateBuilder for `second`, ends set state for `first` + .nonce(2) + .balance(300) + .esdt_balance(TOKEN_ID, 500); // end set state for `second` +``` + + +### Checking accounts + +Similar to setting accounts, the framework provides a `CheckStateBuilder` that we can use to check state values. The check builder is instantiated using `.check_account(...)` from `ScenarioWorld`. + +```rust title=blackbox_test.rs + world + .check_account(first) // CheckStateBuilder for `first` + .nonce(3) + .balance(100); +``` + +The same rules apply when chaining multiple account checks as for chaining accounts set. + +```rust title=blackbox_test.rs + world + .check_account(first) // CheckStateBuilder for `first` + .nonce(3) + .balance(100); + .check_account(second) // CheckStateBuilder for `second`, ends check state for `first` + .check_storage("str:sum", "6"); +``` + + +### Mandos trace + +A mandos [trace](../rust/mandos-trace) can quickly be generated by wrapping the integration test logic into the trace generation as such: + +```rust title=blackbox_test.rs + world.start_trace(); + + // integration test logic + + world.write_scenario_trace("trace1.scen.json"); +``` + +--- + +### Testing in Go + +At some point in the past we built some testing solutions in Go. They were never very popular, but are worth mentioning. + +This page is currently a stub. If there is interest in this type of testing, we will expand it further. + + +## **Embedding in Go** + +Scenario steps can be embedded in Go, in order to program for more flexible behavior. One can even save dynamically generated scenarios. For a comprehensive example on how to do that, check out the [delegation contract fuzzer in MultiversX VM](https://github.com/multiversx/mx-chain-vm-go/tree/master/fuzz/delegation) or the [DNS contract deployment scenario test generator](https://github.com/multiversx/mx-chain-vm-go/tree/master/cmd/testgen/dns). Just a snippet from the fuzzer: + +```rust +_, + (err = pfe.executeTxStep( + fmt.Sprintf( + ` + { + "step": "scDeploy", + "txId": "-deploy-", + "tx": { + "from": "''%s", + "value": "0", + "contractCode": "file:delegation.wasm", + "arguments": [ + "''%s", + "%d", + "%d", + "%d" + ], + "gasLimit": "100,000", + "gasPrice": "0" + }, + "expect": { + "out": [], + "status": "", + "logs": [], + "gas": "*", + "refund": "*" + } + }`, + string(pfe.ownerAddress), + string(pfe.auctionMockAddress), + args.serviceFee, + args.numBlocksBeforeForceUnstake, + args.numBlocksBeforeUnbond + ) + )); +``` + +--- + +### Testing Overview + +What does it mean to test a smart contract? + +Well, smart contracts are little programs that run on the blockchain, so the first step is to find an environment for them to run. + + +## Types of tests + +The simplest answer would be to test them directly on a blockchain. We advise against testing them on mainnet, but we have the public devnet and testnet especially made for this purpose. It is even possible to run a blockchain on your local machine, this way there is no interference with anyone. + +But testing on a blockchain can be cumbersome. It's a great way to test a final product, but until our product reaches maturity, we want something else. Our testing solution should be: +- fast, +- local, +- something that can we can automate (continuous integration is important), +- something that we might also debug. + +This is what integration tests are for. Conveniently, the MultiversX framework offers the possibility to run and debug smart contracts in a sandboxed environment that imitates a real blockchain. + +There are two flavors of integration tests: +- Black-box tests: execution imitates the blockchain, no access to private contract functions; +- White-box tests: the test has access to the inner workings of the contract. + +They both have their uses and we will see more about them further on. + +There is a third type of test: the unit test. This is the most underrated type of test. It is ideal for testing a small function, or a small component of a smart contract. They are quick to write and quick to run. A healthy project should contain plenty of unit tests. + +So, to recap, the ways to test a smart contract are as follows: +- On a blockchain, +- Integration tests: + - Black-box tests, + - White-box tests; +- Unit tests. + + +## What language should I use for my tests? + +Since smart contracts are written in **Rust**, it is most convenient to have the tests also written in Rust. The Rust framework currently supports all types of testing mentioned above. + +Let's, however, quickly go through all options available on MultiversX: +- On a blockchain: + - Rust interactor framework; + - [Any other SDK that can interact with the MultiversX blockchains](/sdk-and-tools/overview); + - Launching transactions from the wallet directly. +- Integration tests: + - Black-box tests: + - Rust, + - JSON, via the [JSON scenarios](/developers/testing/scenario/structure-json), + - Go, by building [around the Go VM tool](/developers/testing/testing-in-go); + - White-box tests: + - Rust only (since direct access to the contract code is needed); +- Unit tests: + - Rust only (since direct access to the contract code is needed). + + +## Testing backends + +A smart contract needs an environment to run. Any smart contract test needs to set up this environment. All these environments are modelled after the same blockchain, so their behavior is necessarily similar. This also means that in general, with some proper engineering, the same test should be able to run on any compatible infrastructure. + +```mermaid +graph TD + sc[Smart Contract] --> interact + interact[Interactor] --> blockchain["⚙️ Blockchain"] + sc --> bb[BlackBox Test] --> vm-go["⚙️ Go VM"] + bb -.-> vm-k["⚙️ K framework VM"] + sc --> wb[WhiteBox Test] --> vm-rust["⚙️ Rust VM (Debugger)"] + bb --> vm-rust + style vm-k stroke-dasharray: 5 5 +``` + +The backends are as follows: +- A blockchain + - Can be thought of as the backend for blockchain interactions; + - Currently the only place to test cross-shard transactions and contract calls. +- The Go VM: + - The is the official VM, and currently the only VM implementation on a public MultiverX blockchain. + - This is currently the most complete implementation. If there is any difference in VM execution, the Go VM is considered the correct implementation. + - The only VM that can currently model gas. +- The Rust VM. + - Alternative implementation of the VM, written specifically for testing and debugging contracts. + - Is able to run contract code directly, without the need to compile contracts to WebAssembly. + - Supports step-by-step debugging of contract code, for all types of tests. + - Can provide code coverage. +- The K Framework VM specification. + - Currently in the works. Only test prototypes are currently available. + - It is a formal specification of WebAssembly and the MultiversX VM. + - It is an _executable_ specification, can therefore be used as backend for tests. + - Supports symbolic execution. + +White-box and unit tests can only run on the Rust VM backend. For the black-box tests, however, all backends are available. + +Interactors and black-box tests are very similar, so our goal is to at some point create a common test API that is compatible with all backends, including the real blockchain. We are half-way there. + +--- + +### The dynamic allocation problem + +### Avoiding memory allocation + +:::caution +**Smart contracts must avoid dynamic allocation**. Due to the performance penalty incurred by dynamic allocation, the MultiversX Virtual Machine is configured with hard limits and will stop a contract that attempts too much allocation. +::: + +Here are a few simple guidelines you can use to ensure your contract performs efficiently. By following them, you might notice a considerable reduction of gas consumption when your contract is called. It is also likely that the WASM binary resulting from compilation may become smaller in size, thus faster and cheaper to call overall. + + +### It's all about the types + +Many basic Rust types (like `String` and `Vec`) are dynamically allocated on the heap. In simple terms, it means the program (in this case, the smart contract) keeps asking for more and more memory from the runtime environment (the VM). For small collections, this doesn't matter much, but for bigger collection, this can become slow and the VM might even stop the contract and mark the execution as failed. + +The main issue is that basic Rust types are quite eager with dynamic memory allocation: they ask for more memory than they actually need. For ordinary programs, this is great for performance, but for smart contracts, where every instruction costs gas, can be quite impactful, on both cost and even runtime failures. + +The alternative is to use **managed types** instead of the usual Rust types. All managed types, like `BigUint`, `ManagedBuffer` etc. store all their contents inside the VM's memory, as opposed to the contract memory, so they have a great performance advantage. But you don't need to be concerned with "where" the contents are, because managed types automatically keep track of the contents with help from the VM. + +The managed types work by only storing a `handle` within the contract memory, which is a `u32` index, while the actual payload resides in reserved VM memory. So whenever you have to add two `BigUint`s for example, the `+` operation in your code will only pass the three handles: the result, the first operand, and the second operand. This way, there is very little data being passed around, which in turn makes everything cheaper. And since these types only store a handle, their memory allocation is fixed in size, so it can be allocated on the stack instead of having to be allocated on the heap. + +:::caution +If you need to update older code to take advantage of managed types, please take the time to understand the changes you need to make. Such an update is important and cannot be done automatically. +::: + + +### Base Rust types vs managed types + +Below is a table of unmanaged types (basic Rust types) and their managed counterparts, provided by the MultiversX framework: + +| Unmanaged (safe to use) | Unmanaged (allocates on the heap) | Managed | +| :---------------------: | :-------------------------------: | :------------------------------------------: | +| - | - | `BigUint` | +| `&[u8]` | - | `&ManagedBuffer` | +| - | `BoxedBytes` | `ManagedBuffer` | +| `ArrayVec`[^1] | `Vec` | `ManagedBuffer` | +| - | `String` | `ManagedBuffer` | +| - | - | `TokenIdentifier` | +| - | `MultiValueVec` | `MultiValueEncoded` / `MultiValueManagedVec` | +| `ArrayVec`[^1] | `Vec` | `ManagedVec` | +| `[T; N]`[^2] | `Box<[T; N]>` | `ManagedByteArray` | +| - | `Address` | `ManagedAddress` | +| - | `H256` | `ManagedByteArray<32>` | +| - | - | `EsdtTokenData` | +| - | - | `EsdtTokenPayment` | + +[^1]: `ArrayVec` allocates on the stack, and so it has a fixed capacity - it cannot grow indefinitely. You can make it as large as you please, but be warned that adding beyond this capacity results in a panic. Use `try_push` instead of `push` for more graceful error handling. +[^2]: Be careful when passing arrays around, since they get copied when returned from functions. This can add a lot of expensive memory copies in your contract. + +In most cases, the managed types can be used as drop-in replacements for the basic Rust types. For a simple example, see [BigUint Operations](/developers/best-practices/biguint-operations). + +We also recommend _allocating Rust arrays directly on the stack_ (as local variables) whenever a contiguous area of useful memory is needed. Moreover, avoid allocating mutable global buffers for this purpose, which require `unsafe` code to work with. + +Also, consider using `ArrayVec`, which provides the functionality of a `Vec`, but without allocation on the heap. Instead, it requires allocation of a block of memory directly on the stack, like a basic Rust local array, but retains the flexibility of `Vec`. + +:::caution +Make sure you migrate to the managed types **incrementally** and **thoroughly test your code** before even considering deploying to the mainnet. +::: + +:::tip +You can use the `sc-meta report` command to verify whether your contract still requires dynamic allocation or not. +::: + +--- + +### The MultiversX Serialization Format + +In MultiversX, there is a specific serialization format for all data that interacts with a smart contract. The serialization format is central to any project because all values entering and exiting a contract are represented as byte arrays that need to be interpreted according to a consistent specification. + +In Rust, the **multiversx-sc-codec** crate ([crate](https://crates.io/crates/multiversx-sc-codec), [docs](https://docs.rs/multiversx-sc-codec/latest/multiversx_sc_codec/)) exclusively deals with this format. Both Go and Rust implementations of scenarios have a component that serializes to this format. DApp developers need to be aware of this format when interacting with the smart contract on the backend. + + +## Rationale + +We want the format to be somewhat readable and to interact with the rest of the blockchain ecosystem as easily as possible. This is why we have chosen **big endian representation for all numeric types.** + +More importantly, the format needs to be as compact as possible, since each additional byte costs additional fees. + + +## The concept of top-level vs. nested objects + +There is a perk that is central to the formatter: we know the size of the byte arrays entering the contract. All arguments have a known size in bytes, and we normally learn the length of storage values before loading the value itself into the contract. This gives us some additional data straight away that allows us to encode less. + +Imagine that we have an argument of type int32. During a smart contract call we want to transmit the value "5" to it. A standard deserializer might expect us to send the full 4 bytes `0x00000005`, but there is clearly no need for the leading zeroes. It's a single argument, and we know where to stop, there is no risk of reading too much. So sending `0x05` is enough. We saved 3 bytes. Here we say that the integer is represented in its **top-level form**, it exists on its own and can be represented more compactly. + +But now imagine that an argument that deserializes as a vector of int32. The numbers are serialized one after the other. We no longer have the possibility of having variable length integers because we won't know where one number begins and one ends. Should we interpret `0x0101` as`[1, 1]` or `[257]`? So the solution is to always represent each integer in its full 4-byte form. `[1, 1]` is thus represented as `0x0000000100000001` and`[257]`as `0x00000101`, there is no more ambiguity. The integers here are said to be in their **nested form**. This means that because they are part of a larger structure, the length of their representation must be apparent from the encoding. + +But what about the vector itself? Its representation must always be a multiple of 4 bytes in length, so from the representation we can always deduce the length of the vector by dividing the number of bytes by 4. If the encoded byte length is not divisible by 4, this is a deserialization error. Because the vector is top-level we don't have to worry about encoding its length, but if the vector itself gets embedded into an even larger structure, this can be a problem. If, for instance, the argument is a vector of vectors of int32, each nested vector also needs to have its length encoded before its data. + + +## A note about the value zero + +We are used to writing the number zero as "0" or "0x00", but if we think about it, we don't need 1 byte for representing it, 0 bytes or an "empty byte array" represent the number 0 just as well. In fact, just like in `0x0005`, the leading 0 byte is superfluous, so is the byte `0x00` just like an unnecessary leading 0. + +With this being said, the format always encodes zeroes of any type as empty byte arrays. + + +## How each type gets serialized + +This guide is split into several sections: +- [Simple values, such as numbers, strings, etc.](/developers/data/simple-values) +- [Lists, tuples, Option](/developers/data/composite-values) +- [Custom types defined in the contracts.](/developers/data/custom-types) +- [Var-args and other multi-values](/developers/data/multi-values) +- [The code metadata flag](/developers/data/code-metadata) + +There is a special section about [uninitialized data and how defaults relate to serialization](/developers/data/defaults). + +--- + +### Time-related Types + +The Supernova release introduces increased block frequency and encourages transitioning to millisecond timestamps, instead of seconds. To support this, the SpaceCraft SDK (starting with `v0.63.0`) provides strong type wrappers for time values to prevent common bugs. + + + +## Overview + +Traditionally, smart contracts used plain `u64` values to represent timestamps and durations. As the ecosystem transitions to millisecond precision, mixing seconds and milliseconds becomes error-prone. Runtime errors are difficult to detect, so the compiler now assists by enforcing type correctness. + + + +## The Four Time-Related Types + +The framework introduces four new types: + +* **`TimestampSeconds`** — moment in time measured in seconds +* **`TimestampMillis`** — moment in time measured in milliseconds +* **`DurationSeconds`** — duration measured in seconds +* **`DurationMillis`** — duration measured in milliseconds + +Each is a **newtype wrapper around `u64`**, with an identical underlying representation but distinct meaning. + + + +## Why These Types Exist + +Using plain `u64` makes it easy to: + +* accidentally mix seconds and milliseconds +* add timestamps together (nonsensical) +* subtract mismatched units +* perform invalid arithmetic without realizing it + +The new types make such mistakes **fail at compile time**. + + + +## Supported Operations + +The framework implements common operators only where they make sense. + +Examples: + +``` +TimestampMillis - TimestampMillis → DurationMillis +TimestampSeconds + DurationSeconds → TimestampSeconds +DurationSeconds + DurationSeconds → DurationSeconds +``` + +Examples of invalid operations (will not compile): + +``` +TimestampMillis - TimestampSeconds +TimestampMillis + TimestampMillis +``` + + + +## Codec and ABI Support + +All four types: + +* support codec and ABI traits +* can be used in storage +* can be used in events and arguments +* behave like `u64` for serialization + +This makes them safe to adopt even in existing contracts. + + + +## When to Use Seconds vs. Milliseconds + +* **Seconds**: acceptable when your contract already stores timestamps in seconds or relies on existing storage/metadata +* **Milliseconds**: recommended for all new contracts and for future-proof logic + +The key rule: **Never mix seconds and milliseconds without explicit conversion.** + + + +## Testing + +Time-related types also work in testing frameworks. + +In blackbox tests, one can write: + +```rust + let block_timestamp_ms = TimestampMillis::new(123_000_000); + + world + .epoch_start_block() + .block_timestamp_ms(block_timestamp_ms) + .block_nonce(15_000) + .block_round(17_000); +``` + + +Mandos supports both `blockTimestamp` and the newer `blockTimestampMs`. Set both if they are used together for backward compatibility. + + + +## Summary + +* Time-related newtypes enforce correctness at compile time +* They eliminate a class of subtle bugs related to timestamp unit mismatches +* Both seconds and milliseconds will continue to work, but milliseconds are recommended for new code + +Use these types to ensure your contract remains safe and consistent across all future protocol updates. + +--- + +### tokens + +This page describes the structure of the `tokens` index (Elasticsearch), and also depicts a few examples of how to query it. + + +## _id + +The `_id` field of this index is represented by token identifier of an ESDT token. + + +## Fields + + +| Field | Description | +|---------------|-----------------------------------------------------------------------------------------------------------------------------------| +| name | The name field holds the name of the token. It contains alphanumeric characters only. | +| ticker | The ticker field represents the token's ticker (uppercase alphanumeric characters). | +| token | The token field is composed of the `ticker` field and a random sequence generated when the token is created(e.g. `ABCD-012345`). | +| issuer | The issuer field holds the bech32 encoded address of the token's issuer. | +| currentOwner | The currentOwner field holds the address in a bech32 format of the current owner of the token. | +| type | The type field holds the type of the token. It can be `FungibleESDT`, `NonFungibleESDT`, `SemiFungibleESDT`, or `MetaESDT`. | +| timestamp | The timestamp field represents the timestamp of the block in which the token was created. | +| ownersHistory | The ownersHistory field holds a list of all the owners of a token. | +| paused | The paused field is true if the token is paused. | + + +## Query examples + + +### Fetch details of a token + +``` +curl --request GET \ + --url ${ES_URL}/tokens/_search \ + --header 'Content-Type: application/json' \ + --data '{ + "query": { + "match": { + "_id":"ABCD-012345" + } + } +}' +``` + +--- + +### Toolchain Setup + +## Installing Rust and sc-meta + +:::note +`sc-meta` is universal smart contract management tool. Please follow [this](/developers/meta/sc-meta) for more information. +::: + + +For systems running Ubuntu or Windows with WSL, you should first ensure the following system-level dependencies required by Rust and sc-meta are in place: + +```bash +sudo apt-get install build-essential pkg-config libssl-dev +``` + +Install Rust as recommended on [rust-lang.org](https://www.rust-lang.org/tools/install): + +```bash +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +``` + +Then, choose **Proceed with installation (default)**. + +:::tip +Generally speaking, you should install Rust `v1.85.0` (stable channel) or later. +::: + +```bash +rustup update +rustup default stable +``` + +Afterwards, open a new terminal (shell) and install `sc-meta`: + +```bash +cargo install multiversx-sc-meta --locked +``` + +Once `sc-meta` is ready, install the `wasm32` target (for the Rust compiler), `wasm-opt`, and others dependencies as follows: + +```bash +# Installs `wasm32`, `wasm-opt`, and others in one go: +sc-meta install all + +cargo install twiggy +``` + + +### Within Continuous Integration / Continuous Delivery + +For automated environments like CI/CD pipelines, start by installing the necessary foundational libraries. On platforms such as Ubuntu (or WSL), this means installing: + +```bash +sudo apt-get install build-essential pkg-config libssl-dev +``` + +For CI / CD, install Rust as follows: + +```bash +wget -O rustup.sh https://sh.rustup.rs && \ + chmod +x rustup.sh && \ + ./rustup.sh --verbose --default-toolchain stable -y + +cargo install multiversx-sc-meta --locked + +sc-meta install wasm32 +``` + + +## Check your Rust installation + +You can check your Rust installation by invoking `rustup show`: + +```sh +$ rustup show + +Default host: x86_64-unknown-linux-gnu +rustup home: /home/ubuntu/.rustup + +installed toolchains +-------------------- +stable-x86_64-unknown-linux-gnu (default) +[...] + +active toolchain +---------------- +name: stable-x86_64-unknown-linux-gnu +installed targets: + wasm32-unknown-unknown + wasm32v1-none + x86_64-unknown-linux-gnu + +--- + +### Tooling Overview + +## Introduction + +We have developed a universal smart contract management tool, called `multiversx-sc-meta` (`sc-meta` in short). + +It is called that, because it provides a layer of meta-programming over the regular smart contract development. It can read and interact with some of the code written by developers. + +You can find it on [crates.io](https://crates.io/crates/multiversx-sc-meta) [![crates.io](https://img.shields.io/crates/v/multiversx-sc-meta.svg?style=flat)](https://crates.io/crates/multiversx-sc-meta) + +To install it, simply call + +``` +cargo install multiversx-sc-meta --locked +``` + +After that, try calling `sc-meta help` or `sc-meta -h` to see the CLI docs. + +:::note endure dependencies +Ubuntu users have to ensure the existence of the `build_essential` package installed in their system. +::: + + +## Standalone tool vs. contract tool + +The unusual thing about this tool is that it comes in two flavors. One of them is the standalone tool, installed as above. The other is a tool that gets provided specifically for every contract, and which helps with building. + +The contract tool lies in the `meta` folder under each contract. It just contains these 3 lines of code: + +```rust +fn main() { + multiversx_sc_meta::cli_main::(); +} +``` + +... but they are important, because they link the contract tool to the contract code, via the [ABI](/developers/data/abi). + +The contract tool is required in order to build contracts, because it is the only tool that we have that calls the ABI generator, manages the wasm crate and the multi-contract config, and has the data on how to build the contract. + +Therefore, all the functionality that needs the ABI goes into the contract tool, whereas the rest in the standalone tool. + +To see the contract meta CLI docs, `cd` into the `/meta` crate and call `cargo run help` or `cargo run -- -h`. + + +## Contract functionality + +Currently the contract functionality is: + - `abi` Generates the contract ABI and nothing else. + - `build` Builds contract(s) for deploy on the blockchain. + - `build-dbg` Builds contract(s) with symbols and WAT. + - `twiggy` Builds contract(s) and generate twiggy reports. + - `clean` Clean the Rust project and the output folder. + - `update` Update the Cargo.lock files in all wasm crates. + - `snippets` Generates a snippets project, based on the contract ABI. + +To learn more about the smart contract ABI and ABI-based individual contract tools, see [the CLI reference](/developers/meta/sc-meta-cli). + + +## Standalone functionality + +The standalone functionality is: + - `info` General info about the contract an libraries residing in the targeted directory. + - `all` Calls the meta crates for all contracts under given path with the given arguments. + - `new` Creates a new smart contract from a template. + - `templates` Lists the available templates. + - `upgrade` Upgrades a contract to the latest version. Multiple contract crates are allowed. + - `local-deps` Generates a report on the local dependencies of contract crates. Will explore indirect dependencies too. + +All the standalone tools take an optional `--path` argument. if not provided, it will be the current directory. + +--- + +### Tools for signing + +In order to sign a transaction without actually dispatching it, several tools can be used. One of the most popular ones is [mxpy](/sdk-and-tools/sdk-py). + + +## **Sign using [mxpy](/sdk-and-tools/sdk-py/) (Command Line Interface)** + +Using a **pem** file: + +``` +$ mxpy tx new --nonce=41 --data="Hello, World" --gas-limit=70000 \ + --receiver=erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz \ + --pem=aliceKey.pem --pem-index=0 --outfile=myTransaction.json + +``` + +Using a JSON wallet key (and its password): + +``` +mxpy tx new --nonce=41 --data="Hello, World" --gas-limit=70000 \ + --receiver=erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz \ + --keyfile=walletKeyOfAlice.json --passfile=passwordOfAlice.txt \ + --outfile=myTransaction.json + +``` + +In either case, the output file looks like this: + +``` +{ + "tx": { + "nonce": 41, + "value": "0", + "receiver": "erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz", + "sender": "erd1aedmqfsflx4rhwvs7v9z52e7eylkevz4w342jzuaa9ezy5unsc5qqy963v", + "gasPrice": 1000000000, + "gasLimit": 70000, + "data": "SGVsbG8sIFdvcmxk", + "chainID": "1596807148", + "version": 123, + "signature": "f432442ebfee6edf4518c10d006ab571d8ecbd6f2601995554c75d3402b424364908235d45449ba5dd28575e4a8129271020e4718cf8a4c6f44e22c0885ac40a" + }, + "hash": "", + "data": "Hello, World" +} +``` + + +## **Other signing tools** + +Each SDK includes functions for signing and broadcasting transactions. Please refer to [SDKs & Tools](/sdk-and-tools/overview) for the full list. + +--- + +### Transaction Overview + +A big part of the life of a blockchain developer is to create and launch blockchain transactions. + +Whether it's an off-chain tool, smart contract code, or a testing scenario, it is important that we have a powerful syntax to express any conceivable transaction. + +During the process of creating the development framework, we realized that the following are equivalent to a large extent and could be expressed using the same syntax: +- transactions launched from smart contracts, +- blackbox integration tests, +- off-chain calls. + +We called this "unified transaction syntax" or "unified syntax", and the first version of it was released in [multiversx-sc version 0.49.0](https://github.com/multiversx/mx-sdk-rs/releases/tag/v0.49.0). + +In this documentation, you will get a complete explanation of all the features of this syntax, organized around the various components of a blockchain transaction. + + + +## Motivation and design + +Transactions can be fairly complex, and in order to get them to use the same syntax in very different environments, we had a few challenges: + +1. Transactions have many fields, which can be configured in many ways. It is important to be able to configure most of them independently, otherwise the framework becomes too large, or unreliable. +2. Since this syntax is also used in contracts, it was essential to choose a design that adds almost no runtime and code size overhead. So the syntax must make heavy use of generics, to resolve at compile-time all type checks, conversions, and restrictions. +3. Also, because syntax is used in contracts, it had to rely on managed types. +4. We wanted as much type safety as possible, so we had to find a way to rely on the ABI to produce type checks both for contract inputs and outputs. + + + +## The `Tx` object + +We decided to model all transactions using a single object, but with exactly 7 generic arguments, one for each of the transaction fields. + +```mermaid +graph LR + subgraph Tx + Env + From + To + Payment + Gas + Data + rh[Result Handler] + end +``` + +
+ +Each one of these 7 fields has a trait that governs what types are allowed to occupy the respective position. + +All of these positions (except the environment `Env`) can be empty, uninitialized. This is signaled at compile time by the unit type, `()`. In fact, a transaction always starts out with all fields empty, except for the environment. + +For instance, if we are in a contract and write `self.tx()`, the universal start of a transaction, the resulting type will be `Tx, (), (), (), (), (), ()>`, where `TxScEnv` is simply the smart contract call environment. Of course, the transaction at this stage is unusable, it is up to the developer to add the required fields and send it. + + + + +## The `Tx` fields + +We have dedicated a page to each of these 7 fields: + +| Field | Description | +| ------------------------------------- | --------------------------------------------------------------------------------------------------------- | +| [Environment](tx-env) | Some representation of the environment where the transaction runs. | +| [From](tx-from) | The transaction sender. Implicit for SC calls (the contract is the sender), but mandatory anywhere else. | +| [To](tx-to) | The receiver. Needs to be specified for any transaction expect for deploys. | +| [Payment](tx-payment) | Optional, can be EGLD, single or multi-ESDT. We also have some payment types that get decided at runtime. | +| [Gas](tx-gas) | Some transactions need explicit gas, others don't. | +| [Data](tx-data) | [Proxies](tx-proxies) (ideally) or raw | The main part of the payload. Can be inhabited by a function call, deploy data, upgrade data, or nothing, in the case of direct transfers. | +| [Result Handlers](tx-result-handlers) | Anything that deals with results, from callbacks to decoding logic. | + +We could also group them in three broad categories: +- The [environment](tx-env) is its own category, pertaining to both **inputs and outputs**. +- 5 **inputs**: [From](tx-from), [To](tx-to), [Payment](tx-payment), [Gas](tx-gas), [Data](tx-data). +- 1 field dealing with the **output**: [the result handlers](tx-result-handlers). + + + +## Transaction builder + +Now that we've seen what the contents of a transaction are, let's see how we can specify them in code. + +Of course, the developer shouldn't access the fields of the transaction directly. There is sort of a builder pattern when constructing a transaction. + +In its most basic form, a transaction might be constructed as follows: + +```rust +self.tx() + .from(from) + .to(to) + .payment(payment) + .gas(gas) + .raw_call("function") + .with_result(result_handler) +``` + +:::caution Important +While this may look like a traditional OOP builder pattern, there is one important aspect to point out: + +*Each of these setters outputs a type that is different from its input.* + +We are not merely setting new data into an existing field, we are also specifying the type of each field, at compile-time. At each step we are not only specifying the data, but also the types. +::: + +Even though these look like simple setters, we are first of all constructing a type using this syntax. + +Also note that the way these methods are set up, it is impossible to call most of them twice. They only work when the respective field is not yet set (of unit type `()`), so writing something like `self.tx().from(a).from(b)` causes a compiler error. + +Here we have some of the most common methods used to construct a transaction: + +| Field | Filed name | Initialize with | +| ------------------------------------- | ---------------- | ----------------------------------------------------------------------------------------------- | +| [Environment](tx-env) | `env` | `.tx()` | +| [From](tx-from) | `from` | `.from(...)` | +| [To](tx-to) | `to` | `.to(...)` | +| [Payment](tx-payment) | `payment` | `.payment(...)` | +| [Gas](tx-gas) | `gas` | `.gas(...)` | +| [Data](tx-data) | `data` | `.typed(...)` (ideally)
`.raw_call(...)`
`.raw_deploy()`
`.raw_upgrade()` | +| [Result Handlers](tx-result-handlers) | `result_handler` | `.callback(...)`
`.returns(...)`
`.with_result(...)` | + +The list is by no means exhaustive, it is just an initial overview. Please find the full documentation on each of the linked pages. + + +## Execution + +Ultimately, the purpose of a transaction is to be executed. Simply constructing a transaction has no effect in itself. So we must finalize each transaction construct with a call that sends it to the blockchain. + +The allowed execution methods depend on the environment. More specifically: +- In a contract, the options are `.transfer()`, `.transfer_execute()`, `.async_call_and_exit()`, `.sync_call()`, etc. +- In tests just `.run()` is universal. +- In interactors, we call `.run().await`. It's the same method name, but this time it's asynchronous Rust, so it can only be called in an `async` function, and `.await` is necessary. + +More on this in the [launch page](tx-run). + + +## Map of the fields types + +This is a graph of what the common types are that fit in each of the transaction fields. + + +```mermaid +graph LR + subgraph Tx + Env + From + To + Payment + Gas + Data + rh[Result Handler] + end + Env --> TxScEnv + Env --> ScenarioEnvExec + Env --> ScenarioEnvQuery + Env --> InteractorEnvExec + Env --> InteractorEnvQuery + From --> from-unit["()"] + From --> from-man-address[ManagedAddress] + From --> from-address[Address] + From --> from-bech32[Bech32Address] + From --> from-test-addr[TestAddress] + To --> to-unit["()"] + To --> to-man-address[ManagedAddress] + To --> to-address[Address] + To --> to-bech32[Bech32Address] + To --> to-test-addr[TestAddress] + To --> to-test-sc[TestSCAddress] + To --> to-caller[ToCaller] + To --> to-self[ToSelf] + Payment --> payment-unit["()"] + Payment --> egld["Egld(EgldValue)"] + egld --> egld-biguint["Egld(BigUint)"] + egld --> egld-u64["Egld(u64)"] + Payment --> EsdtTokenPayment + Payment --> EsdtTokenPaymentRefs + Payment --> MultiEsdtPayment + Payment --> EgldOrEsdtTokenPaymentRefs + Payment --> EgldOrMultiEsdtPayment + Gas --> gas-unit["()"] + Gas --> gas-explicit["ExplicitGas(u64)"] + Gas --> GasLeft + Data --> data-unit["()"] + Data --> deploy["DeployCall<()>"] + deploy --> deploy-from-source["DeployCall<FromSource<ManagedAddress>>"] + deploy --> deploy-code["DeployCall<Code<ManagedBuffer>>"] + Data --> upgrade["UpgradeCall<()>"] + upgrade --> upgrade-from-source["UpgradeCall<CodeSource<ManagedAddress>>"] + upgrade --> upgrade-code["UpgradeCall<Code<ManagedBuffer>>"] + Data --> fc[FunctionCall] + rh --> rh-unit("()") + rh --> rh-ot("OriginalTypeMarker") + rh --> CallbackClosure --> CallbackClosureWithGas + rh --> Decoder +``` + + +## Map of the setters + +Constructing a transaction is similar to exploring a map, or running a finite state machine. You start with an initial position, and then get to choose in which direction to go. + +Choosing a path at one point closes off many other options. The compiler is always guiding us and preventing us from ending up with an invalid transaction. + +Here is a map of all the paths you can take when configuring a transaction. The fields are mostly independent, so the map is split into 7 sections. See more details on each of their respective pages. + + +```mermaid +graph LR + subgraph Environment + sc-code["SC Code"] -->|"self.tx()"| sc-env[TxScEnv] + test-code["Test Code"] -->|"world.tx()"| ScenarioEnvExec + test-code["Test Code"] -->|"world.query()"| ScenarioEnvQuery + intr-code["Interactor Code"] -->|"interactor.tx()"| InteractorEnvExec + intr-code["Interactor Code"] -->|"interactor.query()"| InteractorEnvQuery + end +``` + +```mermaid +graph LR + subgraph From + from-unit["()"] + from-unit -->|from| from-man-address[ManagedAddress] + from-unit -->|from| from-address[Address] + from-unit -->|from| from-bech32[Bech32Address] + end +``` + +```mermaid +graph LR + subgraph To + to-unit["()"] + to-unit -->|to| to-man-address[ManagedAddress] + to-unit -->|to| to-address[Address] + to-unit -->|to| to-bech32[Bech32Address] + to-unit -->|to| to-esdt-system-sc[ESDTSystemSCAddress] + to-unit -->|to| to-caller[ToCaller] + to-unit -->|to| to-self[ToSelf] + end +``` + +```mermaid +graph LR + subgraph Payment + payment-unit["()"] + payment-unit -->|egld| egld-biguint["Egld(BigUint)"] + payment-unit -->|egld| egld-u64["Egld(u64)"] + payment-unit -->|esdt| EsdtTokenPayment + EsdtTokenPayment -->|esdt| MultiEsdtPayment + MultiEsdtPayment -->|esdt| MultiEsdtPayment + payment-unit -->|payment| MultiEsdtPayment + payment-unit -->|multi_esdt| EsdtTokenPayment + payment-unit -->|payment| EgldOrEsdtTokenPaymentRefs + payment-unit -->|egld_or_single_esdt| EgldOrEsdtTokenPaymentRefs + payment-unit -->|payment| EgldOrMultiEsdtPayment + payment-unit -->|egld_or_multi_esdt| EgldOrMultiEsdtPayment + end +``` + +```mermaid +graph LR + subgraph Gas + gas-unit["()"] + gas-unit -->|gas| gas-explicit["ExplicitGas(u64)"] + gas-unit -->|gas| GasLeft + end +``` + +```mermaid +graph LR + subgraph Data + data-unit["()"] + data-unit ----->|raw_deploy| deploy["DeployCall<()>"] + deploy -->|from_source| deploy-from-source["DeployCall<FromSource<_>>"] + deploy -->|code| deploy-code["DeployCall<Code<_>>"] + deploy -->|code_metadata| deploy + data-unit ----->|raw_upgrade| upgrade["UpgradeCall<()>"] + upgrade -->|from_source| upgrade-from-source["UpgradeCall<CodeSource<_>>"] + upgrade -->|code| upgrade-code["UpgradeCall<Code<_>>"] + upgrade -->|code_metadata| upgrade + data-unit ---->|raw_call| fc[FunctionCall] + data-unit -->|typed| Proxy + Proxy -->|init| deploy + Proxy -->|upgrade| upgrade + Proxy -->|endpoint| fc[Function Call] + end +``` + +```mermaid +graph LR + subgraph Result Handlers + rh-unit("()") + rh-unit -->|original_type| rh-ot("OriginalTypeMarker") + rh-ot -->|callback| CallbackClosure -->|gas_for_callback| CallbackClosureWithGas + dh[Decode Handler] + rh-unit -->|"returns
with_result"| dh + rh-ot -->|"returns
with_result"| dh + dh -->|"returns
with_result"| dh + end +``` + +--- + +### transactions + +This page describes the structure of the `transactions` index (Elasticsearch), and also depicts a few examples of how to query it. + + +## _id + +:::warning Important + +**The `transactions` index will be deprecated and removed in the near future.** +We recommend using the [operations](/sdk-and-tools/indices/es-index-operations) index, which contains all the transaction data. +The only change required in your queries is to include the `type` field with the value `normal` to fetch all transactions. + +Please make the necessary updates to ensure a smooth transition. +If you need further assistance, feel free to reach out. +::: + +The `_id` field for this index is composed of hex-encoded transaction hash. +(example: `cad4692a092226d68fde24840586bdf36b30e02dc4bf2a73516730867545d53c`) + + +## Fields + + +| Field | Description | +|-------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| miniBlockHash | The miniBlockHash field represents the hash of the miniblock in which the transaction was included. | +| nonce | The nonce field represents the transaction sequence number of the sender address. | +| round | The round field represents the round of the block when the transaction was executed. | +| value | The value field represents the amount of EGLD to be sent from the sender to the receiver. | +| receiver | The receiver field represents the destination address of the transaction. | +| sender | The sender field represents the address of the transaction sender. | +| receiverShard | The receiverShard field represents the shard ID of the receiver address. | +| senderShard | The senderShard field represents the shard ID of the sender address. | +| gasPrice | The gasPrice field represents the amount to be paid for each gas unit. | +| gasLimit | The gasLimit field represents the maximum gas units the sender is willing to pay for. | +| gasUsed | The gasUsed field represents the amount of gas used by the transaction. | +| fee | The fee field represents the amount of EGLD the sender paid for the transaction. | +| initialPaidFee | The initialPaidFee field represents the initial amount of EGLD the sender paid for the transaction, before the refund. | +| data | The data field holds additional information for a transaction. It can contain a simple message, a function call, an ESDT transfer payload, and so on. | +| signature | The signature of the transaction, hex-encoded. | +| timestamp | The timestamp field represents the timestamp of the block in which the transaction was executed. | +| status | The status field represents the status of the transaction. | +| senderUserName | The senderUserName field represents the username of the sender address. | +| receiverUserName | The receiverUserName field represents the username of the receiver address. | +| hasScResults | The hasScResults field is true if the transaction has smart contract results. | +| isScCall | The isScCall field is true if the transaction is a smart contract call. | +| hasOperations | The hasOperations field is true if the transaction has smart contract results. | +| tokens | The tokens field contains a list of ESDT tokens that are transferred based on the data field. The indices from the `tokens` list are linked with the indices from `esdtValues` list. | +| esdtValues | The esdtValues field contains a list of ESDT values that are transferred based on the data field. | +| receivers | The receivers field contains a list of receiver addresses in case of ESDTNFTTransfer or MultiESDTTransfer. | +| receiversShardIDs | The receiversShardIDs field contains a list of receiver addresses shard IDs. | +| type | The type field represents the type of the transaction based on the data field. | +| operation | The operation field represents the operation of the transaction based on the data field. | +| function | The function field holds the name of the function that is called in case of a smart contract call. | +| isRelayed | The isRelayed field is true if the transaction is a relayed transaction. | +| version | The version field represents the version of the transaction. | +| hasLogs | The hasLogs field is true if the transaction has logs. | + + +## Query examples + + +### Fetch the latest transactions of an address + +``` +curl --request GET \ + --url ${ES_URL}/transactions/_search \ + --header 'Content-Type: application/json' \ + --data '{ + "query": { + "bool": { + "should": [ + { + "match": { + "sender": "erd..." + } + }, + { + "match": { + "receiver": "erd..." + } + }, + { + "match": { + "receivers": "erd..." + } + } + ] + } + }, + "sort": [ + { + "timestamp": { + "order": "desc" + } + } + ] +}' +``` + +--- + +### Upgrading smart contracts + +## Sirius Mainnet Release - Version 1.6.0 + +:::important +The new Sirius Mainnet version 1.6.0 brings a significant update to how smart contracts can be upgraded. This release introduces a dedicated `upgrade` function, replacing the previous usage of the `init` function during contract upgrades. This change enhances the upgrade process and provides a clearer separation of concerns between contract initialization and subsequent upgrades. +**Note: Contracts deployed before version 1.6.0 will continue to function as before. The change in upgrade behavior applies only when upgrading the code of the contract. Existing contracts deployed only with an init function will still operate correctly without modifications.** +::: + +Please take note of the following important information to ensure a smooth transition. + +For contracts developed on or after version 1.6.0, developers should ensure that the `upgrade` function is implemented to handle the necessary upgrade logic. The `init` function will no longer be called during contract upgrades. + +Let's look at an example of a simple Adder SC. + +```rust +#[init] +fn init(&self, initial_value: u64) { + // Save the initial value in storage only if it is empty. + self.sum().set_if_empty(initial_value); +} + +#[upgrade] +fn upgrade(&self, new_value: u64) { + self.sum().set(new_value); +} +``` + +Let's assume we deploy the contract with the argument **1u64**, and then we upgrade it using the argument **2u64**. If before the new release, after upgrading the SC, we would have the value **1u64** in storage (as the `init` function would have been called, which saves the value in the storage only when it is empty), with the new release, the new value in the storage would be **2u64**. + + +## Deep diving into the Smart Contract Upgrade Process + +Upgrading a smart contract is a relatively easy process, but its implications are not exactly obvious. To upgrade a smart contract, simply run the following command: + +``` +mxpy --verbose contract upgrade SC_ADDRESS \ + --pem=PEM_PATH --bytecode=WASM_PATH \ + --gas-limit=100000000 \ + --send --proxy=https://devnet-gateway.multiversx.com --chain=D +``` + +Replace SC_ADDRESS, PEM_PATH and WASM_PATH accordingly. Also, if you want to use testnet/mainnet, also change the proxy and chain ID. + +This will replace the given SC's code with the one from the provided file, but that is not all. Additionally, it will run the new code's `upgrade` function. So, if your `upgrade` function has any arguments, the command has to be run by also giving said arguments: + +``` +mxpy --verbose contract upgrade SC_ADDRESS \ + --pem=PEM_PATH --bytecode=WASM_PATH \ + --arguments arg1 arg2 arg3 + --gas-limit=100000000 \ + --send --proxy=https://devnet-gateway.multiversx.com --chain=D +``` + +You might've seen in many of the MultiversX contracts, we often use the `set_if_empty` method in `init` and `upgrade` functions, instead of plain `set`. This is so we don't accidentally overwrite an important config value during the upgrade process. + + +## What about the old contract's storage? + +Storage is kept intact, except for the changes the `upgrade` function might do during upgrade. + + +## Migrating storage or token attributes + +If you modify your core data's design, you will run into "Decode error: Input too short". Fear not, as there are ways to avoid that. + +For example, let's assume you had the following struct type, which keeps track of user information: + +```rust +#[derive(TypeAbi, TopEncode, TopDecode, NestedEncode, NestedDecode)] +pub struct UserData { + pub stake_amount: BigUint, + pub last_update_block: u64, +} +``` + +In your new code, you decided it would be nice to also keep track of the last update epoch, not only block, so you changed the struct: + +```rust +#[derive(TypeAbi, TopEncode, TopDecode, NestedEncode, NestedDecode)] +pub struct UserData { + pub stake_amount: BigUint, + pub last_update_block: u64, + pub last_update_epoch: u64, +} +``` + +This will not work. The struct will decode `stake_amount`, it will then decode `last_update_epoch`, and it will have no bytes left to decode the `last_update_epoch`. + +:::caution +You always need to add new fields at the end of the struct, otherwise, this approach will not work. +::: + +To fix this, we need to manually implement the decoding traits, which were previously automatically added through the derives. + +```rust +use multiversx_sc::codec::{NestedDecodeInput, TopDecodeInput}; + +#[derive(TypeAbi, TopEncode, NestedEncode)] +pub struct UserData { + pub stake_amount: BigUint, + pub last_update_block: u64, + pub last_update_epoch: u64, +} + +impl TopDecode for UserData { + fn top_decode(input: I) -> Result + where + I: TopDecodeInput, + { + let mut buffer = input.into_nested_buffer(); + Self::dep_decode(&mut buffer) + } +} + +impl NestedDecode for UserData { + fn dep_decode(input: &mut I) -> Result { + let stake_amount = BigUint::dep_decode(input)?; + let last_update_block = u64::dep_decode(input)?; + + let last_update_epoch = if !input.is_depleted() { + u64::dep_decode(input)? + } else { + 0 + }; + + if !input.is_depleted() { + return Result::Err(DecodeError::INPUT_TOO_LONG); + } + + Result::Ok(UserData { + stake_amount, + last_update_block, + last_update_epoch + }) + } +} +``` + +With the above code, we manually decode each field, and then, if there are any bytes left, we decode the new fields we've added, using a default value (0 in this case) if there are no more bytes - as this means it's an encoded version of the old struct. + +Remember to also check if there are any remaining bytes after that and throw an error, otherwise, your struct could potentially be decoded from almost any input bytes. + + +### "But what if I want to remove a field?" + +Unless you want to remove the very last field of the struct, and change nothing else, this is not possible, as you'd have almost no way of distinguishing between old and new data. + +Assuming you simply want to remove `last_update_block` for the example above, the implementation would be as follows: + +```rust +use multiversx_sc::codec::{NestedDecodeInput, TopDecodeInput}; + +#[derive(TypeAbi, TopEncode, NestedEncode)] +pub struct UserData { + pub stake_amount: BigUint, +} + +impl TopDecode for UserData { + fn top_decode(input: I) -> Result + where + I: TopDecodeInput, + { + let mut buffer = input.into_nested_buffer(); + Self::dep_decode(&mut buffer) + } +} + +impl NestedDecode for UserData { + fn dep_decode(input: &mut I) -> Result { + let stake_amount = BigUint::dep_decode(input)?; + + if input.is_depleted() { + return Result::Ok(UserData { stake_amount }); + } + + let _last_update_block = u64::dep_decode(input)?; + if !input.is_depleted() { + return Result::Err(DecodeError::INPUT_TOO_LONG); + } + + Result::Ok(UserData { stake_amount }) + } +} +``` + +In this case, if there are any bytes left after we decode the `stake_amount`, we decode the old fields and ignore their values. Same as last time, remember to throw an error if there are bytes remaining still after that. + + +## Conclusion + +This approach works for both stored instances, and token attributes. Keep in mind you'll have to keep adding more and more levels of partial decoding to the above implementation if you change the struct often, and new fields always have to be at the end. + +--- + +### User-defined Smart Contracts + +For user-defined Smart Contract deployments and function calls, the **actual gas consumption** of processing contains both of the previously mentioned cost components - though, while the **value movement and data handling** component is easily computable (using the previously depicted formula), the **contract execution** component is hard to determine precisely _a priori_. Therefore, for this component we have to rely on _simulations_ and _estimations_. + +For **simulations**, we will start a local testnet using `mxpy` (detailed setup instructions can be found [here](/developers/setup-local-testnet)). Thus, before going further, make sure your local testnet is up and running. + + +## Contract deployments + +In order to get the required `gasLimit` (the **actual gas cost**) for a deployment transaction, one should use the well-known `mxpy contract deploy` command, but with the `--simulate` flag set. + +At first, pass the maximum possible amount for `gas-limit` (no guessing). + +```bash +$ mxpy --verbose contract deploy --bytecode=./contract.wasm \ + --gas-limit=600000000 \ + --pem=~/multiversx-sdk/testwallets/latest/users/alice.pem \ + --simulate +``` + +In the output, look for `txGasUnits`. For example: + +```json +"txSimulation": { + ... + "cost": { + "txGasUnits": 1849711, + ... + } +} +``` + +:::note +The simulated cost `txGasUnits` contains both components of the cost. +::: + +After that, check the cost simulation by running the simulation once again, but this time with the precise`gas-limit`: + +```bash +$ mxpy --verbose contract deploy --bytecode=./contract.wasm \ + --gas-limit=1849711 \ + --pem=~/multiversx-sdk/testwallets/latest/users/alice.pem \ + --simulate +``` + +In the output, look for the `status` - it should be `success`: + +```json +"txSimulation": { + "execution": { + "result": { + "status": "success", + ... + }, + ... + } +``` +In the end, let's actually deploy the contract: + +```bash +$ mxpy --verbose contract deploy --bytecode=./contract.wasm \ + --gas-limit=1849711 \ + --pem=~/multiversx-sdk/testwallets/latest/users/alice.pem \ + --send --wait-result +``` + +:::important +For deployments, the **execution** component of the cost is associated with instantiating the Smart Contract and calling its `init()` function. + +If the flow of `init()` is dependent on input arguments or it references blockchain data, then the cost will vary as well, depending on these variables. Make sure you simulate sufficient deployment scenarios and increase (decrease) the `gas-limit`. +::: + + +## Contract calls + +In order to get the required `gasLimit` (the **actual gas cost**) for a contract call, one should first deploy the contract, then use the `mxpy contract call` command, with the `--simulate` flag set. + +:::important +If the contract makes further calls to other contracts, please read the next section. +::: + +Assuming we've already deployed the contract (see above) let's get the cost for calling one of its endpoints: + +```bash +$ mxpy --verbose contract call erd1qqqqqqqqqqqqqpgqygvvtlty3v7cad507v5z793duw9jjmlxd8sszs8a2y \ + --pem=~/multiversx-sdk/testwallets/latest/users/alice.pem \ + --function=increment\ + --gas-limit=600000000\ + --simulate +``` + +In the output, look for `txGasUnits`. For example: + +```json +"txSimulation": { + ... + "cost": { + "txGasUnits": 1225515, + ... + } +} +``` +In the end, let's actually call the contract: + +```bash +$ mxpy --verbose contract call erd1qqqqqqqqqqqqqpgqygvvtlty3v7cad507v5z793duw9jjmlxd8sszs8a2y \ + --pem=~/multiversx-sdk/testwallets/latest/users/alice.pem \ + --function=increment\ + --gas-limit=1225515\ + --send --wait-result +``` + +:::important +If the flow of the called function is dependent on input arguments or it references blockchain data, then the cost will vary as well, depending on these variables. Make sure you simulate sufficient scenarios for the contract call and increase (decrease) the `gas-limit`. +::: + + +## Contracts calling (asynchronously) other contracts + +:::important +Documentation in this section is preliminary and subject to change. Furthermore, **only asynchronous calls are covered**. +::: + +Before moving forward, make sure you first have a look over the following: + +- [Asynchronous calls between contracts](/learn/space-vm#asynchronous-calls-between-contracts) +- [Asynchronous calls (Rust framework)](/developers/transactions/tx-legacy-calls#asynchronous-calls) +- [Callbacks (Rust framework)](/developers/developer-reference/sc-annotations/#callbacks) + +Suppose we have two contracts: `A` and `B`, where `A::foo(addressOfB)` asynchronously calls `B::bar()` (e.g. using `asyncCall()`). + +Let's deploy the contracts `A` and `B`: + +```bash +$ mxpy --verbose contract deploy --bytecode=./a.wasm \ + --gas-limit=5000000 \ + --pem=~/multiversx-sdk/testwallets/latest/users/alice.pem \ + --send --wait-result --outfile=a.json + +$ mxpy --verbose contract deploy --bytecode=./b.wasm \ + --gas-limit=5000000 \ + --pem=~/multiversx-sdk/testwallets/latest/users/alice.pem \ + --send --wait-result --outfile=b.json +``` + +Assuming `A` is deployed at `erd1qqqqqqqqqqqqqpgqfzydqmdw7m2vazsp6u5p95yxz76t2p9rd8ss0zp9ts`, and `B` is deployed at `erd1qqqqqqqqqqqqqpgqj5zftf3ef3gqm3gklcetpmxwg43rh8z2d8ss2e49aq`, let's **simulate** `A::foo(addressOfB)` (at first, pass a _large-enough_ or maximum `gas-limit`): + +```bash +$ export hexAddressOfB=0x$(mxpy wallet bech32 --decode erd1qqqqqqqqqqqqqpgqj5zftf3ef3gqm3gklcetpmxwg43rh8z2d8ss2e49aq) + +$ mxpy --verbose contract call erd1qqqqqqqqqqqqqpgqfzydqmdw7m2vazsp6u5p95yxz76t2p9rd8ss0zp9ts \ + --pem=~/multiversx-sdk/testwallets/latest/users/alice.pem \ + --function=foo\ + --gas-limit=50000000\ + --arguments ${hexAddressOfB}\ + --simulate +``` + +In the output, look for the simulated cost (as above): + +```json +"txSimulation": { + ... + "cost": { + "txGasUnits": 3473900, + ... + } +} +``` + +The simulated cost represents the **actual gas cost** for invoking `A::foo()`, `B::bar()` and `A::callBack()`. + +**However, the simulated cost above isn't the value we are going to use as `gasLimit`.** If we were to do so, we would be presented the error `not enough gas`. + +Upon reaching the call to `B::bar()` inside `A::foo()`, the MultiversX VM inspects the **remaining gas _at runtime_** and **temporarily locks (reserves) a portion of it**, to allow for the execution of `A::callBack()` once the call to `B::bar()` returns. + +With respect to the [VM Gas Schedule](https://github.com/multiversx/mx-chain-mainnet-config/tree/master/gasSchedules), the aforementioned **remaining gas _at runtime_** has to satisfy the following conditions in order for the **temporary gas lock reservation** to succeed: + +```go +onTheSpotRemainingGas > gasToLockForCallback + +gasToLockForCallback = + costOf(AsyncCallStep) + + costOf(AsyncCallbackGasLock) + + codeSizeOf(callingContract) * costOf(AoTPreparePerByte) +``` + +:::note +Subsequent asynchronous calls (asynchronous calls performed by an asynchronously-called contract) will require temporary gas locks as well. +::: + +For our example, where `A` has 453 bytes, the `gasToLockForCallback` would be (as of February 2022): + +``` +gasToLockForCallback = 100000 + 4000000 + 100 * 453 = 4145300 +``` + +It follows that the value of `gasLimit` should be: + +``` +simulatedCost < gasLimit < simulatedCost + gasToLockForCallback +``` + +For our example, that would be: + +``` +3473900 < gasLimit < 7619200 +``` + +:::important +As of February 2022, for contracts that call other contracts, the lowest `gasLimit` required by a successful execution isn't easy to determine using **mxpy**. While this value can be determined by a careful inspection of the local testnet logs, for the moment, the recommended approach is to **start with the right-hand side of the inequality above (`simulatedCost + gasToLockForCallback`) and gradually decrease the value** while simulating the call and looking for a successful output. +::: + +For our example, let's simulate using the following values for `gasLimit`: `7619200, 7000000, 6000000`: + +```bash +$ mxpy --verbose contract call erd1qqqqqqqqqqqqqpgqfzydqmdw7m2vazsp6u5p95yxz76t2p9rd8ss0zp9ts \ + --pem=~/multiversx-sdk/testwallets/latest/users/alice.pem \ + --function=foo \ + --gas-limit=7619200 \ + --arguments ${hexAddressOfB} \ + --simulate + +... inspect output (possibly testnet logs); execution is successful + +mxpy --verbose contract call erd1qqqqqqqqqqqqqpgqfzydqmdw7m2vazsp6u5p95yxz76t2p9rd8ss0zp9ts \ + --pem=~/multiversx-sdk/testwallets/latest/users/alice.pem \ + --function=foo \ + --gas-limit=7000000 \ + --arguments ${hexAddressOfB} \ + --simulate + +... inspect output (possibly testnet logs); execution is successful + +mxpy --verbose contract call erd1qqqqqqqqqqqqqpgqfzydqmdw7m2vazsp6u5p95yxz76t2p9rd8ss0zp9ts \ + --pem=~/multiversx-sdk/testwallets/latest/users/alice.pem \ + --function=foo \ + --gas-limit=6000000 \ + --arguments ${hexAddressOfB} \ + --simulate + +... inspect output (possibly testnet logs); ERROR: out of gas when executing B::bar() + +``` + +Therefore, in our case, a reasonable value for `gasLimit` would be 7000000. + +:::important +In case of a highly gas-demanding callback (not recommended) which would consume more than `gasToLockForCallback`, one should appropriately increase the right-hand side of the `gasLimit` inequation depicted above, by inspecting the contract call output and the testnet logs. +::: + +--- + +### validators + +This page describes the structure of the `validators` index (Elasticsearch), and also depicts a few examples of how to query it. + + +## _id + +The `_id` field of this index is composed in this way: `{shardID}_{epoch}` (example: `1_123`) + + +## Fields + + +| Field | Description | +|-------------|-------------------------------------------------------------------------------------------------| +| publicKeys | The publicKeys field contains a list of all validators' public keys from an epoch and a shard. | + + +## Query examples + + +#### Fetch all validators from a shard by epoch +In the example below, we fetch all the validators' public keys from shard 1, epoch 600. + +``` +curl --request GET \ + --url ${ES_URL}/validators/_search \ + --header 'Content-Type: application/json' \ + --data '{ + "query": { + "match": { + "_id":"1_600" + } + } +}' +``` + +--- + +### Versions and Changelog + +## **Overview** + +This page offers a high level overview of the important releases of the MultiversX Proxy API, along with recommendations (such as upgrade or transitioning recommendations) for API consumers. + +:::caution +Documentation in this section is preliminary and subject to change. +::: + + +## **MultiversX Proxy HTTP API [v1.1.0](https://github.com/multiversx/mx-chain-proxy-go/releases/tag/v1.1.0)** + +This is the API launched at the Genesis. + + +## **MultiversX Proxy HTTP API [v1.1.1](https://github.com/multiversx/mx-chain-proxy-go/releases/tag/v1.1.1)** + +This API version brought new features such as the [_hyperblock_-related endpoints](/sdk-and-tools/rest-api/blocks#get-hyperblock-by-nonce), useful for monitoring the blockchain. Furthermore, the `GET transaction` endpoint has been adjusted to include extra fields - for example, the so-called _hyperblock coordinates_ (the _nonce_ and the _hash_ of the containing hyperblock). + +This API **has never been deployed to the central instance** of the MultiversX Proxy, available at [gateway.multiversx.com](https://gateway.multiversx.com/). However, until November 2020, **this API has been deployed on-premises** to several partners and 3rd party services (such as Exchange systems) - in the shape of [Observing Squads](/integrators/observing-squad), set up via the nodes scripts - [mx-chain-scripts](https://github.com/multiversx/mx-chain-scripts). + +This version of the API requires Observer Nodes with tag [e1.1.0](https://github.com/multiversx/mx-chain-go/releases/tag/v1.1.6/releases/tag/e1.1.0) or greater. + +There are **no breaking changes** between API **v1.1.0** and API **v1.1.1** - neither in terms of structure and format of the requests / responses, nor in the scheme of the URL. **However**, in terms of semantics, the following fixes might lead to breaking changes for some API consumers: + +- `GET transaction` endpoint has been fixed to report the appropriate status **invalid** for actually _invalid transactions_ (e.g. not enough balance). In v1.1.0, the reported status was imprecise: **partially-executed**. + +:::caution +As of November 2020, new API consumers are recommended to use a newer version of the API. + +**v1.1.0 and v1.1.1 will be deprecated** once all existing API consumers are known to have been upgraded to a more recent version. +::: + + +## **MultiversX Proxy HTTP API [v1.1.3](https://github.com/multiversx/mx-chain-proxy-go/releases/tag/v1.1.3)** + +This API version brought additions - new endpoints, such as `network/economics` or `address/shard`. Furthermore, the response of `vm-values` endpoints has been altered. Though, perhaps the most significant change is **the renaming of transaction statuses**. + +This version of the API requires Observer Nodes with tag [v1.1.6](https://github.com/multiversx/mx-chain-go/releases/tag/v1.1.6) or greater. + +Between API **v1.1.1** and API **v1.1.3**, the API consumer would observe the following **breaking changes**: + +- All fields of `vm-values` endpoints has been renamed (changed casing, among others). +- The possible set of values for the transaction statuses has been changed: **executed** has been renamed to **success.** The statuses **received** and **partially-executed** have been merged under the status **pending**, while the status **not-executed** has been renamed to **fail**. For API consumers to not be affected by this change, they should follow the recommendations in [Querying the Blockchain](/integrators/querying-the-blockchain). + +:::important +As of November 2020, new API consumers are recommended to switch to this version (or a more recent one) of the API. + +For API consumers that use **on-premises Observing Squads**, an updated installer will be provided (on this matter, the work is in progress). +::: + +--- + +### WalletConnect 2.0 Migration + +## Using `sdk-dapp` + +Using `@multiversx/sdk-dapp` >= `2.2.8` or `@elrondnetwork/dapp-core` >= `2.0.0` + +WalletConnect 2.0 is already integrated, only not enabled by default. + +Follow [these steps](/sdk-and-tools/sdk-dapp/#walletconnect-setup) to generate and add a `walletConnectV2ProjectId` + +--- + +----------- + + +## Using `sdk-wallet-connect-provider` + +Using `@multiversx/sdk-wallet-connect-provider` >= 3.0.1 or `@elrondnetwork/erdjs-wallet-connect-provider` + +Since the WalletConnect 2.0 implementation was complimentary to the existing 1.0 version, there should be no breaking changes to upgrade the library + +Check out some detailed examples [here](/sdk-and-tools/sdk-js/sdk-js-signing-providers/#the-walletconnect-provider). + +To Set up the 2.0 functionality you can check out the examples here: [https://github.com/multiversx/mx-sdk-js-examples](https://github.com/multiversx/mx-sdk-js-examples) or a more advanced implementation on sdk-dapp here: [https://github.com/multiversx/mx-sdk-dapp/blob/main/src/hooks/login/useWalletConnectV2Login.ts](https://github.com/multiversx/mx-sdk-dapp/blob/main/src/hooks/login/useWalletConnectV2Login.ts) + +The main difference between V1 and V2 is in the `login` method where we now have to first `connect()` to get the `uri` and the `approval` Promise + +```js +await provider.init(); +const { uri, approval } = await provider.connect(); + +await openModal(uri); + +try { + await provider.login({ approval, token }); // optional token +} catch (err) { + console.log(err); + alert('Connection Proposal Refused') +} +``` + +Another difference is in the `callbacks` where on WalletConnect 2.0 we have a third one for events: + +```js +const callbacks = { + onClientLogin: async function () { + // closeModal() is defined above + closeModal(); + const address = await provider.getAddress(); + console.log("Address:", address); + }, + onClientLogout: async function () { + console.log("onClientLogout()"); + }, + onClientEvent: async function (event) { + console.log("onClientEvent()", event); + } +}; +``` + +`signTransaction`, `signTransactions`, `logout`, etc remain the same for the library user since the action happens in the provider directly. + +--- + +### Whitebox Framework + +## Introduction + +The Rust testing framework was developed as an alternative to manually writing scenario tests. This comes with many advantages: + +- being able to calculate values using variables +- type checking +- automatic serialization +- far less verbose +- semi-automatic generation of the scenario tests + +The only disadvantage is that you need to learn something new! Jokes aside, keep in mind that this whole framework runs in a mocked environment. So while you get powerful testing and debugging tools, you are ultimately running a mock and have no guarantee that the contract will work identically with the current VM version deployed on the mainnet. + +This is where the scenario generation part comes into play. The Rust testing framework allows you to generate scenarios with minimal effort, and then run said scenarios by invoking `cargo test`. There will be a bit of manual effort required on the developer's part, but we'll get to that in its specific section. + +Please note that scenario generation is more of an experiment rather than a fully fledged implementation, which we might even remove in the future. Still, some examples are provided here if you still wish to attempt it. + + +## Prerequisites + +You need to have the latest multiversx-sc version (at the time of writing this, the latest version is 0.39.0). You can check the latest version here: https://crates.io/crates/multiversx-sc + +Add `multiversx-sc-scenario` and required packages as dev-dependencies in your Cargo.toml: + +```toml +[dev-dependencies.multiversx-sc-scenario] +version = "0.39.0" + +[dev-dependencies] +num-bigint = "0.4.2" +num-traits = "0.2" +hex = "0.4" +``` + +For this tutorial, we're going to use the crowdfunding SC, so it might be handy to have it open or clone the repository: https://github.com/multiversx/mx-sdk-rs/tree/master/contracts/examples/crowdfunding + +You need a `tests` and a `scenarios` folder in your contract. Create a `.rs` file in your `tests` folder. + +In your newly created test file, add the following code (adapt the `crowdfunding` namespace, the struct/variable names, and the contract wasm path according to your contract): + +```rust +use crowdfunding::*; +use multiversx_sc::{ + sc_error, + types::{Address, SCResult}, +}; +use multiversx_sc_scenario::{ + managed_address, managed_biguint, managed_token_id, rust_biguint, whitebox::*, + DebugApi, +}; + +const WASM_PATH: &'static str = "crowdfunding/output/crowdfunding.wasm"; + +struct CrowdfundingSetup +where + CrowdfundingObjBuilder: + 'static + Copy + Fn() -> crowdfunding::ContractObj, +{ + pub blockchain_wrapper: BlockchainStateWrapper, + pub owner_address: Address, + pub first_user_address: Address, + pub second_user_address: Address, + pub cf_wrapper: + ContractObjWrapper, CrowdfundingObjBuilder>, +} +``` + +The `CrowdfundingSetup` struct isn't really needed, but it helps de-duplicating some code. You may add other fields in your struct if needed, but for now this is enough for our use-case. The only fields you'll need for any contract are `blockchain_wrapper` and `cf_wrapper`. The rest of the fields can be adapted according to your test scenario. + +And that's all you need to get started. + + +## Writing your first test + +The first test you need to write is the one simulating the deploy of your smart contract. For that, you need a user address and a contract address. Then you simply call the `init` function of the smart contract. + +Since we're going to be using the same token ID everywhere, let's add it as a constant (and while we're at it, have the deadline as a constant as well): + +```rust +const CF_TOKEN_ID: &[u8] = b"CROWD-123456"; +const CF_DEADLINE: u64 = 7 * 24 * 60 * 60; // 1 week in seconds +``` + +Let's create our initial setup: + +```rust +fn setup_crowdfunding( + cf_builder: CrowdfundingObjBuilder, +) -> CrowdfundingSetup +where + CrowdfundingObjBuilder: 'static + Copy + Fn() -> crowdfunding_esdt::ContractObj, +{ + let rust_zero = rust_biguint!(0u64); + let mut blockchain_wrapper = BlockchainStateWrapper::new(); + let owner_address = blockchain_wrapper.create_user_account(&rust_zero); + let first_user_address = blockchain_wrapper.create_user_account(&rust_zero); + let second_user_address = blockchain_wrapper.create_user_account(&rust_zero); + let cf_wrapper = blockchain_wrapper.create_sc_account( + &rust_zero, + Some(&owner_address), + cf_builder, + WASM_PATH, + ); + + blockchain_wrapper.set_esdt_balance(&first_user_address, CF_TOKEN_ID, &rust_biguint!(1_000)); + blockchain_wrapper.set_esdt_balance(&second_user_address, CF_TOKEN_ID, &rust_biguint!(1_000)); + + blockchain_wrapper + .execute_tx(&owner_address, &cf_wrapper, &rust_zero, |sc| { + let target = managed_biguint!(2_000); + let token_id = managed_token_id!(CF_TOKEN_ID); + + sc.init(target, CF_DEADLINE, token_id); + }) + .assert_ok(); + + blockchain_wrapper.add_mandos_set_account(cf_wrapper.address_ref()); + + CrowdfundingSetup { + blockchain_wrapper, + owner_address, + first_user_address, + second_user_address, + cf_wrapper, + } +} +``` + +The main object you're going to be interacting with is the `BlockchainStateWrapper`. It holds the entire (mocked) blockchain state at any given moment, and allows you to interact with the accounts. + +As you can see in the above test, we use the said wrapper to create an owner account, two other user accounts, and the Crowdfunding smart contract account. + +Then, we set the ESDT balances for the two users, and deploy the smart contract, by using the `execute_tx` function of the `BlockchainStateWrapper` object. The arguments are: + +- caller address +- contract wrapper (which contains the contract address and the contract object builder) +- EGLD payment amount +- a lambda function, which contains the actual execution + +Since this is a SC deploy, we call the `init` function. Since the contract works with managed objects, we can't use the built-in Rust BigUint, so we use the one provided by `multiversx_sc` instead. To create managed types, we use the `managed_` functions. Alternatively, you can create those objects by: + +```rust +let target = BigUint::::from(2_000u32); +``` + +Keep in mind you can't create managed types outside of the `execute_tx` functions. + +Some observations for the `execute_tx` function: + +- The return type for the lambda function is a `TxResult`, which has methods for checking for success or error: `assert_ok()` is used to check the tx worked. If you want to check error cases, you would use `assert_user_error("message")`. +- After running the `init` function, we add a `setState` step in the generated scenario, to simulate our deploy: `blockchain_wrapper.add_mandos_set_account(cf_wrapper.address_ref());` + +To test the scenario and generate the trace file, you have to create a test function: + +```rust +#[test] +fn init_test() { + let cf_setup = setup_crowdfunding(crowdfunding_esdt::contract_obj); + cf_setup + .blockchain_wrapper + .write_mandos_output("_generated_init.scen.json"); +} +``` + +And you're done for this step. You successfully tested your contract's init function, and generated a scenario for it. + + +## Testing transactions + +Let's test the `fund` function. For this, we're going to use the previous setup, but now we use the `execute_esdt_transfer` method instead of `execute_tx`, because we're sending ESDT to the contract while calling `fund`: + +```rust +#[test] +fn fund_test() { + let mut cf_setup = setup_crowdfunding(crowdfunding_esdt::contract_obj); + let b_wrapper = &mut cf_setup.blockchain_wrapper; + let user_addr = &cf_setup.first_user_address; + + b_wrapper + .execute_esdt_transfer( + user_addr, + &cf_setup.cf_wrapper, + CF_TOKEN_ID, + 0, + &rust_biguint!(1_000), + |sc| { + sc.fund(); + + let user_deposit = sc.deposit(&managed_address!(user_addr)).get(); + let expected_deposit = managed_biguint!(1_000); + assert_eq!(user_deposit, expected_deposit); + }, + ) + .assert_ok(); +} +``` + +As you can see, we can directly call the storage mappers (like `deposit`) from within the contract and compare with a local value. No need to encode anything. + +If you also want to generate a scenario file for this transaction, this is where the bit of manual work comes in: + +```rust + let mut sc_call = ScCallMandos::new(user_addr, cf_setup.cf_wrapper.address_ref(), "fund"); + sc_call.add_esdt_transfer(CF_TOKEN_ID, 0, &rust_biguint!(1_000)); + + let expect = TxExpectMandos::new(0); + b_wrapper.add_mandos_sc_call(sc_call, Some(expect)); + + cf_setup + .blockchain_wrapper + .write_mandos_output("_generated_fund.scen.json"); +``` + +You have to add this at the end of your `fund_test`. The more complex the call, the more arguments you'll have to add and such. The `SCCallMandos` struct has the `add_argument` method so you don't have to do any encoding by yourself. + + +## Testing queries + +Testing queries is similar to testing transactions, just with less arguments (since there is no caller, and no payment, and any modifications are automatically reverted): + +```rust +#[test] +fn status_test() { + let mut cf_setup = setup_crowdfunding(crowdfunding_esdt::contract_obj); + let b_wrapper = &mut cf_setup.blockchain_wrapper; + + b_wrapper + .execute_query(&cf_setup.cf_wrapper, |sc| { + let status = sc.status(); + assert_eq!(status, Status::FundingPeriod); + }) + .assert_ok(); + + let sc_query = ScQueryMandos::new(cf_setup.cf_wrapper.address_ref(), "status"); + let mut expect = TxExpectMandos::new(0); + expect.add_out_value(&Status::FundingPeriod); + + b_wrapper.add_mandos_sc_query(sc_query, Some(expect)); + + cf_setup + .blockchain_wrapper + .write_mandos_output("_generated_query_status.scen.json"); +} +``` + + +## Testing smart contract errors + +In the previous transaction test, we've tested the happy flow. Now let's see how we can check for errors: + +```rust +#[test] +fn test_sc_error() { + let mut cf_setup = setup_crowdfunding(crowdfunding_esdt::contract_obj); + let b_wrapper = &mut cf_setup.blockchain_wrapper; + let user_addr = &cf_setup.first_user_address; + + b_wrapper.set_egld_balance(user_addr, &rust_biguint!(1_000)); + + b_wrapper + .execute_tx( + user_addr, + &cf_setup.cf_wrapper, + &rust_biguint!(1_000), + |sc| { + sc.fund(); + }, + ) + .assert_user_error("wrong token"); + + b_wrapper + .execute_tx(user_addr, &cf_setup.cf_wrapper, &rust_biguint!(0), |sc| { + let user_deposit = sc.deposit(&managed_address!(user_addr)).get(); + let expected_deposit = managed_biguint!(0); + assert_eq!(user_deposit, expected_deposit); + }) + .assert_ok(); + + let mut sc_call = ScCallMandos::new(user_addr, cf_setup.cf_wrapper.address_ref(), "fund"); + sc_call.add_egld_value(&rust_biguint!(1_000)); + + let mut expect = TxExpectMandos::new(4); + expect.set_message("wrong token"); + + b_wrapper.add_mandos_sc_call(sc_call, Some(expect)); + + cf_setup + .blockchain_wrapper + .write_mandos_output("_generated_sc_err.scen.json"); +} +``` + +Notice how we've changed the payment intentionally to an invalid token to check the error case. Also, we've changed the expected deposit to "0" instead of the previous "1_000". And lastly: the `.assert_user_error("wrong token")` call on the result. + + +## Testing a successful funding campaign + +For this scenario, we need both users to fund the full amount, and then owner to claim the funds. For simplicity, we've left the scenario generation out of this one: + +```rust +#[test] +fn test_successful_cf() { + let mut cf_setup = setup_crowdfunding(crowdfunding_esdt::contract_obj); + let b_wrapper = &mut cf_setup.blockchain_wrapper; + let owner = &cf_setup.owner_address; + let first_user = &cf_setup.first_user_address; + let second_user = &cf_setup.second_user_address; + + // first user fund + b_wrapper + .execute_esdt_transfer( + first_user, + &cf_setup.cf_wrapper, + CF_TOKEN_ID, + 0, + &rust_biguint!(1_000), + |sc| { + sc.fund(); + + let user_deposit = sc.deposit(&managed_address!(first_user)).get(); + let expected_deposit = managed_biguint!(1_000); + assert_eq!(user_deposit, expected_deposit); + }, + ) + .assert_ok(); + + // second user fund + b_wrapper + .execute_esdt_transfer( + second_user, + &cf_setup.cf_wrapper, + CF_TOKEN_ID, + 0, + &rust_biguint!(1_000), + |sc| { + sc.fund(); + + let user_deposit = sc.deposit(&managed_address!(second_user)).get(); + let expected_deposit = managed_biguint!(1_000); + assert_eq!(user_deposit, expected_deposit); + }, + ) + .assert_ok(); + + // set block timestamp after deadline + b_wrapper.set_block_timestamp(CF_DEADLINE + 1); + + // check status + b_wrapper + .execute_query(&cf_setup.cf_wrapper, |sc| { + let status = sc.status(); + assert_eq!(status, Status::Successful); + }) + .assert_ok(); + + // user try claim + b_wrapper + .execute_tx(first_user, &cf_setup.cf_wrapper, &rust_biguint!(0), |sc| { + sc.claim(); + }) + .assert_user_error("only owner can claim successful funding"); + + // owner claim + b_wrapper + .execute_tx(owner, &cf_setup.cf_wrapper, &rust_biguint!(0), |sc| { + sc.claim(); + }) + .assert_ok(); + + b_wrapper.check_esdt_balance(owner, CF_TOKEN_ID, &rust_biguint!(2_000)); + b_wrapper.check_esdt_balance(first_user, CF_TOKEN_ID, &rust_biguint!(0)); + b_wrapper.check_esdt_balance(second_user, CF_TOKEN_ID, &rust_biguint!(0)); +} +``` + +You've already seen most of the code in this test before already. The only new things are the `set_block_timestamp` and the `check_esdt_balance` methods of the wrapper. There are similar methods for setting block nonce, block random seed, etc., and the checking EGLD and SFT/NFT balances. + + +## Testing a failed funding campaign + +This is simimlar to the previous one, but instead we have the users claim instead of the owner after deadline. + +```rust +#[test] +fn test_failed_cf() { + let mut cf_setup = setup_crowdfunding(crowdfunding_esdt::contract_obj); + let b_wrapper = &mut cf_setup.blockchain_wrapper; + let owner = &cf_setup.owner_address; + let first_user = &cf_setup.first_user_address; + let second_user = &cf_setup.second_user_address; + + // first user fund + b_wrapper + .execute_esdt_transfer( + first_user, + &cf_setup.cf_wrapper, + CF_TOKEN_ID, + 0, + &rust_biguint!(300), + |sc| { + sc.fund(); + + let user_deposit = sc.deposit(&managed_address!(first_user)).get(); + let expected_deposit = managed_biguint!(300); + assert_eq!(user_deposit, expected_deposit); + }, + ) + .assert_ok(); + + // second user fund + b_wrapper + .execute_esdt_transfer( + second_user, + &cf_setup.cf_wrapper, + CF_TOKEN_ID, + 0, + &rust_biguint!(600), + |sc| { + sc.fund(); + + let user_deposit = sc.deposit(&managed_address!(second_user)).get(); + let expected_deposit = managed_biguint!(600); + assert_eq!(user_deposit, expected_deposit); + }, + ) + .assert_ok(); + + // set block timestamp after deadline + b_wrapper.set_block_timestamp(CF_DEADLINE + 1); + + // check status + b_wrapper + .execute_query(&cf_setup.cf_wrapper, |sc| { + let status = sc.status(); + assert_eq!(status, Status::Failed); + }) + .assert_ok(); + + // first user claim + b_wrapper + .execute_tx(first_user, &cf_setup.cf_wrapper, &rust_biguint!(0), |sc| { + sc.claim(); + }) + .assert_ok(); + + // second user claim + b_wrapper + .execute_tx(second_user, &cf_setup.cf_wrapper, &rust_biguint!(0), |sc| { + sc.claim(); + }) + .assert_ok(); + + b_wrapper.check_esdt_balance(owner, CF_TOKEN_ID, &rust_biguint!(0)); + b_wrapper.check_esdt_balance(first_user, CF_TOKEN_ID, &rust_biguint!(1_000)); + b_wrapper.check_esdt_balance(second_user, CF_TOKEN_ID, &rust_biguint!(1_000)); +} +``` + + +## Conclusion + +These tests cover pretty much every flow in the crowdfunding smart contract. Keep in mind that code can be deduplicated even more by having functions similar to the `setup_crowdfunding` function, but for the sake of the example, we've kept this as simple as possible. We hope this will make writing tests and debugging a lot easier moving forward! + +--- + +### Whitebox Functions Reference + +## Introduction + +This page contains a list of all the currently available functions for the Rust Testing Framework, specifically the ones that the `BlockchainStateWrapper` type provides. + +Note: You will notice that most functions use the `num_bigint::BigUint` type for numbers. This is NOT the same as the BigUint type you use inside smart contracts. It comes from a Rust library, and they are different types. It is recommended to always use the Rust version outside of lambda functions, and only use the managed type when interacting with the sc directly. + + +## State-checking functions + +These functions check the blockchain state. They will panic and the test will fail if the check is unsuccessful. + + +### check_egld_balance + +```rust +check_egld_balance(&self, address: &Address, expected_balance: &num_bigint::BigUint) +``` + +Checks the EGLD balance for the given address. + + +### check_esdt_balance + +```rust +check_esdt_balance(&self, address: &Address, token_id: &[u8], expected_balance: &num_bigint::BigUint) +``` + +Checks the fungible ESDT balance for the given address. + + +### check_nft_balance + +```rust +check_nft_balance(&self, address: &Address, token_id: &[u8], nonce: u64, expected_balance: &num_bigint::BigUint, opt_expected_attributes: Option<&T>) +``` + +Where T has to implement TopEncode, TopDecode, PartialEq, and core::fmt::Debug. This is usually done through `#[derive(TopEncode, TopDecode, PartialEq, Debug)]`. + +This function checks the NFT balance for a specific nonce for an address, and optionally checks the NFT attributes as well. If you are only interested in the balance, pass `Option::None` for `opt_expected_attributes`. The Rust compiler might complain that it can't deduce the generic `T`, in which case, you can do one of the following: + +```rust +b_mock.check_nft_balance::(..., None); + +b_mock.check_nft_balance(..., Option::::None); +``` + +Where `...` are the rest of the arguments. + + +## State-getter functions + +These functions get the current state. They are generally used after a transaction to check that the tokens reached their intended destination. Most functions will panic if they're given an invalid address as argument. + + +### get_egld_balance + +```rust +get_egld_balance(&self, address: &Address) -> num_bigint::BigUint +``` + +Gets the EGLD balance for the given account. + + +### get_esdt_balance + +```rust +get_esdt_balance(&self, address: &Address, token_id: &[u8], token_nonce: u64) -> num_bigint::BigUint +``` + +Gets the ESDT balance for the given account. If you're interested in fungible token balance, set `token_nonce` to 0. + + +### get_nft_attributes + +```rust +get_nft_attributes(&self, address: &Address, token_id: &[u8], token_nonce: u64) -> Option +``` + +Gets the NFT attributes for a token owned by the given address. Will return `Option::None` if there are no attributes. + + +### dump_state + +```rust +dump_state(&self) +``` + +Prints the current state to console. Useful for debugging. + + +### dump_state_for_account_hex_attributes + +```rust +dump_state_for_account_hex_attributes(&self, address: &Address) +``` + +Similar to the function before, but dumps state only for the given account. + + +### dump_state_for_account + +```rust +dump_state_for_account(&self, address: &Address) +``` + +Similar to the function before, but prints the attributes in a user-friendly format, given by the generic type given. This is useful for debugging NFT attributes. + + +## State-altering functions + +These functions alter the state in some way. + + +### create_user_account + +```rust +create_user_account(&mut self, egld_balance: &num_bigint::BigUint) -> Address +``` + +Creates a new user account, with the given EGLD balance. The Address is pseudo-randomly generated by the framework. + + +### create_user_account_fixed_address + +```rust +create_user_account_fixed_address(&mut self, address: &Address, egld_balance: &num_bigint::BigUint) +``` + +Same as the function above, but it lets you create an account with a fixed address, for the few cases when it's needed. + + +### create_sc_account + +```rust +create_sc_account(&mut self, egld_balance: &num_bigint::BigUint, owner: Option<&Address>, obj_builder: ContractObjBuilder, contract_wasm_path: &str) -> ContractObjWrapper +``` + +Where: + +```rust + CB: ContractBase + CallableContract + 'static, + ContractObjBuilder: 'static + Copy + Fn() -> CB, +``` + +Creates a smart contract account. For `obj_builder`, you will have to pass `sc_namespace::contract_obj`. This function will return a `ContractObjWrapper`, which contains the address of the newly created SC, and the function that is used to create instances of your contract. + +The `ContractObjWrapper` will be used whenever you interact with the SC, which will be through the execution functions. If you only need the address (for setting balance, for example), you can use the `address_ref` method to get a reference to the stored address. + +`contract_wasm_path` is the path towards the wasm file. This path is relative to the `tests` folder that the current test file resides in. The most usual path will be `output/wasm_file_name.wasm`. + + +### create_sc_account_fixed_address + +```rust +create_sc_account_fixed_address(&mut self, address: &Address, egld_balance: &num_bigint::BigUint, owner: Option<&Address>, obj_builder: ContractObjBuilder, contract_wasm_path: &str) -> ContractObjWrapper +``` + +Same as the function above, but the address can be set by the caller instead of being randomly generated. + + +### set_egld_balance + +```rust +set_egld_balance(&mut self, address: &Address, balance: &num_bigint::BigUint) +``` + +Sets the EGLD balance for the given account. + + +### set_esdt_balance + +```rust +set_esdt_balance(&mut self, address: &Address, token_id: &[u8], balance: &num_bigint::BigUint) +``` + +Sets the fungible token balance for the given account. + + +### set_nft_balance + +```rust +set_nft_balance(&mut self, address: &Address, token_id: &[u8], nonce: u64, balance: &num_bigint::BigUint, attributes: &T) +``` + +Sets the non-fungible token balance for the given account, and the attributes. Attributes can be any serializable type. If you don't need attributes, you can pass "empty" in various ways: `&()`, `&Vec::::new()`, `BoxedBytes::empty()`, etc. + + +### set_esdt_local_roles + +```rust +set_esdt_local_roles(&mut self, address: &Address, token_id: &[u8], roles: &[EsdtLocalRole]) +``` + +Sets the ESDT token roles for the given address and token. Usually used during setup steps. + + +### set_block_epoch + +```rust +set_block_epoch(&mut self, block_epoch: u64) +``` + + +### set_block_nonce + +```rust +et_block_nonce(&mut self, block_nonce: u64) +``` + + +### set_block_round + +```rust +set_block_round(&mut self, block_round: u64) +``` + + +### set_block_timestamp + +```rust +set_block_timestamp(&mut self, block_timestamp: u64) +``` + + +### set_block_random_seed + +```rust +set_block_random_seed(&mut self, block_random_seed: Box<[u8; 48]>) +``` + +Set various values for the current block info. + + +### set_prev_block_epoch + +```rust +set_prev_block_epoch(&mut self, block_epoch: u64) +``` + + +### set_prev_block_nonce + +```rust +set_prev_block_nonce(&mut self, block_nonce: u64) +``` + + +### set_prev_block_round + +```rust +set_prev_block_round(&mut self, block_round: u64) +``` + + +### set_prev_block_timestamp + +```rust +set_prev_block_timestamp(&mut self, block_timestamp: u64) +``` + + +### set_prev_block_random_seed + +```rust +set_prev_block_random_seed(&mut self, block_random_seed: Box<[u8; 48]>) +``` + +Same as the ones above, but sets the block info for the previous block. + + +## Smart Contract execution functions + +These functions help interacting with smart contracts. While they would still fit into the state-altering category, we feel they deserve their own section. + +Note: We will shorten the signatures by not specifying the complete types for the ContractObjWrapper. For reference, the contract wrapper is of type `ContractObjWrapper`, where + +```rust + CB: ContractBase + CallableContract + 'static, + ContractObjBuilder: 'static + Copy + Fn() -> CB, +``` + + +### execute_tx + +```rust +execute_tx(&mut self, caller: &Address, sc_wrapper: &ContractObjWrapper<...>, egld_payment: &num_bigint::BigUint, tx_fn: TxFn) -> TxResult +``` + +Executes a transaction towards the given SC (defined by the wrapper), with optional EGLD payment (pass 0 if you want no payment). `tx_fn` is a lambda function that accepts a contract object as an argument. For more details about how to write such a lambda, you can take a look at the [Crowdfunding test examples](/developers/testing/rust/whitebox-legacy). + + +### execute_esdt_transfer + +```rust +execute_esdt_transfer(&mut self, caller: &Address, sc_wrapper: &ContractObjWrapper<...>, token_id: &[u8], esdt_nonce: u64, esdt_amount: &num_bigint::BigUint, tx_fn: TxFn) -> TxResult +``` + +Same as the function above, but executes an ESDT/NFT transfer instead of EGLD transfer. + + +### execute_esdt_multi_transfer + +```rust +execute_esdt_multi_transfer(&mut self, caller: &Address, sc_wrapper: &ContractObjWrapper<...>, esdt_transfers: &[TxInputESDT], tx_fn: TxFn) -> TxResult +``` + +Same as the function above, but executes a MultiESDTNFT transfer instead. + + +### execute_query + +```rust +execute_query(&mut self, sc_wrapper: &ContractObjWrapper<...>, query_fn: TxFn) -> TxResult +``` + +Executes a SCQuery on the SC. None of the changes are committed into the state, but it still needs to be a mutable function to perform the temporary changes. Just like on the real blockchain, there is no caller and no token transfer for queries. + + +### execute_in_managed_environment + +```rust +execute_in_managed_environment(&self, f: Func) -> T +``` + +Executes an arbitrary function and returns its result. The result can be any type. This function is rarely used. It can be useful when you want to perform some checks that involve managed types and such. (since you cannot create managed types outside of the lambda functions). + + +## Undocumented functions + +There are some scenario generation functions that have not been included, but we recommend not bothering with scenario generation. The process is very time-consuming and the results are some unreadable scenario files. If you need to write scenarios, we recommend writing them yourself. If you still want to dabble into the scenario generation, there are some examples in the [Crowdfunding test examples](/developers/testing/rust/whitebox-legacy). + +--- + +### 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). 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) - 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). + +--- + +## Validators +### Convert An Existing Validator Into A Staking Provider + +Staking Phase 3.5 introduced the ability for an existing Validator to create a new delegation smart contract and have their validator node(s) added in the delegation smart contract directly. This is different from before, when in order to do this, a Validator node was to be unstaked, and then placed at the back of the queue. With Staking Phase 3.5, Validators can retain the place inside the 3,200 Validator nodes, and start accepting non-custodial delegations. + +1. Create a new Delegation Smart Contract for an Existing Validator + +Send the following transaction from the wallet you staked the Validator from: + +``` +Generate Contract for Validator transaction + Sender: + Receiver: erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqylllslmq6y6 + Value: 0 + Gas Limit: 510000000 + Data: "makeNewContractFromValidatorData" + + "@" + "" + + "@" + "" +``` +*For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format).* + +Where: +Max cap = total delegation cap in EGLD, fully denominated, in hexadecimal encoding + +For example, to obtain the fully denominated form of 7231.941 EGLD, the amount must be multiplied by 10^18, resulting in 7231941000000000000000. Do not encode the ASCII string "7231941000000000000000", but encode the integer 7231941000000000000000 itself. This would result in "01880b57b708cf408000". + +00 = uncapped + +Fee: service fee as hundredths of percents, in hexadecimal encoding + +For example, a service fee of 37.45% is expressed by the integer 3745. This integer must then be encoded hexadecimally (3745 becomes "0ea1"). + +After successfully deploying your new delegation smart contract, make sure you manage it with the [Delegation Manager](/validators/delegation-manager). + +:::caution +Whenever merging or converting direct staked keys into a staking provider pool, please be aware that the BLS key's signature will be altered automatically and re-staking an unbonded node is no longer possible. +In other words, if you attempt this flow of operations unStakeNodes->unBondNodes for a merged key, make sure you call also the removeNodes operation. Otherwise, the stakeNodes or reStakeUnStakedNodes will fail. +::: + +--- + +### FAQs + +This page contains frequently asked questions about validators and nodes. + + +## FAQs + +**How to choose a VPS?** + +Get started with a few popular VPS choices: + +- Amazon - https://aws.amazon.com/lightsail/pricing/ +- DigitalOcean - https://www.digitalocean.com/pricing/ +- UpCloud - https://upcloud.com/pricing/ +- Vultr - https://www.vultr.com/products/cloud-compute/ +- Alibaba Cloud - https://www.alibabacloud.com/product/ecs#pricing +- Google Cloud - https://cloud.google.com/products/calculator +- Microsoft Azure - https://azure.microsoft.com/en-us/pricing/calculator/ + +Find more via a comparison calculator - https://www.vpsbenchmarks.com/screener + +Recommendations from experienced validators: + +- Jose - https://medium.com/@josefcoap/elrond-vps-guide-ebfa6655cb9f + +:::caution +Consider the price of cheap services that offer same specs as other providers at half of the price, when running a validator - the protocol dynamically phases out low performance nodes, which will lead to fewer returns for operators. +::: + + +**What if I have Windows?** + +Use a virtualization service to deploy a virtual Linux server on your Windows machine. This setup is recommended for testing purposes only. Try this tutorial, for example: https://itsfoss.com/install-linux-in-virtualbox/ + + +**General note** + +There are hundreds of people actively engaging with MultiversX infrastructure. Engage with them over our official [Telegram channel](https://t.me/MultiversXValidators), and you will get your questions answered. Weigh in favor of people with “admin” in their name. Disregard any direct messages offering private support - those are most likely scam attempts. + +--- + +### How to use the Docker Image + +This page will guide you through the process of using the Docker image to run a MultiversX node. + + +## Docker node images +As an alternative to the recommended installation flow, one could choose to run a MultiversX Node using the official Docker images: [here](https://hub.docker.com/u/multiversx) + +On the `dockerhub` there are Docker images for every chain (mainnet, devnet and testnet). + +Images name: +- for mainnet: [chain-mainnet](https://hub.docker.com/r/multiversx/chain-mainnet) +- for devnet: [chain-devnet](https://hub.docker.com/r/multiversx/chain-devnet) +- for testnet: [chain-testnet](https://hub.docker.com/r/multiversx/chain-testnet) + +:::note Attention required + +In order to get the latest tag for an image check the latest `RELEASE` from the config repository ([mainnet](https://github.com/multiversx/mx-chain-mainnet-config/releases), [devnet](https://github.com/multiversx/mx-chain-devnet-config/releases) or [testnet](https://github.com/multiversx/mx-chain-testnet-config/releases)). +::: + + +## How to pull a Docker image from Dockerhub for node ? +```docker +IMAGE_NAME=chain-mainnet +IMAGE_TAG=[latest_release_tag] +docker pull multiversx/${IMAGE_NAME}:${IMAGE_TAG} +``` + + +## How to generate a BLS key ? +In order to generate a new BLS key one has to pull from `dockerhub` an image for the `chain-keygenerator` tool: +``` +# pull image from dockerhub +docker pull multiversx/chain-keygenerator:latest + +# create a folder for the bls key +BLS_KEY_FOLDER=~/bls-key +mkdir ${BLS_KEY_FOLDER} + +# generate a new BLS key +docker run --rm --mount type=bind,source=${BLS_KEY_FOLDER},destination=/keys --workdir /keys multiversx/chain-keygenerator:latest +``` + + +## How to run a node with Docker ? + +The following commands run a Node using the Docker image and map a container folder to a local one that holds the necessary configuration: + +```docker +PATH_TO_BLS_KEY_FILE=/absolute/path/to/bls-key +IMAGE_NAME=chain-mainnet +IMAGE_TAG=[latest_release_tag] + +docker run --mount type=bind,source=${PATH_TO_BLS_KEY_FILE}/,destination=/data multiversx/${IMAGE_NAME}:${IMAGE_TAG} \ + --validator-key-pem-file="/data/validatorKey.pem" +``` + +In the snippet above, make sure you adjust the path to a valid key file and also provide the appropriate command-line arguments to the Node. For more details go to [Node CLI](https://docs.multiversx.com/validators/node-cli). + +:::note Attention required + +**Devnet** and **Testnet** validators **should carefully** specify the precise tag when using the Docker setup, always test the new releases themselves, and only deploy them once they understand and agree with the changes. +::: + + +:::tip For CentOS users +If the node's docker image runs on CentOS, the machine needs the `allow_execheap` flag to be enabled. + +In order to do this, run the command `sudo setsebool allow_execheap=true` +::: + +--- + +### Import DB + +This page will guide you through the process of starting a node in `import-db` mode, allowing the reprocessing of older transactions. + + +## Introduction + +The node is able to reprocess a previously produced database by providing the database and starting +the node with the import-db related flags explained in the section below. + +Possible use cases for the import-db process: + +- index in ElasticSearch (or something similar) all the data from genesis to present time; +- validate the blockchain state; +- make sure there aren't backwards compatibility issues with a new software version; +- check the blockchain state at a specified time (this includes additional code changes, but for example if you are + interested in the result of an API endpoint at the block 255255, you could use import db and force the node to stop + at the block corresponding to that date). + + +## How to start the process + +Let's suppose we have the following data structure: + +``` + ~/mx-chain-go/cmd/node +``` + +the `node` binary is found in the above-mentioned path. +Now, we have a previously constructed database (from an observer that synced with the chain from the +genesis and never switched the shards). This database will be placed in a directory, let's presume +we will place it near the node's binary, yielding a data structure as follows: + +``` +. +├── config +│ ├── api.toml +│ ├── config.toml +│ ... +├── import-db +│ └── db +│ └── 1 +│ ├── Epoch_0 +│ │ └── Shard_1 +│ │ ├── BlockHeaders +│ │ │ ... +│ │ ├── BootstrapData +│ │ │ ... +│ │ ... +│ └── Static +│ └── Shard_1 +│ ... +├── node +``` + +It is very important that the directory called `db` is a subdirectory (in our case of the `import-db`). +Also, please check that the `config` directory matches the one of the node that produced the `db` data +structure, including the `prefs.toml` file. + +:::caution +Please make sure the `/mx-chain-go/cmd/node/db` directory is empty so the import-db process will start +from the genesis up until the last epoch provided. +::: + +Next, the node can be started by using: + +``` + cd ~/mx-chain-go/cmd/node + ./node -use-log-view -log-level *:INFO -import-db ./import-db +``` + +:::note +Please note that the `-import-db` flag specifies the path to the directory containing the source db directory. The value provided in the example above assumes that the import db directory is called `import-db` and is located near the `node` executable file. +::: + +The node will start the reprocessing of the provided database. It will end with a message like: + +``` +import ended because data from epochs [x] or [y] does not exist +``` + +:::tip +The import-db process can be sped up by skipping the block header's signature check if the import-db data comes from a trustworthy source. +In this case the node should be started with all previously mentioned flags, adding the `-import-db-no-sig-check` flag. +::: + + +## Import-DB with populating an Elasticsearch cluster + +One of the use-cases for utilizing the `import-db` mechanism is to populate an Elasticsearch cluster with data that is +re-processed with the help of this process. + +:::tip +Import-DB for populating an Elasticsearch cluster should be used only for a full setup (a node in each Shard + a Metachain node) +::: + +The preparation implies the update of the `external.toml` file for each node. More details can be found [here](/sdk-and-tools/elastic-search/#setup). + +If everything is configured correctly, nodes will push the re-processed data into the Elasticsearch cluster. + +--- + +### Installing a Validator Node + +This page will guide you through the process of installing and updating a validator node. + + +## **Install your node(s)** + +After preparing the user permissions, the script configurations, and the keys, the actual node installation can begin. The Validator script is a multi-purpose tool for managing your node, it is accessible to Mainnet, Devnet or Testnet. + +Following these few steps, we will work on installing the MultiversX Network validator node to get it up and running on your local machine. + +For installation, one must start the scripts by: + +```bash +cd ~/mx-chain-scripts +./script.sh +``` + +After that, a menu will appear with the following options. Select the option `1` to install the node. + +```bash + 1) install + 2) observing_squad + 3) multikey_group + 4) upgrade + 5) upgrade_multikey + 6) upgrade_squad + 7) upgrade_proxy + 8) remove_db + 9) start +10) start_all +11) stop +12) stop_all +13) cleanup +14) github_pull +15) add_nodes +16) get_logs +17) benchmark +18) quit + Please select an action:1 +``` + +:::note +As an alternative, the installation can be triggered by executing the following command: + +```bash +~/mx-chain-scripts/script.sh install +``` +::: + +- When asked, indicate the number of nodes you want to run, i.e. `1` +- When asked, indicate the name of your validator, i.e. `Valar` +- Quit the menu without starting (we need keys first) by using `18 - quit` + + +### **Prepare your keys** + +Create a new folder "VALIDATOR_KEYS" to serve as a local backup when updating: + +```bash +cd ~ +mkdir -p ~/VALIDATOR_KEYS + +``` + +Generate a certificate file containing your Validator key by running the `keygenerator`: + +```bash +./elrond-utils/keygenerator + +``` + +Copy the generated `validatorKey.pem` file to the `config` folder of your node(s), and repeat for each node. + +```bash + cp validatorKey.pem ~/elrond-nodes/node-0/config/ + +``` + +:::tip +Each node needs its unique `validatorKey.pem` file +::: + +Then copy the `validatorKey.pem` file - in ZIP form - to the `$HOME/VALIDATOR_KEYS/` folder . This is important for your node to be able to restart correctly after an upgrade. + +```bash +zip node-0.zip validatorKey.pem +mv node-0.zip $HOME/VALIDATOR_KEYS/ + +``` + +Repeat the above process for all your “n” nodes. When complete, please refer to our Key management section for instructions about how to properly backup and protect your keys. + + +### **Start the node(s)** + +```bash +~/mx-chain-scripts/script.sh start +``` + + +### **Start the node visual interface** + +Once the node has started, you can check its progress, using the `TermUI` interface. Navigate to your `$HOME/elrond-utils` directory and start the `TermUI`, one for each of your nodes: + +```bash +cd $HOME/elrond-utils +./termui -address localhost:8080 +``` + +:::tip + +Your first node is called `node-0` and it is a REST API that will run on port `8080` by default. The next node is `node-1`on port `8081`, and so on. +::: + + +## **Update your node(s)** + +Upgrade your node by running the script and selecting either of these options: + +- `14 - github_pull` downloads the latest version of the scripts +- `4 - upgrade` +- `9 - start` +- `18 - quit` + +```bash +~/mx-chain-scripts/script.sh +``` + +These are the basic steps. Please carefully read the on-screen instructions, refer to the scripts [readme file](https://github.com/multiversx/mx-chain-scripts/blob/master/README). You can also ask any questions in the MultiversX [Validators chat](https://t.me/MultiversXValidators) + + +## **Mandatory: Backup your keys** + +Your private keys are needed to run your node. Losing them means losing control of your node. A 3rd party gaining access to them could result in loss of funds. + +Find them in `$HOME/elrond-nodes/node-0/config` [be mindful of your “`n`” nodes] + +:::important +Create a safe backup for them on storage outside of the server running your node(s). +::: + + +## **Migration from old scripts** + +Before the release of the current `mx-chain-scripts`, there were the `elrond-go-scripts-testnet`, `elrond-go-scripts-devnet` and `elrond-go-scripts-mainnet` for setting up nodes +on the testnet, devnet and mainnet respectively. Those three repositories have been deprecated because `elrond-go-scripts` can be used to manage nodes regardless of their target network (`testnet`, `devnet` or `mainnet`). + +If one wants to migrate from the old scripts to the new ones, it is generally possible to do so while preserving the validator keys, current node installation, DB and logs. +These are the steps to be followed: + +- clone the `mx-chain-scripts` repo near the old one (`elrond-go-scripts-testnet`/`elrond-go-scripts-devnet`/`elrond-go-scripts-mainnet`); assuming the old scripts were located in the home directory, run the following: + +``` +cd ~ +git clone https://github.com/multiversx/mx-chain-scripts +``` + +- configure the new scripts as described in the sections above; +- make sure you set the new `ENVIRONMENT` variable declared within `~/mx-chain-scripts/config/variables.cfg`; it must contain one of `"testnet"`, `"devnet"` or `"mainnet"`; +- call the `migrate` operation on the scripts: + +``` +cd ~/mx-chain-scripts +./script.sh migrate +``` + +Be careful as to not mix the previous installation network with the new one. This might lead to unpredictable results. + + +## **Choosing a custom configuration tag or branch** + +:::caution +This option should be only used when debugging or testing in advance of a pre-release tag. +Use this on your own risk! +::: + +The power of the scripts set has been leveraged with a new addition: the possibility to tell the scripts a specified tag +or branch (not recommended using a branch due to the fact that an unsigned commit might bring malicious code or configs) + +To accomplish this, edit the variables.cfg file + +``` +cd ~/mx-chain-scripts/config +nano variables.cfg +``` + +locate the `OVERRIDE_CONFIGVER` option and input a value there, something like `tags/T1.3.14.0`. +The `tags/` prefix will tell the scripts to use the tag and not search a branch called `T1.3.14.0`. +Call the `upgrade` command on the scripts to install the desired configuration version. + +Resetting the value to `""` will make the scripts to use the released version. + +:::caution +The `OVERRIDE_CONFIGVER` is not backed up when calling `github_pull` operation. +::: + +## **Troubleshooting** + +If the node fails to start and the termui window shows messages like: +``` +termui websocket error, retrying in 10s... +termui websocket error, retrying in 10s... +termui websocket error, retrying in 10s... +``` + +a good method to check what the node is trying to do at startup (and fails) is to issue this command: + +```bash +sudo journalctl -f -u elrond-node-XXX.service +``` + by replacing `XXX` with the actual node instance on the machine: 0, 1, 2, 3... + +--- + +### Manage a validator node + +Your node will start as an observer. In order to make it into a validator, you will need to have 2500 xEGLD tokens. You can reach out to an admin in our [Telegram community](https://t.me/MultiversXValidators) who will gladly help. + +Follow these steps to manage your validator node. + +Let’s begin! + +First, you need to create a MultiversX wallet. You can create this wallet on either the [mainnet](https://wallet.multiversx.com), [devnet](https://devnet-wallet.multiversx.com) or [testnet](https://testnet-wallet.multiversx.com). + +:::tip +For devnet and testnet only: + +Share your wallet's public address (erd1...) with an admin on the Telegram community, and you will receive test xEGLD. If a smaller amount of tokens is needed, +you can use the [wallet's faucet](/wallet/web-wallet#testnet-and-devnet-faucet). +::: + +Once you have sufficient funds, you can use the wallet to send a stake transaction for your node, in order for it to become a Validator. + +In the wallet, navigate to the “Stake” section and click on the “Stake now” button at the top right of the page. +![img](https://files.gitbook.com/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FlZkUHx72OLJMbqkEHff2%2Fuploads%2FlJg5GyCzq7NI9nmqiKJ5%2Fwalletelrond2.PNG?alt=media&token=136796b5-b10b-4df0-b83b-038419e57ed6) + +Select the `validatorKey.pem` file you created for your node and proceed with the instructions displayed. + +![img](https://gblobscdn.gitbook.com/assets%2F-LhHlNldCYgbyqXEGXUS%2F-MKj4PGWn3kQ197_YcJQ%2F-MKjC2SwfiK2OdVWTz49%2Fimage.png?alt=media&token=9d38ba79-9d47-452e-8fb3-303f0edf5740) + +You can check the status of your Stake transaction and other information about the validator node in the explorer at [mainnet explorer](https://explorer.multiversx.com), [devnet explorer](https://devnet-explorer.multiversx.com) or the [testnet explorer](https://testnet-explorer.multiversx.com). Make sure to check out the Validators section too. + +![img](https://gblobscdn.gitbook.com/assets%2F-LhHlNldCYgbyqXEGXUS%2F-MKj4PGWn3kQ197_YcJQ%2F-MKjCya_zwNCJWCZ4ryI%2Fimage.png?alt=media&token=7a1a0e1c-dc77-41ef-afcd-296dd23da18b) + +:::important +To distinguish between the mainnet and other networks (devnet and testnet), we have carefully created different addresses for the devnet tools, which are also presented in a predominantly black theme. Be cautious and know the difference, to avoid mistakes involving your mainnet validators and real EGLD tokens. +::: + +--- + +### Merging A Validator Into An Existing Delegation Smart Contract + +Introduced in Staking Phase 3.5, the ability of merging one or more existing standalone validator node into a staking provider gives more flexibility for staking provider operators. + +There are two steps required for this action: The owner of the Delegation SC has to whitelist the wallet from which the Merging Validator was staked from. Then the Merging Validator has to send the merge transaction from the whitelisted wallet. + +1. Merging a Validator into an Existing Delegation Smart Contract + +From the Delegation Smart Contract owner's wallet, send a transaction with the following parameters: + +```rust +Whitelist Wallet For Merging + Sender: + Receiver: + Value: 0 + Gas Limit: 5000000 + Data: "whitelistForMerge" + + "@" + "" +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +:::tip +You can obtain the HEX format of an address by first converting its bech32 (erd1...) form into binary, and then converting the resulting binary into HEX. +::: + +2. The Merging Validator sends the merge transaction from the whitelisted wallet: + +```rust +Whitelist Wallet For Merging + Sender: + Receiver: erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqylllslmq6y6 + Value: 0 + Gas Limit: 510000000 + Data: "mergeValidatorToDelegationWithWhitelist" + + "@" "" +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +:::caution +We advise against using this method to buy or sell validator slots - it requires the transfer of private keys (validatorKey.pem) which can't be changed. This puts the buyer at risk of slashing, should the seller deploy a node with the same key, either intentionally or by mistake. +::: + +## **Merging own node(s)** + +If the owner address of the node(s) and Delegation SC is the same use, whitelisting is not needed. + +```rust +Merge Own Nodes + Sender: + Receiver: erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqylllslmq6y6 + Value: 0 + Gas Limit: 510000000 + Data: "mergeValidatorToDelegationSameOwner" + + "@" "" +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +:::caution +Whenever merging or converting direct staked keys into a staking provider pool, please be aware that the BLS key's signature will be altered automatically and re-staking an unbonded node is no longer possible. +In other words, if you attempt this flow of operations unStakeNodes->unBondNodes for a merged key, make sure you call also the removeNodes operation. Otherwise, the stakeNodes or reStakeUnStakedNodes will fail. +::: + +--- + +### Multikey nodes management + +This page contains information about how to manage multiple keys on a group of nodes. + + +## Multikey architecture overview + +Since the mainnet launch, and up until the release candidate RC/v1.6.0, each node could have managed only +one key. So the relationship between the nodes and staked validator keys is `1:1`, the node "following" the shard where +the managed key is assigned. +The multikey feature allows a node instance to hold more than one key. However, since MultiversX is a sharded blockchain +and a single node is only able to store the state of a single shard at a time, we need a group of nodes with exactly +one node in each shard, similar with what we have on observing squad. Also, in each epoch, the keys can be shuffled among shards. +This means that running multiple keys will require at least the number of shards + 1 node instances (one for each shard + metachain). +The same set of keys should be provided for all node instances. + +This type of nodes used in multikey operation can be assimilated as a hybrid between an observer node and a validator. +It behaves as an observer by holding in the `validatorKey.pem` file a BLS key that will never be part of the consensus +group and the shard should be specified in the `prefs.toml` file, so the node will not change shards. +The node behaves also as a validator (or multiple validators) by monitoring and emitting consensus messages, +whenever required on the behalf of the managed keys set. + +Since an observer already performs block validation in its own shard, it can be easily used to manage a group of validator +keys and propose or validate blocks on behalf of the keys it possesses. +To summarize, this type of node can use any provided keys, in any combination, to generate consensus messages provided +that those used keys are part of the consensus group in the current round. With the multikey feature, the relationship +now becomes `n:m`, providing that `n` is the number of keys managed by an entity and `m` is the number of shards + 1. + +:::important +This feature is purely optional. Normal `1:1` relationship between the keys and the nodes is still supported. The multikey +mode should optimize the costs when running a set of keys (check [Economics running multikey nodes](#economics-running-multikey-nodes) section) +::: + +The following image describes the keys and nodes relationship between the single operation mode versus multikey operation mode. + +![img](/validators/multikey-diagram.drawio.png) + + +## General implementation details + +The nodes running with the multikey feature, beside deciding the consensus group (which is normally done on each node), +can access the provided keys set and use, in any combination, one or more keys, if the node detects that at least one managed +key is part of the consensus group. +The code changes to support multikey nodes affected mainly the `consensus`, `keyManagement` and `heartbeat` packages. + + +### Heartbeat information + +The group managing the set of keys (we will call them multikey nodes or multikey group), will pass the validators BLS +information tight to "virtual" peer IDs. A "virtual peer ID" is a generated p2p identity that the p2p network can not +connect to as it does not have a real address bind to. Consequently, this feature brings a new layer of security as +the multikey nodes will hide the relationship between the validator BLS keys and the host that manages those BLS keys. + + +### Redundancy + +The redundancy sub-system has been upgraded to accommodate the multikey requirements keeping the multiple redundancy +fallback groups operation. A fallback multikey group will monitor each managed key for missed consensus activity **independent on +each managed node**. So, a bad configured main group, offline or stuck main group nodes should trigger fallback events on +the redundancy group. +Example: if main multikey group was set to manage the following key set `[key_0, key_1 ... key_e-1, key_e+1 ... key_n]` +(mind the missing `key_e`) and the redundancy fallback multikey group has the set `[key_0, key_1 ... key_e-1, key_e, key_e+1 ... key_n]`, +then, the fallback group, after `k` misses in the consensus activity (propose/sign block) will start using that `key_e` as it +was the only key assigned to the multikey group (`k` is the value defined in the `prefs.toml` file, `RedundancyLevel` option). + + +## Economics running multikey nodes + +As for `n` managed keys we will need at least a group of nodes, there is a threshold that a staking operator +will want to consider when deciding to switch the operation towards the multikey mode. The switch becomes attractive for the +operator when the number of managed keys is greater than the number of shards. So, for the time being, when we have +at least 5 keys that are either *eligible* or *waiting*, the switch to multikey mode becomes feasible. + +:::caution +Although there are no hard limits in the source code to impose a maximum number of keys for a multikey group, the MultiversX team +strongly recommends the node operators to not use more than 50 keys per group. The reason behind this recommendation is that a single node +controlling enough keys could cause damage to the chain as, in extreme cases, it could propose consecutive bad blocks, disrupting the +possibility of blocks synchronization or blocks cross-notarization. +::: + + +## Usage + + +### allValidatorsKeys.pem file +The switch towards the multikey operation will only require aggregating all BLS keys in a new file, called `allValidatorsKeys.pem` +that will be loaded by default from the same directory where the `validatorKey.pem` file resides. The path can be altered by using the +binary flag called `--all-validator-keys-pem-file`. +Example of an `allValidatorsKeys.pem` file: +``` +-----BEGIN PRIVATE KEY for e296e97524483e6b59bce00cb7a69ec8c0d1ac4227925f07fdd57b3ab4ec2f64b240728a0a3c5be2930aea570bf12c12314e25d942b106472800e51524add26ec9546475c1cfae91dd7e799f256d1b0758e17aaa3898c29d489bd87c86d04498----- +YzJlODM0NTdmOTVmYMDVjZGRiNzdiODc1N2YyZGEx +ZGRhYWY5MTI5Y2NlOWQyOQ== +-----END PRIVATE KEY for e296e97524483e6b59bce00cb7a69ec8c0d1ac4227925f07fdd57b3ab4ec2f64b240728a0a3c5be2930aea570bf12c12314e25d942b106472800e51524add26ec9546475c1cfae91dd7e799f256d1b0758e17aaa3898c29d489bd87c86d04498----- +-----BEGIN PRIVATE KEY for 5585ddceb6b7bf0d308162efd895d0717b22bab6b0412f09fb9cee234be73d197bfef8ae10064be5733472c573894015029672b70f63e0b58c7ab2e831ee0aff88b868e4d712bec0baf9a1cd1982e138af9b6cc55e4454b01cb8ad02a064f515----- +MzNlZjQyYTRhZDc3ZDBkZDk1M2JmNGIwNWE2MzczMmYxZWUy +ZWVkNzNiOGQ1ZDQ0NmEzMg== +-----END PRIVATE KEY for 5585ddceb6b7bf0d308162efd895d0717b22bab6b0412f09fb9cee234be73d197bfef8ae10064be5733472c573894015029672b70f63e0b58c7ab2e831ee0aff88b868e4d712bec0baf9a1cd1982e138af9b6cc55e4454b01cb8ad02a064f515----- +-----BEGIN PRIVATE KEY for 791c7e2bd6a5fb1371af18269267ad8ef9e56e264c4c95703c57526b16b84dd8df6347c0cc14f93d595a12316d38ae11264e05d2fa26d80387d12db52c1a98e93064d073d02549c71ec4e352d73724c21c02245b25d3643b532fac25d7580f0b----- +OTcxYjYyNWMzMzlkY2JhNTAyODMwNzZlYjMyY2MxMmYzNThiMjNiNzYz +NTA4YjFjMTVlYTIwNDYyMw== +-----END PRIVATE KEY for 791c7e2bd6a5fb1371af18269267ad8ef9e56e264c4c95703c57526b16b84dd8df6347c0cc14f93d595a12316d38ae11264e05d2fa26d80387d12db52c1a98e93064d073d02549c71ec4e352d73724c21c02245b25d3643b532fac25d7580f0b----- +``` + + +### prefs.toml file + +The existing fields `NodeDisplayName` and `Identity` will be applied to all managed and loaded BLS keys. The `NodeDisplayName` will +be suffixed with an order index for all managed keys. +For example, suppose we have the above example of the `allValidatorsKeys.pem` file and the `NodeDisplayName` is set to `example`. +The name for the managed keys will be: +``` +example-0 e296e97524483e6b59... +example-1 585ddceb6b7bf0d308... +example-2 791c7e2bd6a5fb1371... +``` + +If part of the managed BLS keys will need to be run on a different identity and/or different naming, the file section called +`NamedIdentity` will be of great use. +Following the above example, if we need to use a different identity and/or node name for the `791c7e2bd6a5fb1371...` key, +we will need to define the section as: +``` +# NamedIdentity represents an identity that runs nodes on the multikey +# There can be multiple identities set on the same node, each one of them having different bls keys, just by duplicating the NamedIdentity +[[NamedIdentity]] + # Identity represents the keybase/GitHub identity for the current NamedIdentity + Identity = "identity2" + # NodeName represents the name that will be given to the names of the current identity + NodeName = "random" + # BLSKeys represents the BLS keys assigned to the current NamedIdentity + BLSKeys = [ + "791c7e2bd6a5fb1371af18269267ad8ef9e56e264c4c95703c57526b16b84dd8df6347c0cc14f93d595a12316d38ae11264e05d2fa26d80387d12db52c1a98e93064d073d02549c71ec4e352d73724c21c02245b25d3643b532fac25d7580f0b" + ] +``` + +which will generate the naming as: +``` +example-0 e296e97524483e6b59... +example-1 585ddceb6b7bf0d308... +random-0 791c7e2bd6a5fb1371... +``` + + +### Security notes for the multikey nodes + +As stated above, the multikey feature is able to use any number of keys on a small group of nodes. +At the first sight, this can be seen as a security degradation in terms of means of attacking a large staking provider but there are ways to mitigate these concerns as explained in the following list: +1. use the recommendation found in this page regarding the maximum number of keys per multikey group; +2. for each main multikey group use at least one backup multikey group in case something bad happens with the main group; +3. use the `NamedIdentity` configuration explained above to obfuscate the BLS keys and their declared identity from the actual nodes that manage the keys. + +Regarding point 3, each managed BLS key will create a virtual p2p identity that no node from the network can connect to since it does not advertise the connection info but is only used to sign p2p messages. +Associated with a separate named identity, the system will make that BLS key virtually unreachable, and its origin hidden from the multikey nodes. Therefore, the node operators will need to apply the following changes on the `prefs.toml` file: +* in the `[Preference]` section, the 2 options called `NodeDisplayName` and `Identity` should be changed to something different used in the BLS' definitions to prevent easy matching. Generic names like `gateway` or `observer` are suitable for this section. +Also, completely random strings can be used as to be easier to identify the nodes in explorer. The `Identity` can be left empty; +* in the `[[NamedIdentity]]` section, the 2 options called `NodeName` and `Identity` will be changed to the real identities of the BLS keys: such as the staking provider brand names. **They should be different from the ones defined in the `[Preference]` section.** + +In this way, the operation will be somewhat similar to the *sentinel nodes* seen elsewhere. +The difference in our case is that the setup is greatly simplified as there is no separate network for the protected nodes that will need to be maintained. +The security of our setup (if points 1, 2 and 3 are applied) should be the same with a *sentinel setup*. + + +### Configuration example + +Let's suppose we have 5 BLS keys that belong to a staking provider called `testing-staking-provider` and we want to apply the security notes described above. +So, for the sake of the example, we generated 5 random BLS keys, the `allValidatorsKeys.pem` should contain something like this: +``` +-----BEGIN PRIVATE KEY for 15eb03756fae81d2fbae392a4d7d82abdf7618ce3056b89376c2a46bc6e8403ed3cc84e12bc819c0b088ee46e7c28302d2b666b011714cc8ea2b75488907d07e194a6e83f0f3d15c7699de412de425314be5cc3ce6ab2c594690006f9915dd15----- +NDA5MWVjODMwZjU3MDhkYmQwNzk5ZWEwNjg2MDc0MzUzYmZjNThjM2ZhYzU2Y2I1 +ZGRhMjY3YTY1NjhkZjI1YQ== +-----END PRIVATE KEY for 15eb03756fae81d2fbae392a4d7d82abdf7618ce3056b89376c2a46bc6e8403ed3cc84e12bc819c0b088ee46e7c28302d2b666b011714cc8ea2b75488907d07e194a6e83f0f3d15c7699de412de425314be5cc3ce6ab2c594690006f9915dd15----- +-----BEGIN PRIVATE KEY for ff12bc7f471e2e375c6e8b981f13ed823dcca857c41a2ffc3a0956283a8428a95754375dabc0b412df3ec41d2a51ef1490a8d23f4e4f9348787f9615093e0129969085488b59d2ab550467cd0d0fa33df22e2ed2d8c8c0c0f59042dafd0c1098----- +MTcwN2ZlMzFhMzk3Y2VjOWM4ZjdmMWU3Njg4MjY3YTAwOWU5ZjJmMWYxY2Y0ZjFl +MzI2Y2M5NGJiZGFjNGQwZA== +-----END PRIVATE KEY for ff12bc7f471e2e375c6e8b981f13ed823dcca857c41a2ffc3a0956283a8428a95754375dabc0b412df3ec41d2a51ef1490a8d23f4e4f9348787f9615093e0129969085488b59d2ab550467cd0d0fa33df22e2ed2d8c8c0c0f59042dafd0c1098----- +-----BEGIN PRIVATE KEY for 3dec570c02a4444197c1ed53fefd7e57acb9bc99ae47db7661cfbfb47170418702162a46ed40e113e3381d68b713e903e286ffaf9cac77fed8f9c79e83f2abb0ccd690ef4f689607b6414a6f893e0c0ced93d7456240bbccbf223f7603dd8e05----- +ZWMwYWRjYjNiYTQ0YmM4MGM5ZjhmNTlkNTU5YTRlMWJlMTI2ODFmMDlmM2JiNTM4 +MmMyYzdlYmNhYjNkNTk2MA== +-----END PRIVATE KEY for 3dec570c02a4444197c1ed53fefd7e57acb9bc99ae47db7661cfbfb47170418702162a46ed40e113e3381d68b713e903e286ffaf9cac77fed8f9c79e83f2abb0ccd690ef4f689607b6414a6f893e0c0ced93d7456240bbccbf223f7603dd8e05----- +-----BEGIN PRIVATE KEY for 38a93e3c00128c31769823710aa7deb145591b99a78c87dbd74c894afd540ade6de3906b45001d3f5a5882db34eaf30e412bef77ed43cf5a394edd0aa70254a74db1c80eef5d41342cae76fbbae596bc811fa491e00f16a7e011a836f7ceaa15----- +YWMzMDk2ZjY3NmExNjhiNTQ5ODQzM2JiM2NiZWFmNzkyYjQyYWZhZjJlZmMwNjNl +YzdhMWI5OGM1ZDdjODg1MQ== +-----END PRIVATE KEY for 38a93e3c00128c31769823710aa7deb145591b99a78c87dbd74c894afd540ade6de3906b45001d3f5a5882db34eaf30e412bef77ed43cf5a394edd0aa70254a74db1c80eef5d41342cae76fbbae596bc811fa491e00f16a7e011a836f7ceaa15----- +-----BEGIN PRIVATE KEY for 1fce426b632e5a5941d9989e4f8bbb93a0a08a0e85dfe16d4d65c08b351dfbff1a1104d5e75e1be7565b4bbc6a583103bfc4b4075727133a54fa421983d894e549576364694b3e8910359b3de5260360bfe9f9bea2fec1cb50c2cf79a3fd590d----- +ZmYzMjM2ODljODQwMDRiMDI1MGU0NjcyMzhjYjJlMDNlNzg0OGI0YzQ1ZTM0ZjQz +YTZkZDVmNTBjYjAwMjAyNg== +-----END PRIVATE KEY for 1fce426b632e5a5941d9989e4f8bbb93a0a08a0e85dfe16d4d65c08b351dfbff1a1104d5e75e1be7565b4bbc6a583103bfc4b4075727133a54fa421983d894e549576364694b3e8910359b3de5260360bfe9f9bea2fec1cb50c2cf79a3fd590d----- +``` + +The staking operators that will create the actual `allValidatorsKeys.pem` file used on the chain should concatenate all keys from their `validatorKey.pem` files with a text editor. +The content should resemble the one depicted in this example. + +For the `prefs.toml` file, we can have definitions like: + +```toml +[Preferences] + # DestinationShardAsObserver represents the desired shard when running as observer + # value will be given as string. For example: "0", "1", "15", "metachain" + # if "disabled" is provided then the node will start in the corresponding shard for its public key or 0 otherwise + DestinationShardAsObserver = "0" + + # NodeDisplayName represents the friendly name a user can pick for his node in the status monitor when the node does not run in multikey mode + # In multikey mode, all bls keys not mentioned in NamedIdentity section will use this one as default + NodeDisplayName = "s14" + + # Identity represents the GitHub identity when the node does not run in multikey mode + # In multikey mode, all bls keys not mentioned in NamedIdentity section will use this one as default + Identity = "" + + # RedundancyLevel represents the level of redundancy used by the node (-1 = disabled, 0 = main instance (default), + # 1 = first backup, 2 = second backup, etc.) + RedundancyLevel = 0 + + # FullArchive, if enabled, will make the node able to respond to requests from past, old epochs. + # It is highly recommended to enable this flag on an observer (not on a validator node) + FullArchive = false + + # PreferredConnections holds an array containing valid ips or peer ids from nodes to connect with (in top of other connections) + # Example: + # PreferredConnections = [ + # "127.0.0.10", + # "16Uiu2HAm6yvbp1oZ6zjnWsn9FdRqBSaQkbhELyaThuq48ybdorrr" + # ] + PreferredConnections = [] + + # ConnectionWatcherType represents the type of the connection watcher needed. + # possible options: + # - "disabled" - no connection watching should be made + # - "print" - new connection found will be printed in the log file + ConnectionWatcherType = "disabled" + + # OverridableConfigTomlValues represents an array of items to be overloaded inside other configuration files, which can be helpful + # so that certain config values need to remain the same during upgrades. + # (for example, an Elasticsearch user wants external.toml->ElasticSearchConnector.Enabled to remain true all the time during upgrades, while the default + # configuration of the node has the false value) + # The Path indicates what value to change, while Value represents the new value in string format. The node operator must make sure + # to follow the same type of the original value (ex: uint32: "37", float32: "37.0", bool: "true") + # File represents the file name that holds the configuration. Currently, the supported files are: config.toml, external.toml, p2p.toml and enableEpochs.toml + # ------------------------------- + # Un-comment and update the following section in order to enable config values overloading + # ------------------------------- + # OverridableConfigTomlValues = [ + # { File = "config.toml", Path = "StoragePruning.NumEpochsToKeep", Value = "4" }, + # { File = "config.toml", Path = "MiniBlocksStorage.Cache.Name", Value = "MiniBlocksStorage" }, + # { File = "external.toml", Path = "ElasticSearchConnector.Enabled", Value = "true" } + #] + +# BlockProcessingCutoff can be used to stop processing blocks at a certain round, nonce or epoch. +# This can be useful for snapshotting different stuff and also for debugging purposes. +[BlockProcessingCutoff] + # If set to true, the node will stop at the given coordinate + Enabled = false + + # Mode represents the cutoff mode. possible values: "pause" or "process-error". + # "pause" mode will halt the processing at the block with the given coordinates. Useful for snapshots/analytics + # "process-error" will return an error when processing the block with the given coordinates. Useful for debugging + Mode = "pause" + + # CutoffTrigger represents the kind of coordinate to look after when cutting off the processing. + # Possible values: "round", "nonce", or "epoch" + CutoffTrigger = "round" + + # The minimum value of the cutoff. For example, if CutoffType is set to "round", and Value to 20, then the node will stop processing at round 20+ + Value = 0 + +# NamedIdentity represents an identity that runs nodes on the multikey +# There can be multiple identities set on the same node, each one of them having different bls keys, just by duplicating the NamedIdentity +[[NamedIdentity]] + # Identity represents the GitHub identity for the current NamedIdentity + Identity = "testing-staking-provider" + # NodeName represents the name that will be given to the names of the current identity + NodeName = "tsp" + # BLSKeys represents the BLS keys assigned to the current NamedIdentity + BLSKeys = [ + "15eb03756fae81d2fbae392a4d7d82abdf7618ce3056b89376c2a46bc6e8403ed3cc84e12bc819c0b088ee46e7c28302d2b666b011714cc8ea2b75488907d07e194a6e83f0f3d15c7699de412de425314be5cc3ce6ab2c594690006f9915dd15", + "ff12bc7f471e2e375c6e8b981f13ed823dcca857c41a2ffc3a0956283a8428a95754375dabc0b412df3ec41d2a51ef1490a8d23f4e4f9348787f9615093e0129969085488b59d2ab550467cd0d0fa33df22e2ed2d8c8c0c0f59042dafd0c1098", + "3dec570c02a4444197c1ed53fefd7e57acb9bc99ae47db7661cfbfb47170418702162a46ed40e113e3381d68b713e903e286ffaf9cac77fed8f9c79e83f2abb0ccd690ef4f689607b6414a6f893e0c0ced93d7456240bbccbf223f7603dd8e05", + "38a93e3c00128c31769823710aa7deb145591b99a78c87dbd74c894afd540ade6de3906b45001d3f5a5882db34eaf30e412bef77ed43cf5a394edd0aa70254a74db1c80eef5d41342cae76fbbae596bc811fa491e00f16a7e011a836f7ceaa15", + "1fce426b632e5a5941d9989e4f8bbb93a0a08a0e85dfe16d4d65c08b351dfbff1a1104d5e75e1be7565b4bbc6a583103bfc4b4075727133a54fa421983d894e549576364694b3e8910359b3de5260360bfe9f9bea2fec1cb50c2cf79a3fd590d" + ] +``` + +:::important +These 2 configuration files `allValidatorsKeys.pem` and `prefs.toml` should be copied on all n nodes that assemble the multikey group of nodes. + +**Do not forget to change the `DestinationShardAsObserver` accordingly for each node.** +::: + +After starting the multikey nodes, in ~10 minutes, the explorer will reflect the changes. All n nodes that run the multikey group will broadcast their identity as an empty string and their names will be `s14`. +The BLS keys' identities, on the other hand will have the following names & identities: + +| Key | Name | Identity | +|--------------|--------|--------------------------| +| 15eb03756... | tsp-00 | testing-staking-provider | +| ff12bc7f4... | tsp-01 | testing-staking-provider | +| 3dec570c0... | tsp-02 | testing-staking-provider | +| 38a93e3c0... | tsp-03 | testing-staking-provider | +| 1fce426b6... | tsp-04 | testing-staking-provider | + + +### Migration guide from single-key operation to multikey + +:::warning +This guide can lead to potential node jailing if done incorrectly. Make sure that you understand completely all the steps involved. + +We strongly suggest to practice this process first on the public testnet. You should gather invaluable experience and know how. +::: + +Whenever deciding to switch from single-key operation to multikey, the following steps on how to execute this process can be considered: +1. create your `allValidatorsKeys.pem` by manually (or through a text tool) concatenate all your `validatorKey.pem` files; +2. start a multikey group, **configure it as a backup group**, provide the `allValidatorsKeys.pem` file to all the nodes forming the group; +3. let this backup multikey group nodes sync and go the next step **after all these nodes are synced**; +4. switch off your single-key backup nodes (if you previously had ones); +5. create a new multikey group, configure it as main group and let it sync. **Do not provide the `allValidatorsKeys.pem` keys yet!**. Go the next step **after all these nodes are synced**; +6. after the main group nodes are synced, copy the `allValidatorsKeys.pem` file to all nodes from the main group, switch off the main single-key nodes and restart the multikey nodes from the main group, so they will load the `allValidatorsKeys.pem` file; +7. closely monitor all your nodes in the explorer, should be online and with their rating status increasing/at 100%. Repeat this step for a few times at 10 minutes interval. + +Make sure that all operations from step 6 are made as quickly as possible. In case this step takes a long time, the backup multikey group should take over. + +:::caution +Always attempt this process while closely monitor your nodes. If done correctly, your nodes might experience a brief rating drop (until the backup group takes over - if necessary) +::: + +--- + +### MultiversX Node upgrades + +As opposed to a hard fork, which is a change in the protocol that is not backward compatible, MultiversX performs regular node upgrades, which are changes in the protocol +that are backward compatible and bring new features, improvements and bugs fixes. Nodes operators must be aware of the upgrade process and the steps they need to take in order +to avoid any downtime. + + +## **Introduction** + +Once a new node's binary is ready to be deployed on one of the networks (mainnet, testnet or devnet), nodes operators must +perform the upgrade to the newest version. These releases are always announced on MultiversX [Validators chat](https://t.me/MultiversXValidators) +plus via other communication channels, depending on the case. + + +## **When to upgrade** + +Subscribe to email notifications for new releases from the official GitHub repositories that hold the chain configuration ([mx-chain-mainnet-config](https://github.com/multiversx/mx-chain-mainnet-config), [mx-chain-testnet-config](https://github.com/multiversx/mx-chain-mainnet-config) and [mx-chain-devnet-config](https://github.com/multiversx/mx-chain-mainnet-config). Also, make sure to join the Validators Telegram channel. + +Setup monitoring and get alerts for new updates. As last resort, check the status of your validator software version using the relevant Explorer section https://explorer.multiversx.com/nodes - outdated versions will be marked with ⚠ + + +## **Types of upgrades** + +Currently, we have the following types of upgrades: + +A. - **all nodes need to upgrade**: upgrades that involve processing changes with an activation epoch (as explained below) +and have to be performed by all nodes operators in order to keep the same view over the network and not cause service disruptions. + +B. - **optional upgrades**: upgrades that, for example, simply add a new Rest API endpoint or improve the trie syncing timing +are not critical from a processing point of view, and they are optional. If the nodes operators think the new feature will help them, +they can proceed with the upgrade without losing the compatibility with the network. + +C. - **only validators need to upgrade**: upgrades that, for example, include new features that only trigger validators (ratings changes, +transactions selection improvements and so on). Observers (nodes that don't have a stake attached) don't need to perform the upgrade +(but can upgrade nonetheless if desired). + + +## **Activation epochs** + +In order to make the upgrades as smooth as possible and to ensure that each node has the same view over the network at a given moment, +MultiversX has a so called _activation epoch_ mechanism that allows the node to implement both behaviors of the protocol - +the old (current) one, and the new one, planned for activation at a specific epoch. This mechanisms ensures that, +**until the protocol change becomes active**, nodes with an upgraded codebase / binary remain compatible with nodes that +did not perform the upgrade, and consensus is held. Although this happens in 99.9% cases, this is not 100% guaranteed due +to the unforeseen consequences when upgrading the code base in parallel with executing transactions generated by 3rd parties (MultiversX blockchain users) + + +### **Deterministic time / height for upgrades** + +As compared to other protocols that perform upgrades that start at a specific block height, releases for MultiversX nodes +don't have a specific block height where the new updates become effective, but rather the first block in the +activation epoch will make the nodes proceed with the updated versions of the components. + +Since the height of the first block in an epoch cannot be known in advance (due to possible roll-backs), the network height +where a feature becomes effective cannot be calculated. + +However, the time when a new feature of a bugfix becomes effective can be calculated, as epochs have fixed lengths in rounds. +Currently, MultiversX Mainnet has epochs of `14,400` rounds and a round is `6 sec`. This results in a `24h` epoch. However, +there can be delays of a few rounds, due to rollbacks of the start of epoch metablocks. + + +### _Activation epoch example_ + +For example, let's say that we want to introduce a feature so that smart contracts can receive a `PayableBySC` metadata that +will allow them to receive EGLD or other tokens from other smart contracts. + +_Timeline example_ + +- the MultiversX Mainnet is at epoch `590`. +- currently, the node binary doesn't know about the `PayableBySC` metadata so if one wants to try it, an error like `invalid metadata` + will be returned. +- at epoch `600`, we release a new node binary that contains the `PayableBySC` metadata that will become active starting with epoch `613`. +- all nodes operators perform the upgrade. +- when epoch `613` begins, the new feature activates and the new metadata is recognized and accepted. +- if one wants to issue a smart contract that is `PayableBySC`, it will work. + +- nodes that didn't perform the upgrade will produce a different output of the transaction (as compared to the majority) + and won't be able to keep up with the rest of the chain. + +_Backwards compatibility explained_: +If one wants to process all the blocks since genesis (via `full archive` or via `import-db`) with the released binary +it will behave this way: + +- if for example, in epoch `455` there was a transaction that tried to set the `PayableBySC` metadata, it will process it + as `invalid metadata` +- for transactions in epochs newer than `613` it will process the new metadata. + +| | Epoch < 613 | Epoch >= 613 | +| ------------- | ------------------ | ------------ | +| IsPayableBySC | `invalid metadata` | `successful` | + +--- + +### Node CLI + +This page will guide you through the CLI fields available for the node and other tools from the `mx-chain-go` repository. + + +## **Introduction** + +Command Line Interface for the Node and the associated Tools + +The **Command Line Interface** of the **Node** and its associated **Tools** is described at the following locations: + +- [Node](https://github.com/multiversx/mx-chain-go/blob/master/cmd/node/CLI.md) +- [SeedNode](https://github.com/multiversx/mx-chain-go/blob/master/cmd/seednode/CLI.md) +- [Keygenerator](https://github.com/multiversx/mx-chain-go/blob/master/cmd/keygenerator/CLI.md) +- [TermUI](https://github.com/multiversx/mx-chain-go/blob/master/cmd/termui/CLI.md) +- [Logviewer](https://github.com/multiversx/mx-chain-go/blob/master/cmd/logviewer/CLI.md) + + +## **Examples** + +For example, the following command starts an **Observer Node** in **Shard 0**: + +``` +./node --rest-api-interface=localhost:8080 \ + --log-save --log-level=*:DEBUG --log-logger-name \ + --destination-shard-as-observer=0 --start-in-epoch\ + --validator-key-pem-file=observer0.pem +``` + +While the following starts a Node as a **Metachain Observer**: + +``` +./node --rest-api-interface=localhost:8080 \ + --use-log-view --log-save --log-level=*:DEBUG --log-logger-name \ + --destination-shard-as-observer=metachain --start-in-epoch\ + --validator-key-pem-file=observerMetachain.pem +``` + +--- + +### Node Configuration + +## Introduction + +The node relies on some configuration files that are meant to allow the node operator to easily change some values +that won't require a code change, a new release, or so on. + + +## Configuration files + +All the configuration files are located by default in the `config` directory that resides near the node's binary. The paths can be changed +by using the node's CLI flags. + +:::important +Not all configuration values can be user-defined. For example, it is perfectly fine if a node operator increases the size of a cacher or sets an Elasticsearch instance, but changing the genesis total supply, for example, will lead to an inconsistent state as compared to the Network. +::: + +Below you can find an example of how the configuration files look like for the `v1.5.8` node. + +``` +├── api.toml +├── config.toml +├── economics.toml +├── enableEpochs.toml +├── enableRounds.toml +├── external.toml +├── gasSchedules +│ ├── gasScheduleV1.toml +│ ├── gasScheduleV2.toml +│ ├── gasScheduleV3.toml +│ ├── gasScheduleV4.toml +│ ├── gasScheduleV5.toml +│ ├── gasScheduleV6.toml +│ └── gasScheduleV7.toml +├── genesisContracts +│ ├── delegation.wasm +│ └── dns.wasm +├── genesis.json +├── genesisSmartContracts.json +├── nodesSetup.json +├── p2p.toml +├── prefs.toml +├── ratings.toml +├── systemSmartContractsConfig.toml +├── testKeys +│ ├── delegationWalletKey.pem +│ ├── dnsWalletKey.pem +│ ├── esdtWalletKey.pem +│ └── protocolSustainabilityWalletKey.pem +└── upgradeContracts + └── dns + └── v3.0 + ├── deploy.json + └── dns.wasm + +``` + +- `api.toml` contains the Rest API endpoints configuration (open or closed endpoints, logging and so on) +- `config.toml` contains the main configuration of the node (storers & cachers type and size, type of hasher, type of marshaller, and so on) +- `economics.toml` contains the economics configuration (such as genesis total supply, inflation per year, developer fees, and so on) +- `enableEpochs.toml` contains a list of new features or bugfixes and their activation epoch +- `enableRounds.toml` contains a list of new features or bugfixes and their activation epoch +- `external.toml` contains external drivers' configuration (for example: Elasticsearch or event notifier) +- `gasSchedules` is the directory that contains the gas consumption configuration to be used for SC execution, depending on activation epochs specified on enableEpochs.toml -> GasSchedule -> GasScheduleByEpochs +- `genesisContracts` is the directory that contains the WASM contracts that were deployed at the genesis +- `genesis.json` contains all the addresses and their balance/active delegation at the genesis +- `genesisSmartContracts.json` specifies the SCs to be deployed at Genesis time, alongside additional parameters +- `nodesSetup.json` holds all the Genesis nodes' public keys, alongside their wallet address +- `p2p.toml` contains peer-to-peer configurable values, such as the number of peers to connect to +- `prefs.toml` contains a set of custom configuration values, that should not be replaced from an upgrade to another +- `ratings.toml` contains the parameters used for the nodes' rating mechanism, for example, the start rating, decrease steps, and so on +- `systemSmartContractsConfig.toml` contains System Smart Contracts configurable values, such as parameters for Staking, ESDT, or Governance + + +### Overriding config.toml values + +As mentioned in the above descriptions, `prefs.toml` is not overwritten by the installation scripts when performing an upgrade. + +However, there are some more custom values that nodes operators use (antiflood disabled or with fewer constraints, db lookup extension, and so on) +and they don't want these values to be changed during an upgrade. + +For this use-case, release `v1.4.x` introduces the `OverridableConfigTomlValues` setting inside `prefs.toml` that is able to override certain configuration +values from `config.toml`. + +Here's how to use it: + +``` + OverridableConfigTomlValues = [ + { Path = "StoragePruning.NumEpochsToKeep", Value = "4" }, + { Path = "MiniBlocksStorage.Cache.Name", Value = "MiniBlocksStorage" } + ] +``` + +Therefore, after each upgrade, the node will override these values to the newly provided values. The path points to an entry +in `config.toml` file before setting a new overridable value. + +--- + +### Node Databases + +This page will describe the databases used by the Node. These are simple key-value storage units that will hold different types of data, as described below. + + +## **Node databases** + +Nodes use simple Key-Value type databases. + +Nodes use Serial LevelDB databases to persist processed blocks, transactions, and so on. + +The data can be removed or not, depending on the pruning flags that can be enabled or not in `config.toml`. +The flags used to specify if a node should delete databases or not are `ValidatorCleanOldEpochsData` and `ObserverCleanOldEpochsData`. +Older versions of the configuration only have one flag `CleanOldEpochsData`. If set to false, then old databases won't be removed. + +By default, validators only keep the last 4 epochs and delete older ones for freeing disk space. + +The default databases directory is `/db` and it's content should match the following structure: +``` +/db +└── + ├── Epoch_X + │ └── Shard_X + │ ├── BlockHeaders + │ │ ├── 000001.log + │ │ ├── CURRENT + │ │ ├── LOCK + │ │ ├── LOG + │ │ └── MANIFEST-000000 + │ ├── BootstrapData + │ │ ├── 000001.log + | ............. + └── Static + └── Shard_X + ├── AccountsTrie + │ └── MainDB + │ ├── 000001.log + ............. +``` + +Nodes will fetch the state from an existing database if one is detected during the startup process. If it does not match +the current network height, it will sync the rest of the data from the network, until fully synced. + + +## **Starting a node with existent databases** + +There are use-cases when a node can receive the entire database from other node that is fully synced in order to speed up the process. +In order to perform this, one has to copy the entire database directory to the new node. This is as simple as copying the `db/` +directory from one node to the other one. + +The configuration files must be the same as the old node, except the BLS key which is independent of databases. + +Two nodes in the same shard generate the same databases. These databases are interchangeable between them. However, starting +a node as observer and setting the `--destination-shard-as-observer` so it will join a pre-set shard, requires that it's database +is from the same shard. So starting an observer in shard 1 with a database of a shard 0 node will result in ignoring the database +and network-only data fetch. + +If the configuration and the database's shard are the same, then the node should have the full state from the database and +start to sync with the network only remaining items. If, for instance, a node starts with a database of 255 epochs, and the current epoch is +256, then it will only sync from network the data from the missing epoch. + +--- + +### Node operation modes + +Without configuration changes, nodes will start by using the default settings. However, there are several ways to configure the node, depending on the desired operation mode. +Instead of manually (or programmatically via `sed`s for example) editing the `toml` files, you can use the `--operation-mode` CLI flag described below to specify a custom +operation mode that will result in config changes. + + +## Introduction + +Starting with `v1.4.x` release, a new CLI flag has been introduced to the node. It is `--operation-mode` and its purpose +is to override some configuration values that will allow the node to act differently, depending on the use-case. + + +## List of available operation modes + +Below you can find a list of operation modes that are supported: + + +### Full archive + +Usage: +``` +./node --operation-mode full-archive +``` + +The `full-archive` operation mode will change the node's configuration in order to make it able to sync from genesis and also +be able to serve historical requests. +Syncing a node from genesis might take some time since there aren't that many full archive peers to sync from. + + +### Db Lookup Extension + +Usage: +``` +./node --operation-mode db-lookup-extension +``` + +The `db-lookup-extension` operation mode will change the node's configuration in order to support extended databases that are +able to store more data that is to be used in further Rest API requests, such as logs, links between blocks and epoch, and so on. + +For example, the proxy's `hyperblock` endpoint relies on the fact that its observers have this setting enabled. Other examples +are `/network/esdt/supply/:tokenID` or `/transaction/:txhash?withResults=true`. + + +### Historical balances + +Usage: +``` +./node --operation-mode historical-balances +``` + +The `historical-balances` operation mode will change the node's configuration in order to support historical balances queries. +By setting this mode, the node won't perform the usual trie pruning, resulting in a more disk usage, but also in +the ability to query the balance or the nonce of an address at blocks that were proposed long time ago. + + +### Snapshotless observers + +Usage: +``` +./node --operation-mode snapshotless-observer +``` + +The `snapshotless-observer` operation mode will change the node's configuration in order to make it efficient for real-time requests +by disabling the trie snapshotting mechanism and making sure that older data is removed. + +A use-case for such an observer would be serving live balances requests, or broadcasting transactions, eliminating the costly operations +of the trie snapshotting. + +--- + +### Node redundancy + +MultiversX Validator Nodes can be configured to have one or more hot-standby nodes. +This means additional nodes will run on different servers, in sync with the Main Validator node. +Their role is to stand in for the Main Validator node in case it fails, to ensure high availability. + + +This is a redundancy mechanism which allows the Main Validator operator to start additional 'n' hot-standby nodes, +each of them running the same 'validatorKey.pem' file. The difference between +configurations consists on an option inside the `prefs.toml` file. + +Hot standby nodes are configured using the 'RedundancyLevel' option in the 'prefs.toml' configuration file: + +- a 0 value will represent that the node is the Main Validator. + The value 0 will be the default, therefore if the option is missing it will still make that node the Main Validator by default. With consideration to backwards compatibility, the already-running Validators are not affected by the addition of this option. Moreover, we never overwrite the `prefs.toml` files during the node's upgrade. + +The values of `RedundancyLevel` are interpreted as follows: + +- a positive value will represent the "order of the hot-standby node" in the automatic fail-over sequence. + Example: suppose we have 3 nodes running with the same BLS key. One has the redundancy level set to 0, + another has 1 and another with 3. The node with level 0 will propose and sign blocks. The other 2 will + sync data with the same shard as the Main Validator (and shuffle in and out of the same shards) but will + not sign anything. If the Main Validator fails, the hot-standby node + with level 1 will start producing/signing blocks after `level*5` missed rounds. So, after 5 + missed rounds by the Main Validator, the hot-standby node with level 1 will take the turn. + If hot-standby node 1 is down as well, hot-standby node 2 will step in + after `3*5 = 15 rounds` after the Main Validator failed and 10 rounds after the failed hot-standby node 1 + should have been produced a block. +- a large value for this level option (say 1 million), or a negative value (say -1) will mean that the + hot-standby nodes won't get the chance to produce/sign blocks but will sync with the network and + shuffle between shards just as the Main Validator will. + +:::tip +The hot-standby nodes will advertise on the network a different public key (autogenerated at start-up) and thus, concealing the real public key that will be used when signing the header blocks. +::: + +:::tip +If the Main Validator (RedundancyLevel 0) gets back online, the hot-standby node(s) revert to standby mode. +::: + + +:::caution +Do not use the same redundancy level on more than one node. Otherwise, the nodes with the same `RedundancyLevel` value will start signing blocks in parallel in the same time. Although the protocol is not negatively affected by double signing, in the near future the BLS key that will perform double signing will have its stake slashed. +::: + +The random BLS key on hot-standby nodes has the following purposes: + +- the hot-standby node(s) will not cause BLS signature re-verification when idle. +- it slightly prevents DDoS attacks as an attacker can not find all IPs behind a targeted BLS public key: + when an attacker takes down the Main Validator, the hot-standby nodes will advertise the public key when they + will need to sign blocks, but not sooner. + +--- + +### Protecting your keys + +This page contains information about how to protect your validator and wallet keys. + + +## How sensitive are your keys + +Validator Keys are very sensitive: + +- if you lose them and your node crashes irreparably (i.e. you delete the virtual machine, your VPS provider deletes/loses it), you lose access to that node, you won't be able to bring it back up online and will thus stop earning money with it +- if someone steals them and maliciously uses them in the MultiversX network, they can engage in bad behavior such as double-signing, produce bad blocks, inject fake transactions, mint new coins, etc. - all of those actions are slashable, meaning you can lose your EGLD stake - all 2500! + +Wallet Keys are extremely sensitive because: + +- if you lose the keys, you can't recover your stake or claim your rewards -> you lose all the money +- if someone steals your keys, they can send an unstake transaction from it and claim the EGLD -> the bad guys steal your money + + +## How to protect your keys + +How to protect them: + +- make multiple safe backups of the private keys & files + - paper + - hardware + - encrypted physical storage + - distributed cloud storage, etc + - [some hints](https://coinsutra.com/bitcoin-private-key/) + +:::tip +Wallet Keys are not required on host running the Node. Store them on a different location. +::: + + +## How to secure your node + +Secure your MultiversX node + +- no ports should open in the firewall except for the ones used by the node's normal operation +(the port range can be checked [here](/validators/system-requirements).) +- don't run the node as `root` +- use encryption, all other measures +- [some hints ](https://www.liquidweb.com/kb/security-for-your-linux-server/) + +--- + +### Rating + +This page exposes the rating system used for MultiversX validators. + + +## **Introduction** + +Each individual validator has a **rating score**, which expresses its overall reliability, performance and responsiveness. It is an important value, and node operators should be always mindful of the rating of their validators. + +Rating influences the probability of a validator to be selected for consensus in each round. A performant validator will be preferred in consensus, as opposed to a validator which sometimes fails to contribute or which is not always online. + +:::tip +Observer nodes do not have a rating score. Only validator nodes do. +::: + +When validators join the network immediately after staking, they start with an initial score of `50` points. + +Validators gain or lose rating points in a round depending on their role in that round (consensus proposer vs. consensus validator) and on their behavior within that role. Rating penalties are currently set to be `4` times as large as the corresponding gains. This means that a validator has to perform an action correctly 4 times in order to compensate for performing it once incorrectly. Moreover, consecutive losses are _compounding_, which means that the rating penalty increases with each transgression. See [Rating shard validators](/validators/rating#rating-shard-validators) and [Rating metashard validators](/validators/rating#rating-metashard-validators) for details on the calculations. + +:::tip +Rating gains and losses on the metashard are different from the gains and losses on the normal shards. +::: + +The past and current rating of an individual validator can be found in the MultiversX Network Explorer at https://explorer.multiversx.com/nodes. Use the "Search" box to find a validator and click on its entry in the list. The "Node Details" page opens, which contains status information about the validator. + +The "Node Details" page displays a plot of the validator rating during the past epochs: + +![img](https://gblobscdn.gitbook.com/assets%2F-LhHlNldCYgbyqXEGXUS%2F-MA1wJCHfE7ffob9gOjE%2F-MA1we9u12mvMRF1PU9y%2Fplot-rating.png?alt=media&token=6a1f0071-66d0-4aec-8192-2a8f716e67bb) + +The X-axis represent the epochs, and the Y-axis represents the rating. + + +## **The jail** + +For the overall health of the network, if the rating of a validator drops below `10` points, it will be **jailed**. Being jailed means that the validator will be taken out of the shards, it will not participate in consensus, and thus it will not earn any rewards. + +There is an exception to jailing, though. If the network finds itself in a situation where jailing a validator would reduce the size of a shard below an allowed limit, the validator will not be jailed. + +:::important +Jailing only occurs at the end of an epoch. This means that a validator with low rating still has until the end of the epoch to recover. If the validator fails to recover, and its rating remains below `10` at the end of the epoch, then it will start the new epoch in jail. +::: + +To reinstate a jailed validator, its operator must submit an **unjail** transaction to the Staking SmartContract. This causes the validator to be taken out of the "jail" and added to the network as if it were a new validator. + +A reinstated validator will be passive during the epoch of its unjailing. In the immediately following epoch, the validator will be assigned to a shard, where it must wait the entire epoch and spend it to synchronize with its new shard. + +:::note rating reset +Rating is **not** reset to 50 due to shard shuffling. The rating of a validator is retained when changing shards due to shuffling. +::: + +:::tip +The only way to increase the rating of a validator is to keep it up-to-date, keep it well-connected and make sure it is running on hardware that conforms to the [System requirements](/validators/system-requirements). +::: + +:::note multiple validators on the same machine +Running **multiple validators on a single machine** will impact your rating and consequently _your rewards,_ if the machine doesn't have the as many times the minimum requirements as there are validators running on it. +::: + + +## **Consensus probabilities** + +Rating affects the probability of a validator to be selected in the consensus group of a round. This is done by applying **rating modifiers** on the probability of selection for each validator. + +Without rating, all validators of a shard would have the same probability of being chosen for consensus. But rating modifiers will alter the probability of individual validators based on their rating score, in order to give performant validators an edge over the average validators, and also to diminish the probability of selecting weak validators. + +The following table shows how the rating of a validator influences its probability of being chosen for consensus: + +| Rating interval | Modifier | +|-----------------|----------| +| [0-10] | -100% | +| (10-20] | -20% | +| (20-30] | -15% | +| (30-40] | -10% | +| (40-50] | -5% | +| (50-60] | 0% | +| (60-70] | +5% | +| (70-80] | +10% | +| (80-90] | +15% | +| (90-100] | +20% | + +:::important +The algorithm that selects validators for consensus treats these modified selection probabilities as being relative to each other. +::: + + +## **Calibration** + +Assuming a **24-hour-long epoch**, the rating mechanism has been calibrated with the following intentions: + +- A new validator requires **approx. 72 hours** to reach maximum rating, assuming it remains in the same shard and won't be shuffled out (therefore it will be productive all the time, without any waiting time). +- The amount of rating gains earned as a block validator should be in balance with the amount of rating gains earned as a block proposer. This balance must take into account the fact that being selected as proposer is considerably less likely than being selected in consensus as block validator. + + +## **Rating shard validators** + + +### **Rating the shard block proposer** + +The node chosen to propose the block for a specific round will: + +- Gain `0.23148` points for a successful proposal: (1) the block is built correctly, (2) it is accepted by the consensus validators and (3) the proposer applies the final signature and propagates the block throughout the network; +- Lose `0.92592` points for an unsuccessful proposal. + +Observe that the loss is 4 times larger than the gain, which means that a proposer must succeed 4 times to gain the points lost for a single missed block. + +Rating for proposers is even stricter: there is a compounding penalty rule, which makes the rating of a node drop even faster when it proposes unsuccessfuly. + +The amount of `0.92592` points is deducted from the rating of the proposer on the first unsuccessful proposal, but the second unsuccessful proposal will be penalized by `0.92592 × 1.1`. The third, by `0.92592 × 1.1 × 1.1`. The general formula is: + +`0.92592 × 1.1^{cfp-1}0.92592×1.1^cfp^−1` + +where `cfp` is the number of consecutive failed proposals. + +This compounding penalty has the effect of quickly jailing repeatedly unsuccessful proposers. + + +### **Rating the shard block validator** + +The nodes that take part in the consensus of a round (other than the proposer) will: + +- Gain `0.00367` points for a successful validation: (1) the proposer has built and proposed a block, (2) the validator appears as a "signer" on that block; being a "signer" of a block means that the validator has approved of the block and was fast enough to be among the first ⅔ + 1 validators to have its signature received by the block proposer; +- Lose `0.01469` points for an unsuccessful proposal. + +Observe that the first bulled mentions "the proposer has built and proposed a block". This sentence implies that _all validators will lose rating_ if the proposer fails to propose in the respective round. + +Moreover, the validator must have been a "signer" in at least 1% of the previous blocks, otherwise it will not gain rating. In other words: if the validator has been performing poorly in the past, it will have to perform well for a while until it can start receiving any gains. + + +## **Rating metashard validators** + +The rating mechanism for the metashard is identical with the rating mechanism of the normal shards, but the gain / loss values themselves are configured differently. + + +### **Rating the metashard block proposer** + +The metachain proposer will: + +- Gain `0.23148` points for a successful proposal; +- Lose `0.92592` points for an unsuccessful proposal. + +The compounding penalty rule also applies to block proposers of the metachain. See [Rating the shard block proposer](#rating-the-shard-block-proposer) for details. + + +### **Rating the metashard block validator** + +A validator taking part in consensus on the metachain will: + +- Gain `0.00057` points for a successful validation; +- Lose `0.00231` points for an unsuccessful validation. + +The rules from [Rating the shard block validator](#rating-the-shard-block-validator) apply for the metashard validators as well. + +--- + +### Scripts & User config + +MultiversX provides scripts designed to streamline the process of installing a MultiversX node. This validator script is a general script for accessing the Mainnet, Devnet and Testnet networks. + +To get started, you will begin by getting a copy of the latest version of the scripts from Github and configure it to match your local setup. + +:::caution +Nodes scripts should not be run as a root user. Such usage is not supported and may result in unexpected behavior. +::: + + +## **Download the MultiversX Scripts** + +```bash +cd ~ +git clone https://github.com/multiversx/mx-chain-scripts +``` + + +## **Configure the scripts correctly** + +The scripts require a few configurations to be set in order to work correctly. + +First and foremost, you need your exact username on your local machine. You can find out your current username by running the `whoami` command, which will print it out: + +```bash +whoami +``` + +Next, in the `variables.cfg` file, edit and add your username in the following variables: + +- `ENVIRONMENT`: The MultiversX network to be used: mainnet, testnet or devnet. +- `CUSTOM_HOME`: This refers to the folder on the computer in which you will install your node. +- `CUSTOM_USER`: which is the username on the computer under which you will run the installation, upgrade, and other processes + +Open `variables.cfg` in the `nano` editor: + +```bash +cd ~/mx-chain-scripts/config +nano variables.cfg +``` + +Change the variables `ENVIRONMENT`, `CUSTOM_HOME` and `CUSTOM_USER` as highlighted in the image below: + +![img](/validators/scripts/variables.png) + +For `CUSTOM_USER` variable, use the output of the `whoami` command that was run earlier. + +Save the file and exit: + +- If you’re editing with **nano**, press `Ctrl+X`, then `y`, and `Enter` +- If you’re editing with **vi** or **vim**, hold down `Shift` and press `z` twice. + + +## **Ensure user privileges** + +Ensure your user has `sudo` enabled and accessible so that it doesn't ask for a password every time it executes something. + +If you are certain this is already done, feel free to skip forward. Otherwise, you will need to add your username to a special list. + +So let's add it to the overrides: + +```bash +sudo visudo -f /etc/sudoers.d/myOverrides +``` + +Now, navigate to the end of the file by pressing `Shift + G`. Next, press `o` to add a new line, and type the following, replacing `username` with the output of the `whoami` command that was run earlier. + +```bash +yourusername ALL=(ALL) NOPASSWD:ALL +``` + +Conclude by pressing `Esc`, then save and close the file by holding down `Shift` while pressing `z` twice. + +Your user should now be able to execute `sudo` commands. + +--- + +### Staking & Unstaking + +This page will guide you through the process of staking and unstaking nodes. + + +## **Introduction** + +Before staking, a node is a mere observer. After staking, the node becomes a validator, which means that it will be eligible for consensus and will earn rewards. Validators play a central role in the operation of the network. + +**Staking** is the process by which the operator of the node sends a sum of 2500 EGLD to be locked in a system SmartContract. Multiple nodes can be staked at once, and their operator must lock 2500 EGLD for each of the nodes. This sum acts as a collateral, and it will be released back to the node operator through the process of **unstaking**, with a final step called **unbonding**. + +A validator node produces rewards, which are transferred to the node operator at their **reward address** of choice, decided upon during the staking process. The reward address may be changed after staking as well. + +Each staking or unstaking process requires a transaction to be sent to the Staking Smart Contract. These transactions must contain all the required information, encoded properly, and must provide a high enough gas limit to allow for successful execution. These details are described in the following pages. + +There are currently 2 supported methods of constructing and submitting these transactions to the Staking SmartContract: + +- Manually constructing the transaction, then submitting it to [wallet.multiversx.com](https://wallet.multiversx.com/); +- Automatically constructing the transaction and submitting it using the `mxpy` command-line tool. + +The following pages will describe both approaches in each specific case. + + +## **Prerequisites** + +In order to submit a staking transaction, you must have the following: + +- 2500 EGLD for each node and 0.006 EGLD per node as transaction fee +- A unique `validatorKey.pem` file of each node + +You have the option of staking through the online Wallet at [https://wallet.multiversx.com](https://wallet.multiversx.com/) or by using `mxpy`. + + +## **Staking through the Wallet** + +1. Go to https://wallet.multiversx.com and log into your wallet +2. Go to the Validate section +3. Press "Stake now" + +![staking1](/validators/staking1.png) + +4. Navigate to the location of the .pem file or drag & drop it +5. Press "Continue" + +![staking2](/validators/staking2.png) + +6. The staking transaction data is automatically populated using the public key in the .pem certificate you provided. The private key is not touched and the data does not leave your browser. Only the transaction with this public information will be sent to the network once you press Confirm +7. Press "Confirm" + +![staking3](/validators/staking3.png) + +8. The status of the transaction will be displayed on screen, together with a success message. Click "Done" once you see the Success message. + +![staking4](/validators/staking4.png) + +9. You can review the transaction in your history. Based on the current staking capacity of the network, you will get an OK message indicating that your node has become a validator, or a response indicating that the network staking is at capacity and your node has been put in the Queue. + +![staking5](/validators/staking5.png) + +10. The information about the staked nodes from the current wallet will be updated +11. You can further interact with your node(s) by clicking on the three vertical dots next to the public key, which brings up a menu for performing actions such as Unjail, Unstake and Unbond. + +![staking6](/validators/staking6.png) + + +## **Staking through mxpy** + +Submitting the staking transaction using `mxpy` avoids having to write the "Data" field manually. Instead, the staking transaction is constructed automatically by `mxpy` and submitted to the network directly, in a single command. + +Make sure `mxpy` is installed by issuing this command on a terminal: + +```bash +mxpy --version +``` + +If `mxpy` is not installed (`command not found`), please follow [these instructions](/sdk-and-tools/mxpy/installing-mxpy). + +Make sure `mxpy` is installed and has the latest version before continuing. + + +## **Your Wallet PEM file** + +To send transactions on your behalf _without_ using the online MultiversX Wallet, `mxpy` must be able to sign for you. For this reason, you have to generate a PEM file using your Wallet mnemonic. + +Please follow the guide [Deriving the Wallet PEM file](/sdk-and-tools/mxpy/mxpy-cli#converting-a-wallet). Make sure you know exactly where the PEM file was generated, because you'll need to reference its path in the `mxpy` commands. + +After the PEM file was generated, you can issue transactions from `mxpy` directly. + + +## **The staking transaction** + +The following commands assume that the PEM file for your Wallet was saved with the name `walletKey.pem` in the current folder, where you are issuing the commands from. + +The command to submit a staking transaction with `mxpy` is this: + +```bash +mxpy --verbose validator stake --pem=walletKey.pem --value="" --validators-file= --proxy=https://gateway.multiversx.com +``` + +Notice that we are using the `walletKey.pem` file. Moreover, before executing this command, you need to replace the following: + +- Replace `` with the amount you are staking. You need to calculate this value with respect to the number of nodes you are staking for. See the [beginning of the "Staking through the Wallet"](/validators/staking#staking-through-the-wallet) section for info on how to do it. +- Replace `` with the JSON file that lists the nodes you are staking for. This JSON file should look like this: + +```json +{ + "validators": [ + { + "pemFile": "valPem1.pem" + }, + { + "pemFile": "valPem2.pem" + }, + { + "pemFile": "valPem3.pem" + } + ] +} +``` + +The `pemFile` field should point to valid Validator PEM file. **Note that paths must be relative to the JSON file itself.** + +Here's an example for a staking command for one node: + +``` +mxpy --verbose validator stake --pem=walletKey.pem --value="2500000000000000000000" --validators-file=my-validators.json --proxy=https://gateway.multiversx.com +``` + +:::note important +You must take **denomination** into account when specifying the `value` parameter in **mxpy**. +::: + +For two nodes, it becomes this: + +``` +mxpy --verbose validator stake --pem=walletKey.pem --value="5000000000000000000000" --validators-file=my-validators.json --proxy=https://gateway.multiversx.com +``` + + +## **The --reward-address parameter** + +When you submit a staking transaction, the Staking SmartContract remembers the wallet you sent it from, and the rewards from your staked validators will go to that wallet. This is the _default_ behavior. In this case, it will be the wallet which you used to generate the `walletKey.pem` file in the earlier subsection ["Your Wallet PEM file"](/validators/staking#your-wallet-pem-file). + +Alternatively, you can tell `mxpy` to specify another wallet to which your rewards should be transferred. You will need the **address of your reward wallet** (it looks like `erd1xxxxx…`) for this, which you will pass to `mxpy` using the `--reward-address` parameter. + +For example, a staking command for a single node, with a reward address specified, looks like this: + +``` +mxpy --verbose validator stake --reward-address="erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" --pem=walletKey.pem --value="2500000000000000000000" --validators-file=my-validators.json --proxy=https://gateway.multiversx.com +``` + +The above command will submit a staking command and will also inform the Staking SmartContract that the rewards should be transferred to the wallet `erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th` . + +--- + +### Staking Providers + +```mdx-code-block +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; +``` + + +## Introducing staking providers + +A **staking provider** is defined as a custom delegation smart contract, the associated nodes and the funds staked in the pool by participants. **Node operators** may wish to set up a staking provider for their nodes, which can then be funded by anyone in exchange for a proportion of the validator rewards. This form of funding the stake for validators is called **delegation**. + +Staking providers bridge the gap between node operators, who need funds to stake for their nodes, and fund holders who wish to earn rewards by staking their funds, but are not interested in managing validator nodes. + +Node operators can set up a staking provider to manage one or more validator nodes. For this purpose, they may use the **delegation manager** built into the MultiversX Protocol to create their own **delegation contract**. A delegation contract automates certain tasks required for the management of a staking provider, such as keeping track of every account that has funded the staking provider, keeping track of the nodes themselves, as well as providing information to the delegators. + +:::important +A staking provider requires 1250 EGLD deposited by the node operator at the moment of its creation. However, 2500 EGLD is required to stake a single validator node and start earning rewards. +::: + +This page describes how to request a new delegation contract from the delegation manager and how to use it. It will focus on the delegation _contract_ more than the delegation _manager_, but the two concepts are intimately linked. However, it is important to remember that it is the delegation _contract_ which handles the staking provider and the nodes associated with it. + +Note that the delegation manager is not required to set up a staking provider. For example, it is also possible to set up delegation using a regular smart contract, although that is a more complex process and is not discussed here. + +Node operators may also choose to set up a delegation dashboard, although they may use any user interface or none whatsoever. As an example, the boilerplate for such a delegation dashboard can be found here: https://github.com/multiversx/mx-delegation-dapp. Alternatively, the old boilerplate is located here: https://github.com/multiversx/mx-deprecated-starter-dapp/tree/master/react-delegationdashboard. + +A detailed description of the delegation process can be consulted at https://github.com/multiversx/mx-specs/blob/main/sc-delegation-specs.md. + + +## Creating a new delegation contract + +The delegation contract for a new staking provider can be created by issuing a request to the delegation manager. This is done by submitting a transaction of the following form: + +```rust +NewDelegationContractTransaction { + Sender: + Receiver: erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqylllslmq6y6 + Value: 1250000000000000000000 (1250 EGLD) + GasLimit: 60000000 + Data: "createNewDelegationContract" + + "@" + + + "@" + +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +The `Receiver` address is set to `erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqylllslmq6y6`, which is the fixed address of the delegation manager, located on the Metachain. + +The `Value` is set to 1250 EGLD, which will be automatically added into the funds of the newly created delegation contract, i.e. this is the initial amount of EGLD in the staking provider. This amount of EGLD always belongs to the owner of the delegation contract. + +:::important +The initial 1250 EGLD count towards the total delegation cap, like all the funds in the staking provider. + +The initial amount of 1250 EGLD added to the pool makes the owner the first delegator of the staking provider. This means that the owner is also entitled to a proportion of the rewards, which can be claimed like any other delegator. +::: + +In the `Data field`, the first argument passed to `createNewDelegationContract` is the total delegation cap (the maximum possible size of the staking provider). It is expressed as a fully denominated amount of EGLD, meaning that it is the number of $10^{-18}$ subdivisions of the EGLD, and not the actual number of EGLD tokens. The fully denominated total delegation cap must then be encoded hexadecimally. Make sure not to encode the ASCII string representing the total delegation cap. + +:::tip +For example, to obtain the fully denominated form of 7231.941 EGLD, the amount must be multiplied by $10^{18}$, resulting in 7231941000000000000000. Do not encode the ASCII string `"7231941000000000000000"`, but encode the integer 7231941000000000000000 itself. This would result in `"01880b57b708cf408000"`. +::: + +Setting the total delegation cap to 0 ("00" in hexadecimal) specifies an unlimited total delegation amount. It can always be modified later (see [Delegation cap](/validators/delegation-manager#delegation-cap)). + +The second argument passed to `createNewDelegationContract` is the service fee that will be reserved for the owner of the delegation contract. It is computed as a proportion of the total rewards earned by the validator nodes. The remaining rewards apart from this proportion will be available to delegators to either claim or redelegate. The service fee is expressed as hundredths of a percent. + +:::tip +For example, a service fee of 37.45% is expressed by the integer 3745. This integer must then be encoded hexadecimally (3745 becomes `"0ea1"`). +::: + +Setting the service fee to 0 (`"00"` in hexadecimal) specifies that no rewards are reserved for the owner of the delegation contract - all rewards will be available to the delegators. The service fee can always be modified later (see [Service fee](/validators/delegation-manager#service-fee)). + +The following is a complete example of a transaction requesting the creation of a new delegation contract: + +```rust +NewDelegationContractTransaction { + Sender: + Receiver: erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqylllslmq6y6 + Value: 1250000000000000000000 + GasLimit: 60000000 + Data: "createNewDelegationContract" + + "@01880b57b708cf408000" + + "@0ea1" +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +The above transaction creates a new delegation contract owned by the sender, with total delegation cap of 7231.941 EGLD and service fee of 37.45% from the rewards. Moreover, the newly created delegation contract will start with a staking provider of 1250 EGLD. + + +## Configuring the delegation contract + +The owner of the delegation contract has a number of operations at their disposal. + + +### Metadata + +The delegation contract can store information that identifies the staking provider: its human-readable name, its website and its associated GitHub identity. + +```rust +SetMetadataTransaction { + Sender: + Receiver:
+ Value: 0 + GasLimit: 2000000 + Data: "setMetaData" + "@" + + + "@" + + + "@" + +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +An example for the `Data` field that sets the name to `"Test Mx Provider"`, the website to `"testmx.provider"` and the GitHub identifier to `"testmxprovider"` is: + +```rust + "setMetaData" + + "@54657374204d782050726f7669646572" // Test Mx Provider + "@746573746d782e70726f7669646572" // testmx.provider + "@746573746d7870726f7669646572" // testmxprovider +``` + +:::important +Setting the identity of the staking provider in the metadata is the **first step** in connecting the delegation contract and a GitHub identity. The second step is explained in the next section [Display information](/validators/delegation-manager#display-information) where the inverse connection is made: from the GitHub identity to the delegation contract address. +::: + + +### Display information + +:::caution +As of January 2024, the only accepted way to customize the information of a delegation contract is via MultiversX Assets. + +Provisioning of information from `keybase` or your `GitHub` repository is no longer accepted. + +The only accepted changes are on `mx-assets` identities. + +You can safely delete the `keybase` profile and also the `multiversx` repository inside your organization's `GitHub`. +::: + +To customize the information for your delegation contract, which will be available inside the MultiversX ecosystem (such as Explorer, Web Wallet, xPortal, and so on), +some additional information has to be added to the MultiversX assets repository. + +In order to do so, the owner of the nodes/staking provider must open a Pull Request against https://github.com/multiversx/mx-assets (`master` branch). + +**Step 1: choose the environment** + +- for mainnet go to `mx-assets/identities` +- for testnet go to `mx-assets/testnet/identities` +- for devnet go to `mx-assets/devnet/identities` + +**Step 2: create a directory with an unique name** + +Must be lowercase, alphanumeric, with no spaces. For example, `my-new-identity` + +**Step 3: add a `info.json` file and a logo.png** + +Create a `info.json` file where you specify the owned address(es), the name, the description and social links. +Example: +```json +{ + "description": "This is my new identity", + "name": "New identity", + "website": "http://newidentity.com", + "twitter": "https://twitter.com/newidentity", + "location": ", ", + "owners": [ + "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqxgeryqxzqjkh", // staking provider address + "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" // the address of regular validator nodes owner + ] +} +``` + +Inside the owners array: +- if you want to define an identity of a Staking Provider, add its SC address +- if you want to define an identity of simple nodes, add the owner address (all the keys owned by that address will have that identity) +- if you want to define the same identity for both a Staking Provider and simple nodes, set both the address of the Staking Provider and of the owner of the nodes + +Also, upload a `logo.png` that will be displayed near the identity. + +**Step 4: Open PR and validate the ownership** + +After opening the PR, you also have to validate the ownership. Please refer to the [this](#assets-ownership-validation) section. + +**Done** + +After the PR is merged, the specified information together with the **service fee, percentage filled** and **APR** (for Staking Providers) will be displayed across the ecosystem. +If this information cannot be found a generic logo and the address is displayed. + +An example of how the delegation contract will be displayed based on the information provided in the GitHub is provided below. + +![stakingpool](/img/stakingpool.png) + +:::note +Before MultiversX Assets, nodes owners needed to update the `Identity` field inside the `prefs.toml` file of each node. This is not required anymore now and you can safely leave the identity empty on your node configuration. +::: + +### Assets ownership validation + +This applies for both adding of updating an identity over the MultiversX assets. + +In order to validate the ownership of a staking provider or validator nodes, one will need to sign a message by using +the owner wallet and then submit a comment on the PR with the branding of a validator node or a staking provider. +In case of a staking provider, its owner must perform this message signing. + +Message signing can be performed either via [Web Wallet](https://wallet.multiversx.com), either via [MultiversX Utils](https://utils.multiversx.com/sign-message). +The message to be signed is the commit hash of the latest commit in the PR. Inside the PR, go to the `Commits` tab and copy the latest commit hash (the bottom one is the latest commit). + +![commit-hash](/img/commit-hash.png) + +Then, by using a message signer (Web Wallet of MultiversX Utils), sign that commit hash (make sure you don't have additional spaces or other characters) by using the owner wallet. + +![commit-hash-sign](/img/commit-hash-sign.png) + +After that, leave a comment on that PR with the resulted signature. + +![commit-hash-sig-comm](/img/commit-hash-sig-comm.png) + +That's it. A GitHub workflow will validate the signature and if everything is ok, the PR will merged by a MultiversX responsible and the identity will be live anytime soon. + + +### Service fee + +The service fee is a percentage of the validator rewards that will be reserved for the owner of the delegation contract. The rest of the rewards will be available to delegators to either claim or redelegate. + +The service fee can be changed at any time using a transaction of the form: + +```rust +ChangeServiceFeeTransaction { + Sender: + Receiver:
+ Value: 0 + GasLimit: 2000000 + Data: "changeServiceFee" + + "@" + +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +In the `Data` field, the only argument passed to `changeServiceFee` is the new value of the service fee, expressed as hundredths of a percent. + +Setting the service fee to 0 (`"00"` in hexadecimal) specifies that no rewards are reserved for the owner of the delegation contract - all rewards will be available to the delegators. The service fee can always be modified later. + +:::tip +For example, a service fee of 37.45% is expressed by the integer 3745. This integer must then be encoded hexadecimally (3745 becomes `"0ea1"`). + +Finally, a `Data` field containing `changeServiceFee@0ea1` will change the service fee to 37.45%. +::: + + +### Automatic activation + +When automatic activation is enabled, the delegation contract will activate (stake) inactive nodes as soon as funds have become available in sufficient amount. Consequently, any [delegation transaction](/validators/delegation-manager#delegating-funds) can potentially trigger the activation of inactive nodes, assuming the transaction has sufficient gas. + +Automatic activation can be enabled or disabled using a transaction of the form: + +```rust +SetAutomaticActivationTransaction { + Sender: + Receiver:
+ Value: 0 + GasLimit: 2000000 + Data: "setAutomaticActivation" + + "@" + <"true" or "false" in hexadecimal encoding> +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +The only argument passed to `setAutomaticActivation` is either `true` or `false`, as an ASCII string encoded hexadecimally. For reference, `true` is `"74727565"` and `false` is `"66616c7365"`. + +:::tip +For example, a `Data` field containing `"setAutomaticActivation@74727565"` enables automatic activation. +::: + + +### Delegation cap + +The total delegation cap is the maximum possible size amount of EGLD which can be held by the delegation contract. After reaching the total delegation cap, the contract will reject any subsequent funds. + +The total delegation cap can be modified at any time using a transaction of the form: + +```rust +ModifyTotalDelegationCapTransaction { + Sender: + Receiver:
+ Value: 0 + GasLimit: 2000000 + Data: "modifyTotalDelegationCap" + + "@" + +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +In the `Data` field, the only argument passed to `modifyTotalDelegationCap` is the new value for the delegation cap. It is expressed as a fully denominated amount of EGLD, meaning that it is the number of $10^{-18}$ subdivisions of the EGLD, and not the actual number of EGLD tokens. Take sure not to encode the ASCII string representing the total delegation cap. + +:::tip +For example, to obtain the fully denominated form of 7231.941 EGLD, the amount must be multiplied by the denomination factor $10^{18}$, resulting in 7231941000000000000000. Do not encode the ASCII string `"7231941000000000000000"`, but encode the integer 7231941000000000000000 itself. This would result in "01880b57b708cf408000". + +Finally, a `Data` field containing `"modifyTotalDelegationCap@01880b57b708cf408000"` will change the total delegation cap to 7231.941 EGLD. +::: + +Setting the total delegation cap to 0 (`"00"` in hexadecimal) specifies an unlimited total delegation amount. It can always be modified later. + +:::important +The total delegation cap cannot be set to a value lower than the amount staked for currently active nodes. It must be either higher than that amount or set to 0 (infinite cap). +::: + + +## Managing nodes + + +### Adding nodes + +When a delegation contract is first created, it contains no information about nodes. The owner of the contract must then register nodes into the contract, so that they can be later activated. Any newly added node is "inactive" by default. + +Adding nodes requires the BLS key pairs belonging to each of them, which the owner of the contract uses to prove that they have access to the nodes. This proof consists of signing the address of the delegation contract itself with the secret BLS key of each node, individually. This results in as many signed messages as there are nodes. + +Adding `N` nodes to the delegation contract is done by submitting a transaction with the values set as follows: + +```rust +AddNodesTransaction { + Sender: + Receiver:
+ Value: 0 + GasLimit: 1000000 + N·6000000 + Data: "addNodes" + + "@" + + + "@" +
+ + "@" + + + "@" +
+ + <...> + + "@" + + + "@" +
+} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +As shown above, the `Data` field contains an enumeration of `N` pairs. Such a pair consists of the public BLS key of a node along with the message produced by signing the address of the delegation contract with the secret BLS key of the respective node. There are as many pairs as there are nodes to add. + + +### Staking nodes + +When the staking provider held by the delegation contract contains a sufficient amount of EGLD, the inactive (non-staked) nodes can be staked (activated). This promotes the nodes to the status of **validator**, which means they participate in consensus and earn rewards. + +This subsection describes the _manual_ staking (activation) of nodes. To automatically stake (activate) nodes when funds become available, [automatic activation](/validators/delegation-manager#automatic-activation) can be enabled. + +To stake specific nodes manually, a transaction of the following form can be submitted: + +```rust +StakeNodesTransaction { + Sender: + Receiver:
+ Value: 0 + GasLimit: 1000000 + N·6000000 + Data: "stakeNodes" + + "@" + + + "@" + + + <...> + + "@" + + +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +The `Data` field contains an enumeration of `N` public BLS keys corresponding to the nodes to be staked. + + +### Unstaking nodes + +Validator nodes that are already staked (active) can be manually unstaked. + +:::important +Validators are demoted to observer status at the beginning of the next epoch _after unstaking_. This means that they stop receiving rewards. + +Unstaking _does not_ mean that the staked amount returns to the staking provider (see [undelegating](/validators/delegation-manager#undelegating-funds) and [withdrawing](/validators/delegation-manager#withdrawing)). +::: + +To cancel the deactivation before the unstaking is complete, the nodes can be [restaked](/validators/delegation-manager#restaking-nodes). + +To begin the deactivation process for a selection of validator nodes, a transaction of the following form is used: + +```rust +UnstakeNodesTransaction { + Sender: + Receiver:
+ Value: 0 + GasLimit: 1000000 + N·6000000 + Data: "unStakeNodes" + + "@" + + + "@" + + + <...> + + "@" + + +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +The `Data` field contains an enumeration of `N` public BLS keys corresponding to the nodes to be unstaked. + + +### Restaking nodes + +Validator nodes that have been unstaked can be restaked (reactivated) before their deactivation is complete. To cancel their deactivation, a transaction of the following form is used: + +```rust +RestakeNodesTransaction { + Sender: + Receiver:
+ Value: 0 + GasLimit: 1000000 + N·6000000 + Data: "reStakeUnStakedNodes" + + "@" + + + "@" + + + <...> + + "@" + + +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +The `Data` field contains an enumeration of `N` public BLS keys corresponding to the nodes to be restaked. + + +### Unbonding nodes + +Nodes that have been [unstaked](/validators/delegation-manager#unstaking-nodes) can be completely deactivated, a process called **unbonding**. + +:::important +Validators are demoted to observer status at the beginning of the next epoch _after unstaking_, not unbonding. See [unstaking](/validators/delegation-manager#unstaking-nodes) above. +::: + +Validator nodes that have been unbonded cannot be restaked (reactivated). They must be staked anew. + +```rust +UnbondNodesTransaction { + Sender: + Receiver:
+ Value: 0 + GasLimit: 1000000 + N·6000000 + Data: "unBondNodes" + + "@" + + + "@" + + + <...> + + "@" + + +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +The `Data` field contains an enumeration of `N` public BLS keys corresponding to the nodes to be unbonded. + + +### Removing nodes + +Inactive (not staked, unbonded) nodes can be removed from the delegation contract by the owner at any time. Neither active (staked) nor unstaked nodes cannot be removed. + +Unlike [adding nodes](/validators/delegation-manager#adding-nodes), this step does not require the BLS key pairs of the nodes. + +Removing `N` nodes from the delegation contract is done by submitting a transaction with the values set as follows: + +```rust +RemoveNodesTransaction { + Sender: + Receiver:
+ Value: 0 + GasLimit: 1000000 + N·6000000 + Data: "removeNodes" + + "@" + + + "@" + + + <...> + + "@" + + +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +The `Data` field contains an enumeration of `N` public BLS keys corresponding to the nodes to be removed. + + +### Unjailing nodes + +When active validator nodes perform poorly or to the detriment of the network, they are penalized by having their rating reduced. Rating is essential to earning rewards, because it directly determines the likelihood of a validator to be [selected for consensus](/learn/consensus). + +However, it can happen that rating of a validator might drop under the acceptable threshold. As a consequence, the validator will begin its next epoch **jailed**, which prevents it from participating in consensus. + +:::important +A jailed validator does not lose its stake nor its status. It remains active, but it cannot earn rewards while in jail. +::: + +Recovering a validator from jail and restoring it is called **unjailing**, for which a fine of 2.5 EGLD must be paid. Multiple validators can be recovered from jail at the same time by paying 2.5 EGLD for each validator. The format of the unjailing transaction is as follows: + +```rust +UnjailNodesTransaction { + Sender: + Receiver:
+ Value: 2.5 EGLD × + GasLimit: 1000000 + N·6000000 + Data: "unJailNodes" + + "@" + + + "@" + + + <...> + + "@" + + +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +Note that the `Value` field depends on `N`, the number of validators to unjail. + +The `Data` field contains an enumeration of `N` public BLS keys corresponding to the nodes to be unjailed. + + +## Delegating and managing delegated funds + +Accounts that delegate their own funds to the staking provider are called **delegators**. The delegation contract offers them a set of actions as well. This means that these actions are available to the owner of the delegation contract as well. + + +### Delegating funds + +Accounts become delegators by funding the staking provider, i.e. they delegate their funds. The delegators are rewarded for their contribution with a proportion of the rewards earned by the validator nodes. By default, the owner of the delegation contract is the first delegator, having already contributed 1250 EGLD to the staking provider at its creation. + +:::important +Extra funds received by the delegation contract from delegators will be immediately used to top-up the stake of the existing active validators, consequently increasing their rewards. +::: + +Submitting a delegation transaction takes into account the status of [automatic activation](/validators/delegation-manager#automatic-activation): if the delegated funds cause the amount in the staking provider to become sufficient for the staking of extra nodes, it can trigger their activation automatically. This happens only if the transaction contains enough gas. + +But if gas is insufficient, or if automatic activation is disabled, the amount received through the delegation transaction simply becomes top-up for the stake of already active validators. Subsequent [manual staking](/validators/delegation-manager#staking-nodes) will be necessary to use the funds for staking, assuming they are sufficient. + +Funds can be delegated by any fund holder by submitting a transaction of the following form: + +```rust +DelegateTransaction { + Sender: + Receiver:
+ Value: minimum 1 EGLD + GasLimit: 12000000 + Data: "delegate" +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +If the transaction is successful, the funds' holder has become a delegator and the funds either become a top-up amount for the stake of active validators, or may trigger the staking of inactive nodes, as described above. + + +### Claiming rewards + +A portion of the rewards earned by validator nodes is reserved for each delegator. To claim the rewards, a delegator may issue a transaction of the following form: + +```rust +ClaimRewardsTransaction { + Sender: + Receiver:
+ Value: 0 + Gas: 6000000 + Data: "claimRewards" +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +If the transaction is successful, the delegator receives the proportion of rewards they are entitled to. + + +### Redelegating rewards + +Current delegation rewards can also be immediately delegated instead of [claimed](/validators/delegation-manager#claiming-rewards). This makes it an operation very similar to [delegation](/validators/delegation-manager#delegating-funds). + +:::important +Just like delegation, redelegation of rewards takes into account the status of [automatic activation](/validators/delegation-manager#automatic-activation): if the redelegated rewards cause the amount in the staking provider to become sufficient for the staking of extra nodes, it can trigger their activation automatically (requires sufficient gas in the redelegation transaction). +::: + +Rewards are redelegated using a transaction of the form: + +```rust +RedelegateRewardsTransaction { + Sender: + Receiver:
+ Value: 0 + Gas: 12000000 + Data: "reDelegateRewards" +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +If the transaction is successful, the delegator does not receive any EGLD at the moment, but the rewards they were entitled to will be added to their delegated amount. + + +### Undelegating funds + +Delegators may express the intent to withdraw a specific amount of EGLD from the staking provider. However, this process cannot happen at once and may take a few epochs before the amount is actually available for withdrawal, because the funds may already be used to stake for active validators and this means that unstaking of nodes may be necessary. + +:::important +If the amount to undelegate requested by the delegator will cause the staking provider to drop below the sufficient amount required to keep all the current validators active, some validators will inevitably end up unstaked. The owner of the delegation contract may intervene and add extra funds to prevent such situations. +::: + +Funds that have been previously used as stake for validators have been transferred into a separate system smart contract at the moment of staking, therefore the delegation contract itself does not hold these funds. But submitting an undelegation request will cause the delegation contract to attempt their retrieval. + +The delegation contract may receive the funds immediately if they're not currently used as stake; this makes them available for subsequent [withdrawal](/validators/delegation-manager#withdrawing). This is the case where previously delegated funds acted as top-up to the stake of existing validators. + +On the other hand, if the requested funds are currently in use as stake, the delegation contract cannot receive them yet. + +:::important +Funds used as stake can only be retrieved after 144000 blocks have been built on the Metachain (a little over 10 chronological epochs). It doesn't matter whether the validator was already demoted to observer, or whether it has been decommissioned entirely - the funds may not return until the aforementioned time has passed. + +After 144000 blocks, the funds can be [withdrawn](/validators/delegation-manager#withdrawing) normally by their rightful owner. +::: + +To express the intention of future withdrawal of funds from the staking provider, a delegator may submit the following transaction: + +```rust +UndelegateTransaction { + Sender: + Receiver:
+ Value: 0 + Gas: 12000000 + Data: "unDelegate" + "@" + +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +In the `Data` field, the only argument passed to `unDelegate` is the desired amount of EGLD to undelegate and later withdraw. It is expressed as a fully denominated amount of EGLD, meaning that it is the number of $10^{-18}$ subdivisions of the EGLD, and not the actual number of EGLD tokens. The fully denominated amount must then be encoded hexadecimally. Make sure not to encode the ASCII string representing the amount. + + +### Withdrawing + +After submitting an [undelegation transaction](/validators/delegation-manager#undelegating-funds), a delegator may finally withdraw funds from the staking provider. + +:::important +Funds must always be [undelegated](/validators/delegation-manager#undelegating-funds) first. They cannot be directly withdrawn. +::: + +This action withdraws _all the currently undelegated funds_ belonging to the specific delegator. + +Withdrawing funds is done using a transaction of the following form: + +```rust +WithdrawTransaction { + Sender: + Receiver:
+ Value: 0 + GasLimit: 12000000 + Data: "withdraw" +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +If the transaction is successful, the delegator receives all the EGLD they have previously requested to undelegate. The amount is removed from the staking provider. + + +## Delegation contract view functions + +The delegation contract can be queried using the following view functions. These queries should be done on a local proxy on the `/vm-values/query` endpoint. + +The following documentation sections only show the value of the relevant `returnData` field and omit the other fields for simplicity. + +```json +{ + "data": { + "data": { + "returnData": [], + "returnCode": "ok", + "returnMessage": "", + "gasRemaining": 0, + "gasRefund": 0, + "outputAccounts": null, + "deletedAccounts": null, + "touchedAccounts": null, + "logs": [] + } + }, + "error": "", + "code": "successful" +} +``` + + +### POST Contract config {#contract-config} + +The response contains an array of the properties in a fixed order (base64 encoded): owner address, service fee, maximum delegation cap, initial owner funds, automatic activation, with delegation cap, can change service fee, check cap on redelegate, nonce on creation and unbond period. + + + + +```bash +https://proxy:port/vm-values/query +``` + +```json +{ + "scAddress": "
", + "funcName": "getContractConfig" +} +``` + + + + +Only `returnData` shown below; see [view functions](/validators/delegation-manager#delegation-contract-view-functions) for complete response + +```json +{ + "returnData": [ + "", + "", + "", + "", + "", + "" + ] +} +``` + + + +Request + +```json +{ + "scAddress": "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqhllllsajxzat", + "funcName": "getContractConfig" +} +``` + +Response (only `returnData` shown below; see [view functions](/validators/delegation-manager#delegation-contract-view-functions) for complete response) + +```json +{ + "returnData": [ + "gKzHUD288mzScNX6nEmkGm4CHneMdrrPhJyPET9iGA8=", + null, + "", + "Q8M8GTdWSAAA", + "dHJ1ZQ==", + "ZmFsc2U=", + "dHJ1ZQ==", + "AuU=", + "+g==" + ] +} +``` + + + + + +### POST Contract metadata {#contract-metadata} + +The response contains an array of the properties in a fixed order (base64 encoded): staking provider name, website and identifier. + + + + +```bash +https://proxy:port/vm-values/query +``` + +```json +{ + "scAddress": "
", + "funcName": "getMetaData" +} +``` + + + + +Only `returnData` shown below; see [view functions](/validators/delegation-manager#delegation-contract-view-functions) for complete response + +```json +{ + "returnData": [ + "", + "", + "" + ] +} +``` + + + +Request + +```json +{ + "scAddress": "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqhllllsajxzat", + "funcName": "getMetaData" +} +``` + +Response (only `returnData` shown below; see [view functions](/validators/delegation-manager#delegation-contract-view-functions) for complete response) + +```json +{ + "returnData": [ + "U3Rha2luZyBwcm92aWRlciB0ZXN0", + "d3d3LmVscm9uZHN0YWtpbmcuY29t", + "dGVzdEtleWJhc2VJZGVudGlmaWVy" + ] +} +``` + + + + + +### POST Number of delegators {#number-of-delegators} + +The response contains a value representing the number of delegators in base64 encoding of the hex encoding. + + + + +```bash +https://proxy:port/vm-values/query +``` + +```json +{ + "scAddress": "
", + "funcName": "getNumUsers" +} +``` + + + + +Only `returnData` shown below; see [view functions](/validators/delegation-manager#delegation-contract-view-functions) for complete response + +```json +{ + "returnData": [ + "" + ] +} +``` + + + +Request + +```json +{ + "scAddress": "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqhllllsajxzat", + "funcName": "getNumUsers" +} +``` + +Response (only `returnData` shown below; see [view functions](/validators/delegation-manager#delegation-contract-view-functions) for complete response) + +```json +{ + "returnData": ["BQ=="] +} +``` + + + + + +### POST Number of nodes {#number-of-nodes} + +The response contains the number of nodes in base64 encoding of the hex encoding. + + + + +```bash +https://proxy:port/vm-values/query +``` + +```json +{ + "scAddress": "
", + "funcName": "getNumNodes" +} +``` + + + + +Only `returnData` shown below; see [view functions](/validators/delegation-manager#delegation-contract-view-functions) for complete response + +```json +{ + "returnData": [ + "" + ] +} +``` + + + +Request + +```json +{ + "scAddress": "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqhllllsajxzat", + "funcName": "getNumNodes" +} +``` + +Response (only `returnData` shown below; see [view functions](/validators/delegation-manager#delegation-contract-view-functions) for complete response) + +```json +{ + "returnData": ["Dg=="] +} +``` + + + + + +### POST Nodes states {#nodes-states} + +The response contains an enumeration of alternating status codes and BLS keys. Each status code is followed by the BLS key of the node it describes. Both status codes and BLS keys are encoded in base64. + + + + +```bash +https://proxy:port/vm-values/query +``` + +```json +{ + "scAddress": "
", + "funcName": "getAllNodeStates" +} +``` + + + + +Only `returnData` shown below; see [view functions](/validators/delegation-manager#delegation-contract-view-functions) for complete response + +```json +{ + "returnData": [ + "", + "" + ] +} +``` + + + +Request + +```json +{ + "scAddress": "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqhllllsajxzat", + "funcName": "getAllNodeStates" +} +``` + +Response (only `returnData` shown below; see [view functions](/validators/delegation-manager#delegation-contract-view-functions) for complete response) + +```json +{ + "returnData": [ + "c3Rha2Vk", + "KJ6auG3rKQydktc9soWvyBOa5UPA7DYezttTqlS6JIIvsvOaH8ghs2Qruc4aXLUXNJ1if7Ot9gbt5dNUrmNfkLtZl1hpLvPllrGmFP4bKCzZ25UNiTratwOMcXhhCmSD", + "bm90U3Rha2Vk", + "7gJzQ3GQ4htSx6CYvOkXPDdwGfzdahuDY4agZkGhIAMfB44K08FP6z3wLQEnn2IULfZ8/Hds38LEu3Xq+mJZ4FktF0vm8C1T34b5uAEpZWtDZLICAEFCuQZrqS5Qb1CR", + "vTyNQ/vDxg0L8LmoGuKP+4/wsbyWv8RaqeQ+WH+xrMvk1m7Q3wjheOpjYtQPz80YZ1CrwKj6ObsCUejP4uuvi3MQ1oMEGKg5yh3kRgybRb4TXAWEpAPszYMLIQhrIn2P", + "9TbGQCcrbyXH9HBAhzIWOuH/cdSNO1dwxO5foM2L28tWU0p9Kos6DKsPMtKMx4sAeRal08K3Dk0gQxeTSAvC2fb3DAQt01rmPSAqCSXZetSX12BVcTi+pYGUHaXKJ/OW" + ] +} +``` + + + + + +### POST Total active stake {#total-active-stake} + +The response contains a value representing the total active stake in base64 encoding of the hex encoding. + + + + +```bash +https://proxy:port/vm-values/query +``` + +```json +{ + "scAddress": "
", + "funcName": "getTotalActiveStake" +} +``` + + + + +Only `returnData` shown below; see [view functions](/validators/delegation-manager#delegation-contract-view-functions) for complete response + +```json +{ + "returnData": [""] +} +``` + + + +Request + +```json +{ + "scAddress": "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqhllllsajxzat", + "funcName": "getTotalActiveStake" +} +``` + +Response (only `returnData` shown below; see [view functions](/validators/delegation-manager#delegation-contract-view-functions) for complete response) + +```json +{ + "returnData": ["ArXjrxaxiAAA"] +} +``` + + + + + +### POST Total unstaked stake {#total-unstaked-stake} + +The response contains a value representing the total unstaked stake in base64 encoding of the hex encoding. + + + + +```bash +https://proxy:port/vm-values/query +``` + +```json +{ + "scAddress": "
", + "funcName": "getTotalUnStaked" +} +``` + + + + +Only `returnData` shown below; see [view functions](/validators/delegation-manager#delegation-contract-view-functions) for complete response + +```json +{ + "returnData": [ + "" + ] +} +``` + + + +Request + +```json +{ + "scAddress": "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqhllllsajxzat", + "funcName": "getTotalUnStaked" +} +``` + +Response (only `returnData` shown below; see [view functions](/validators/delegation-manager#delegation-contract-view-functions) for complete response) + +```json +{ + "returnData": ["ArXjrxaxiAAA"] +} +``` + + + + + +### POST Total cumulated rewards {#total-cumulated-rewards} + +The response contains a value representing the sum of all accumulated rewards in base64 encoding of the hex encoding. + + + + +```bash +https://proxy:port/vm-values/query +``` + +```json +{ + "scAddress": "
", + "funcName": "getTotalCumulatedRewards", + "caller": "erd1qqqqqqqqqqqqqqqpqqqqqqqqlllllllllllllllllllllllllllsr9gav8" +} +``` + + + + +Only `returnData` shown below; see [view functions](/validators/delegation-manager#delegation-contract-view-functions) for complete response + +```json +{ + "returnData": [ + "" + ] +} +``` + + + +Request + +```json +{ + "scAddress": "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqhllllsajxzat", + "funcName": "getTotalCumulatedRewards", + "caller": "erd1qqqqqqqqqqqqqqqpqqqqqqqqlllllllllllllllllllllllllllsr9gav8" +} +``` + +Response (only `returnData` shown below; see [view functions](/validators/delegation-manager#delegation-contract-view-functions) for complete response) + +```json +{ + "returnData": ["czSCSSYZr8E="] +} +``` + + + + + +### POST Delegator claimable rewards {#delegator-claimable-rewards} + +The response contains a value representing the total claimable rewards for the delegator in base64 encoding of the hex encoding. + + + + +```bash +https://proxy:port/vm-values/query +``` + +```json +{ + "scAddress": "
", + "funcName": "getClaimableRewards", + "args": [ + "" + ] +} +``` + + + + +Only `returnData` shown below; see [view functions](/validators/delegation-manager#delegation-contract-view-functions) for complete response + +```json +{ + "returnData": [ + "" + ] +} +``` + + + +Request + +```json +{ + "scAddress": "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqhllllsajxzat", + "funcName": "getClaimableRewards", + "args": ["ebfd923cd251f857ed7639e87143ac83f12f423827abc4a0cdde0119c3e37915"] +} +``` + +Response (only `returnData` shown below; see [view functions](/validators/delegation-manager#delegation-contract-view-functions) for complete response) + +```json +{ + "returnData": ["Ft9RZzF7Dyc"] +} +``` + + + + + +### POST Delegator total accumulated rewards {#delegator-total-accumulated-rewards} + +The response contains a value representing the total accumulated rewards for the delegator in base64 encoding of the hex encoding. + + + + +```bash +https://proxy:port/vm-values/query +``` + +```json +{ + "scAddress": "
", + "funcName": "getTotalCumulatedRewardsForUser", + "args": [ + "" + ] +} +``` + + + + +Only `returnData` shown below; see [view functions](/validators/delegation-manager#delegation-contract-view-functions) for complete response + +```json +{ + "returnData": [ + "" + ] +} +``` + + + +Request + +```json +{ + "scAddress": "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqhllllsajxzat", + "funcName": "getTotalCumulatedRewardsForUser", + "args": ["ebfd923cd251f857ed7639e87143ac83f12f423827abc4a0cdde0119c3e37915"] +} +``` + +Response (only `returnData` shown below; see [view functions](/validators/delegation-manager#delegation-contract-view-functions) for complete response) + +```json +{ + "returnData": ["Ft9RZzF7Dyc"] +} +``` + + + + + +### POST Delegator active stake {#delegator-active-stake} + +The response contains a value representing the active stake for the delegator in base64 encoding of the hex encoding. + + + + +```bash +https://proxy:port/vm-values/query +``` + +```json +{ + "scAddress": "
", + "funcName": "getUserActiveStake", + "args": [ + "" + ] +} +``` + + + + +Only `returnData` shown below; see [view functions](/validators/delegation-manager#delegation-contract-view-functions) for complete response + +```json +{ + "returnData": [""] +} +``` + + + +Request + +```json +{ + "scAddress": "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqhllllsajxzat", + "funcName": "getUserActiveStake", + "args": ["ebfd923cd251f857ed7639e87143ac83f12f423827abc4a0cdde0119c3e37915"] +} +``` + +Response (only `returnData` shown below; see [view functions](/validators/delegation-manager#delegation-contract-view-functions) for complete response) + +```json +{ + "returnData": ["slsrv1so8QAA"] +} +``` + + + + + +### POST Delegator unstaked stake {#delegator-unstaked-stake} + +The response contains a value representing the unstaked stake for the delegator in base64 encoding of the hex encoding. + + + + +```bash +https://proxy:port/vm-values/query +``` + +```json +{ + "scAddress": "
", + "funcName": "getUserUnStakedValue", + "args": [ + "" + ] +} +``` + + + + +Only `returnData` shown below; see [view functions](/validators/delegation-manager#delegation-contract-view-functions) for complete response + +```json +{ + "returnData": [ + "" + ] +} +``` + + + +Request + +```json +{ + "scAddress": "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqhllllsajxzat", + "funcName": "getUserUnStakedValue", + "args": ["ebfd923cd251f857ed7639e87143ac83f12f423827abc4a0cdde0119c3e37915"] +} +``` + +Response (only `returnData` shown below; see [view functions](/validators/delegation-manager#delegation-contract-view-functions) for complete response) + +```json +{ + "returnData": ["ARWORgkT0AAA"] +} +``` + + + + + +### POST Delegator unbondable stake {#delegator-unbondable-stake} + +The response contains a value representing the unbondable stake in base64 encoding of the hex encoding. + + + + +```bash +https://proxy:port/vm-values/query +``` + +```json +{ + "scAddress": "
", + "funcName": "getUserUnBondable", + "args": [ + "" + ] +} +``` + + + + +Only `returnData` shown below; see [view functions](/validators/delegation-manager#delegation-contract-view-functions) for complete response + +```json +{ + "returnData": [ + "" + ] +} +``` + + + +Request + +```json +{ + "scAddress": "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqhllllsajxzat", + "funcName": "getUserUnBondable", + "args": ["ebfd923cd251f857ed7639e87143ac83f12f423827abc4a0cdde0119c3e37915"] +} +``` + +Response (only `returnData` shown below; see [view functions](/validators/delegation-manager#delegation-contract-view-functions) for complete response) + +```json +{ + "returnData": ["ARWORgkT0AAA"] +} +``` + + + + + +### POST Delegator undelegated stake {#delegator-undelegated-stake} + +The response contains an enumeration representing the different undelegated stake values in base64 encoding of the hex encoding. + + + + +```bash +https://proxy:port/vm-values/query +``` + +```json +{ + "scAddress": "
", + "funcName": "getUserUnDelegatedList", + "args": [ + "" + ] +} +``` + + + + +Only `returnData` shown below; see [view functions](/validators/delegation-manager#delegation-contract-view-functions) for complete response + +```json +{ + "returnData": [""] +} +``` + + + +Request + +```json +{ + "scAddress": "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqhllllsajxzat", + "funcName": "getUserUnDelegatedList", + "args": ["ebfd923cd251f857ed7639e87143ac83f12f423827abc4a0cdde0119c3e37915"] +} +``` + +Response (only `returnData` shown below; see [view functions](/validators/delegation-manager#delegation-contract-view-functions) for complete response) + +```json +{ + "returnData": ["Q8M8GTdWSAAA", "iscjBInoAAA="] +} +``` + + + + + +### POST Delegator funds data {#delegator-funds-data} + +The response contains an enumeration for the delegator encoded base64 of the hexadecimal encoding of the following: active stake, unclaimed rewards, unstaked stake and unbondable stake. + + + + +```bash +https://proxy:port/vm-values/query +``` + +```json +{ + "scAddress": "
", + "funcName": "getDelegatorFundsData", + "args": [ + "" + ] +} +``` + + + + +Only `returnData` shown below; see [view functions](/validators/delegation-manager#delegation-contract-view-functions) for complete response + +```json +{ + "returnData": [ + "", + "", + "", + "" + ] +} +``` + + + +Request + +```json +{ + "scAddress": "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqhllllsajxzat", + "funcName": "getUserUnDelegatedList", + "args": ["ebfd923cd251f857ed7639e87143ac83f12f423827abc4a0cdde0119c3e37915"] +} +``` + +Response (only `returnData` shown below; see [view functions](/validators/delegation-manager#delegation-contract-view-functions) for complete response) + +```json +{ + "returnData": ["REAihYg4zAAA", "Q8M8GTdWSAAA", "REAihYg4zAAA", "Q8M8GTdWSAAA"] +} +``` + + + + + +### POST Get reward data for epoch {#get-reward-data-for-epoch} + +The response contains an enumeration for the specified epoch representing the base64 encoding of the hexadecimal encoding for the rewards to distribute, total active stake and service fee. + + + + +```bash +https://proxy:port/vm-values/query +``` + +```json +{ + "scAddress": "
", + "funcName": "getRewardData", + "args": [""] +} +``` + + + + +Only `returnData` shown below; see [view functions](/validators/delegation-manager#delegation-contract-view-functions) for complete response + +```json +{ + "returnData": [ + "", + "", + "" + ] +} +``` + + + +```json +{ + "scAddress": "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqp0llllswfeycs", + "funcName": "getRewardData", + "args": ["fc2b"] +} +``` + +Response (only `returnData` shown below; see [view functions](/validators/delegation-manager#delegation-contract-view-functions) for complete response) + +```json +{ + "returnData": ["REAihYg4zAAA", "Q8M8GTdWSAAA", "REAihYg4zAAA"] +} +``` + + + + + +## Delegation manager view functions + + +### POST All contract addresses {#all-contract-addresses} + +The response contains an enumeration of bech32 keys bytes in base64 encoding. + + + + +```bash +https://proxy:port/vm-values/query +``` + +```json +{ + "scAddress": "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqylllslmq6y6", + "funcName": "getAllContractAddresses" +} +``` + + + + +Only `returnData` shown below; see [view functions](/validators/delegation-manager#delegation-contract-view-functions) for complete response + +```json +{ + "returnData": [ + "
" + ] +} +``` + + + +Request + +```json +{ + "scAddress": "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqylllslmq6y6", + "funcName": "getAllContractAddresses" +} +``` + +Response (only `returnData` shown below; see [view functions](/validators/delegation-manager#delegation-contract-view-functions) for complete response) + +```json +{ + "returnData": [ + "AAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAL///8=", + "AAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAP///8=", + "AAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAT///8=", + "AAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAX///8=", + "AAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAb///8=", + "AAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAf///8=", + "AAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAj///8=", + "AAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAn///8=", + "AAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAr///8=" + ] +} +``` + + + + + +### POST Contract config {#contract-config-1} + +The response contains an enumeration of the properties in a fixed order (base64 encoded): current number of contracts, last created contract address, minimum and maximum service fee, minimum deposit and delegation. + + + + +```bash +https://proxy:port/vm-values/query +``` + +```json +{ + "scAddress": "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqylllslmq6y6", + "funcName": "getContractConfig" +} +``` + + + + +Only `returnData` shown below; see [view functions](/validators/delegation-manager#delegation-contract-view-functions) for complete response + +```json +{ + "returnData": [ + "", + "", + "", + "", + "", + "" + ] +} +``` + + + +Request + +```json +{ + "scAddress": "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqylllslmq6y6", + "funcName": "getContractConfig" +} +``` + +Response (only `returnData` shown below; see [view functions](/validators/delegation-manager#delegation-contract-view-functions) for complete response) + +```json +{ + "returnData": [ + "Gw==", + "AAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAABz///8=", + "", + "JxA=", + "Q8M8GTdWSAAA", + "iscjBInoAAA=" + ] +} +``` + + + + +--- + +### Staking Providers APR + +Staking Providers are entities that allow their delegators, plus the owner's funds to deploy validators on the Network. The Staking Provider owner +is responsible for the nodes' up-time, as well as for configuring the contract's parameters. According to the contract's configuration and operations, the delegation cap, +the contract fee, the number of tokens kept as top-up or the number of nodes to be deployed are all variables in computing the final APR (Annual Percentage Return) +of the provider. + + +## Introduction + +By using the [Delegation Manager](/validators/delegation-manager/) system smart contract, a new staking provider can be +set up. According to the initial deposits (half of the minimum node stake) + delegations from other users (or even the owner itself) +the staking contract can spawn new nodes. Currently, the minimum node cost is 2500 EGLD, so, for example, if a staking contract +gathered 7500 EGLD it can spawn 3 new nodes. + + +### Base stake and top-up + +As stated, a validator node requires at least 2500 EGLD. So multiple nodes would mean at least _2500 multiplied by the number of nodes_ EGLD in the contract. +The difference is considered top-up. Also, the staking provider owner can choose to keep the tokens as top-up, even +if the top-up is enough to spawn a new validator node. + +Let's take some examples: + +a). A Staking Contract has 2550 EGLD. This would mean a base stake of 2500 EGLD + 50 EGLD top-up + +b). A Staking Contract has 5200 EGLD. This could mean: + +- a base stake of 2500 EGLD (1 node) + 2700 EGLD top-up +- a base stake of 5000 EGLD (2 nodes) + 200 EGLD top-up + +Network-wise, the base stake is currently limited to 8,000,000 EGLD (3200 nodes \* 2500 EGLD / node). However, current staking +metrics indicate that the total EGLD staked is around 13,000,000 EGLD, resulting in a base stake of 8 millions EGLD + ~5 millions EGLD top-up. + + +### Service Fee + +At each epoch change, the delegation contract receives the rewards in accordance to its stake. Over those rewards, +a service fee applies so the owner can cover the hosting and nodes management costs. + +So for example, if the rewards are 10 EGLD in an epoch, and the service fee is set to 10%, the owner of the staking +contract will be eligible for 1 EGLD, while the difference (9 EGLD) will be allocated to the delegators. + + +### Inflation Rate + +MultiversX's economics model is based on an inflation rate that decreases each year. More about this can be read on the +[blog](https://multiversx.com/blog/the-wealth-of-crypto-networks-elrond-economics-paper). + +This means that for each year the estimated rewards for a validator will change. This has to be taken into account +when computing the APR. + +The configuration for the inflation rate can be found [here](https://github.com/multiversx/mx-chain-mainnet-config/blob/master/economics.toml) (`YearSettings`). + +The protocol doesn't take leap years into consideration, but rather approximate each year at 365 days. + +The approximated inflation rate is as follows: + +| Year | Start Date | Inflation rate | +|------|-------------|----------------| +| 1 | 2020.07.30 | 10.84% | +| 2 | 2021.07.30 | 9.7% | +| 3 | 2022.07.30 | 8.56% | +| 4 | 2023.07.30 | 7.42% | +| 5 | 2024.07.29 | 6.27% | +| 6 | 2025.07.29 | 5.13% | +| 7 | 2026.07.29 | 3.99% | +| 8 | 2027.07.29 | 2.85% | +| 9 | 2028.07.28 | 1.71% | +| 10 | 2029.07.28 | 0.57% | +| 11 | 2030.07.28 | 0% | + + +### Protocol Sustainability + +In accordance to the Mainnet's [configuration](https://github.com/multiversx/mx-chain-mainnet-config/blob/master/economics.toml#L35) (`ProtocolSustainabilityPercentage`). +at each epoch change, when new tokens are distributed among the validators, 10% of the value goes to Protocol Sustainability Address. + +This also has to be taken into account when calculating the APR. + + +## Rewards calculation + +When wanting to calculate the APR (Annual Percentage Return) of a Staking Provider, there are multiple factors that have +to be taken into account, such as total value locked at Network-level, the inflation based on the current year, the +staking provider base stake and top-up stake, and so on. + + +### Network Top-Up rewards + +The formula for determining the rewards received by the validators for the top-up in a given epoch is: + +$$ +topUpRewards(e) = \frac{2 * topUpRewardLimit(e)}{\pi} * atan(\frac{eligibleCumulatedTopUp(e)}{p}) +$$ + +Where: + +- `e` represents the given epoch +- `topUpRewardLimit(e)` represents the maximum top-up rewards that can be distributed in the given epoch. This can be viewed + as the maximum value out of the epoch rewards that can be distributed as rewards for the top-up stake, and depends + on the total rewards to be distributed in the epoch and a configured network parameter that defines the proportion out of the total rewards. +- `eligibleCumulatedTopUp(e)` represents the rewards distributed in the epoch for signing and proposing blocks. + This does not include the protocol sustainability rewards, developer fees or the penalty for missed blocks. +- `p` represents a chosen parameter to control the gradient of top-up rewards. It can be viewed as the cumulated top-up stake + where the given top-up rewards reach ½ of the top-up rewards set limit. It is currently set to 2M EGLD. + + +### Network base rewards + +$$ +baseRewards(e) = blocksRewards(e) - topUpReward(e) +$$ + +Where: + +- `blocksRewards(e)` represents the rewards received by the validators by either signing or proposing blocks during the epoch that are now + part of the canonical chain +- `topUpReward(e)` is computed above + + +## APR calculation + +After determining the base and the top-up rewards for an epoch, the APR can be calculated for a Staking Provider. + +First, we have to determine the maximum rewards that can be reached in ideal situations (no missed block in an epoch). + + +### Staking Provider base stake rewards + +We have to calculate the estimated rewards received by a Staking Provider in one epoch for the base stake. + +This is done by calculating the share of the total rewards in accordance to the provider's number of nodes. + +Therefore, if a Staking Provider has 320 nodes out of 3200 nodes, it will receive 10% of the base rewards. Note that the base rewards +are calculated after decreasing the protocol sustainability rewards, as well as the computed top-up rewards. + +$$ +stakingProviderBaseStakeRewards(e) = \frac{stakingProviderNumberOfNodes}{totalNumberOfNodesInNetwork} * baseRewards(e) +$$ + + +### Top-Up rewards + +Similar to base stake rewards, the rewards for top-up are estimated by computing the share of the provider's top-up in +accordance to the network's total top-up. + +$$ +stakingProviderTopUpRewards(e) = \frac{stakingProviderTopUpAmount}{networkTotalTopUp} * topUpRewards(e) +$$ + + +### APR calculation + +In order to obtain the estimation of the APR, we first need to calculate the share of the provider's earnings in an epoch as compared +to it's total stake locked. Then we will multiply by 365 (the number of days in a year) and get the result. + +$$ +aprWithoutFee = \frac{stakingProviderBaseStakeRewards(e) + stakingProviderTopUpRewards(e)}{providerTotalStake} * 365 +$$ + +The last step is to decrease the fee deducted by the staking provider owner: + +$$ +apr = \frac{100 - fee}{100} * aprWithoutFee +$$ + + +## Example + +The formulas and all the mathematics involved might be quite complicated, so let's take an example. + +Let's say we have the following parameters: + +Network parameters + +``` +genesisTotalSupply = 20M EGLD +inflationRate = 9.7% (year 2) +p = 2M EGLD +totalNodes = 3200 +eligibleCumulatedTopUp = 2.6M +totalCumulatedTopUp = 5.2M +protocolSustainabilityRewards = 10% +numDaysInAYear = 365 +topUpFactor = 0.5 +``` + +Staking provider parameters: + +``` +stakingProviderNumberOfNodes = 10 +stakingProviderBaseStake = 25,000 EGLD +stakingProviderTopUpAmount = 6,472 EGLD +stakingProviderTotalStake = 31,472 EGLD +fee = 2% +``` + +For a random day, the maximum rewards that can be distributed is: + +``` +maximumRewardsInADay(e) = inflationRate * genesisTotalSupply / numDaysInAYear = 9.7% * 20M / 365 = 5315 EGLD +``` + +We have to decrease the protocol sustainability rewards, resulting in: + +``` +maximumRewardsInADay(e) = 4783 EGLD +``` + +The maximum top-up reward for the epoch is: + +``` +topUpRewardLimit(e) = topUpFactor * maximumRewardsInADay(e) = 0.5 * 4783 =~ 2391 EGLD +``` + +Therefore, the network top-up would be: + +``` +topUpRewards(e) = (2 * topUpRewardLimit(e) / pi) * atan(eligibleCumulatedTopUp(e) / p) = (2 * 2391 / pi) * atan(2.6M / 2M) =~ 1522 * 0.91 =~ 1385 EGLD +``` + +The base rewards would be: + +``` +baseRewards(e) = blocksRewards(e) - topUpReward(e) = maximumRewardsInADay(e) - topUpReward(e) = 4783 - 1385 = 3398 EGLD +``` + +Moving to the staking provider: + +``` +stakingProviderBaseStakeRewards(e) = stakingProviderNumberOfNodes / totalNodes * baseRewards(e) = 10 / 3200 * 3398 = 10.61 EGLD +stakingProviderTopUpRewards(e) = stakingProviderTopUpAmount / totalCumulatedTopUp * topUpRewards(e) = 6472 / 5.2M * 1385 = 1.72 EGLD +``` + +And, finally, calculating the APR: + +``` +aprWithoutFee = (stakingProviderBaseStakeRewards(e) + stakingProviderTopUpRewards(e)) / providerTotalStake * 365 = (10.61 + 1.72) / 31472 * 365 = 14.29 % +``` + +After deducting the fee: + +``` +apr = (100 - fee) / 100 * aprWithoutFee = (100 - 2) / 100 * 14.29 = 14.00 % +``` + +--- + +### Staking v4 + +# **Introduction** + +Staking and delegation are processes that evolve over time. No system has to remain static. Our assumptions about how +the market works and reacts can change, just as user behavior and market dynamics may evolve. Currently, we have +approximately 400 validators, with some acting as staking providers and others as individual validator operators. While +most nodes have a comfortable top-up on the base stake of 2.500 eGLD, some do not contribute to the network's security by +adding more top-up. + + +# **Limitations of the Current Implementation** + +- Limiting the number of nodes to 3200, creating an additional queue. New validators can join the network only if + someone leaves the system. +- Concentration of power among large providers, hindering decentralization. Top 11 staking agencies control 33%. + +One of our primary objectives is to eliminate the additional queue and leverage the top-up value per node to determine +the best nodes. This ensures that we do not restrict the entry of new validators, as the current system requires an old +validator to leave for a new one to enter. The market will determine the actual node price, operating as a soft auction +where anyone paying the node price (2.500 eGLD) can register, but the node becomes a validator only if it has sufficient +top-up. The shuffling from the waiting list to the eligible list remains unaffected, focusing solely on selecting nodes +from the auction list to the waiting list. + +This approach, known as a _Soft Auction_, democratizes the validator system, transforming it into a free market. + +The selection process for the best X nodes from the auction into the "to be shuffled into waiting" list can be executed +in two ways. + +**The first** and easy version is a strict selection where all nodes from the auction list are sorted based on their +topUp, and only the first ones are selected. + +This approach is strict, requiring validators to manage their number of nodes if their topUp is close to the selection +threshold. The topUp is computed as TotalStake/NumberOfNodes - 2.500 eGLD. If a validator registers more nodes, their +topUp per node decreases. For example: + +- 10 nodes, 50.000 eGLD = 2.500 eGLD topUp per node +- 20 nodes, 50.000 eGLD = 0 eGLD topUp per node + +In this scenario, if a staking provider registers all 20 nodes, they will be at the end of the list in case of an +auction selection. If only 320 nodes out of 340 can be selected, and every other node has at least 1 eGLD topUp on each +of them, none of the 20 nodes from this staking provider will be selected. + +Since this selection occurs at the end of every epoch, staking providers near the topUp limit must continually monitor +and adjust their nodes, unstaking or staking nodes based on the topUp per node of other providers. Sorting nodes based +on topUp does not provide adequate protection for staking providers, requiring constant supervision and action. + +**The second version**, currently implemented and explained in the following chapters, addresses the shortcomings of the +first version. + + +# **Current Implementation** + +:::note +Please note that the numbers below are indicative and only used to better exemplify the model. +::: + +In the current implementation (staking 3.5), we have: + +1. A capped number of 3200 **nodes in the network**, including: + - 1600 active/eligible validators globally, split into 400 nodes per shard + - 1600 waiting validators globally, split into 400 nodes per shard +2. An uncapped `FIFO` queue where newly staked nodes are placed and await participation in the network. + +:::important + +Currently, a queued node can participate in the network only if an existing node is either unstaked or jailed. + +::: + +![Current Staking](/validators/stakingV4/current-staking.png) + +Nodes are distributed in the following steps: + +1. Randomly shuffle out 80 eligible validators for each shard, resulting in 320 (80 validators per 4 shards) + shuffled-out validators. +2. Select these 320 shuffled-out validators to be randomly but evenly distributed at the end of each shard's waiting + list. +3. For each shard, replace the previously shuffled-out validators with 80 waiting validators from the top of each + shard's waiting list. + +In the current implementation, each node, regardless of its top-up, has equal chances of participating in the consensus. +Starting with staking phase 4, the probability of validators entering the validation process will be significantly +influenced by the amount of their staked top-up. Validators with a higher staked top-up will have considerably greater +chances of participation, while those with little or no top-up will find their chances of entering into validation +markedly reduced. + + +# **Staking V4** + +Staking phase 4 will unfold in three consecutive steps, each corresponding to a specific epoch. + + +## Staking v4. Step 1. + +In the first step, we will completely **remove the staking queue** and **unstake all nodes from the staking queue**. +This process will occur automatically at the end of the epoch and requires no interaction from validators. +Nodes' distribution remains unchanged. + +For owners which had **unstaked** nodes, these can be **restaked** using 'RestakeUnstakedNodes' complete details [here](https://docs.multiversx.com/validators/delegation-manager/#restaking-nodes) + +![Staking V4 Step 1](/validators/stakingV4/stakingV4-step1.png) + +:::important Important notes + +Starting with this epoch: + +- Every **newly staked** node will be placed in the **auction list**. +- Every **unjailed** node will be placed in the **auction list**. +- The global **number of new nodes** that can take part in the system is **uncapped**. +- Owners with **insufficient base staked EGLD** for their nodes will have them **removed** at the end of the epoch in + the following order: `auction` -> `waiting` -> `eligible`. + +::: + +For example, if an owner has insufficient base stake for their nodes, the nodes will be removed from the network at the +end of the epoch based on the order: `auction` -> `waiting` -> `eligible`. This ensures that nodes contributing to the +ecosystem with a healthy top-up will not be adversely affected. + +Below is an example of how nodes are unstaked based on insufficient base stake. Suppose an owner has four nodes: + +- 1 eligible: `node1` +- 2 waiting: `node2`, `node3` +- 1 auction: `node4` + +Assuming a minimum price of 2500 EGLD per staked node, the owner should have a minimum base stake of 10,000 EGLD (4 * +2500 EGLD). If, during the epoch, the owner unstakes 4000 EGLD, resulting in a base stake of 6000 EGLD, only two staked +nodes can be covered. At the end of the epoch, the nodes `node4` and `node3` will be unstaked in the specified order. + + +## Staking v4. Step 2. + +In the second step, all **shuffled-out** nodes from the **eligible list** will be sent to the **auction list**. Waiting +lists will not be filled by any shuffled-out nodes. + +Using the example above, this will resize each waiting list per shard from 400 nodes to 320 nodes. + +![Staking V4 Step 2](/validators/stakingV4/stakingV4-step2.png) + + +## Staking v4. Step 3. + +Starting with this epoch: + +- Maximum number of nodes in the network will be changed from 3200 to 2880 (3200 - 320), consisting of: + - a global number of 1600 active/eligible validators, split into 400 nodes/shard + - a global number of 1280 waiting validators to join the active list, split into 320 nodes/shard +- All **shuffled out** nodes from the eligible list will be sent to the auction list to take part in the auction + selection. The more topUp an owner has, the higher the chances of having their auction nodes selected will be. +- Based on the _soft auction selection_ (see the next section), all **qualified** nodes from the **auction** will be + distributed to the waiting list (depending on the `available slots`). The other **unqualified** nodes will remain in + the auction and wait to be selected in the next epoch if possible. The number of available slots is based on the + number of shuffled-out nodes and other nodes leaving the network (e.g.: `unstake/jail`). It guarantees that the + waiting list is filled, and the nodes' configuration is maintained. +- Distribution from the `waiting` to the `eligible` list will remain unchanged. + +![](/validators/stakingV4/stakingV4-step3.png) + + +## Staking v4. Soft Auction Selection Mechanism + +Nodes from the auction list will be selected to be distributed in the waiting list based on the **soft auction** +mechanism. For each owner, based on their topUp, we compute how many validators they would be able to run by +distributing their total topUp per fewer nodes (considering we would not select all of their auction nodes, but only a +part of them). This mechanism ensures that for each owner, we select as many nodes as possible, based on the **minimum +required topUp** to fill the`available slots`. This is a global selection, not per shard. We preselect the best global +nodes at the end of the epoch. + +Suppose we have the following auction list, and 3 available slots: + +![](/validators/stakingV4/soft-auction1.png) + +``` ++--------+------------------+------------------+-------------------+--------------+-----------------+-------------------------+ +| Owner | Num staked nodes | Num active nodes | Num auction nodes | Total top up | Top up per node | Auction list nodes | ++--------+------------------+------------------+-------------------+--------------+-----------------+-------------------------+ +| owner1 | 3 | 2 | 1 | 3669 | 1223 | pubKey1 | +| owner2 | 3 | 1 | 2 | 2555 | 851 | pubKey2, pubKey3 | +| owner3 | 2 | 1 | 1 | 2446 | 1223 | pubKey4 | +| owner4 | 4 | 1 | 3 | 2668 | 667 | pubKey5, pubKe6, pubKe7 | ++--------+------------------+------------------+-------------------+--------------+-----------------+-------------------------+ +``` + +For the configuration above: + +- Minimum possible `topUp per node` = 667, considering `owner4` will have all of his **3 auction nodes selected** + - owner4's total top up/(1 active node + 3 auction nodes) = 2668 / 4 = 667 +- Maximum possible `topUp per node` = 1334, considering `owner4` will only have **one of his auction nodes selected** + - owner4's total top up/(1 active node + 1 auction node) = 2668 / 2 = 1334 + +Based on the above interval: `[667, 1334]`, we compute the `minimum required topUp per node` to be qualified from the +auction list. We gradually increase from min to max possible topUp per node with a step, such that we can fill +the `available slots`. At each step, we compute for each owner what's the maximum number of nodes that they could run by +distributing their total topUp per fewer auction nodes, leaving their other nodes as unqualified in the auction list. +This is a soft auction selection mechanism, since it is dynamic at each step and does not require owners to "manually +unstake" their nodes so that their topUp per node would be redistributed (and higher). This threshold ensures that we +maximize the number of owners that will be selected, as well as their number of auction nodes. + +In this example, if we use a step of 10 EGLD in the `[667, 1334]` interval, the `minimum required topUp per node` would +be 1216, such that: + +![](/validators/stakingV4/soft-auction2.png) + +``` ++--------+------------------+----------------+--------------+-------------------+-----------------------------+------------------+---------------------------+-----------------------------+ +| Owner | Num staked nodes | TopUp per node | Total top up | Num auction nodes | Num qualified auction nodes | Num active nodes | Qualified top up per node | Selected auction list nodes | ++--------+------------------+----------------+--------------+-------------------+-----------------------------+------------------+---------------------------+-----------------------------+ +| owner1 | 3 | 1223 | 3669 | 1 | 1 | 2 | 1223 | pubKey1 | +| owner2 | 3 | 851 | 2555 | 2 | 1 | 1 | 1277 | pubKey2 | +| owner3 | 2 | 1223 | 2446 | 1 | 1 | 1 | 1223 | pubKey4 | +| owner4 | 4 | 667 | 2668 | 3 | 1 | 1 | 1334 | pubKey5 | ++--------+------------------+----------------+--------------+-------------------+-----------------------------+------------------+---------------------------+-----------------------------+ +``` + +- `owner1` possesses one auction node, `pubKey1`, with a qualified topUp per node of 1223, surpassing the threshold of + 1216. +- `owner2` holds two auction nodes, `pubKey2` and `pubKey3`, with a topUp per node of 851. By leaving one + node (`pubKey3`) in the auction while selecting only one (`pubKey2`), the topUp per node is rebalanced to 1277 ( + 2555/2), exceeding the minimum threshold of 1216. +- `owner3` has one auction node, `pubKey4`, with a qualified topUp per node of 1223, surpassing the threshold of 1216. +- `owner4` possesses three auction nodes, `pubKey5`, `pubKey6`, and `pubKey7`, with a topUp per node of 667. By leaving + two nodes (`pubKey6`, `pubKey7`) in the auction while selecting only one (`pubKey5`), the topUp per node is rebalanced + to 1334 (2668/2), exceeding the minimum threshold of 1216. + +If the threshold were increased by one more step from `1216` to `1226`, only two nodes, `pubKey2` and `pubKey5`, would +qualify, which is insufficient to fill all slots. + +:::note + +If an owner has multiple nodes in the auction, but only a portion is selected for distribution in the waiting list, the +selection will be based on sorting the BLS keys. + +::: + +:::note + +The minimum required topUp per node, along with the real-time auction list, is accessible in the explorer at all +times. This allows owners to determine the optimal strategy for maximizing the number of selected auction nodes. + +::: + +Finally, validators are sorted based on the qualified topUp per node, and the selection is made considering available +slots. In instances where two or more validators share the same topUp (e.g., `pubKey1` and `pubKey4`), the selection +process is random but deterministic. The selection involves an XOR operation between the validators' public keys and the +current block's randomness. This mechanism prevents validators from "minting" their BLS keys to gain an advantage in +selection, as the randomness is only revealed at the time of selection. + +``` + +--------+----------------+--------------------------+ + | Owner | Registered key | Qualified TopUp per node | + +--------+----------------+--------------------------+ + | owner4 | pubKey5 | 1334 | + | owner2 | pubKey2 | 1277 | + | owner1 | pubKey1 | 1223 | + +--------+----------------+--------------------------+ + | owner3 | pubKey4 | 1223 | + +--------+----------------+--------------------------+ +``` + +Following the example above, there are two nodes with a qualified top-up of 1223 per node: + +- `owner1` with 1 BLS key = `pubKey1` +- `owner3` with 1 BLS key = `pubKey4` + +Assuming the result of the XOR operation between their BLS keys and randomness is: + +- `XOR1` = `pubKey1` XOR `randomness` = `[143...]` +- `XOR2` = `pubKey4` XOR `randomness` = `[131...]` + +Since `XOR1` > `XOR2`, `pubKey1` will be selected, while `pubKey4` remains in the auction list. + + +## Introducing Node Limitations for Enhanced Decentralization + +In tandem with the upcoming staking v4 feature, we are implementing a crucial change aimed at fostering +decentralization, increasing the Nakamoto coefficient, and reinforcing the principles of a decentralized network. + + +### Dynamic Node Limitation + +To achieve our decentralization goals, a cap on the number of nodes an owner can have will be introduced. This +limitation is dynamic, recalculated at each epoch, ensuring adaptability to the evolving network conditions. + + +### Impact and Considerations + +This restriction primarily affects scenarios where users wish to stake new nodes. If an individual already possesses +more nodes than the specified threshold, their existing nodes will not be affected. However, they won't be able to stake +additional nodes beyond the limit; only unstaking will be allowed. + + +### Decentralization in Action + +This initiative encourages staking providers to critically evaluate their node count. For larger providers, having an +excessive number of nodes may lead to a decrease in overall APR. Achieving enough top-up to select numerous nodes from +the auction could become challenging. + + +### Proactive Measures + +Staking providers are encouraged to strategize accordingly. For instance, they might choose to unstake some nodes +themselves or explore collaboration with other small providers. Merging resources can enhance their chances of being +selected in the auction, especially for those with limited top-up. + +No immediate action is required from users; however, thoughtful consideration of their node portfolio and strategic +decisions will play a pivotal role in navigating this shift toward a more decentralized network. + + +# **FAQ** + + +## How much topUp should I have as a validator? + +The required topUp for validators depends on various factors, including the number of nodes in the auction and the soft +auction selection mechanism. The soft auction selection dynamically computes the minimum required topUp per node to +qualify for distribution from the auction to the waiting list. To maximize the chances of having auction nodes selected, +validators are encouraged to maintain a competitive topUp. Real-time auction list information and the minimum required +topUp per node is available in the explorer, allowing validators to strategize effectively. + + +## What happens if there are fewer nodes in the auction than available slots? + +In this case, all nodes will be selected, regardless of their topUp. + + +## One of my nodes was sent to auction during stakingV4 step 2. Will I lose rewards? + +If one of your nodes is shuffled out into the auction list during step2, it will enter into competition with the other +existing nodes. If you have enough topUp, nothing changes, and no rewards will be lost. For owners contributing to the +ecosystem and maintaining a sufficient topUp, this change will not have any negative impact. However, if you have low +topUp or close to zero, your nodes might be unqualified and remain in the auction list. + + +## Why downsize the waiting list? + +Short answer: _to keep the APR unchanged_. + +Before stakingV4, if a node was shuffled out and moved to the waiting list, it was guaranteed to be "idle" (not +participating in consensus) for 5 epochs. During this time, the node would not gain any rewards. + +During stakingV4 step2, no node from the waiting list is moved to active. If we were to keep the same configuration, a +shuffled out node from this step would have to wait 6 epochs until eligible (if selected from the auction) and +therefore decreasing the overall APR: + + +## How does the dynamic node limitation work? + +The dynamic node limitation is determined by the `NodeLimitPercentage`, which defines a percentage of +the `TotalNumOfEligibleNodes` from the current epoch. For example, if `NodeLimitPercentage` is set to 0.005 (0.5%) and +the `TotalNumOfEligibleNodes` for a given epoch is 1600 nodes, this means owners cannot exceed having more than 8 nodes. +The specific parameters, including the initial limit and `NodeLimitPercentage`, can be decided through a governance +vote. This ensures community involvement in determining the rules governing node ownership. + +The actual limit is 50 nodes per provider with the calculation details from the 'systemSmartContractsConfig.toml' [here](https://github.com/multiversx/mx-chain-mainnet-config/blob/2ca2da07427c5a802202d1ed364a923f0e366f13/systemSmartContractsConfig.toml#L15) + +--- + +### System Requirements + +This page provides the system requirements for running a MultiversX node. + + +## **MultiversX Nodes explained** + +Nodes are computers running the MultiversX software, so they contribute to the MultiversX network by relaying information and validating it. Each node needs to stake 2500 EGLD to become a **Validator** and is rewarded for its service. Nodes without a stake are called **Observers** - they are connected to the network and relay information, but they have no role in processing transactions and thus do not earn rewards. + + +## **Minimum System Requirements for running 1 MultiversX Node** + +- 4 x dedicated/physical CPUs, either Intel or AMD, **with `SSE4.1` and `SSE4.2` flags** (use [lscpu](https://manpages.ubuntu.com/manpages/trusty/man1/lscpu.1.html) to verify) +- 8 GB RAM +- 200 GB SSD +- 100 Mbit/s always-on internet connection, at least 4 TB/month data plan +- Linux OS (Ubuntu 22.04/Debian 12 minimum) / MacOS + +:::caution +1. The CPUs must be `SSE4.1` and `SSE4.2` capable, otherwise the node won't be able to use the Wasmer 2 VM available through the VM 1.5 (and above) and the node will not be able to sync blocks from the network. +2. If the system chosen to host the node is a VPS, the host must have dedicated CPUs. Using shared CPUs can hinder your node's performance that will result in a decrease of node's rating and eventually the node might get jailed. +3. If you run multiple MultiversX Nodes on the same machine, the host running those nodes should have the specs at least equal to the minimum system requirements multiplied by the number of nodes running on that host. +::: + +:::tip +We are promoting using processors that support the `fma` or `fma3` instruction set since it is widely used by our VM. Displaying the available CPU instruction set can be done using the Linux shell command `sudo lshw` or `lscpu` +::: + + +## **ARM Architecture Support** + +Processors with ARM architecture are now supported, starting from mainnet epoch 1265 (related to [this](https://github.com/multiversx/mx-chain-mainnet-config/releases/tag/v1.6.7.0) release). +Synchronization from genesis to epoch 1265 is not possible on ARM processors. + +:::caution +This update comes after extensive testing to ensure compatibility and functionality. However, we advise caution with its use in production environments. +::: + +Usage recommendations: +- Testnet/Devnet Validators: ARM processors can be utilized effectively as validator nodes on Testnet or Devnet. +- Mainnet Observers: ARM processors can be utilized effectively as observer nodes that can provide API support to non-critical services. +- Mainnet Validators: Despite successful testing, **it is NOT recommended to use ARM processors as mainnet validators** at this time due to potential performance and reliability concerns. + +We will continue to monitor and improve support for ARM architecture, and we encourage the community to provide [feedback](https://t.me/MultiversXValidators) on their experiences. + + +### **Networking** + +In order for a node to be reachable by other nodes several conditions have to be met: + +1. The port opened by the node on the interfaces must not be blocked by a firewall that denies inbound connections on it +2. If behind a NAT device, the node must be able to use the UPnP protocol to successfully negotiate a port that the NAT device will forward the incoming connections to (in other words, the router should be UPnP compatible) +3. There must be maximum 1 NAT device between the node and the Internet at large. Otherwise, the node will not be reachable by other nodes, even if it can connect itself to them. + +To make sure the required ports are open, use the following command before continuing: + +``` +sudo ufw allow 37373:38383/tcp +``` + +:::note +The above ports need to be open in order to allow the node to communicate with other nodes via p2p. The configuration for the port range is set [here](https://github.com/multiversx/mx-chain-go/blob/master/cmd/node/config/p2p.toml#L7). +::: + +:::caution +In case a firewall for outgoing traffic is used please make sure traffic to ports 10000 (p2p seeder) as well as 123 (NTP) is explicitly allowed. +::: + +--- + +### The Staking Smart Contract + +This page will guide you through the the operations of the Staking System Smart Contract. + + +## **Staking** + +Nodes are _promoted_ to the role of **validator** when their operator sends a _staking transaction_ to the Staking smart contract. Through this transaction, the operator locks ("stakes") an amount of their own EGLD for each node that becomes a validator. A single staking transaction contains the EGLD and the information needed to stake for one or more nodes. Such a transaction contains the following: + +- The number of nodes that the operator is staking for +- The concatenated list of BLS keys belonging to the individual nodes +- The stake amount for each individual node, namely the number of nodes × 2500 EGLD +- A gas limit of 6 000 000 gas units × the number of nodes +- Optionally, a separate address may be specified, to which the rewards should be transferred, instead of the address from which the transaction itself originates. The reward address must be first decoded to bytes from the Bech32 representation, then re-encoded to base16 (hexadecimal). + +For example, if an operator manages two individual nodes with the 96-byte-long BLS keys `45e7131ba....294812f004` and `ecf6fdbf5....70f1d251f7`, then the staking transaction would be built as follows: + +```rust +StakingTransaction { + Sender: + Receiver: erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqplllst77y4l + Value: 5000 EGLD + GasLimit: 12000000 + Data: "stake" + + "@0002" + + "@45e7131ba....294812f004" + + "@67656e65736973" + "@ecf6fdbf5....70f1d251f7" + + "@67656e65736973" + "@optional_reward_address_HEX_ENCODED" +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +Because this transaction is a call to the Staking smart contract, it passes information via the `Data` field: + +- `stake` is the name of the smart contract function to be called; +- `0002` is the number of nodes (unsigned integer, hex-encoded); +- 45e7131ba....294812f004 is the BLS key of the first node, represented as a 192-character-long hexadecimal string; +- `67656e65736973` is a reserved placeholder, required after each BLS key; +- `ecf6fdbf5....70f1d251f7` is the BLS key of the second node, represented as a 192-character-long hexadecimal string; +- `67656e65736973` is the aforementioned reserved placeholder, repeated; +- `optional_reward_address_HEX_ENCODED` is the address of the account which will receive the rewards for the staked nodes (decoded from its usual Bech32 representation into binary, then re-encoded to a hexadecimal string). + + +## **Changing the reward address** + +Validator nodes produce rewards, which are then transferred to an account. By default, this account is the same one from which the staking transaction was submitted (see the section above). In the staking transaction, the node operator has the option to select a different reward address. + +The reward address can also be changed at a later time, with a special transaction to the Staking smart contract. It is essential to know exactly how many nodes were specified in the original staking transaction, in order to properly compute the gas limit for changing the reward address. + +- An amount of 0 EGLD +- A gas limit of 6 000 000 gas units × the nodes for which the reward address is changed (as specified by the original staking transaction). +- The new reward address. The reward address must be first decoded into binary from its normal Bech32 representation, then re-encoded to base16 (hexadecimal). + +For example, changing the reward address for two nodes requires the following transaction: + +```rust +ChangeRewardAddressTransaction { + Sender: + Receiver: erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqplllst77y4l + Value: 0 EGLD + Data: "changeRewardAddress@reward_address_HEX_ENCODED" + GasLimit: 12000000 +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + + +## **Unstaking** + +A node operator may _demote_ their validator nodes back to **observer** status by sending an _unstaking transaction_ to the Staking smart contract, containing the following: + +- An amount of 0 EGLD +- The concatenated list of the BLS keys belonging to the individual nodes which are to be demoted from validator status +- A gas limit of 6 000 000 gas units × the number of nodes + +Note that the demotion does not happen instantaneously: the unstaked nodes will remain validators until the network releases them, a process which is subject to various influences. + +Moreover, the amount of EGLD which was previously locked as stake will not return instantaneously. It will only be available after a predetermined number of rounds, after which the node operator may claim back the amount with a third special transaction (see the following section). + +Continuing the example in the previous section, an unstaking transaction for the two nodes contains the following: + +```rust +UnstakingTransaction { + Sender: + Receiver: erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqplllst77y4l + Value: 0 EGLD + GasLimit: 12000000 + Data: "unStake" + + "@45e7131ba....294812f004" + + "@ecf6fdbf5....70f1d251f7" +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +Note that: + +- `45e7131ba....294812f004` is the BLS key of the first node, represented as a 192-character-long hexadecimal string; +- `ecf6fdbf5....70f1d251f7` is the BLS key of the second node, represented as a 192-character-long hexadecimal string; +- no reserved placeholder is needed, as opposed to the staking transaction (see above) + + +## **Unbonding** + +A node operator may reclaim the stake which was previously locked for their validator nodes using an _unbonding transaction_ to the Staking smart contract. Before unbonding, the node operator must have already sent an unstaking transaction for some of their validators, and a predetermined amount of rounds must have passed after the unstaking transaction was processed. + +The unbonding transaction is almost identical to the unstaking transaction, and contains the following: + +- An amount of 0 EGLD +- The concatenated list of the BLS keys belonging to the individual nodes for which the stake is claimed back +- A gas limit of 6 000 000 gas units × the number of nodes + +Following the example in the previous sections, an unbonding transaction for the two nodes contains the following information: + +```rust +UnbondingTransaction { + Sender: + Receiver: erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqplllst77y4l + Value: 0 EGLD + GasLimit: 12000000 + Data: "unBond" + + "@45e7131ba....294812f004" + + "@ecf6fdbf5....70f1d251f7" +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +Note that: + +- 45e7131ba....294812f004 is the BLS key of the first node, represented as a 192-character-long hexadecimal string; +- `ecf6fdbf5....70f1d251f7` is the BLS key of the second node, represented as a 192-character-long hexadecimal string; +- no reserved placeholder is needed, as opposed to the staking transaction (see above) + + +## **Unjailing** + +If a node operator notices that some of their validator nodes have been jailed due to low rating, they can restore the nodes back to being active validators by paying a small fine. This is done using an _unjailing transaction_, sent to the Staking smart contract, which contains the following: + +- An amount of 2.5 EGLD (the fine) for each jailed node - this value must be correctly calculated; any other amount will result in a rejected unjail transaction +- The concatenated list of the BLS keys belonging to the individual nodes that are to be unjailed +- A gas limit of 6 000 000 gas units × the number of nodes + +Continuing the example in the previous section, if the nodes `45e7131ba....294812f004` and `ecf6fdbf5....70f1d251f7` were placed in jail due to low rating, they can be unjailed with the following transaction: + +```rust +UnjailTransaction { + Sender: + Receiver: erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqplllst77y4l + Value: 5 EGLD + GasLimit: 12000000 + Data: "unJail" + + "@45e7131ba....294812f004" + + "@ecf6fdbf5....70f1d251f7" +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +Note that: + +- `45e7131ba....294812f004` is the BLS key of the first node, represented as a 192-character-long hexadecimal string; +- `ecf6fdbf5....70f1d251f7` is the BLS key of the second node, represented as a 192-character-long hexadecimal string; +- no reserved placeholder is needed, as opposed to the staking transaction (see above) + + +## **Claiming unused tokens from Staking** + +If a node operator has sent a staking transaction containing an amount of EGLD higher than the requirement for the nodes listed in the transaction, they can claim back the remainder of the sum with a simple _claim transaction_, containing: + +- An amount of 0 EGLD +- A gas limit of 6 000 000 gas units + +An example of a claim transaction is: + +```rust +ClaimTransaction { + Sender: + Receiver: erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqplllst77y4l + Value: 0 EGLD + Data: "claim" + GasLimit: 6000000 +} +``` + +_For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format)._ + +After this transaction is processed, the Staking smart contract will produce a transaction _back_ to the sender account, but only if the sender account has previously staked for nodes, using a staking transaction. + +--- + +### Unjailing + +This page will guide you through the process of unjailing a validator node. + + +## **Introduction** + +In the unfortunate situation of losing too much **rating score**, a validator will be **jailed**, which means that they will be taken out of the shards, they will not participate in consensus, and thus they will not earn any more rewards. Currently, the rating limit at which a node will be jailed is `10`. Read more on the [Ratings](/validators/rating) page. + +You can reinstate one of your jailed validators using an **unjailing transaction**. This transaction effectively represents the payment of a fine. After the transaction is successfully executed, your validator will return to the network in the next epoch, and treated as if the validator is brand new, with the rating reset to `50`. + +It is easy to submit an unjailing transaction. You have the option of unjailing your validators either through the online Wallet at [https://wallet.multiversx.com](https://wallet.multiversx.com/), or by using `mxpy` in the command-line. + +You'll see some BLS public keys in the examples on this page. Make sure you don't copy-paste them into your staking transaction. These BLS keys have been randomly generated and do not belong to any real node. + +Each unjailing process requires a transaction to be sent to the Staking Smart Contract. These transactions must contain all the required information, encoded properly, and must provide a high enough gas limit to allow for successful execution. These details are described in the following pages. + +There are currently 2 supported methods of constructing and submitting these transactions to the Staking SmartContract: + +- Manually constructing the transaction, then submitting it to [wallet.multiversx.com](https://wallet.multiversx.com/); +- Automatically constructing the transaction and submitting it using the `mxpy` command-line tool. + +The following pages will describe both approaches in each specific case. + + +## **Prerequisites** + +In order to submit an unjailing transaction, you require the following: + +- A wallet with at least 2.5 EGLD (the cost of unjailing a _single validator_). If you want to unjail multiple validators at once, you need to multiply that minimum amount with the number of validators. For example, unjailing 3 validators at once will require 7.5 EGLD. Make sure you have enough in your wallet. +- The **BLS public keys** of the validators you want to unjail. You absolutely **do not require the secret key** of the validators. The BLS public keys of the validators are found in the `validatorKey.pem` files. Please read [Validator Keys](/validators/key-management/validator-keys) to find out how to extract the public key only. Remember that the BLS public key consists of exactly 192 hexadecimal characters (that is, `0` to `9` and `a` to `f` only). + + +## **Unjailing through the Wallet** + +Open your wallet on [https://wallet.multiversx.com](https://wallet.multiversx.com/) and click the "Send" button. Carefully fill the form with the following information. Make sure it is clear to you what this information is, and where to adjust it with your own information. + +In the "To" field, paste the address of the Staking SmartContract, which also handles unjailing: `erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqplllst77y4l` + +For the "Amount" field, you first need to calculate the amount of EGLD required for unjailing. This is done by multiplying 2.5 EGLD by the _number of nodes_ you want to unjail. For example, if you want to unjail a single node, you need to enter `2.5`. For two nodes, it's `5` and for three nodes it is `7.5`. + +Next, expand the "Fee limit" section of the form. You'll see the "Gas limit" field appear. The value that needs to be entered here also depends on the _number of nodes_ you want to unjail. To calculate the "Gas limit" value, multiply `6000000` (six million gas units) by the number of nodes. For example, if you want to unjail a single node, enter `6000000`. For two nodes, enter `12000000`, for three nodes enter `18000000` and so on. Observe how the "Fee limit" field automatically calculates the cost of this transaction. + + +## **The "Data" field** + +Next, you must fill the "Data" field. The text you will write here will be read by the Staking SmartContract to find out what nodes you want to unjail. Remember, you can unjail any number of nodes at once. + +When writing in the "Data" field, you must adhere to a strict format, described in the following subsections. + + +### **Unjailing a single node** + +If you want to unjail a single node, the format of the "Data" field is simple: + +```python +unJail@ +``` + +Do not copy-paste the above format as-is into the "Data". Instead, you must **replace** `` with the **BLS public key** of the node you want to stake for. You can find the BLS public key in the `validatorKey.pem` file of that node. Read the page [Validator Keys](/validators/key-management/validator-keys) to help you interpret the contents of the file and locate the BLS public key. + +Make sure you do not remove the `@` character. They are used to separate the pieces of information in the "Data" field. Only replace``. The angle-brackets `<` and `>` must be removed as well. + +As an example, the "Data" field of an unjailing transaction for a single node looks like this: + +unJail@b617d8bc442bda59510f77e04a1680e8b2d3293c8c4083d94260db96a4d732deaaf9855fa0cef2273f5a67b4f442c725efc06a5d366b9f15a66da9eb8208a09c9ab4066b6b3d38c3cf1ea7fab6489a90713b3b56d87de68c6558c80d7533bf27 + +![img](https://gblobscdn.gitbook.com/assets%2F-LhHlNldCYgbyqXEGXUS%2F-MA1YB7F53LJCTlFj8qn%2F-MA1_N1up06vncGVTyfp%2Funjailing-single-node.png?alt=media&token=fe0ca638-6433-4c07-b7ac-ef3fcf199835) + + +### **Unjailing multiple nodes at once** + +Unjailing more than one node at a time isn't very different from unjailing a single node. You only need to append the BLS public keys of the remaining nodes, separated by `@`, to the "Data" field constructed for a single node. Please read the previous section "Unjailing a single node" before continuing, if you haven't already. Also, _do not forget_ to update the "Amount" and "Gas Limit" fields according to the number of nodes you are unjailing. + +For a _single_ node, as explained in the previous subsection, the format is this one: + +```python +unJail@ +``` + +For _two_ nodes, the format is as follows: + +```python +unJail@@ +``` + +And for _three_ nodes, the format is: + +```python +unJail@@@ +``` + +Notice how each extra node adds the part `@` to the previous format. You need to replace with `` with the actual **BLS public keys** of your nodes, which you can find inside their individual `validatorKey.pem` files. Make sure you **do not write the BLS secret keys**! Read the page [Validator Keys](/validators/key-management/validator-keys) to see how to interpret the `validatorKey.pem` files. + +For example, the "Data" field for an unjailing transaction for two nodes looks like this: + +unJail@b617d8bc442bda59510f77e04a1680e8b2d3293c8c4083d94260db96a4d732deaaf9855fa0cef2273f5a67b4f442c725efc06a5d366b9f15a66da9eb8208a09c9ab4066b6b3d38c3cf1ea7fab6489a90713b3b56d87de68c6558c80d7533bf27@f921a0f76ed70e8a806c6f9119f87b12700f96f732e6070b675e0aec10cb0723803202a4c40194847c38195db07b1001f6d50c81a82b949e438cd6dd945c2eb99b32c79465aefb9144c8668af67e2d01f71b81842d9b94e4543a12616cb5897d + +![img](https://gblobscdn.gitbook.com/assets%2F-LhHlNldCYgbyqXEGXUS%2F-MA1mbsWLwDtxs1LX3w-%2F-MA1nGcSQTZqmnGoxtRA%2Funjailing-two-nodes.png?alt=media&token=991f11c8-fe7c-46f5-93fb-566ab0590279) + + +### **The general format** + +You can write the text for the "Data" field for _any_ number of nodes. The general format looks like this: + +```python +unJail@@@…@ +``` + + +## **Unjailing through mxpy** + +Submitting the unjailing transaction using `mxpy` avoids having to write the "Data" field manually. Instead, the transaction is constructed automatically by `mxpy` and submitted to the network directly, in a single command. + +Make sure `mxpy` is installed and has the latest version before continuing. If `mxpy` is not installed, please follow [these instructions](/sdk-and-tools/mxpy/installing-mxpy). + + +## **Your Wallet PEM file** + +To send transactions on your behalf _without_ using the online MultiversX Wallet, `mxpy` must be able to sign for you. For this reason, you have to generate a PEM file using your Wallet mnemonic. + +Please follow the guide [Deriving the Wallet PEM file](/sdk-and-tools/mxpy/mxpy-cli). Make sure you know exactly where the PEM file was generated, because you'll need to reference its path in the `mxpy` commands. + +After the PEM file was generated, you can issue transactions from `mxpy`directly. + + +## **The unjailing transaction** + +The following commands assume that the PEM file for your Wallet was saved with the name `walletKey.pem` in the current folder, where you are issuing the commands from. + +The command to submit an unjailing transaction with `mxpy` is this: + +```sh +mxpy --verbose validator unjail --pem=walletKey.pem --value="" --nodes-public-keys=",,...," --proxy=https://gateway.multiversx.com +``` + +Notice that we are using the `walletKey.pem` file. Moreover, before executing this command, you need to replace the following: + +- Replace `` with the amount of EGLD required for unjailing your validators. You need to calculate this value with respect to the number of nodes you are unjailing. See the [beginning of the Unjailing through the Wallet](/validators/staking/unjailing#unjailing-through-the-wallet) section for info on how to do it. +- Replace all the `` with the actual **BLS public keys** of your nodes, which you can find inside their individual `validatorKey.pem` files. Make sure you **do not write the BLS secret keys**! Read the page [Validator Keys](/validators/key-management/validator-keys) to see how to interpret the `validatorKey.pem` files. + +Here's an example for an unjailing command for one validator: + +```sh +mxpy --verbose validator unjail --pem=walletKey.pem --value="2500000000000000000000" --nodes-public-keys="b617d8bc442bda59510f77e04a1680e8b2d3293c8c4083d94260db96a4d732deaaf9855fa0cef2273f5a67b4f442c725efc06a5d366b9f15a66da9eb8208a09c9ab4066b6b3d38c3cf1ea7fab6489a90713b3b56d87de68c6558c80d7533bf27" --proxy=https://gateway.multiversx.com +``` + +:::note important +You must take **denomination** into account when specifying the `value` parameter in **mxpy**. +::: + +For two validators, the command becomes this one: + +```sh +mxpy --verbose validator unjail --pem=walletKey.pem --value="5000000000000000000000" --nodes-public-keys="b617d8bc442bda59510f77e04a1680e8b2d3293c8c4083d94260db96a4d732deaaf9855fa0cef2273f5a67b4f442c725efc06a5d366b9f15a66da9eb8208a09c9ab4066b6b3d38c3cf1ea7fab6489a90713b3b56d87de68c6558c80d7533bf27,f921a0f76ed70e8a806c6f9119f87b12700f96f732e6070b675e0aec10cb0723803202a4c40194847c38195db07b1001f6d50c81a82b949e438cd6dd945c2eb99b32c79465aefb9144c8668af67e2d01f71b81842d9b94e4543a12616cb5897d" --proxy=https://gateway.multiversx.com +``` + +Notice that the two BLS public keys are separated by a comma, with no extra space between them. + +--- + +### Useful Links & Tools + +This page offer useful links and resources that can be used by validators and nodes operators. + + +## Resources + +Official resources: + +- Blockchain Explorer: [https://explorer.multiversx.com/](https://explorer.multiversx.com/) +- Wallet: [https://wallet.multiversx.com](https://wallet.multiversx.com/) +- GitHub: https://github.com/multiversx +- Dockerhub: https://hub.docker.com/u/multiversx +- Telegram Validators Chat: https://t.me/MultiversXValidators +- Telegram Bot for Staking & Validators: https://t.me/ElrondNetwork_Bot + + +Community resources: + +- Zabbix monitoring guide: https://thepalmtree.network/zabbix-elrond-guide +- Zabbix plugin: https://github.com/arcsoft-ro/zabbix-elrond-plugin +- Configure Validator API & NetData https access: https://gist.github.com/hiddentao/e6283952b9fffe3f6b42dfeec87c684e + +--- + +### Validator Keys + +Each validator required a private key to be used for signing blocks. This key is called the **Validator Key**. +The Validator Key is also used to sign the consensus messages that the validator sends to the other validators. + + +## Validator key format + +A file containing the keys for your node. + +The **Validator Keys** are located in the `validatorKey.pem` file, which is generated in the node setup process. By default, each node stores its own .pem file in the `$HOME/elrond-nodes/node-0` folder. A copy also archived as a zip file in the `$HOME/VALIDATOR_KEYS` folder, for restore purposes. + +Below you can find their anatomy and how to extract the information from them + +Example: + +-----BEGIN PRIVATE KEY for _45e7131ba37e05c5de3f8862b4d8294812f004a5b660abb793e89b65816dbff2b02f54c25f139359c9c98be0fa657d0bf1ae4115dcf6fdbf5f3a470f1d251f769610b48fe34eeab59e82ac1cc0336d1d9109a14b768b97ccb4db4c2431629688_----- + +**YmRiNmViOGYzMmQ3OWY0YjE4ODJjMzE1ODA4YjQyZmZjODhiZDQxNzMwNmE5MTRiZjQ4OTAyNjM0MTcyNjMzMw==** + +-----END PRIVATE KEY for _45e7131ba37e05c5de3f8862b4d8294812f004a5b660abb793e89b65816dbff2b02f54c25f139359c9c98be0fa657d0bf1ae4115dcf6fdbf5f3a470f1d251f769610b48fe34eeab59e82ac1cc0336d1d9109a14b768b97ccb4db4c2431629688_----- + +In plain English: + +``` +-----The private key for this``*PUBLIC KEY*``starts below----- +**PRIVATE KEY** +-----The private key for this``*PUBLIC KEY*``was listed above----- +``` + +The string in _italics_ from the example is the _PUBLIC KEY_. The string in **bold** from the example is the **PRIVATE KEY**. + +More clearly: + +`*PUBLIC KEY:* `_45e7131ba37e05c5de3f8862b4d8294812f004a5b660abb793e89b65816dbff2b02f54c25f139359c9c98be0fa657d0bf1ae4115dcf6fdbf5f3a470f1d251f769610b48fe34eeab59e82ac1cc0336d1d9109a14b768b97ccb4db4c2431629688_ + +`**PRIVATE KEY:**`**YmRiNmViOGYzMmQ3OWY0YjE4ODJjMzE1ODA4YjQyZmZjODhiZDQxNzMwNmE5MTRiZjQ4OTAyNjM0MTcyNjMzMw==** + +Always save and protect **private keys**, they are like your username + password + 2FA at your bank, all combined. + +_Public keys_ are like your phone number - no harm in others knowing it, it actually is needed for some scenarios. Still, only share it on a need to basis, like you would do with your own phone number. + + +## How to generate a new key + +The easiest way to generate a new validator key is by using the `keygenerator` tool that resides near the node. + +- [https://github.com/multiversx/mx-chain-go/tree/master/cmd/keygenerator](https://github.com/multiversx/mx-chain-go/tree/master/cmd/keygenerator) + +How to generate a new validator key if golang is already set on the host: + +```shell +$ git clone https://github.com/multiversx/mx-chain-go.git +$ cd mx-chain-go/cmd/keygenerator +$ go build +$ ./keygenerator --key-type validator +``` + +Alternatively, if you've already installed a node on the host, you can issue the following command: + +```shell +$ cd ~/elrond-utils/ +$ ./keygenerator --key-type validator +``` + +--- + +### Validators - Overview + +This page provides an overview of the Validator Nodes and the associated Tools. + + +## Table of contents + + +### Install and maintain a node + +| Name | Description | +| --------------------------------------------------------------------------------- | ------------------------------------------------------------------------ | +| [System requirements](/validators/system-requirements) | System requirements for running a MultiversX node. | +| [Install a Mainnet/Testnet/Devnet Node](/validators/nodes-scripts/config-scripts) | Instructions about how to get a Testnet or a Devnet node up and running. | + + +### Keys Management + +| Name | Description | +|-----------------------------------------------------------------|--------------------------------------------------------------| +| [Validator keys](/validators/key-management/validator-keys) | Learn about a validator key. | +| [Wallet keys](/validators/key-management/wallet-keys) | Learn about a wallet key. | +| [Protecting your keys](/validators/key-management/protect-keys) | Learn how you can secure your keys. | +| [Multikey nodes](/validators/key-management/multikey-nodes) | Learn how to manage a set of keys by using a group of nodes. | + + +### Staking + +| Name | Description | +| ----------------------------------------------------------------------------------- | ------------------------------------------------------------- | +| [Staking, unstaking and unjailing](/validators/staking/staking-unstaking-unjailing) | Learn about how to stake, unstake or unjail a Node. | +| [How to Stake a Node](/validators/staking) | Learn how to stake a node via a step-by-step tutorial. | +| [How to unJail a Node](/validators/staking/unjailing) | Learn how to unJail a node. | +| [The Staking Smart Contract](/validators/staking/staking-smart-contract) | How to interact with the Smart Contract that manages Staking. | + + +### Delegation Manager + +| Name | Description | +| ---------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | +| [Delegation Manager](/validators/delegation-manager) | Learn how to create a new Staking Provider, how to configure it and how to interact with it. | +| [How to convert an existing Validator into a Staking Pool](/validators/staking/convert-existing-validator-into-staking-provider) | Learn how to create a new Staking Provider, starting from an existing Validator. | +| [Merge an existing Validator into a Staking Pool](/validators/staking/merge-validator-delegation-sc) | Learn how to merge a validator into a Staking Provider. | + + +### Useful + +| Name | Description | +| -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | +| [Node operation modes](/validators/node-operation-modes) | Learn about the nodes' operation modes and how to use them for setting up node for various use-cases. | +| [Node Configuration](/validators/node-configuration) | Learn about the nodes' configuration files: how to use them, how to override values, and so on. | +| [Rating](/validators/rating) | Learn about the nodes' rating, how it increases and decreases and how it impacts the earnings. | +| [Node Upgrade](/validators/node-upgrades) | Learn about the periodical or emergency upgrades that nodes' owner have to do. | +| [Node Redundancy / Back-up](/validators/redundancy) | How to set up a back-up node for your main machine. | +| [Import DB](/validators/import-db) | Learn how to start the node in import-db mode, that allows reprocessing of old data, without syncing the network. | +| [Node CLI](/validators/node-cli) | How to use the Command Line Interface of the Node. | +| [Node Databases](/validators/node-databases) | How to use the nodes' databases and how to copy them from one node to another. | +| [Useful link & tools](/validators/useful-links) | Useful links about the explorer, wallet and some guides. | +| [FAQ](/validators/faq) | Frequently Asked Questions about nodes. | + + +## Overview + +The MultiversX network is made up of nodes and their interconnectivity - balanced by virtue of its design, secured through its size and fast, _very_ fast, because efficiency is what motivated its development. Every time a node joins the network, it adds more security and efficiency. The network, in turn, rewards the nodes for their contribution, generating a virtuous cycle. + +We will call a _node_ any running instance of the software application developed by the MultiversX team, [publicly available as open source](https://github.com/multiversx/mx-chain-go). Anyone can run a node on their machine - great care was taken to make the node consume as little computing resources as possible. Mid-level recent hardware can effortlessly run multiple individual nodes at the same time, earning more rewards for the same physical machine. + +We will call a _node operator_ any person or entity who manages one or more nodes. These pages are for them. + + +## Background + +MultiversX is a decentralized blockchain network. This means that its nodes collaborate to create sequential **blocks** with strict regularity - blocks which contain the results of operations that were requested by the users of the network. Such operations may be simple transfers of tokens, or may be calls to SmartContracts. Either way, _all_ operations take the form of **transactions**. + +Any user who submits a transaction to the network must pay a fee, in EGLD tokens. These fees are what produces **rewards** for the nodes. + +Note that not all nodes earn rewards from these fees. Only **validator nodes** qualify, because they are the nodes which are allowed to take part in [consensus](/learn/consensus), to produce and validate blocks and to earn rewards. + +Because of the influence they have in the network, validator nodes are required to have a **stake**, which is a significant amount of EGLD locked as collateral for the good behavior of the validator. Currently, the stake amount is set to 2500 EGLD. Nodes without a stake are called **observer nodes** - they don't participate in consensus and do not earn rewards, but they support the network in different ways. + +If the validator consistently misbehaves or performs malicious actions, it will be fined accordingly and lose EGLD, an action known as _stake slashing,_ and by also having its validator status removed. This form of punishment is reserved for serious offences. + +Validator nodes each have an individual **rating score**, which expresses their overall reliability and responsiveness. Rating will increase for well-behaved nodes: every time a validator takes part in a successful consensus, its rating is increased. + +The opposite is also true: a validator which is either offline during consensus or fails to contribute to the block being produced will be considered unreliable. And a consistently unreliable validator will see its rating drop. + +**Consensus selection probability** is strongly influenced by a validators rating. The consensus process _favors validators with high rating_ and will avoid selecting validators with low rating. + +This implies that a node with high rating produces far more rewards than a node with low rating, so it is essential that operators maintain their validators online, up-to-date and responsive. + +Moreover, if the rating of a validator becomes too low, it will be **jailed**. A jailed validator will not be selected for consensus - thus earning no rewards. To restore the validator, it must be **unjailed**, which requires a fine to be paid, currently set to 2.5 EGLD. + +--- + +### Wallet Keys + +This page describes the wallet keys, that are used for staking and managing nodes. + + +## Wallet keys description + +As a Validator you use the Wallet Keys to access the address from which you send the staking transaction. Your EGLD holdings leave this address and are deposited into a staking smart contract. Rewards are sent to this address. You can change it later on by using a `changeRewards` transaction. + +This wallet is the only one that can be used to send an un-stake transaction, meaning to recover your 2500 EGLD from the staking smart contract. + +A Wallet Key can be created via multiple ways that are described on the [Wallets section](/wallet/overview/). + +The wallets use the bip44 standard with the mention that because MultiversX uses Ed25519 only hardened paths are used. Our coin_type is 508, making the path for the first address:m/44'/508'/0'/0'/0’ + +--- + +## Integrators +### Accounts Management + +Managing Wallets and Addresses + +This page summarizes the recommended approach for managing accounts in an application that integrates with the Network. + +:::tip +If integrating a **system** with the **Network** involves transfers between different users (accounts) - a good example for this case is the integration between an **exchange system** and the **Network** - the recommended approach is to have **a MultiversX Account (Address) for each user of the system**. +::: + +Accounts creation can be achieved through different approaches: + +- using the [MultiversX Web Wallet](https://wallet.multiversx.com/) +- programmatically, using the [sdk-js - JavaScript SDK](/sdk-and-tools/sdk-js) +- programmatically, using the [mxpy - Python SDK](/sdk-and-tools/sdk-py/) +- programmatically, using the [sdk-go - Golang SDK](/sdk-and-tools/sdk-go) +- programmatically, using the [sdk-java - Java SDK](/sdk-and-tools/mxjava) +- using the [lightweight CLI](https://www.npmjs.com/package/@multiversx/sdk-wallet-cli) +- using our [lightweight HTTP utility](https://github.com/multiversx/mx-sdk-js-wallet-http) +- programmatically, using the [TrustWalletCore extension](https://github.com/trustwallet/wallet-core/tree/master/src/MultiversX) for MultiversX + +--- + +### Advanced Observer Settings + +This page describes some of the settings an integrator might want to apply on the observers in order to better make use of the nodes. + +For the settings from the `config.toml` file that are needed to be altered, we recommend using the `OverridableConfigTomlValues` section found in the `prefs.toml` file. More info can be found [here](/validators/node-configuration#overriding-configtoml-values) + + +## Web antiflood settings + +Each node, either observer or validator, will be configured automatically with the web antiflood turned on and set to relatively low limits. This was chosen as a protection measure during the initial setup of the node but can be easily changed through the configuration files. + +The original section found in the `config.toml` file looks like this: + +```toml +[WebServerAntiflood] + WebServerAntifloodEnabled = true + # SimultaneousRequests represents the number of concurrent requests accepted by the web server + # this is a global throttler that acts on all http connections regardless of the originating source + SimultaneousRequests = 100 + # SameSourceRequests defines how many requests are allowed from the same source in the specified + # time frame (SameSourceResetIntervalInSec) + SameSourceRequests = 10000 + # SameSourceResetIntervalInSec time frame between counter reset, in seconds + SameSourceResetIntervalInSec = 1 + # TrieOperationsDeadlineMilliseconds represents the maximum duration that an API call targeting a trie operation + # can take. + TrieOperationsDeadlineMilliseconds = 10000 + # GetAddressesBulkMaxSize represents the maximum number of addresses to be fetched in a bulk per API request. 0 means unlimited + GetAddressesBulkMaxSize = 100 + # VmQueryDelayAfterStartInSec represents the number of seconds to wait when starting node before accepting vm query requests + VmQueryDelayAfterStartInSec = 120 + # EndpointsThrottlers represents a map for maximum simultaneous go routines for an endpoint + EndpointsThrottlers = [{ Endpoint = "/transaction/:hash", MaxNumGoRoutines = 10 }, + { Endpoint = "/transaction/send", MaxNumGoRoutines = 2 }, + { Endpoint = "/transaction/simulate", MaxNumGoRoutines = 1 }, + { Endpoint = "/transaction/send-multiple", MaxNumGoRoutines = 2 }] +``` + +The general off/on switch is done through the `WebServerAntifloodEnabled` config value. As an integrator you might want to turn off the web antiflooder, **in case the node is not directly reachable over the Internet** as a mean to provide your gateway instance (proxy) to use as many node resources it wants. +This is the easiest way to tell the node to not bother with that type of antiflooding. However, this does not imply the integrator to remove all the web antiflooding protection, especially before his/her owned gateway instance (proxy). + +The following list explains what the parameters do: +- `SimultaneousRequests` tells the node how many REST API calls can simultaneous serve; +- `SameSourceRequests` tells how many REST API requests originating from the same source the node can serve per time unit. The time unit is specified in the `SameSourceResetIntervalInSec` parameter and is expressed in seconds; +- `TrieOperationsDeadlineMilliseconds` tells the node when it should time out the REST API requests that involves data trie traversing. This should be increased in case the node is trying to fetch data from large smart contracts or user accounts; +- `GetAddressesBulkMaxSize` tells the node what is the maximum number of addresses that can be fetched in a bulk-get operation; +- `VmQueryDelayAfterStartInSec` is a parameter that should is taken into account only when the node is started. The vm-query requests are not executed in this initial startup phase; +- `EndpointsThrottlers` is a list that defines some REST API endpoints and their maximum simultaneous requests that the node can handle. + + +## VM settings for query operations + +The node can handle one or more query-services able to simultaneous execute vm queries. + +The original section found in the `config.toml` file looks like this: + +```toml +[VirtualMachine.Querying] + NumConcurrentVMs = 1 +``` + +The `NumConcurrentVMs` value specifies the number of VMs instances allocated for vm-query operations. The higher this value is, the more concurrent vm-query operation the node can handle. + +:::important +The increased number of VMs allocated for the vm-query engine will put pressure on the RAM allocation for the node process at around 500-600 MB / extra instance. Disk & CPU utilization will also get higher in a proportional manner. +::: + + +## UserAccounts cache parameters + +As found during heavy testing, spending more RAM on caches might help in vm-query execution. The reason behind this statement is that, usually, smart contract execution need smart contract data to be read from storage and then processed. +The data fetch mechanism costs in terms of CPU cycles and disk utilization, so they affect the execution time of the vm-query. +If the node has more than the [minimum required RAM](/validators/system-requirements) then the following section from the `config.toml` file can be altered: + +```toml +[AccountsTrieStorage] + [AccountsTrieStorage.Cache] + Name = "AccountsTrieStorage" + Capacity = 500000 + Type = "SizeLRU" + SizeInBytes = 314572800 #300MB +``` + +- `Capacity` can be increased to 10000000 or to a larger value; +- `SizeInBytes` can be increased to `1073741824` (1GB) or even pushed to `4294967296` (4GB). + +--- + +### Creating Transactions + +This page describes how to create, sign and broadcast transactions to the MultiversX Network. + + +## **Transaction structure** + +As described in section [Signing Transactions](/developers/signing-transactions), a ready-to-broadcast transaction is structured as follows: + +```json +{ + "nonce": 42, + "value": "100000000000000000", + "receiver": "erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r", + "sender": "erd1ylzm22ngxl2tspgvwm0yth2myr6dx9avtx83zpxpu7rhxw4qltzs9tmjm9", + "gasPrice": 1000000000, + "gasLimit": 70000, + "data": "Zm9vZCBmb3IgY2F0cw==", + "chainID": "1", + "version": 1, + "signature": "5845301de8ca3a8576166fb3b7dd25124868ce54b07eec7022ae3ffd8d4629540dbb7d0ceed9455a259695e2665db614828728d0f9b0fb1cc46c07dd669d2f0e" +} +``` + + +## **SDK and tools support for creating and signing transactions** + +There are SDKs or tools with support for interacting with the MultiversX blockchain, so one can use one of the following SDKs to perform +transactions creation and signing: + +- [sdk-js - JavaScript SDK](/sdk-and-tools/sdk-js) +- [sdk-py - Python SDK](/sdk-and-tools/sdk-py) +- [sdk-go - Golang SDK](/sdk-and-tools/sdk-go) +- [sdk-java - Java SDK](/sdk-and-tools/mxjava) +- [lightweight JS CLI](https://www.npmjs.com/package/@multiversx/sdk-wallet-cli) +- [lightweight HTTP utility](https://github.com/multiversx/mx-sdk-js-wallet-http) + + +## **General network parameters** + +General network parameters, such as the **chain ID**, **the minimum gas price**, **the minimum gas limit** and the **oldest acceptable transaction version** are available at the API endpoint [Get Network Configuration](/sdk-and-tools/rest-api/network#get-network-configuration). + +```json +{ + "config": { + "erd_chain_id": "1", + "erd_gas_per_data_byte": 1500, + "erd_min_gas_limit": 50000, + "erd_min_gas_price": 1000000000, + "erd_min_transaction_version": 1, + ... + } +} +``` + + +## **Nonce management** + +Each transaction broadcasted to the Network must have the **nonce** field set consistently with the **account nonce**. In the Network, transactions of a given sender address are processed in order, with respect to the transaction nonce. + +The account nonce can be fetched from the API: [Get Address Nonce](/sdk-and-tools/rest-api/addresses#get-address-nonce). + +**The nonce must be a strictly increasing number, scoped to a given sender.** The sections below describe common issues and possible solutions when managing the nonce for transaction construction. + + +### **Issue: competing transactions** + +Broadcasted transactions that reach the _mempool_ having the same sender address and the same nonce are _competing transactions_, and only one of them will be processed (the one providing a higher gas price or, if they have the same gas price, the one that arrived the second - but keep in mind that arrival time is less manageable). + +:::tip +Avoid competing transactions by maintaining a strictly increasing nonce sequence when broadcasting transactions of the same sender address. +::: + +Although an explicit _transaction cancellation trigger_ is not yet available in the Network, cancellation of a transaction T1 with nonce 42 could be _possible_ if one broadcasts a second transaction T2 with same nonce 42, with higher gas price (and without a value to transfer) **immediately** (e.g. 1 second) after broadcasting T1. + + +### **Issue: nonce gaps** + +If broadcasted transactions have their nonces higher than the current account nonce of the sender, this is considered a _nonce gap_, and the transactions will remain in the mempool unprocessed, until new transactions from the same sender arrive _to resolve the nonce gap -_ or until the transactions are swept from the mempool (sweeping takes place regularly). + +:::tip +**Avoid nonce gaps** by regularly fetching the current account nonce, in order to populate the nonce field correctly before broadcasting the transactions. This technique is also known as **periodically recalling the nonce**. +::: + + +### **Issue: too many transactions from the same account** + +Starting with the [Sirius Mainnet Upgrade](https://github.com/multiversx/mx-specs/blob/main/releases/protocol/release-specs-v1.6.0-Sirius.md), the transaction pool allows a maximum of **100** transactions from the same sender to exist, at a given moment. + +For example, if an address broadcasts `120` transactions with nonces from `1` to `120`, then the transactions with nonces `1 - 100` will be accepted for processing, while the remaining `20` transactions will be dropped. + +The solution is to use chunks holding a **maximum of `100` transactions** and a place a generous **delay between sending the chunks**. Let's suppose an account has the nonce `1000` and it wants to send `120` transactions. It should send the first chunk, that is, the transactions with nonces `1000 - 1099`, wait until all of them are processed (the account nonce increments on each processed transaction), then send the second chunk, the transactions with nonces `1100 - 1019`. + + +### **Issue: fetching a stale account nonce** + +You should take care when fetching the current account nonce from the API immediately after broadcasting transactions. + +Example: + +1. Time 12:00:01 - the sender's nonce is recalled, and its value is 42 +2. Time 12:00:02 - the sender broadcasts the transaction T1 with nonce 42 +3. Time 12:00:03 - the sender's nonce is recalled again, in order to broadcast a new transaction. **The nonce is still 42. It is stale, not yet incremented on the Network (since T1 is still pending or being processed at this very moment).** +4. Time 12:00:04 - the sender broadcasts T2 with nonce 42, which will compete with T1, as they have the same nonce. + +:::tip +Avoid fetching stale account nonces by **periodically recalling the nonce.** + +Avoid recalling the nonce in between **rapidly sequenced transactions from the same sender** . For rapidly sequenced transactions, you have to programmatically manage, keep track of the account nonce using a **local mirror (copy) of the account nonce** and increment it appropriately. +::: + + +### **Issue: sending large batches of transactions cause nonce gaps** + +Whenever sending a large batch of transactions, even if the node/gateway returned transaction hashes for each transaction in the batch and no error, there is no strict guarantee that those transactions will end up being executed. +The reason is that the node will not immediately send each transaction or transaction batch but rather accumulate them in packages to be efficiently send through the p2p network. +At this moment, the node might decide to drop one or more packet because it detected a possible p2p flooding condition. This can happen independent of the transaction sender, the number of transactions sent and so on. + +To handle this behavior, special care should be carried by the integrators. One possible way to handle this efficiently is to temporarily store all transactions that need to be sent on the network and continuously monitor the senders accounts involved if their nonces increased. +If not, a resend of the required transaction is needed, otherwise the transaction might be discarded from the temporary storage as it was executed. + +We have implemented several components written in GO language that solve the transaction send issues along with the correct nonce management. +The source code can be found [here](https://github.com/multiversx/mx-sdk-go/tree/main/interactors/nonceHandlerV2) +The main component is the `nonceTransactionsHandlerV2` that will create an address-nonce handler for each involved address. This address nonce handler will be specialized in the nonce and transactions sending mechanism for a single address and will be independent of the other addresses involved. +The main component has a few exported functionalities: +- `ApplyNonceAndGasPrice` method that is able to apply the current handled nonce of the sender and the network's gas price on a provided transaction instance +- `SendTransaction` method that will forward the provided transaction towards the proxy but also stores it internally in case it will need to be resent. +- `DropTransactions` method that will clean all the stored transactions for a provided address. +- `Close` cleanup method for the component. + + +## **Gas limit computation** + +Please follow [Gas and Fees](/developers/gas-and-fees/overview/). + + +## **Signing transactions** + +Please follow [Signing Transactions](/developers/signing-transactions). + + +## **Simulate transaction execution** + +:::important +Documentation about transaction simulation is preliminary and subject to change. +::: + +--- + +### Deep History Squad + +This page describes the Deep History Squad, which holds the entire trie data, so it can be used to query the state of an account at any point in time. + + +## General information + +A variant of the standard [**observing squad**](/integrators/observing-squad) is one that retains a non-pruned history of the blockchain and allows one to query the state of an account at an arbitrary block in the past. Such a setup is called a [**deep-history observing squad**](https://github.com/multiversx/mx-chain-deep-history). + +:::tip +The standard observing squad is sufficient for most use-cases. It is able to resolve past blocks, miniblocks, transactions, transaction events etc. Most often, you do not need a deep-history squad, but a [**regular observing squad**](/integrators/observing-squad), instead. +::: + +A deep-history setup is able to resolve historical account (state) queries, that is, to answer questions such as: + +> What was Alice's balance on [May the 4th](https://explorer.multiversx.com/blocks/5f6a02d6a5d2a851fd6dc1fb53435083830c2a13121e003958d97c2389711f06)? + +```bash +GET http://squad:8080/address/erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th?blockNonce=9250000 +``` + +:::tip +The API client has to perform the conversion from _desired timestamp_ to _block nonce_. Timestamp-based queries aren't directly supported yet. +::: + +> How much UTK were in the [`UTK / WEGLD` Liquidity Pool](https://explorer.multiversx.com/accounts/erd1qqqqqqqqqqqqqpgq0lzzvt2faev4upyf586tg38s84d7zsaj2jpsglugga) on [1st of October](https://explorer.multiversx.com/blocks/cefd41e1e9bbe3ba023a695f412b99cecb15ef789475648ee7c31e7d9fef31d1)? + +```markup +GET http://squad:8080/address/erd1qqqqqqqqqqqqqpgq0lzzvt2faev4upyf586tg38s84d7zsaj2jpsglugga/key/726573657276650000000a55544b2d326638306539?blockNonce=11410000 +``` + +In the example above, the key `726573657276650000000a55544b2d326638306539` is decoded as `reserve\x00\x00\x00\nUTK-2f80e9`. + + +### Historical VM queries + +Starting with the [Sirius Patch 5](https://github.com/multiversx/mx-chain-mainnet-config/releases/tag/v1.6.18.0), deep-history observers can resolve historical VM queries. Such a query specifies the `blockNonce` parameter: + +``` +POST http://localhost:8080/vm-values/query?blockNonce={...} HTTP/1.1 +Content-Type: application/json + +{ + "scAddress": "...", + "funcName": "...", + "args": [ ... ] +} +``` + + +### MultiversX squad + +The observing squads backing the public Gateways, in addition to being full history squads (serving past blocks, transactions and events up until the Genesis), also act as 3-epochs deep-history squads. That is, for **mainnet**, one can use https://gateway.multiversx.com to resolve historical account (state) queries, for the last 3 days. This interval is driven by the configuration parameter `[StoragePruning.NumEpochsToKeep]`, which is set to `4`, by default. + +In general: + +``` +... deep history not available +CurrentEpoch - NumEpochsToKeep - 1: deep history not available +CurrentEpoch - NumEpochsToKeep: deep history not available +CurrentEpoch - NumEpochsToKeep + 1: deep history partially available +CurrentEpoch - NumEpochsToKeep + 2: deep history available +CurrentEpoch - NumEpochsToKeep + 3: deep history available +... deep history available +CurrentEpoch: deep history available +``` + +In particular, for the public Gateway: + +``` +... deep history not available +CurrentEpoch - 5: deep history not available +CurrentEpoch - 4 deep history not available +CurrentEpoch - 3: deep history partially available +CurrentEpoch - 2: deep history available +CurrentEpoch - 1: deep history available +CurrentEpoch: deep history available +``` + + +### On-premises squad + +Deep-history squads can be set up on-premises, just as regular observing squads. However, the storage requirements is significantly higher. For example, a deep-history squad for **mainnet**, configured for the interval July 2020 (Genesis) - January 2024 (Sirius), requires about 7.5 TB of storage: + +``` +307G ./node-metachain +1.4T ./node-0 +3.9T ./node-1 +2.0T ./node-2 +``` + +Since each observer of a deep-history squad must have a non-pruned history, their (non-ordinary) databases have to be either **downloaded** or **reconstructed**, in advance (covered later, in detail). + + +## Observer installation and configuration + +The installation of a deep history squad is almost the same as that of a [regular observing squad](/integrators/observing-squad). +The difference is that the deep history observers are set up to retain the whole, non-pruned history of the blockchain. Therefore, the following flag must be added in the `config/variables.cfg` file before running the `observing_squad` command of the installation script: +```bash +NODE_EXTRA_FLAGS="--operation-mode=historical-balances" +``` +:::tip +Apart from the flag mentioned above, the setup of a deep-history observer is identical to a regular full-history observer. +::: + +:::warning +Never attach a non-pruned database to a regular observer (i.e. that does not have the above **operation-mode**) - unless you are not interested into the deep-history features. The regular observer irremediably removes, truncates and prunes the data (as configured, for storage efficiency). +::: + +Now that we have finished with the installation part, we can proceed to populate the non-pruned database. There are two options here: +- Reconstruct non-pruned database (recommended). +- Download non-pruned database (we can provide archives for the required epochs, on request). + + + +## Reconstructing non-pruned databases + +The recommended method for populating a non-pruned database is to reconstruct it locally (on your own infrastructure). + +There are also two options for reconstructing a non-pruned database: +- Based on the **[import-db](/validators/import-db/)** feature, which re-processes past blocks - and, while doing so, retains the whole, non-pruned accounts history. +- By performing a regular sync from the network (e.g. from Genesis), using a properly configured deep-history observer. + +:::note +The reconstruction flow has to be performed **for each shard, separately**. +::: + +### Reconstructing using import-db + +First, you need to decide whether to reconstruct a **complete** or a **partial** history. A _complete_ history provides the deep-history squad the ability to resolve historical account (state) queries **up until the Genesis**. A _partial_ history, instead, allows it to resolve state queries up until **a chosen past epoch** or **between two chosen epochs**. + + +#### Reconstruct a complete history + +:::note +Below, the reconstruction is exemplified for **mainnet, shard 0**. The same applies to other networks (devnet, testnet) and shards (including the metachain). +::: + + +Next, we need to obtain (download) and extract **a recent daily archive (snapshot)** for the shard in question (e.g. shard 0). The daily archives are available to download **on request** ([Discord](https://discord.gg/multiversxbuilders) or [Telegram](https://t.me/MultiversXValidators)), from a cloud-based, _S3-compatible storage_ (Digital Ocean Spaces) - or you could fetch them from an existing regular full-history observer that you own. + +Upon extracting the downloaded archive, you'll have a new folder in your workspace: `db`, which contains the blockchain data for the shard in question. This data should be moved to a new folder, named `import-db`. All in all, the steps are as follows: + +``` +# Ask for the full download link on Discord or Telegram: +wget https://.../23-Jan-2024/Full-History-DB-Shard-0.tar.gz +# This produces a new folder: "db" +tar -xf Full-History-DB-Shard-0.tar.gz + +# "import-db" should contain the whole blockchain to be re-processed +mkdir -p ./import-db && mv db ./import-db +# "db" will contain the reconstructed database (empty at first) +mkdir -p ./db +``` + +:::note +Downloading the archives and extracting them might take a while. +::: + +When reconstructing the whole history, the workspace should look as follows (irrelevant files omitted): + +``` +. +├── config # network configuration files +│ ├── api.toml +│ ├── config.toml +│ ... +├── db # empty +├── import-db +│ └── db # blockchain data +│ └── 1 +│ │ ... +│ ├── Epoch_1270 +│ │ └── Shard_0 +│ ├── Epoch_1271 +│ │ └── Shard_0 +│ ├── Epoch_1272 +│ │ └── Shard_0 +│ └── Static +│ └── Shard_0 +├── node # binary file, the Node itself +``` + +We are ready to start the reconstruction process :rocket: + +``` +./node --import-db=./import-db --operation-mode=historical-balances --import-db-no-sig-check --log-level=*:INFO --log-save --destination-shard-as-observer 0 +``` + +:::note +The reconstruction (which uses _import-db_ under the hood, as previously stated) takes a long time (e.g. days) - depending on the machine's resources (CPU, storage capabilities and memory). +::: + +Once the **import-db** is over, the `db` folder contains the reconstructed database for the shard in question, ready to support historical account (state) queries. + +Now, do the same for the other shards, as needed. + +:::tip +You can smoke test the data by launching an (in-place) ephemeral observer: + +``` +./node --operation-mode=historical-balances --log-save --destination-shard-as-observer 0 +``` + +Then, in another terminal, do a historical (state) query against the ephemeral observer: + +``` +curl http://localhost:8080/address/erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx?blockNonce=42 | jq +``` +::: + + +#### Reconstruct a partial history + +:::tip +Make sure to read the section [**Reconstruct a complete history**](#reconstruct-a-complete-history) first, to understand the basics. +::: + +Instead of reconstructing the whole history since Genesis, you may want to reconstruct a partial history, starting from a chosen epoch up until the latest epoch, or a history between two chosen epochs. + +Below, we'll take the example of reconstructing the history for epochs `1255 - 1260` (inclusive), for **mainnet, shard 0**. + +Since we'd like to stop upon reconstruction of epoch `1260` (our example), we enable need to enable the `BlockProcessingCutoff` feature in `config/prefs.toml`: + +``` +[BlockProcessingCutoff] + Enabled = true + Mode = "pause" + CutoffTrigger = "epoch" + # The chosen end epoch, plus 1. + Value = 1261 +``` + +Now, get (download) and extract **two daily archives (snapshots)** for the shard in question (e.g. shard 0): one archive that was created on the epoch preceding the **chosen start epoch** (e.g. the archive created during epoch `1255 - 1 = 1254`); and one archive that was created on the epoch following the **chosen end epoch** (e.g. the archive created during epoch `1260 + 1 = 1261`). You can compute the correct URLs for the two archives manually, given the [**mainnet** Genesis time](https://gateway.multiversx.com/network/config) and the chosen epochs, or you can use the following Python snippet: + +``` +import datetime + +# Available on request (Discord or Telegram) +url_base = "https://..." +shard = 0 +chosen_start_epoch = 1255 +chosen_end_epoch = 1260 +genesis_timestamp = 1596117600 + +genesis_datetime = datetime.datetime.fromtimestamp(genesis_timestamp, tz=datetime.timezone.utc) +first_archive_day = (genesis_datetime + datetime.timedelta(days=chosen_start_epoch - 1)).strftime("%d-%b-%Y") +second_archive_day = (genesis_datetime + datetime.timedelta(days=chosen_end_epoch + 1)).strftime("%d-%b-%Y") + +print("First daily archive:", f"{url_base}/{first_archive_day}/Full-History-DB-Shard-{shard}.tar.gz") +print("Second daily archive:", f"{url_base}/{second_archive_day}/Full-History-DB-Shard-{shard}.tar.gz") +``` + +The first archive should be used as `db` and the second archive should be used as `import-db/db`. + +``` +# Download & extract first archive to "db": +wget https://.../05-Jan-2024/Full-History-DB-Shard-0.tar.gz +tar -xf Full-History-DB-Shard-0.tar.gz + +# Download & extract second archive to "import-db/db": +wget https://.../12-Jan-2024/Full-History-DB-Shard-0.tar.gz +mkdir -p ./import-db +tar -xf Full-History-DB-Shard-0.tar.gz --directory ./import-db +``` + +When reconstructing a partial history, the workspace should look as follows (irrelevant files omitted): + +``` +. +├── config # network configuration files +│ ├── api.toml +│ ├── config.toml +│ ... +├── db # blockchain data (second archive) +│ └── 1 +│ │ ... +│ ├── Epoch_1252 +│ │ └── Shard_0 +│ ├── Epoch_1253 +│ │ └── Shard_0 +│ ├── Epoch_1254 +│ │ └── Shard_0 +│ └── Static +│ └── Shard_0 +├── import-db +│ └── db # blockchain data (first archive) +│ └── 1 +│ │ ... +│ ├── Epoch_1259 +│ │ └── Shard_0 +│ ├── Epoch_1260 +│ │ └── Shard_0 +│ ├── Epoch_1261 +│ │ └── Shard_0 +│ └── Static +│ └── Shard_0 +├── node # binary file, the Node itself +``` + +We are now ready to start the reconstruction process :rocket: + +``` +./node --import-db=./import-db --operation-mode=historical-balances --import-db-no-sig-check --log-level=*:INFO --log-save --destination-shard-as-observer 0 +``` + +Once the **import-db** is over, the `db` folder will contain the archive for the deep-history observer to support historical account (state) queries for the epochs `1255 - 1260`. + +:::warning +Make sure to set the **BlockProcessingCutoff** back to `false` before starting an observer intended to continue processing blocks past the cutoff. +``` +[BlockProcessingCutoff] + Enabled = false +``` +::: + +### Reconstructing by performing a regular sync + +This is the simpler approach (even if it takes a bit more time, depending on the availability of peers, plus the network traffic). + +Basically, if the required [node flag configuration](#observer-installation-and-configuration) was set at installation, all that's left to be done is to start the node and it will begin building the database by synchronizing from the network since Genesis. + +On the other hand, if the observer only needs to support historical account (state) queries starting from a specific past epoch, we again need to download a daily archive (snapshot) for the start epoch in question and extract it in the **db** folder, then proceed to start the node and it will begin building the database by synchronizing from the network starting with the last block available in the **db** folder. + + +## Starting a squad + +Suppose you have prepared the data for a deep-history squad beforehand, whether by downloading it or by reconstructing it locally. Then, the deep-history data root folder should look as follows: + +``` +. +├── node-0 +│ └── db +├── node-1 +│ └── db +├── node-2 +│ └── db +└── node-metachain + └── db + +``` + +The squad and the proxy can be started using the command: + +```bash +~/mx-chain-scripts/script.sh start +``` + +Alternatively, you can set up a squad using any other known approach, **but make sure to apply the proper `operation-mode`** described in the section [**Observer installation and configuration**](#observer-installation-and-configuration). + +**Congratulations!** You've set up a deep-history observing squad; the gateway should be ready to resolve historical account (state) queries :rocket: + +--- + +### EGLD integration guide + +This section provides high-level technical requirements of integrating the MultiversX's native coin, EGLD in a platform that handles EGLD transactions for their users. + + +## Overview + +In order to make possible for a platform to integrate EGLD transactions for its users, these are the minimum requirements: + +- [setting up an observing squad](/integrators/observing-squad) +- [setting up a mechanism for accounts management](/integrators/accounts-management) +- [setting up a mechanism for creating and signing transactions](/integrators/creating-transactions) +- [setting up a mechanism that queries the blockchain for new transactions to process](/integrators/querying-the-blockchain/#querying-hyperblocks-and-fully-executed-transactions) + + +## Integration workflow + +An integration could mean an automatic system that parses all the transactions on the chain and performs different +actions when an integrator's address is the sender or receiver of the transaction. Based on that, it should be able +to sign transactions or update the user's balance internally. Also, different things such as hot wallets can be +integrated as well for a better tokens management and less EGLD spent on gas. + +In order to summarize the information and bring all the pieces together, this section will provide an example of how an integration can look: + + +### 1. Observing squad running + +The integrator has an observing squad (an observer on each shard + proxy) running and synced. + + +### 2. Getting hyperblock by nonce + +The system should always memorize the last processed nonce. After processing a hyperblock at a given nonce, it should +move on to the hyperblock that corresponds to the next nonce (when available, if not already existing). + +In order to fetch the hyperblock for a given nonce, the system should perform an API call to `/hyperblock/by-nonce/`. + +If the response contains an error, it probably means that the nonce isn't yet processed on the chain and a retry should be done after a small waiting period. + +:::tip +A round in the blockchain is set to 6 seconds, so the nonce should change after a minimum of 6 seconds. +A good refresh interval for nonce-changing detection could be 2 seconds. +::: + + +#### 2.1. Fallback mechanism + +If, for example, a server issue occurs and the observing squad gets stuck, the latest processed nonce must be saved +somewhere so when the observing squad is back online, the system should continue processing from the next nonce after the saved one. + + +#### 2.2. Example + +For example, when the system is up, it should start processing from a nonce in the same epoch. Let's say the chain is in epoch +5 and the first hyperblock nonce in that epoch is 900 + +``` +... +-> fetched hyperblock with nonce 900 +-> processed hyperblock with nonce 900 +-> saved last processed nonce = 900 +-> waiting 2 seconds +-> fetching hyperblock with nonce 901: API error (nonce not yet processed on chain side), skip +-> waiting 2 seconds +-> fetching hyperblock with nonce 901: API error (nonce not yet processed on chain side), skip +-> waiting 2 seconds +-> fetched the hyperblock with nonce 901 +-> processed hyperblock with nonce 901 +-> saved last processed nonce = 901 +-> waiting 2 seconds +... +``` + +:::caution +Keep in mind that a hyperblock shouldn't be processed twice as this might cause issues. +Make sure the block processing and the saving of the last processed nonce should be atomic. +::: + + +#### 2.3. Querying the transactions + +The system fetches the response and iterates over each successful transaction and determine if any address from the integrator is involved. + + +### 3. Transaction handling + +After identifying a relevant transaction in step 2.3 (the sender or the receiver is an integrator's address) actions could be taken on integrator's side. + +It is recommended that the integrator performs some balances checks before triggering internal transfers. + +For example, if the receiver is an integrator's address, the integrator can update its balance on internal storage systems. + + +### Mentions + +- steps 2 and 3 should be executed in a continuous manner while always keeping record of the last processed nonce, in order to ensure + that no transaction is skipped. +- other usual actions such as transferring (from time to time) all addresses funds to a hot wallet could also be implemented. + + +## Finality of the transactions / number of confirmations + +The hyperblock includes only finalized transactions so only one confirmation is needed. The integrator however has the flexibility to wait for any number of additional confirmations. + + +## Balances check + +From time to time, or for safety reasons before performing a transaction, an integrator would want to check the balance of some +addresses. This can be performed via [Get address balance endpoint](/sdk-and-tools/rest-api/addresses#get-address-balance). + + +## Useful tools and examples + +MultiversX SDKs or tools can be used for signing transactions and performing accounts management. + +A complete list and more detailed information can be found on the [accounts management](/integrators/accounts-management) and +[signing transaction](/integrators/creating-transactions) sections. + +There is also an example that matches the above-presented workflow and can be found on the Go SDK for MultiversX, [sdk-go](https://github.com/multiversx/mx-sdk-go/tree/main/examples/examplesFlowWalletTracker). + +However, other SDKs can be used as well for handling accounts management or transaction signing. + +--- + +### ESDT tokens integration guide + +## **Introduction** +Integrating ESDT tokens support can be done alongside native EGLD integration, so one should refer to the [egld-integration-guide](/integrators/egld-integration-guide). + +The only differences are internal ways to store ESDT tokens alongside with their token identifier and number of decimals and different approaches +for identifying and parsing ESDT transactions. + + +## **ESDT transactions parsing** +Considering that the platform which wants to support ESDT tokens already supports EGLD transfers, this section will +provide the additional steps that are to be integrated over the existing system. + +If so, all the transactions on the network are being parsed so the integrator will check if the sender or receiver of the transaction +is an address that is interested in. +In addition to this, one can check if the transaction is a successful ESDT transfer. If so, then the transferred token, the amount and the +receiver can be further parsed. + +One has to follow these steps: +- check if the transaction is an ESDT transfer (data field format is `ESDTTransfer@@`. More details [here](/tokens/fungible-tokens#transfers)) +- parse the tokens transfer details from Logs&Events. More details [here](/tokens/fungible-tokens#parse-fungible-tokens-transfer-logs) + + +### Example +Let's suppose we are watching these addresses: +- `erd1zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3z92425g3zygs3mthts` +- `erd1venxvenxvenxvenxvenxvenxvenxvenxvenxvenxvenxvenxvenq5ezmpv` + +And we support the following tokens: +- `TKN-99hh44` (hex encoded: `544b4e2d393968683434`) +- `TKN2-77hh33` (hex encoded: `544b4e322d373768683333`) + +Therefore, we will look for transactions that have the following structure: +``` +TokenTransferTransaction { + Sender: erd1.... + Receiver: erd1zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygspavcaj OR erd1venxvenxvenxvenxvenxvenxvenxvenxvenxvenxvenxvenxvenq5ezmpv + Value: 0 + GasLimit: 60000000 + Data: "ESDTTransfer" + + "@" + // 544b4e2d393968683434 OR 544b4e322d373768683333 + "@" +} +``` +*For more details about how arguments have to be encoded, check [here](/developers/sc-calls-format).* + +Any other transaction that does not follow this structure has to be omitted. + +When we find such a transaction, we will fetch the logs of the transaction, as described [here](/tokens/fungible-tokens#parse-fungible-tokens-transfer-logs). +The logs will provide us information about the receiver, the token and the amount to be transferred. If the log is there, we are sure that +the transfer is successful, and we can start processing with the extracted data. + + +## **Sending ESDT tokens** +Sending ESDT tokens to a given recipient can be done via preparing and broadcasting to the network a transaction that +follows the format described [here](/tokens/fungible-tokens#transfers). + +Also, there is support for building tokens transfer transaction on many SDKs. A few examples are: +- [sdk-js - ESDTTransferPayloadBuilder](https://github.com/multiversx/mx-sdk-js-core/blob/main/src/tokenTransferBuilders.ts) +- [erdjava - ESDTTransferBuilder](https://github.com/multiversx/mx-sdk-erdjava/blob/main/src/main/java/multiversx/esdt/builders/ESDTTransferBuilder.java) + + +## **Balances check** +From time to time, or for safety reasons before performing a transaction, an integrator would want to check the tokens balance of some +addresses. This can be performed via [Get address token balance endpoint](/tokens/fungible-tokens#get-balance-for-an-address-and-an-esdt-token). + + +## **Getting tokens properties** +Each token has some properties such as the name, the ticker, the token identifier or the number of decimals. +These properties can be fetched via an API call described [here](/tokens/fungible-tokens#get-esdt-token-properties). + + +## **Useful tools** +- ESDT documentation can be found [here](/tokens/fungible-tokens). +- ESDT API docs can be found [here](/tokens/fungible-tokens#rest-api). +- sdk-js helper functions can be found [here](https://github.com/multiversx/mx-sdk-js-core/blob/release/v9/src/esdtHelpers.ts). +- sdk-js token transfer transactions builder can be found [here](https://github.com/multiversx/mx-sdk-js-core/blob/main/src/tokenTransferBuilders.ts). +- erdjava token transfer transactions builder can be found [here](https://github.com/multiversx/mx-sdk-erdjava/blob/main/src/main/java/multiversx/esdt/builders/ESDTNFTTransferBuilder.java). +- [@multiversx/sdk-transaction-decoder](https://www.npmjs.com/package/@multiversx/sdk-transaction-decoder). + +--- + +### Frequently Asked Questions + +[comment]: # "mx-abstract" + +This page contains answers to frequently asked questions about connecting an application to MultiversX, be it an exchange, wallet, dApp, Web3 indexer or data provider. + +## General information + +### What is the native token of MultiversX? + +**EGLD** on Mainnet, **XeGLD** on Devnet and Testnet. The atomic unit of the native token (think of `wei` for Ethereum) is not named. + +``` +1 EGLD = 10^18 atomic units = 1000000000000000000 atomic units +``` + +See [constants](/developers/constants). + +### What kind of consensus does MultiversX use? + +See more at [secure proof of stake](/learn/consensus). + +### What is the block time (round duration)? + +The block time (round duration) is [6 seconds](/developers/constants). Also see [the welcome page](/welcome/welcome-to-multiversx). + +### Does MultiversX employ sharding? + +Currently, the MultiversX network has 3 regular shards, plus a special one, called the _metachain_ - this arrangement holds not only on _mainnet_, but also on _devnet_ and _testnet_. + +Transactions between accounts assigned to the same shard are called _intra-shard transactions_. Transactions between accounts located in distinct shards are called _cross-shard transactions_. + +More details about the sharded architecture of MultiversX can be found [here](/learn/sharding). +Integrators may choose to have a unified view of the network, leveraging the [hyperblock](/integrators/egld-integration-guide) abstraction. + +## Wallet + +### What signature scheme does MultiversX use? + +For transactions, [ed25519](/developers/signing-transactions) is used. + +### What BIP-0044 coin type is being used? + +CoinType is **508**, according to: [SLIP-0044](https://github.com/satoshilabs/slips/blob/master/slip-0044.md). + +### What is the derivation path for wallets? + +The derivation path is `m/44'/508'/0'/0'/{address_index}'`. That is, the _account index_ stays fixed at `0`, while the _address index_ is allowed to vary. + +## Transactions + +### What is the schema of a transaction? + +See [transactions](/learn/transactions). + +### How to determine the status of a transaction? + +See [querying the blockchain](/integrators/querying-the-blockchain). + +### What can be said about transactions finality? + +A transaction is final when the block or blocks (for cross-shard transactions) that notarize it have been declared **final**. +Generally speaking, a transaction can be considered final as soon as it presents the _hyperblock coordinates_ (hyperblock nonce and hyperblock hash) when queried from the network, and these coordinates are under (older than) the [latest final hyperblock](/integrators/querying-the-blockchain#querying-finality-information). + +For more details, see [integration guide](/integrators/egld-integration-guide) and [querying the blockchain](/integrators/querying-the-blockchain). + +## Accounts + +### How does an address look like? + +An **account** is identified by an **address**, which is the **bech32-encoded** public key of the account. +For the **bech32** encoding, the human-readable part (HRP) is `erd`. + +### Types of accounts + +Both _regular user accounts_ and _smart contract accounts_ can hold tokens: EGLD and ESDT tokens (fungible, semi-fungible or non-fungible ones). + +Regular user accounts can sign transactions using their private key. Smart contracts cannot create actual transactions, as they cannot sign them. Instead, they interact with other accounts by crafting so-called _unsigned transactions_ (or smart contract results). + +### How are accounts created? + +Regular user accounts get created on the blockchain when the corresponding address receives tokens for the first time. On the other hand, (user-defined) smart contract accounts are created when a smart contract is deployed. The address of a smart contract is a function of `[address of the deployer, nonce of deployment transaction]`. For a smart contract account, there is no (known) private key. + +### How to distinguish between a normal account and a smart contract? + +Examples of addresses: + +- **regular user account:** `erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th` +- **smart contract:** `erd1qqqqqqqqqqqqqpgqq66xk9gfr4esuhem3jru86wg5hvp33a62jps2fy57p` + +If the address (decoded as bytes) has a prefix of 8 bytes of `0x00`, then it refers to a smart contract. + +## Smart Contracts + +There are two types of smart contracts on MultiversX: **system smart contracts** and **user-defined smart contracts**. System smart contracts are coded into the Protocol itself, while user-defined smart contracts are developed and deployed by users. The latter are written primarily in **Rust**. The Virtual Machine uses the **WASM** format, by leveraging the [Wasmer](https://wasmer.io/) engine. + +### How to detect contract deployment events? + +Look for events of type [`SCDeploy`](/developers/event-logs/contract-deploy-events). + +### Is it possible to upgrade a smart contract? + +Yes, if the `upgradeable` flag is set in the contract's [metadata](/developers/data/code-metadata). Also see [upgrading smart contracts](/developers/developer-reference/upgrading-smart-contracts). + +--- + +### Integrators - Overview + +## Introduction + +If you want to integrate the MultiversX Network in your app, even if we are talking about an exchange, wallet, or a dApp that +uses its own infrastructure, please choose a direction from the following table + + +## Table of contents + +| Name | Description | +| ----------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | +| [EGLD integration guide](/integrators/egld-integration-guide) | How to integrate MultiversX's native token EGLD. | +| [ESDT tokens integration guide](/integrators/esdt-tokens-integration-guide) | How to integrate MultiversX's ESDT tokens. | +| [Observing squad](/integrators/observing-squad) | How to run an infrastructure with a general view over all the shards. | +| [Snapshotless Observing squad](/integrators/snapshotless-observing-squad) | How to set up a light Observing squad with access to real time state only. | +| [Deep-history squad](/integrators/deep-history-squad) | How to set up an Observing squad able to resolve historical state queries. | +| [Accounts management](/integrators/accounts-management) | How to create and manage EGLD accounts. | +| [Creating transactions](/integrators/creating-transactions) | How to create and sign transactions. | +| [Querying the blockchain](/integrators/querying-the-blockchain) | How to query the MultiversX Blockchain in order to watch desired addresses or events. | +| [WalletConnect JSON-RPC Methods](/integrators/walletconnect-json-rpc-methods) | How to ensure the proper communication using WalletConnect between a wallet and a dapp. | + +--- + +### Observing Squad + +The N+1 setup for connecting to the MultiversX Network + +In order to integrate with the MultiversX Network and be able to [broadcast transactions](/integrators/creating-transactions) and [query blockchain data](/integrators/querying-the-blockchain) in an _optimized_ approach, one needs to set up an **on-premises Observing Squad**. + +An Observing Squad is defined as a set of `N` **Observer Nodes** (one for each Shard, including the Metachain) plus an [**MultiversX Proxy**](/sdk-and-tools/proxy) instance which will connect to these Observers and provide an HTTP API (by delegating requests to the Observers). + +:::tip +Currently the MultiversX Mainnet has 3 Shards, plus the Metachain. Therefore, the Observing Squad is composed of 4 Observers and one Proxy instance. +::: + +By setting up an Observing Squad and querying the blockchain data through the Proxy, the particularities of MultiversX's sharded architecture are abstracted away. **This means that the client interacting with the Proxy does not have to be concerned about sharding at all.** + + +## **System requirements** + +The Observing Squad can be installed on multiple machines or on a single, but more powerful machine. + +In case of a single machine, our recommendation is as follows: + +- 16 x CPU +- 32 GB RAM +- Disk space that can grow up to 5 TB +- 100 Mbit/s always-on Internet connection +- Linux OS (Ubuntu 20.04 recommended) + +The recommended number of CPUs has been updated from `8` to `16` in April 2021, considering the increasing load over the network. + +:::tip +These specs are only a recommendation. Depending on the load over the API or the observers, one should upgrade the machine as to keep the squad synced and with good performance. +::: + + +## **Setup via the Mainnet scripts** + +:::caution +`elrond-go-scripts-mainnet` are deprecated as of November 2022. Please use `mx-chain-scripts`, explained below. +::: + + +## **Setup via mx-chain-scripts** + + +## **Installation and Configuration** + +The Observing Squad can be set up using the [installation scripts](/validators/nodes-scripts/config-scripts/). Within the installation process, the `DBLookupExtension` feature (required by the Hyperblock API) will be enabled by default. + +Clone the installer repository: + +```bash +git clone https://github.com/multiversx/mx-chain-scripts +``` + +Edit `config/variables.cfg` accordingly. For example: + +```bash +ENVIRONMENT="mainnet" +... +CUSTOM_HOME="/home/ubuntu" +CUSTOM_USER="ubuntu" +``` + +Additionally, you might want to set the following option, so that the logs are saved within the `logs` folder of the node: + +```bash +NODE_EXTRA_FLAGS="-log-save" +``` + +Please check that the `CUSTOM_HOME` directory exists. Run the installation script as follows: + +```bash +./script.sh observing_squad +``` + +After installation, 5 new `systemd` units will be available (and enabled). + +Start the nodes and the Proxy using the command: + +```bash +./script.sh start +``` + +In order to check the status of the Observing Squad, please see the section **Monitoring and trivial checks** below. + + +## **Upgrading the Observing Squad** + +The Observing Squad can be updated using the installation scripts. + + +### **General upgrade procedure** + +:::important +`elrond-go-scripts-mainnet` are deprecated as of November 2022. Users of these scripts have to migrate to [mx-chain-scripts](/validators/nodes-scripts/config-scripts/). +The migration guide can he found [here](/validators/nodes-scripts/install-update/#migration-from-old-scripts). +::: + +In order to upgrade the Observing Squad - that is, both the Observers and the Proxy, one should issue the following commands: + +```bash +$ cd ~/mx-chain-scripts +$ ./script.sh github_pull +$ ./script.sh stop +$ ./script.sh upgrade_squad +$ ./script.sh upgrade_proxy +$ ./script.sh start +``` + +After running the commands above, the upgraded Observing Squad will start again. The expected downtime is about 2-3 minutes. + + +## **February 2023 upgrade** + +:::note +For observing squad users that still use the old `elrond-go-scripts`: since the rebranding to `MultiversX`, the scripts have been rebranded as well to `mx-chain-scripts`. +::: + +In order to upgrade the squad, you first need to migrate to the new scripts, while still running the squad via the old scripts. After that, +we'll use the new scripts to upgrade the squad. + + +### **How to migrate to the new scripts** + +If you already migrated from `elrond-go-scripts` to `mx-chain-scripts`, you can skip this section. + +Make sure you are on the same directory as the old scripts. + +```bash +$ cd ~ +$ git clone https://github.com/multiversx/mx-chain-scripts +$ cd mx-chain-scripts +$ ./script.sh migrate +``` + +The above commands should clone the new scripts and migrate the old configuration files to the new ones. You may now proceed to the next section. + + +### **How to upgrade to the newest version via the new scripts** + +In order to upgrade the squad, we first need to stop the squad, then upgrade the squad and finally start the squad again. These steps are done by: + +```bash +$ cd ~/mx-chain-scripts +$ ./script.sh github_pull +$ ./script.sh stop +$ ./script.sh upgrade_squad +$ ./script.sh upgrade_proxy +$ ./script.sh start +``` + +After successfully migrating to the new scripts and upgrading the squad, you can now remove the old scripts. (example: `rm -rf ~/elrond-go-scripts`) + + +## **Monitoring and trivial checks** + +One can monitor the running Observers using the **termui** utility (installed during the setup process itself in the `CUSTOM_HOME="/home/ubuntu" +` folder), as follows: + +```bash +~/elrond-utils/termui --address localhost:8080 # Shard 0 +~/elrond-utils/termui --address localhost:8081 # Shard 1 +~/elrond-utils/termui --address localhost:8082 # Shard 2 +~/elrond-utils/termui --address localhost:8083 # Metachain +``` + +Alternatively, one can query the status of the Observers by performing GET requests using **curl**: + +```bash +curl http://localhost:8080/node/status | jq # Shard 0 +curl http://localhost:8081/node/status | jq # Shard 1 +curl http://localhost:8082/node/status | jq # Shard 2 +curl http://localhost:8083/node/status | jq # Metachain +``` + +The Proxy does not offer a **termui** monitor, but its activity can be inspected using **journalctl**: + +```bash +journalctl -f -u elrond-proxy.service +``` + +Optionally, one can perform the following smoke test in order to fetch the latest synchronized hyperblock: + +```bash +export NONCE=$(curl http://localhost:8079/network/status/4294967295 | jq '.data["status"]["erd_highest_final_nonce"]') +curl http://localhost:8079/hyperblock/by-nonce/$NONCE | jq + +``` + + +## **Setup via Docker** + +The Observing Squad can be also set up using Docker. + +Clone the Observing Squad repository: + +```bash +git clone https://github.com/multiversx/mx-chain-observing-squad.git +``` + +Install docker-compose if not already installed: + +```bash +apt install docker-compose +``` + +Install and run the whole Observing Squad using the `./start_stack.sh` script from the mainnet folder: + +```bash +cd mainnet +./start_stack.sh +``` + +In order to check if the Observing Squad is running, you can list the running containers: + +```bash +docker ps +``` + +In order to check the status inside a container, you can check the logs on the machine for the last synchronized block nonce: + +```bash +docker exec -it 'CONTAINER ID' /bin/bash +cat logs/mx-chain-.......log +``` + +More detailed commands for installing, building and running an Observing Squad using Docker are described [here](https://github.com/multiversx/mx-chain-observing-squad.git). The images (for the Proxy and for the Observers) are published on [Docker Hub](https://hub.docker.com/u/multiversx). + + +## One click deploy in Tencent Cloud +Tencent Cloud nodes for Full Observing Squads can be easily deployed from the [Tencent Cloud Market](https://www.tencentcloud.com/market/product/P20240326183001630223531). + + +## One click deploy in Digital Ocean +Digital Ocean droplets for Full Observing Squads can be easily deployed via our droplets available in the [Digital Ocean Marketplace](https://marketplace.digitalocean.com/apps/multiversx-full-observing-squad). + +--- + +### Querying the Blockchain + +This page describes how to query the Network in order to fetch data such as transactions and blocks (hyperblocks). + +:::note +On this page, we refer to the [Gateway (Proxy) REST API](/sdk-and-tools/rest-api/gateway-overview) - i.e. the one backed by an [observing squad](/integrators/observing-squad). +::: + + +## **Querying broadcasted transactions** + +In order to fetch a previously-broadcasted transaction, use: + +- [get transaction by hash](/sdk-and-tools/rest-api/transactions#get-transaction) + +:::note +Fetching a _recently_ broadcasted transaction may not return the _hyperblock coordinates_ (hyperblock nonce and hyperblock hash) in the response. However, once the transaction is notarized on both shards (with acknowledgement from the metachain), the hyperblock coordinates will be set and present in the response. +::: + +In order to inspect the **status** of a transaction, use: + +- [get transaction **shallow status** by hash](/sdk-and-tools/rest-api/transactions#get-transaction-status) +- [get transaction **process status** by hash](/sdk-and-tools/rest-api/transactions#get-transaction-process-status) + +For the difference between the _shallow status_ and the _process status_, see the next section. + + +## **Transaction Status** + +### Shallow status + +The **shallow status** of a transaction indicates whether a transaction has been **handled and executed** by the network. +However, the _shallow_ status does not provide information about the transaction's **processing outcome**, and does not capture processing errors. +That is, transactions processed with errors (e.g. _user error_ or _out of gas_) have the status `success` (somehow counterintuitively). + +:::note +The _shallow_ status is, generally speaking, sufficient for integrators that are only interested into simple transfers (of EGLD or custom tokens). +::: + +The **shallow status** of a transaction can be one of the following: + - `success` - the transaction has been fully executed - with respect to the network's sharded architecture, it has been executed in both source and destination shards. + - `invalid` - the transaction has been marked as invalid for execution at sender's side (e.g., not enough balance at sender's side, sending value to non-payable contracts etc.). + - `pending` - the transaction has been accepted in the _mempool_ or accepted and partially executed (in the source shard). + +### Process status + +The **process status** of a transaction indicates whether a transaction has been processed successfully or not. + +:::note +The _process_ status is, generally speaking, useful for integrators that are interested in smart contract interactions. +::: + +:::note +Fetching the _process status_ of a transaction is less efficient than fetching the _shallow status_. +::: + +The **process status** of a transaction can be one of the following: + - `success` - the transaction has been fully executed - with respect to the network's sharded architecture, it has been executed in both source and destination shards. + - `fail` - the transaction has been processed, but with errors (e.g., _user error_ or _out of gas_), or it has been marked as invalid (see _shallow_ status). + - `pending` - the transaction has been accepted in the _mempool_ or accepted and partially executed (in the source shard). + - `unknown` - the processing status cannot be precisely determined yet. + + +## **Querying hyperblocks and fully executed transactions** + +In order to query executed transactions, please follow: + +- [get hyperblock by nonce](/sdk-and-tools/rest-api/blocks#get-hyperblock-by-nonce) +- [get hyperblock by hash](/sdk-and-tools/rest-api/blocks#get-hyperblock-by-hash) + + +## **Querying finality information** + +In order to fetch the nonce (the height) of **the latest final (hyper) block**, one would perform the following request against the on-premises Proxy instance: + +``` +curl http://myProxy:8079/network/status/4294967295 +``` + +Above, `4294967295` is a special number - the ID of the Metachain. + +From the response, one should be interested into the field `erd_highest_final_nonce`, which will point to the latest final hyperblock. + +``` + "data": { + "status": { + "erd_highest_final_nonce": 54321 + ... + } + }, + ... +} + +``` + +--- + +### Snapshotless Observing Squad + +This page describes the Snapshotless Observing Squad, a type of Observing Squad optimized for real-time requests such as accounts data fetching and vm-query operations. +More details related to exposed endpoints are available [here](/sdk-and-tools/proxy#snapshotless-observers-support). + + +## Overview + +Whenever a node is executing the trie snapshotting process, the accounts data fetching & vm-query operations can be greatly affected. +This is caused by the fact that the snapshotting operation has a high CPU and disk I/O utilization. +The nodes started with the flag `--operation-mode snapshotless-observer` will not create trie snapshots on every epoch and will also prune the trie storage in order to save space. + + +## Setup + + +### Creating a Snapshotless Observing Squad from scratch + +If you choose to install a snapshotless Observing Squad from scratch, you should follow the instruction from the [observing squad section](/integrators/observing-squad) and remember to add in the `variables.cfg` file the operation mode in the node's extra flags definition: +``` +NODE_EXTRA_FLAGS="-log-save -operation-mode snapshotless-observer" +``` +After that, you can resume the normal Observing Squad installation steps. + +Then, based on the needs there are multiple options concerning the proxy: +* if only a snapshotless squad is needed, nothing else should be done +* if both regular and snapshotless squads are needed: + * with two different proxies: one started with regular observers and one started with snapshotless observers, nothing else should be done + * with only one proxy (being served by all 8 observers), `IsSnapshotless = true` should be added to each observer started with this flag, in the proxy config (found at `$CUSTOM_HOME/elrond-proxy/config/config.toml`), as follows. Please note that this step is optional, although it would help the proxy to forward the requests in an efficient manner. +```toml +[[Observers]] + ShardId = 0 + Address = "http://127.0.0.1:8080" + IsSnapshotless = true +``` + + +### Converting a normal Observing Squad to a Snapshotless Observing Squad + +If you already have an Observing Squad, and you want to transform it into a Snapshotless Observing Squad, the easiest way is to manually edit the service file `/etc/systemd/system/elrond-node-x.service` (with `sudo`) and append the `-operation-mode snapshotless-observer` flag at the end of the `ExecStart=` line. +In the end, the file should look like: +``` +[Unit] + Description=MultiversX Node-0 + After=network-online.target + + [Service] + User=jls + WorkingDirectory=/home/ubuntu/elrond-nodes/node-0 + ExecStart=/home/ubuntu/elrond-nodes/node-0/node -use-log-view -log-logger-name -log-correlation -log-level *:DEBUG -rest-api-interface :8080 -log-save -profile-mode -operation-mode snapshotless-observer + StandardOutput=journal + StandardError=journal + Restart=always + RestartSec=3 + LimitNOFILE=4096 + + [Install] + WantedBy=multi-user.target +``` + +Save the file, and force a reload units with the command +```bash +sudo systemctl daemon-reload +``` + +After units reload, you can restart the nodes. + +:::caution +Even if the nodes are synced, after changing the operation mode, they will start to re-sync their state in +"snapshotless" format. The nodes should be temporarily started with the extra node flag `--force-start-from-network` that will force the node to start from network. +Let the node sync completely and then remove this extra flag and restart the node. +Failure to do so will make the node error with a message like `consensusComponentsFactory create failed: epoch nodes configuration does not exist epoch=0`. +::: + + +## One click deploy in AWS +AWS instances for Snapshotless Observing Squads can be easily deployed via our Amazon Machine Image available in the [AWS Marketplace](https://aws.amazon.com/marketplace/pp/prodview-pbwpmtdtwmkgs). + + +## One click deploy in Google Cloud +Google Cloud instances for Snapshotless Observing Squads can be easily deployed via our virtual machine image available in the [Google Cloud Marketplace](https://console.cloud.google.com/marketplace/product/multiversx-gcp-markeplace/multiversx-snapshotless-observing-squad). + + +## One click deploy in Tencent Cloud +Tencent Cloud nodes for Snapshotless Observing Squads can be easily deployed from the [Tencent Cloud Market](https://www.tencentcloud.com/market/product/P20240326183001630223531). + + +## One click deploy in Digital Ocean +Digital Ocean droplets for Snapshotless Observing Squads can be easily deployed via our droplets available in the [Digital Ocean Marketplace](https://marketplace.digitalocean.com/apps/multiversx-observing-squad). + +--- + +### WalletConnect JSON-RPC Methods + +The WalletConnect [Sign API](https://specs.walletconnect.com/2.0/specs/clients/sign) establishes a session between a dapp and a wallet in order to expose a set of blockchain accounts that can sign transactions and/or messages using a secure remote JSON-RPC transport with methods and events. +The following methods are already implemented in [xPortal](/wallet/xportal/) and in WalletConnect's [Web Examples](https://github.com/WalletConnect/web-examples). + +- [React Wallet (Sign v2)](https://github.com/WalletConnect/web-examples/tree/main/wallets/react-wallet-v2) ([Demo](https://react-wallet.walletconnect.com/)) +- [React dApp (with standalone client) - v2](https://github.com/WalletConnect/web-examples/tree/main/dapps/react-dapp-v2) ([Demo](https://react-app.walletconnect.com/)) + +The Transaction Object is the same for both the `mvx_signTransaction` and the `mvx_signTransactions` methods. +To get the transaction object into a ready-to-serialize, plain JavaScript object, one can use `.toPlainObject()` from `@multiversx/sdk-core` or [any other available SDKs](/sdk-and-tools/overview). + + +## mvx_signTransactions + +Sign a list of transactions. +This method returns a signature and any additional properties ( e.g. Guardian info ) that must be applied to each of the provided Transactions before broadcasting them on the network. + + +### Parameters + +```text +1. `Object` - Signing parameters: + 1.1. `transactions` : `Array` - Array of Transactions + 1.1.1 `Object` - Transaction Object + 1.1.1.1 `nonce` : `String` - The Nonce of the Sender. + 1.1.1.2 `value` : `String` - The Value to transfer, as a string representation of a Big Integer (can be "0"). + 1.1.1.3 `receiver` : `String` - The Address (bech32) of the Receiver. + 1.1.1.4 `sender` : `String` - The Address (bech32) of the Sender. + 1.1.1.5 `gasPrice` : `Number` - The desired Gas Price (per Gas Unit). + 1.1.1.6 `gasLimit` : `Number` - The maximum amount of Gas Units to consume. + 1.1.1.7 `data` : `String | undefined` - The base64 string representation of the Transaction's message (data). + 1.1.1.8 `chainID` : `String` - The Chain identifier. ( `1` for Mainnet, `T` for Testnet, `D` for Devnet ) + 1.1.1.9 `version` : `String | undefined` - The Version of the Transaction (e.g. 1). + 1.1.1.10 `options` : `String | undefined` - The Options of the Transaction (e.g. 1). + 1.1.1.11 `guardian` : `String | undefined` - The Address (bech32) of the Guardian. + 1.1.1.12 `receiverUsername` : `String | undefined` - The base64 string representation of the Sender's username. + 1.1.1.10 `senderUsername` : `String | undefined` - The base64 string representation of the Receiver's username. +``` + + +### Returns + +```text +1. `Object` + 1.1. `signatures` : `Array` + 1.1.1 `Object` - corresponding signature and optional properties response for the provided transaction + 1.1.1.1 `signature` : The Signature (hex-encoded) of the Transaction. + 1.1.1.2 `guardian` : `String | undefined` - The Address (bech32) of the Guardian. + 1.1.1.3 `guardianSignature` : `String | undefined` - The Guardian's Signature (hex-encoded) of the Transaction. + 1.1.1.4 `options` : `Number | undefined` - The Version of the Transaction (e.g. 1). + 1.1.1.5 `version` : `Number | undefined` - The Options of the Transaction (e.g. 1). +``` + + +### Example + +```javascript +// Request +{ + "id": 1, + "jsonrpc": "2.0", + "method": "mvx_signTransactions", + "params": { + "transactions": [ + { + "nonce": 42, + "value": "100000000000000000", + "receiver": "erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r", + "sender": "erd1uapegx64zk6yxa9kxd2ujskkykdnvzlla47uawh7sh0rhwx6y60sv68me9", + "gasPrice": 1000000000, + "gasLimit": 70000, + "data": "Zm9vZCBmb3IgY2F0cw==", // base64 representation of "food for cats" + "chainID": "1", + "version": 1 + }, + { + "nonce": 43, + "value": "300000000000000000", + "receiver": "erd1ylzm22ngxl2tspgvwm0yth2myr6dx9avtx83zpxpu7rhxw4qltzs9tmjm9", + "sender": "erd1uapegx64zk6yxa9kxd2ujskkykdnvzlla47uawh7sh0rhwx6y60sv68me9", + "gasPrice": 1000000000, + "gasLimit": 70000, + "data": "Zm9vZCBmb3IgZG9ncw==", // base64 representation of "food for dogs" + "chainID": "1", + "version": 1 + } + ] + } +} + +// Result +{ + "id": 1, + "jsonrpc": "2.0", + "result": { + "signatures": [ + { + "signature": "1aa6cdd9f614e2a1cedcc207e6e7c574674c9b05e98f31035cac89fcca2673ca9273c48823418cf44696f64a2c535ab3784f680a0c6d6e84b960c33e586cb30b" + }, + { + "signature": "43127c0ac3d5b124ced9c15e884940fb3c1256c463a74db33c1842fa323971e1f43725eea62225c6b2f9b2634edf68ad2e315241df734d60c41b920dec85b60a" + } + ] + } +} +``` + + +## mvx_signTransaction + +This method returns a signature and any additional properties ( e.g. Guardian info ) that must be applied to the transaction before broadcasting it on the network. +Similar to `mvx_signTransactions`, but only one Transaction can be signed at a time instead of a list of transactions. +The same logic applies to the Transaction Object here too. + + +### Parameters {#mvx_signTransaction-parameters} + +```text +1. `Object` - Signing parameters: + 1.1. `transaction` : `Object` - Transaction Object + 1.1.1 `nonce` : `String` - The Nonce of the Sender. + 1.1.2 `value` : `String` - The Value to transfer, as a string representation of a Big Integer (can be "0"). + 1.1.3 `receiver` : `String` - The Address (bech32) of the Receiver. + 1.1.4 `sender` : `String` - The Address (bech32) of the Sender. + 1.1.5 `gasPrice` : `Number` - The desired Gas Price (per Gas Unit). + 1.1.6 `gasLimit` : `Number` - The maximum amount of Gas Units to consume. + 1.1.7 `data` : `String | undefined` - The base64 string representation of the Transaction's message (data). + 1.1.8 `chainID` : `String` - The Chain identifier. ( `1` for Mainnet, `T` for Testnet, `D` for Devnet ) + 1.1.9 `version` : `String | undefined` - The Version of the Transaction (e.g. 1). + 1.1.10 `options` : `String | undefined` - The Options of the Transaction (e.g. 1). + 1.1.11 `guardian` : `String | undefined` - The Address (bech32) of the Guardian. + 1.1.12 `receiverUsername` : `String | undefined` - The base64 string representation of the Sender's username. + 1.1.10 `senderUsername` : `String | undefined` - The base64 string representation of the Receiver's username. +``` + + +### Returns {#mvx_signTransaction-returns} + +```text +1. `Object` - corresponding signature and optional properties response for the provided transaction + 1.1 `signature` : The Signature (hex-encoded) of the Transaction. + 1.2 `guardian` : `String | undefined` - The Address (bech32) of the Guardian. + 1.3 `guardianSignature` : `String | undefined` - The Guardian's Signature (hex-encoded) of the Transaction. + 1.4 `options` : `Number | undefined` - The Version of the Transaction (e.g. 1). + 1.5 `version` : `Number | undefined` - The Options of the Transaction (e.g. 1). +``` + + +### Example {#mvx_signTransaction-example} + +```javascript +// Request +{ + "id": 1, + "jsonrpc": "2.0", + "method": "mvx_signTransaction", + "params": { + "transaction": { + "nonce": 42, + "value": "100000000000000000", + "receiver": "erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r", + "sender": "erd1ylzm22ngxl2tspgvwm0yth2myr6dx9avtx83zpxpu7rhxw4qltzs9tmjm9", + "gasPrice": 1000000000, + "gasLimit": 70000, + "data": "Zm9vZCBmb3IgY2F0cw==", // base64 representation of "food for cats" + "chainID": "1", + "version": 1 + } + } +} + +// Result +{ + "id": 1, + "jsonrpc": "2.0", + "result": { + "signature": "5845301de8ca3a8576166fb3b7dd25124868ce54b07eec7022ae3ffd8d4629540dbb7d0ceed9455a259695e2665db614828728d0f9b0fb1cc46c07dd669d2f0e" + } +} +``` + + +## mvx_signMessage + +This method returns a signature for the provided message from the requested signer address. + + +### Parameters {#mvx_signMessage-parameters} + +```text +1. `Object` - Signing parameters: + 1.1. `message` : `String` - the message to be signed + 1.2. `address` : `String` - bech32 formatted MultiversX address ( erd1... ) +``` + + +### Returns {#mvx_signMessage-returns} + +```text +1. `Object` + 1.1. `signature` : `String` - corresponding signature for the signed message +``` + + +### Example {#mvx_signMessage-example} + +```javascript +// Request +{ + "id": 1, + "jsonrpc": "2.0", + "method": "mvx_signMessage", + "params": { + "message": "food for cats", + "address": "erd1uapegx64zk6yxa9kxd2ujskkykdnvzlla47uawh7sh0rhwx6y60sv68me9" + } +} + +// Result +{ + "id": 1, + "jsonrpc": "2.0", + "result": { + "signature": "513fb2fa5ac39282ffc3aa90a89024b77057ac4542199673b05601302668bdda36c1076952f4c7445f4c6487a4263d51f72dff325012ab3f236594546ef54408" + } +} +``` + + +## mvx_signNativeAuthToken + +A dApp (and its backend) might want to reliably assign an off-chain user identity to a MultiversX address. On this purpose, the signing providers allow a login token to be used within the login flow - this token is signed using the wallet of the user. Afterwards, a backend application would normally [verify the signature](/sdk-and-tools/sdk-js/sdk-js-signing-providers/#verifying-the-signature-of-a-login-token) of the token. + +The functionality is mostly the same as `mvx_signMessage`, only in this case instead of signing the provided message, the wallet will sign a special format including the requested signer address and the provided login token in the form of `${address}${token}`. + + +### Parameters {#mvx_signNativeAuthToken-parameters} + +```text +1. `Object` - Signing parameters: + 1.1. `token` : `String` - the loginToken to be signed + 1.2. `address` : `String` - bech32 formatted MultiversX address ( erd1... ) +``` + + +### Returns {#mvx_signNativeAuthToken-returns} + +```text +1. `Object` + 1.1. `signature` : `String` - corresponding signature for the signed token +``` + + +### Example {#mvx_signNativeAuthToken-example} + +```javascript +// Request +{ + "id": 1, + "jsonrpc": "2.0", + "method": "mvx_signNativeAuthToken", + "params": { + "token": "aHR0cHM6Ly9kZXZuZXQueGV4Y2hhbmdlLmNvbQ.c6191feb77da75e1acb3c5c3e8d4053be370d925fe7a78c7958ff5edc63d0c8c.86400.eyJ0aW1lc3RhbXAiOjE2OTM3NjQ1ODh9", + "address": "erd1uapegx64zk6yxa9kxd2ujskkykdnvzlla47uawh7sh0rhwx6y60sv68me9" + } +} + +// Result +{ + "id": 1, + "jsonrpc": "2.0", + "result": { + "signature": "2789172fd8e0f3b81767392b4f3450807a5894e5c704073d18a0d5e4d0819cd8fac53ef8ba3e3b0430481d6e396f67ae484ae1f295befa766e49a3abfdf76e0a" + } +} +``` + + +## mvx_signLoginToken + +Exactly the same functionality as `mvx_signNativeAuthToken`, only the login token format differs. The Wallet can display a different UI based on the login token method request. + + +## mvx_cancelAction + +Wallets can implement this method to improve the UX. It is used to transmit that the user wishes to renounce on a triggered action. Close a sign transaction modal or a sign message modal, etc. + + +### Parameters {#mvx_cancelAction-parameters} + +```text +1. `Object` - Action parameters + 1.1. `action` : `String | undefined` - Current action to be cancelled ( for ex. `cancelSignTx` ) +``` + + +### Returns {#mvx_cancelAction-parameters} + +`void` + + +### Example {#mvx_cancelAction-parameters} + +```javascript +// Request +{ + "id": 1, + "jsonrpc": "2.0", + "method": "mvx_cancelAction", + "params": { + "action": "cancelSignTx" + } +} +``` + +--- + +## Advanced +### Architecture + +import useBaseUrl from '@docusaurus/useBaseUrl'; +import ThemedImage from '@theme/ThemedImage'; + + +# Architecture + +Ad-Astra Bridge is a system that allows the transfer of ERC20 tokens between EVM-compatible chains and the MultiversX network. +Currently, there are 2 bridges available: between the Ethereum and MultiversX networks and between the BSC and MultiversX networks. +The system is composed of several contracts and relayers that work together to facilitate the transfer of tokens. + +Without providing many details regarding the smart contracts interactions, this is a simplified view of the entire bridge architecture. + + + + +## EVM-compatible chains contracts +The repository for the Solidity contracts used on the EVM-compatible side can be found here: https://github.com/multiversx/mx-bridge-eth-sc-sol +The main contracts are described below: +1. **Safe**: A contract that allows users to deposit ERC20 tokens that they want to transfer to the MultiversX network; +2. **Bridge**: A contract that facilitates the transfer of tokens from MultiversX to an EVM-compatible chain. Only the relayers are allowed to use this contract. + + +## MultiversX contracts +The repository for the Rust contracts used on the MultiversX side can be found here: https://github.com/multiversx/mx-bridge-eth-sc-rs +1. **Safe**: A contract that allows users to deposit ESDT tokens that they want to transfer to EVM-compatible networks; +2. **Bridge**: A contract that facilitates the transfer of tokens from the EVM-compatible chain to MultiversX. As the Bridge contract on the EVM-compatible chain side, this + the contract is allowed to be operated by the registered relayers; +3. **MultiTransfer**: A helper contract that is used to perform multiple token transfers at once; +4. **BridgedTokensWrapper**: A helper contract that is used to support wrapping the same token from multiple chains into a single ESDT token; +5. **BridgeProxy**: A helper contract that is used to store and handle the smart contract execution and the possible refund operation after the swap is done. + + +## Relayers +The repository for the code that the relayers use can be found here: https://github.com/multiversx/mx-bridge-eth-go + +For each existing bridge, the following list applies: +- **5 Relayers** are managed by the MultiversX Foundation; +- **5 Relayers** are distributed to the MultiversX validators community, with each validator having one relayer. + +--- + +### Axelar Amplifier Setup + +# Axelar Amplifier setup for MultiversX + +## Prerequisites + +- have an [Axelar Validator](https://docs.axelar.dev/validator/setup/overview/) running (node, tofnd & vald) + +## Become an Amplifier Verifier + +For more detailed information check out the [Become a Verifier](https://docs.axelar.dev/validator/amplifier/verifier-onboarding/) Axelar docs. + +You can skip this if already having an Amplifier Verifier up and running. + +### Set up `tofnd` + +If running on the same machine as the Axelar Validator, the existing `tofnd` can be used. + +If you want to setup on a new machine, then you can setup `tofnd` using Docker: + +``` +docker pull axelarnet/tofnd:v1.0.1 +docker run -p 50051:50051 --env MNEMONIC_CMD=auto --env NOPASSWORD=true --env ADDRESS=0.0.0.0 -v tofnd:/.tofnd axelarnet/tofnd:v1.0.1 +``` + +### Set up `ampd` + +Setup the `ampd` process using Docker: + +``` +docker pull axelarnet/axelar-ampd:v1.3.1 +``` + +Make sure that the `ampd` process can communicate with `tofnd`. + +To view your Verifier address you can run: `docker run axelarnet/axelar-ampd:v1.3.1 verifier-address` + +### Configure the verifier + +You need to create a configuration file at `~/.ampd/config.toml` and add the required configuration depending on your environment. + +For complete configuration files for different environments, check out the [Configure the verifier](https://docs.axelar.dev/validator/amplifier/verifier-onboarding/#configure-the-verifier) section in the Axelar Amplifier docs. + +Example basic `config.toml` for mainnet: + +``` +# replace with your Axelar mainnet node +tm_jsonrpc="http://127.0.0.1:26657" +tm_grpc="tcp://127.0.0.1:9090" +event_buffer_cap=100000 + +[service_registry] +cosmwasm_contract="axelar1rpj2jjrv3vpugx9ake9kgk3s2kgwt0y60wtkmcgfml5m3et0mrls6nct9m" + +[broadcast] +batch_gas_limit="20000000" +broadcast_interval="1s" +chain_id="axelar-dojo-1" +gas_adjustment="2" +gas_price="0.007uaxl" +queue_cap="1000" +tx_fetch_interval="1000ms" +tx_fetch_max_retries="15" + +[tofnd_config] +batch_gas_limit="10000000" +key_uid="axelar" +party_uid="ampd" +url="http://127.0.0.1:50051" + +[[handlers]] +cosmwasm_contract="axelar14a4ar5jh7ue4wg28jwsspf23r8k68j7g5d6d3fsttrhp42ajn4xq6zayy5" +type="MultisigSigner" +``` + +You need to configure additional `handlers` for each chain you want to support. Check out the [ampd README file](https://github.com/axelarnetwork/axelar-amplifier/blob/main/ampd/README.md) for more information. +Find below an example for configuring handlers for **MultiversX**. + +### Activate and run the verifier + +For more information check out the [Axelar docs](https://docs.axelar.dev/validator/amplifier/verifier-onboarding/#activate-and-run-the-verifier). + +Find below basic instructions for mainnet: + +1. Bond your verifier: `ampd bond-verifier amplifier 50000000000 uaxl` + +2. Register public key: + +`ampd register-public-key ecdsa` + +`ampd register-public-key ed25519` + +3. Register support for chains for which you have configured handlers: `ampd register-chain-support amplifier flow ethereum multiversx [MORE_CHAINS]` + +Run the `ampd` process with `docker run axelarnet/axelar-ampd:v1.3.1` + +## Add support for MultiversX to Verifier + +### Running a MultiversX Observing Squad + +For security reasons, you will need to run your own MultiversX Observing Squad, which is a collection of nodes, one node for each MultiversX shard + the Proxy API service. This API will be used by the Verifier to get transactions from the MultiversX network in order to be able to verify them. + +You can find detailed steps in the [MultiversX Observing Squad docs](https://docs.multiversx.com/integrators/observing-squad). There exist installation scripts that making setting up an Observing Squad easy. + +Below you can find basic information on how to setup a squad for mainnet: + +1. Clone the `mx-chain-scripts` repo: `git clone https://github.com/multiversx/mx-chain-scripts` + +2. Edit the `config/variables.cfg` according, for example: + +``` +ENVIRONMENT="mainnet" +... +CUSTOM_HOME="/home/ubuntu" +CUSTOM_USER="ubuntu" +``` + +3. Setup the Observing Squad: `./script.sh observing_squad` + +4. Start the nodes & the Proxy: `./script.sh start` + +### Updating Verifier `config.toml` file + +In order to support MultiversX, first you need to add the two required handlers at the end of your `~/.ampd/config.toml` file: + +#### Devnet + +``` +[[handlers]] +type = 'MvxMsgVerifier' +cosmwasm_contract = 'axelar1sejw0v7gmw3fv56wqr2gy00v3t23l0hwa4p084ft66e8leap9cqq9qlw4t' +# replace with your MultiversX Proxy URL +proxy_url = 'http://127.0.0.1:8079' + +[[handlers]] +type = 'MvxVerifierSetVerifier' +cosmwasm_contract = 'axelar1sejw0v7gmw3fv56wqr2gy00v3t23l0hwa4p084ft66e8leap9cqq9qlw4t' +# replace with your MultiversX Proxy URL +proxy_url = 'http://127.0.0.1:8079' +``` + +#### Testnet + +``` +[[handlers]] +type = 'MvxMsgVerifier' +cosmwasm_contract = 'TBD' +# replace with your MultiversX Proxy URL +proxy_url = 'http://127.0.0.1:8079' + +[[handlers]] +type = 'MvxVerifierSetVerifier' +cosmwasm_contract = 'TBD' +# replace with your MultiversX Proxy URL +proxy_url = 'http://127.0.0.1:8079' +``` + +#### Mainnet + +``` +[[handlers]] +type = 'MvxMsgVerifier' +cosmwasm_contract = 'TBD' +# replace with your MultiversX Proxy URL +proxy_url = 'http://127.0.0.1:8079' + +[[handlers]] +type = 'MvxVerifierSetVerifier' +cosmwasm_contract = 'TBD' +# replace with your MultiversX Proxy URL +proxy_url = 'http://127.0.0.1:8079' +``` + +### Register MultiversX chain + +1. (optional) If you have not done so already, first register the `ed25519` public key: `ampd register-public-key ed25519` + +2. Then register support for the `multiversx` chain: `ampd register-chain-support amplifier multiversx` + +At this point you can restart the `ampd` process and you should be able to validate MultiversX messages. + +--- + +### Bitcoin L2 + +# Bitcoin L2 + +:::note + +This documentation is not complete. More content will be added once it is accepted and discussed on Agora or once it is implemented and available for production. + +::: + +--- + +### Concept + +# Concept + +:::note + +This documentation is not complete. More content will be added once it is accepted and discussed on Agora or once it is implemented and available for production. + +The content created here is derived from: +- [Sovereign Shards - MIP7 - part - 1](https://agora.multiversx.com/t/sovereign-shards-mip-7-part-1/185) + +::: + +The MultiversX blockchain utilizes a fully sharded architecture designed for scalability, processing tens of thousands of transactions per second, and allowing developers to deploy decentralized applications using smart contracts. The in-protocol token standard, ESDT, facilitates simple, inexpensive, secure and scalable token transfers. As the network expands, new shards are created, enabling horizontal scaling. Additionally, parallelization within the same shard supports vertical scaling. The system is decentralized, public, open, and uses EGLD as its base token. + +However, certain systems and applications require more customization than what a general-purpose network like MultiversX can offer. App-specific blockchains are tailored to run specific applications, providing several benefits: + +- **Increased Performance**: Dedicating blockspace and computational resources to one concept can improve transaction throughput, reduce latency, and lower fees. This is important for applications needing high performance, such as gaming, real world use cases or DeFi. + +- **Flexibility and Sovereignty**: Developers can customize various aspects of their blockchain, including the security model, fee token and model, governance mechanism, and VM. This customization can attract and retain users by offering unique incentive schemes, tokenomics models, and user experiences. + +Sovereign Chains aim to provide an SDK that allows developers to create highly efficient chains connected to the global MultiversX Network. This enables composability, interoperability, and simplified usability across all chains, leveraging existing infrastructure and enhancing user experience. + +## Historical Context + +### Issues with Layer2 Solutions + +The concept of Sovereign Chains is introduced to address some of the most encountered issues: + +- **Unified Liquidity and Application Ecosystem:** Sovereign Chains eliminate the need for fragmented multi-signature wallets by providing a unified ecosystem. This ensures that liquidity is not dispersed and applications can operate seamlessly without silos. + +- **Simplified User Experience:** With Sovereign Chains, users interact within a single ecosystem that supports all necessary operations, eliminating the need for multiple gas tokens. This streamlines the user experience, making interactions more intuitive and user-friendly. + +- **Built-In Interoperability and Composability:** Sovereign Chains are designed with interoperability and composability as core features. Applications deployed on Sovereign Chains can interact seamlessly, leveraging built-in protocols that ensure secure and efficient cross-chain operations. + +- **Seamless Fund Movement and Deployment Adaptation**: Users can move funds effortlessly across different shards and deployments within the Sovereign Chain ecosystem. This flexibility simplifies adaptation to various applications and enhances overall adoption. + +## Concept of Sovereign Chains + +**High-Level Features** + +- **High Performance**: Capable of >4000 DeFi/Gaming transactions per second, achieved by a new consensus model that dedicates 90-95% of time to processing. Nevertheless this model will also be launched on the mainchain. +- **Configurable Features**: Options for private vs. public chains, staking setups, smart contracts, ESDTs, and customizable gas and transaction fees. + +**Virtual Machine (VM) Configurations** + +- **Default VMs**: SystemVM (GO) and WasmVM. +- **Custom VMs**: + - Ethereum Compatible VM; + - Bitcoin Compatible VM; + - Solana Compatible VM; + +**Built-in Cross-Chain Mechanism** + +A built-in cross-chain mechanism facilitates token transfers between the sovereign chain and the MultiversX network without relayers. The process ensures seamless interoperability and security through validator verification and proof systems. + +## Cross-Chain Mechanism Concept + +### Mainchain to Sovereign Chain Concept + +#### 1. Sovereign Notifier Service and Header Inclusion + +Sovereign Notifier Service + +Validators on the Sovereign Chain should run a notifier service to monitor cross-chain transactions from the MultiversX mainchain. This service can connect to the validators' own nodes or public notifier services to receive notification events. + +Header Inclusion Process + +- *Detection*: When the leader validator detects a new header from the MultiversX mainchain, it attempts to include this header in the current block it is building. +- *Adding* `ShardHeaderHash`: The leader specifically adds the MultiversX `ShardHeaderHash` to the `SovereignShardHeader`. +- *Transaction Processing*: If there are cross-chain transactions from the mainchain, the protocol mandates the leader and validators to process all these transactions before adding the new `ShardHeaderHash`. This mimics the cross-shard transaction processing in MultiversX. + +ShardHeader Inclusion + +1. A `SovereignShardHeader` can contain multiple ShardHeaderHashes. +2. The leader adds only finalized ShardHeaders from the mainchain. +3. New MultiversX HeaderHash cannot be added if previous transactions are not executed. + +#### 2. Execution of Incoming Transactions +Creating Miniblocks + +- *Transaction Collection*: The leader collects all incoming cross-chain transactions and creates a miniblock named `“INCOMING TXS”`. +- *Data Preparation*: The notifier service prepares relevant data, including proof of execution, data availability, correctness, and finality. +- *Data Sending*: This prepared data is pushed to the Sovereign Chain client. + +Validator Verification + +1. Validators on the Sovereign Chain verify the leader's miniblock creation. +2. Validators perform header verification, incoming transaction creation, proof verification, and finality checks. +3. If verification fails, validators do not sign the block, preventing its creation. + +Gas-Free Execution + +1. Incoming cross-chain transactions are executed without gas on the Sovereign Chain. + - these transactions are treated as DestME ESDT transfers, ensuring fast execution and complete integration with the MultiversX chain. + +#### 3. UI and Smart Contract on MultiversX Chain + +The intention is that the user should not feel the complexity of transferring a token from one chain to the other. That's why in 3 easy steps a cross-chain transaction should be felt as successful: + +1. Users execute a transaction on the MultiversX chain directed towards the cross-chain contract. +2. Upon successful transaction on the mainchain, the user receives tokens on the Sovereign Chain. +3. The execution and transfer are done as the initial user, maintaining seamless cross-chain interaction. + +But how do we achieve that? Even though the main components are going to be described at several stages in the documentation, from conceptual point of view we have the: + +ESDTSafe Contract + +- Functionality: Users can deposit tokens and specify the destination address and execution call. +- Endpoint: `deposit@address@gasLimit@functionToCall@arguments` receiving a `MultiESDTNFTTransfer`. +- Verification: The contract verifies the validity of the address and generates a specific `logEvent`. + +`logEvent` Structure +```json +Identifier: deposit +Address: scAddress +Topics: address, LIST +Data: localNonce (increasing), originalSender, gasLimit, functionToCall, arguments +``` + +Customizable Features + +- General Fee Model: Configurable fee and fee token. +- Whitelist/Blacklist: Tokens can be whitelisted or blacklisted for transfer. + +#### 4. Sovereign Chain Cross-Chain Execution +Preparation and Miniblock Creation + +1. The notifier service generates an Extended Shard Header structure and an Incoming Txs miniblock based on logEvents. +2. Validators finalize blocks by including mainchain shard headers and all incoming transactions. + +Token ID Collision Avoidance + +To avoid token ID collisions, each deployed Sovereign Chain adds a unique prefix or increasing nonce when registering token IDs on the ESDT SC. This ensures clear distinction of token origins on the Sovereign Chain. + +#### 5. Mainchain to Sovereign Shard Process + + Notifier Service: Validators receive notifications of mainchain bridge transactions. + Header Inclusion: Leaders include finalized MultiversX ShardHeaders in the SovereignShardHeader. + Transaction Execution: Leaders collect and execute incoming transactions, forming an INCOMING TXS miniblock. + Validator Verification: Validators verify and sign the block, ensuring all transactions are processed. + Gas-Free Execution: Transactions are executed without gas on the Sovereign Shard, treated as DestME ESDT transfers. + +### Sovereign Chain to MultiversX Mainchain + +The Sovereign Chain to MultiversX Mainchain cross-chain communication process involves synchronizing the mainchain with the Sovereign Chain, handling cross-chain transactions, and ensuring smooth token transfers between the two. This description provides a conceptual explanation of the roles, processes, and smart contract interactions involved. + +#### Responsibilities of Sovereign Validators + +Sovereign Validators have two primary responsibilities: + +1. Syncing with the mainchain and pushing bridge transactions. +2. Managing transfers from Sovereign to Main and vice versa. + +Types of Tokens + +There are two types of tokens involved in the bridging process: + +- **Token Type A**: Tokens initially originating from Main to Sovereign, with liquidity held in the safe contract on Main. +- **Token Type B**: New tokens first issued on the Sovereign Chain, requiring creation and specific roles (`localMint`, `nftCreate`) for the `esdt-safe` contract. + +**Cross-Chain tx Process** + +Token Deposit + +**1. Users Deposit Tokens:** + Users on Sovereign deposit tokens into the `esdt-safe` contract on the Sovereign Chain. Now tokens are either burned (for Token Type A) or kept in the safe (for Token Type B). + +**2. Block Creation and Finalization:** + The Sovereign chain creates the next block and finalizes it. Outgoing transactions (Crossing to MultiversX) are compiled into compressed operations data. + +**3. Header Hash Addition:** + The leader adds the hash of the outgoing transaction data to the header of the Sovereign Chain block. Validators reach consensus, signing the header, which the leader combines using BLS multi-signature. + +**4. Posting to Mainchain:** + The signed header and list of outgoing transactions are posted to the MultiversX chain. The transaction data on MultiversX contains the signed header of the Sovereign chain, the outgoing operations data hash, and the full arguments of the outgoing operations. + +**5. Contract Validation and Execution:** + The contract on MultiversX validates the signed header and BLS multi-signature. It verifies if + +```rust +hash(outgoing operations data) = header.outgoingOpsHash +``` + +The bridge contract executes the operations, performing `TransferESDT/MultiTransferESDTNFT` for *Token Type A* and `minting/creating NFTs` for *Token Type B*. + +**Finality and Consensus** + +To ensure the integrity and synchronization between the Sovereign Chain and the mainchain, the following steps are taken: + +- The Sovereign chain header includes an outgoing operations hash, which encapsulates the details of the operations to be executed on the mainchain. +- Once the outgoing operations are executed on the mainchain in subsequent blocks, the Sovereign chain remains synchronized by validating every MultiversX header hash. +- The finality gadget plays an important role by ensuring that a header with *Nonce X* on the Sovereign chain is only finalized after confirming that the mainchain has executed the outgoing operations from the previous header. This interconnected process maintains consistency and order of operations. + +**Incentives and Validation** + +To encourage validators to ensure the smooth operation of bridge transactions and maintain network integrity, the following incentive and validation mechanisms are implemented: + +- **Incentive Structure Proposal:** Validators are incentivized to push outgoing transactions to the mainchain by distributing collected fees among them. Specifically, 10% of the fees are allocated to the leader, while the remaining 90% is distributed among the other validators. This incentivization ensures active participation and prompt processing of transactions. + +- **Validation Process:** Finality is crucial to maintaining the order and integrity of transactions. The system ensures that outgoing operations are executed in sequence without any gaps. This is achieved by generating `logEvents` on the mainchain, which are then pushed to the Sovereign Chain. These `logEvents` serve as proof of execution, ensuring validators can verify the completion and correctness of transactions, thereby maintaining a reliable and secure cross-chain operation. + +#### Smart Contracts for Cross Chain Tx + +Process: + +1. `SovereignMultiSigContract` is created and is the parent of the *ESDTSafe* contract, and only the `sovereignMultiSigContract` is allowed to transfer out funds from the `ESDTSafe` contract. + +2. The OutGoingTXData BLS MultiSigned by the validators will be put inside a mainchain transaction and sent by the leader of the Sovereign Chain for the current block. This transaction contains a set of token operations for a set of addresses. +``` +txData = bridgeOps@LIST, gasLimit,functionToCall, Arguments>>@Nonce@BLSMultiSig +``` + +The `sovereignMultiSigContract` first verifies if the BLSMultiSig is valid and whether it is signed by 67% of the BLSPubkeys registered in the sovereignMultiSigContract. If yes, the contract calls with the ESDTSafe contract to transfer the set of tokens. The ESDTSafe contract will iterate on the given list and make a multiESDTNFTTransfer to the given addresses with the given tokens. + +The contract emits a logEvent the same way as mainchain to sovereign ESDTSafe SC does, in order to keep track of the processed outgoing transactions. This logEvent will be pushed towards the sovereign shard, in order to notarize the finalization of processing of the OutGoingTxData. This will close the loop of processing and offer utmost security for all funds. + +``` +Identifier = bridgeOps +Address = scAddress +Topics = sender, hash(outGoingTxData) +Data = nonce - increasing from internal storage +``` +The created event is an attestation that the `outGoingTx` was executed on the mainchain, and using this attestation on the sovereign chain, the rewards can be distributed from the accumulated fees. + +--- + +### Cross Chain Execution + +# Introduction + +When we take a look at the blockchain industry, we observe a segregated ecosystem lacking cohesion, interoperability and teamwork. The vision lead to the Blockchain Revolution, knows as “Web3” — a new era of the internet that is user-centered, emphasizing data ownership and decentralized trust. + +Sovereign Chains will dismantle the barriers between isolated blockchain networks by allowing smart contracts to seamlessly interact across different Sovereign Chains and the main MultiversX chain. +This cross-chain interoperability is crucial for fostering an environment where decentralized apps (dApps) can utilize functionalities or assets from across the ecosystem. + +## What is Cross-Chain Execution? + +Cross-Chain execution is the ability of a smart contracts or a decentralized applications on one blockchain to invoke actions on another blockchain. This feature allows for smart contract execution or transfer of funds from one chain to another, enabling developers to build applications that are chain agnostic. + +## Cross-Chain Execution within Sovereign Chains + +Since a Sovereign Chain is a separate blockchain with a different rule-set from the MultiversX blockchain, there has to be a way of communication between them. The interaction is being done by Smart Contracts, the Sovereign Bridge Service and Nodes. + +> The MultiversX blockchain will be referred as the _MultiversX Mainchain_ in the further sections. + +![To Sovereign](../../static/sovereign/to-sovereign.png) + +When a transaction starts from the MultiversX Mainchain, either from a wallet or a smart contract, it goes through the `Mvx-ESDT-Safe` smart contract. The Observer nodes monitor the events that the deposit transaction emits and then the Sovereign Nodes notarize the state changes inside the Sovereign Chain. This notarization means the end of a Cross-Chain transfer. + +![From Sovereign](../../static/sovereign/from-sovereign.png) + + + +When the transaction is generated from the Sovereign Chain, the `Sov-ESDT-Safe` smart contract must be used. This smart contract will generate events and from that point, the sovereign nodes, at the end of the round, will read all the outgoing operations (plus all the unconfirmed operations), then these are signed by all the validators, then send to the bridge service, which will make the transaction on main chain + +This is a high-level description of the whole process, the smart contracts that take place in it are far more detailed and have a lot of specific scenarios and behaviours. The current Sovereign Chain suite consists of four main contracts, here is the high-level description for some of the cross chain smart contracts: + +## Mvx-ESDT-Safe & Sov-ESDT-Safe +The two contracts have the same role: to facilitate a cross-chain execution depending on what side the process starts. The reason for the prefix of `Sov` and `Mvx` is to show where the smart contract is deployed, `Sov` means that the contract is deployed on a Sovereign Chain and `Mvx` that is deployed on the MultiversX Mainchain. There will be an in-depth description of each smart contract in the upcoming modules ([`Mvx-ESDT-Safe`](mvx-esdt-safe.md) and [`Sov-ESDT-Safe`](sov-esdt-safe.md)). The description will consist of flows for the cross-chain interactions, important modules and endpoints. + +Cross-chain transfers imply sending funds through the smart contracts mentioned above. There are two types of bridging mechanism available: *Lock&Send* and *Burn&Mint*. + +#### Lock and Send +Lock & Send: a custodial bridging mechanism in which the source-chain tokens are locked inside the bridge contract, while an equal amount is minted on the destination chain. The locked balance on the source chain backs the circulating supply on the sovereign chain 1-for-1 until the tokens are returned and unlocked. + +#### Burn and Mint +Mint & Burn: a non-custodial bridging mechanism where the source-chain tokens are burned (permanently removed from supply) and an identical amount is minted on the destination chain, so the total supply moves between chains without any tokens being held in escrow. + +## Fee-Market +Since every Sovereign Chain will have a customizable fee logic, it was paramount that this configuration had to be separated into a different contract. The rules set inside this contract are: +* fee per transferred token +* fee per gas unit +* users whitelist to bypass the fee + +This contract is also present in the MultiversX Mainchain and in any Sovereign Chain. + +## Header-Verifier +Any cross-chain transaction that happens inside a Sovereign Chain is called and *operation*. The main role of this contract is to verify operations. They have to be signed by the validators of the Sovereign Chain. If the operation is successfully verified it will be registered and then can be executed by the `Mvx-ESDT-Safe` smart contract. All the *BLS keys* of the validators will be stored inside this contract. The in-depth description of how those _operations_ are registered can be found in the [`Header-Verifier`](header-verifier.md) module. + +:::note +The source for the smart contracts can be found at the official [MultiversX Sovereign Chain SCs repository](https://github.com/multiversx/mx-sovereign-sc). +::: + +## Sovereign Bridge Service +This feature facilitates the execution of outgoing operations. This service is an application that receives Sovereign operations. After that, it will call the `execute_operation` endpoint from the `Mvx-ESDT-Safe` smart contract. The registration and execution of operations looks like this: + +- For N operations there is only one [register transaction](mvx-esdt-safe.md#registering-a-set-of-operations) inside the Header-Verifier smart contract. +- N transactions for the [execution](mvx-esdt-safe.md#executing-an-operation) of N operations inside the ESDT-Safe smart contract, one execution transaction per operation. + +> There can be one or more services deployed in the network at the same time. + +--- + +### Custom Configurations + +# Custom Configurations + +## Sovereign network customisations + +The Sovereign Chain SDK is built with flexibility in mind, allowing you to tailor it to your specific needs. This page highlights various customizations you can apply to make your network unique. + +### config.toml + +- `GeneralSettings.ChainID` - defines your unique chain identifier +- `EpochStartConfig.RoundsPerEpoch` - defines how many round are in each epoch + +### economics.toml + +- `GlobalSettings.GenesisTotalSupply` - total native ESDT supply at genesis +- `GlobalSettings.YearSettings` - adjust the inflation rate each year +- `FeeSettings` - adjust the fee settings as needed + +### ratings.toml + +- `General` - adjust the rating parameters as needed + +### systemSmartContractsConfig.toml + +- `ESDTSystemSCConfig.ESDTPrefix` - the prefix for all issued tokens +- `ESDTSystemSCConfig.BaseIssuingCost` - base cost for issuing a token +- `StakingSystemSCConfig.NodeLimitPercentage` [[docs](https://docs.multiversx.com/validators/staking-v4/#how-does-the-dynamic-node-limitation-work)] + +### sovereignConfig.toml + +- `GenesisConfig.NativeESDT` - Native ESDT identifier for the Sovereign Chain + +### prefs.toml + +The `OverridableConfigTomlValues` will overwrite the parameters in the config files. Make sure that your new config parameters are not overwritten by this file. + +:::note +These are just a few examples that you can adjust to make the Sovereign Chain unique. All the files you could adjust when creating a Sovereign Chain can be found in the [deployment guide](/sovereign/distributed-setup#step-4-edit-the-sovereign-configuration). +::: + +:::note +We will continue to add configurations for features such as token-less chains, gas-less chains, and other customizations at a later stage, following their implementation. +::: + +--- + +### Disclaimer + +# Disclaimer + +:::note + This is a living document. More content will be added as it is accepted and discussed on Agora, or once it is implemented and available for production. As this documentation evolves, some sections may be updated or modified to reflect the latest developments and best practices. Community feedback and contributions are encouraged to help improve and refine this guide. Please note that the information provided is subject to change and may not always reflect the latest updates in the technology or procedures. All information and changes will be communicated and discussed with the community. +::: + +## Support and Scope + +While MultiversX provides the **chain sdk** core code and scripts necessary to start a sovereign chain, it is important to note that the infrastructure for all aspects of sovereign setup is not provided. MultiversX focus is on delivering the tools and resources needed to launch and maintain a sovereign chains. However, users may need to describe the need for additional support or resources or for more specific or advanced requirements by using Agora forum or other support channels that are available for them to use. + +### What MultiversX Provides + +- **Core Code**: The fundamental codebase necessary to run a sovereign chain. +- **Scripts**: Automated scripts to assist with the initial setup and deployment processes. +- **Documentation**: Guides and manuals (like this one) to help you understand and implement the core functionalities. +- **Technical Support**: Technical support especially for potential issues or configurations that are caused by the above mentioned materials. + +### What will be supported in the limit of capacity + +- **Comprehensive Technical Support**: In-depth, personalized support for all potential issues or configurations. +- **Custom Solutions**: Any specific customizations or bespoke solutions required by individual users. +- **Operational Management**: Day-to-day management and operational support for running a sovereign chain. + +### Living Document + +As blockchain technology and the concept of sovereign chains continue to evolve, this documentation will also undergo changes. MultiversX may add new sections, update existing content, or deprecate information that becomes outdated. These changes aim to ensure that the documentation remains relevant and useful for all users. + +MultiversX commits to transparency and collaboration in this process. All significant updates and changes will be communicated to the community through appropriate channels, and feedback will be actively sought to ensure the documentation meets the needs of its users. A weekly technical call will be scheduled where all issues, concerns and problems can be directly addressed to the development team. + +### Community Involvement + +Your feedback is important in helping MultiversX improve the documentation. We encourage community members to participate in discussions on Agora and other platforms, sharing their experiences, challenges, and suggestions. Together, we can create a more robust and comprehensive resource for everyone involved in the MultiversX ecosystem. + +--- + +For any questions or further assistance, please refer to the [Engage the developer community section](https://multiversx.com/builders-hub) or the [official agora forum](https://agora.multiversx.com/). + +--- + +### Distributed Setup + +# Distributed Setup + +## Create distributed Sovereign Chain configuration + +This guide will help you deploy a public Sovereign Chain with real validators, enabling a truly decentralized setup. At its core, blockchain technology—and Sovereign Chains in particular—are designed to operate in a decentralized manner, powered by multiple independent validators. This ensures transparency, security, and resilience, as no single entity has control over the entire system. Unlike other guides we’ve provided, which focus on local setups, this solution emphasizes decentralization by involving multiple stakeholders in the validation process. By following the steps below, the owner can create the Sovereign Chain configuration for the network: + +### Step 1: Get the `mx-chain-sovereign-go` Repository + +Before proceeding, ensure that a **SSH key** for GitHub is configured on your machine. + +1. Clone the GitHub repository: + ```bash + git clone git@github.com:multiversx/mx-chain-sovereign-go.git + ``` + +2. Navigate to project directory: + ```bash + cd mx-chain-sovereign-go + ``` + +### Step 2: Seeder Build + +Build and run the seed node +```bash +cd cmd/seednode +go build +./seednode -rest-api-interface 127.0.0.1:9091 -log-level *:DEBUG -log-save +``` + +You should have an output similar to the one displayed below. The highlighted part is important and will be used later. + +| Seednode addresses: | +|---------------------------------------------------------------------------------------------| +| `/ip4/`127.0.0.1`/tcp/10000/p2p/16Uiu2HAmSY5NpuqC8UuFHunJensFbBc632zWnMPCYfM2wNLuvAvL` | +| `/ip4/`192.168.10.100`/tcp/10000/p2p/16Uiu2HAmSY5NpuqC8UuFHunJensFbBc632zWnMPCYfM2wNLuvAvL` | + +:::info +All the validator nodes will have to connect to this seed node. +::: + +### Step 3: Sovereign node build + +Build the sovereign node +```bash +cd .. +cd cmd/sovereignnode/ +go build -v -ldflags="-X main.appVersion=v0.0.1" +``` + +:::info +Use your own custom version instead of `v0.0.1`. +::: + +### Step 4: Edit the sovereign configuration + +Node configs can be found in `cmd/node/config`. Below are the files and folders: +``` +gasSchedules folder +genesisContracts folder +genesis.json* +genesisSmartContracts.json +nodesSetup.json* +api.toml +config.toml +economics.toml +enableEpochs.toml +enableRounds.toml +external.toml +fullArchiveP2P.toml +p2p.toml +prefs.toml +ratings.toml +systemSmartContractsConfig.toml +``` + +_Note: Files marked with * will be discussed later in the document._ + +Sovereign configs can be found in `cmd/sovereignnode/config` +``` +enableEpochs.toml +prefs.toml +sovereignConfig.toml +``` + +#### Minimum recommended changes + +1. Move the config files from `/node/config` into `/sovereignnode/config`, except _economics.toml_, _enableEpochs.toml_, _prefs.toml_. +2. Config changes: + 1. **config.toml** + 1. GeneralSettings.ChainID + 2. EpochStartConfig.RoundsPerEpoch + 2. **p2p.toml** + 1. KadDhtPeerDiscovery:InitialPeerList = `[/ip4/PUBLIC_IP/tcp/10000/p2p/16Uiu2HAmSY5NpuqC8UuFHunJensFbBc632zWnMPCYfM2wNLuvAvL]` + - PUBLIC_IP is the IP of the machine where seed node is running, the other part is seed node address + 3. **systemSmartContractsConfig.toml** + 1. ESDTSystemSCConfig.ESDTPrefix + 2. StakingSystemSCConfig.NodeLimitPercentage [[docs](https://docs.multiversx.com/validators/staking-v4/#how-does-the-dynamic-node-limitation-work)] + 4. **sovereignConfig.toml** + 1. GenesisConfig.NativeESDT +3. Other changes: + - Use the [custom configuration](/sovereign/custom-configurations) page to see more configs we recommend to be changed + +### Step 5: Genesis configuration + +#### `genesis.json` + +This file should contain all the genesis addresses that will be funded and will be validators. Adjust as needed. + +:::note +The sum of `supply` should be equal to `GenesisTotalSupply` from economics.toml +::: + +Example with 2 validators: +``` +[ + { + "address": "erd1a2jq3rrqa0heta0fmlkrymky7yj247mrs54g6fyyx8dm45menkrsmu3dez", + "supply": "10000000000000000000000000", + "balance": "9997500000000000000000000", + "stakingvalue": "2500000000000000000000", + "delegation": { + "address": "", + "value": "0" + } + }, + { + "address": "erd1pn564xpwk4anq9z50th3ae99vplsf7d2p55cnugf00eu0gcq6gdqcg7ytx", + "supply": "10000000000000000000000000", + "balance": "9997500000000000000000000", + "stakingvalue": "2500000000000000000000", + "delegation": { + "address": "", + "value": "0" + } + } +] +``` + +#### `nodesSetup.json` + +This file contains all the initial nodes. Adjust as needed. + +:::note +- `consensusGroupSize` should be equal to `minNodesPerShard` +- each node pair contains one genesis address associated with a validator public key +- `startTime` should be a timestamp from the future, the time when the network will start +- `roundDuration` is the duration in milliseconds per round +- `metaChainConsensusGroupSize` and `metaChainMinNodes` should always be 0 +::: + +Example: +``` +{ + "startTime": 1733138599, + "roundDuration": 6000, + "consensusGroupSize": 2, + "minNodesPerShard": 2, + "metaChainConsensusGroupSize": 0, + "metaChainMinNodes": 0, + "hysteresis": 0, + "adaptivity": false, + "initialNodes": [ + { + "pubkey": "6a1ee46baa8da9279f53addbfbc61a525604eb42d964bd3a25bf7f34097c3b3a31706728718ccdbe3d43386c37ec3011df6ceb4188e14025ab149bd568cafaba18a78b51e71c24046c5276a187a6c1d6da83e30590a6025875b8f6df8984ec05", + "address": "erd1a2jq3rrqa0heta0fmlkrymky7yj247mrs54g6fyyx8dm45menkrsmu3dez", + "initialRating": 0 + }, + { + "pubkey": "40f3857218333f0b2ba8592fc053cbaebec8e1335f95957c89f6c601ce0758372ba31c30700f10f25202d8856bb948055f9f0ef53dea57b62f013ee01c9dc0346a2b3543f2b4d423166ee1981b310f2549fb879d4cd89de6c392d902a823d116", + "address": "erd1pn564xpwk4anq9z50th3ae99vplsf7d2p55cnugf00eu0gcq6gdqcg7ytx", + "initialRating": 0 + } + ] +} +``` + +___ + +:::note +At this point, a `config` folder should be created that will contain all the .toml files and genesis configuration. This folder should be shared with the other validators so they will be able to join the network. +::: + +## Join a Sovereign Chain as validator/observer + +### Sovereign validator setup + +Each validator should have: +- **walletKey.pem** - wallet that will be funded at genesis [[docs](/validators/key-management/wallet-keys)] +- **validatorKey.pem** (or **allValidatorsKey.pem** if multi key node) - validator key [[docs](/validators/key-management/validator-keys/#how-to-generate-a-new-key)] +- **config** folder - received from Sovereign Chain creator + +### Sovereign validator/observer node start + +The following commands will start the sovereign validator node with the configuration from **config** folder and with the **validatorKey** (or multi key from **allValidatorsKey**). +Adjust the flags as needed. You can find all the available flags in `/mx-chain-sovereign-go/cmd/sovereignnode/flags.go` + +#### # single key +``` +./sovereignnode --validator-key-pem-file ./config/validatorKey.pem --profile-mode --log-save --log-level *:INFO --log-logger-name --log-correlation --use-health-service --rest-api-interface :8080 --working-directory ~/my_validator_node +``` + +#### # multi key +``` +./sovereignnode --all-validator-keys-pem-file ./config/allValidatorsKey.pem --profile-mode --log-save --log-level *:INFO --log-logger-name --log-correlation --use-health-service --rest-api-interface :8080 --working-directory ~/my_validator_node +``` + +#### # observer +``` +./sovereignnode --profile-mode --log-save --log-level *:INFO --log-logger-name --log-correlation --use-health-service --destination-shard-as-observer 0 --rest-api-interface :8080 --operation-mode db-lookup-extension --working-directory ~/my_observer_node +``` + +### Staking transaction + +Before staking, a node is a mere observer. After staking, the node becomes a validator, which means that it will be eligible for consensus and will earn rewards. You can find the documentation how to make the staking transaction with mxpy [here](/validators/staking#staking-through-mxpy). + +## Deploy services + +You can find the documentation on how to deploy services [here](/sovereign/services). + +--- + +### Dual Staking + +# Dual Staking + + +:::note +This version of the documentation focuses solely on the essential steps required to set up and deploy a sovereign chain on either a local or remote computer. More content will be added as it is accepted and discussed on [Agora](https://agora.multiversx.com/), or once it is implemented and available for production. +::: + +--- + +### Ethereum L2 + +# Ethereum L2 + +:::note + +This documentation is not complete. More content will be added once it is accepted and discussed on Agora or once it is implemented and available for production. + +::: + +--- + +### Governance + +# Governance + + +:::note +This version of the documentation focuses solely on the essential steps required to set up and deploy a sovereign chain on either a local or remote computer. More content will be added as it is accepted and discussed on [Agora](https://agora.multiversx.com/), or once it is implemented and available for production. +::: + +--- + +### Governance - Overview + +This page provides an overview of the On-chain Governance module that will be available on the `v1.6.x` release. + + +## Table of contents + +| Name | Description | +|-----------------------------------------------------------------------------------|--------------------------------------------------------------------------| +| [Interacting with the on-chain governance SC](/governance/governance-interaction) | Interacting with the governance system smart contract. | + + +## Overview + +The MultiversX network enables on-chain governance by issuing special types of transactions. This mechanism increases decentralization by allowing community members to directly propose and vote on changes, such as protocol upgrades or configuration adjustments. + +- **Anyone** can create a proposal by paying the proposal fee and specifying: + - a **commit hash** (unique identifier, usually a GitHub commit or spec reference) + - a **start epoch** and an **end epoch** (the voting window). +- **Any staked user** (direct or delegated) can vote during the active period. +- Voting power is proportional to stake: + `voting_power = staked_value` (linear voting). + + +## Implementation details + +### Proposals + +In order for a proposal to be submitted, the following requirements need to be met: +- For a period of at least 15 days, the proposal needs to be published on Agora for public debate and analysis; +- Each proposal requires paying a `ProposalFee` (currently **500 EGLD**); +- Each proposal costs around 51 million of gas units to be submitted. +- The starting epoch of the proposal's voting period needs to be set inside an interval of 30 epochs from the epoch in which the proposal was submitted; + +After a proposal is created, the following rules will apply: +- If the proposal passes, the fee is refunded to the issuer. +- If the proposal fails, a penalty fee (called `LostProposalFee`) is deducted from the initial proposal fee, and the remaining amount is returned to the proposer. +- If the proposal is vetoed, the fee is slashed (transferred to the **Community Governance Pool**) or reduced by the configured `LostProposalFee`. +- Proposals cannot be duplicated: the same commit hash cannot be submitted twice. +- Proposals can be **cancelled** by the issuer **before the voting period starts**. (introduced in v1.10.0 Barnard release) +- Lost proposal fees may either be accumulated in the governance contract (retrievable by the contract owner) or redirected to a **Community Governance Pool**, depending on configuration. + + +### Voting +- The voting starts from the provided starting epoch and lasts at most *10 days*. +- There are four vote types: **Yes**, **No**, **Abstain**, and **Veto**. +- Voting consumes gas (approx. 6 million units). +- Voting power is derived from staked and delegated EGLD. +- Delegation and liquid staking contracts can forward user votes. +- Voting power is **linear** with stake: the more EGLD staked or delegated, the higher the voting power. +- The contract tracks both **used voting power/stake** (applied in a proposal) and **total available voting power/stake** for each address. + +### Quorum and thresholds +A proposal can pass only if all conditions are met: + +- **Quorum**: at least `MinQuorum%` of the total voting power must participate. (currently at least **20%** of total voting power) +- **Acceptance threshold**: YES / (YES + NO + VETO) ≥ **66.67%** (`MinPassThreshold`) +- **Veto threshold**: if VETO votes exceed **33%** of total participating voting power, the proposal is rejected and the proposal fee is slashed. + +### Closing and cleanup +**Proposal closing permissions:** +- **Before voting starts:** Only the proposal issuer can close the proposal. +- **After voting ends:** Any user can close the proposal. + +**Effects of closing:** +1. Finalizes the proposal outcome (passed/failed/vetoed). +2. Unlocks any locked funds. +3. Updates the proposal status in the governance system. + +**Cleanup options:** +- The `clearEndedProposals` function can be called to clean up expired but unclosed proposals. +- This helps maintain system efficiency by removing inactive proposals. + +### Governance configuration +All thresholds and fees are defined in `GovernanceConfigV2`: +- `MinQuorum` +- `MinPassThreshold` +- `MinVetoThreshold` +- `ProposalFee` +- `LostProposalFee` +- `LastProposalNonce` + +These values are set by the system and can be updated by contract owner calls. + + +### Example +Let's suppose we have the following addresses that cast the following votes: +- `alice`: staked value **2000 EGLD** that vote **Yes** +- `bob`: staked value **3000 EGLD** that vote **Yes** +- `charlie`: staked value **4000 EGLD** that vote **Yes** +- `delta`: staked value **1500 EGLD** that vote **No** + +The totals are: +- Quorum = `(2000+3000+4000+1500) = 10,500 EGLD` +- **Yes** = `9000 EGLD` +- **No** = `1500 EGLD` +- **Abstain** = `0` +- **Veto** = `0` + +Assume: +- total staked in the system = `20,000 EGLD` +- `MinQuorum` = 20% +- `MinPassThreshold` = 50% +- `MinVetoThreshold` = 33% + +Evaluation: +- Quorum is satisfied: `10,500 > 4000` +- Yes > No: `9000 > 1500` +- Yes is ≥ 50% of votes: `9000 / 10,500 = 85.7%` +- Veto is below 33%: `0 < 3465` + +To sum it all, the proposal **passes**. + +--- + +### Governance interaction + +### Introduction + +The interaction with the governance system smart contract is done through correctly formatted transactions to submit actions and through the usage of the vm-query REST API calls for reading the proposal(s) status. + + +### Creating a proposal + +The proposal creation transaction has the following parameters: + +```rust +GovernanceProposalTransaction { + Sender: + Receiver: erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqrlllsrujgla + Value: 500 EGLD + GasLimit: 51000000 + Data: "proposal" + + "@" + + "@" + + "@" +} +``` + +The value parameter represents the mandatory proposal submission deposit of 500 EGLD. + +The proposal identifier is a hex string containing exactly 40 characters. This is the git commit hash on which the proposal is made. + +The starting & ending epochs should be an even-length hex string containing the starting epoch and the ending epoch. During this time frame, lasting at most 10 days, the votes can be cast. + +>**Note:** When providing the starting epoch, it should be taken into consideration that the governance contract enforcing a configured maximum gap of 30 epochs between the current epoch and the proposal’s starting epoch. + +After issuing the proposal, there is a log event generated having the `proposal` identifier that will contain the following encoded topics: + +- `nonce` as encoded integer which uniquely identifies the proposals +- `identifier` is the provided 40 hex characters long identifier +- `start epoch` as encoded integer for the starting epoch +- `end epoch` as encoded integer for the ending epoch + + +### Voting a proposal using the direct staked or delegation-system amount + +Any wallet that has staked EGLD (either direct staked or through the delegation sub-system) can cast a vote for a proposal. + +```rust +GovernanceVoteTransaction { + Sender: + Receiver: erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqrlllsrujgla + Value: 0 EGLD + GasLimit: 6000000 + Data: "vote" + + "@" + + "@" +} +``` + +The value parameter for the voting transaction must be set to 0, since the function is non-payable. + +The `nonce` is the hex encoded value of the proposal's unique nonce and the `vote_type` can be one of the following values: +- for **Yes**: `796573`; +- for **No**: `6e6f`; +- for **Abstain**: `6162737461696e`; +- for **Veto**: `7665746f`. + +The vote value for the account that will vote a proposal is the sum of all staked values along with the sum of all delegated values in the delegation sub-system. + +After issuing the vote, a log event is generated containing the `proposal` identifier and the following encoded topics: + +- `nonce` as encoded integer which uniquely identifies the proposals +- `vote_type` as encoded string representing the vote +- `total_stake` total staked EGLD for the sender address +- `total_voting_power` total available voting power for the sender address + +Example response: + +```json +{ + "returnData": [ + "WQ==", (nonce: hex = 59 -> decimal = 89) + "eWVz", (vote_type: hex = "79657" -> "yes") + "BxRA1dqcrTxyQw==", (total_stake: hex = "071440d5da9cad3c7243" -> 33430172142116630458947 -> ~33430 EGLD) + "BxRA1dqcrTxyQw==", (total_voting_power: hex = "071440d5da9cad3c7243" -> 33430172142116630458947 -> ~33430 EGLD) + ] +} +``` + + +### Voting a proposal through smart contracts (delegation voting) + +Whenever we deal with a smart contract that delegated through the delegation sub-system or owns staked nodes it is the out of scope of the metachain's governance contract to track each address that sent EGLD how much is really staked (if any EGLD is staked). + +That is why we offered an endpoint to the governance smart contact that can be called **only by a shard smart contract** and the governance contract will record the address provided, the vote type and vote value. + +This is very useful whenever implementing liquid-staking-like smart contracts. The liquid-staking contract knows the balance for each user, so it will delegate the call to the governance contract providing the corresponding voting power. + +:::important +The maximum value for the staked value & the voting power for the liquid-staking contract is known by the governance contract, so it can not counterfeit the voting. If, due to a bug the liquid-staking contract allows, for example, for a user to vote with a larger quota, the liquid-staking contract will deplete its maximum allowance making other voting transactions (on his behalf) to fail. +::: + +The user that delegated through a liquid-staking-like contract in order to vote on a proposal, sends a transaction to the liquid-staking-like contract. The smart contract then creates the GovernanceVoteThroughDelegationTransaction based on the user’s transaction inputs, forwarding the vote and the computed voting power to the governance contract. + +```rust +GovernanceVoteThroughDelegationTransaction { + Sender: + Receiver: erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqrlllsrujgla + Value: 0 EGLD + GasLimit: 51000000 + Data: "delegateVote" + + "@" + + "@" + + "@" + + "@" +} +``` + +The value parameter for the voting transaction must be set to 0, since the function is non-payable. + +The `nonce` is the hex encoded value of the proposal's unique nonce and the `vote_type` can be one of the following values: +- for **Yes**: `796573`; +- for **No**: `6e6f`; +- for **Abstain**: `6162737461696e`; +- for **Veto**: `7665746f`. + +The `account address handled by the smart contract` is the address handled by the smart contract that will delegate the vote towards the governance smart contract. This address will be recorded for casting the vote. + +The `vote_balance` is the amount of stake the address has in the smart contract. The governance contract will "believe" that this is the right amount as it impossible to verify the information. The balance will diminish the total voting power the smart contract has. + +After issuing the vote, a log event is generated containing the `proposal` identifier and the following encoded topics: + +- `nonce` as encoded integer which uniquely identifies the proposals +- `vote_type` as encoded string representing the vote +- `voter` account address handled by the smart contract +- `total_stake` total staked EGLD for the sender address +- `total_voting_power` total available voting power for the sender address + +Example response: + +```json +{ + "returnData": [ + "WQ==", (nonce: hex = 59 -> decimal = 89) + "eWVz", (vote_type: hex = "79657" -> "yes") + "ATlHLv9ohncamC8wg9pdQh8kwpGB5jiIIo3IHKYNaeE=", (voter: hex = "0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1" -> erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th) + "BxRA1dqcrTxyQw==", (total_stake: hex = "071440d5da9cad3c7243" -> 33430172142116630458947 -> ~33430 EGLD) + "BxRA1dqcrTxyQw==", (total_voting_power: hex = "071440d5da9cad3c7243" -> 33430172142116630458947 -> ~33430 EGLD) + ] +} +``` + + +### Closing a proposal + +A proposal can be closed by the issuer before the voting period starts but with an early closing fee equal to `LostProposalFee`. A proposal can also be closed by anyone in an epoch that is strictly higher than the end epoch value provided when the proposal was opened. + +```rust +CloseProposalTransaction { + Sender: + Receiver: erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqrlllsrujgla + Value: 0 EGLD + GasLimit: 51000000 + Data: "closeProposal" + + "@" +} +``` + +#### Rules for closing +- The proposal can be closed by the issuer before the voting starts or by anyone after voting ended. +- If the proposal **passes** → the full proposal fee is refunded. +- If the proposal **fails** or is **vetoed** → the refund is reduced by the `LostProposalFee`. +- Once a proposal is closed, it cannot be reopened. +- Closing also finalizes the vote tally (the proposal is marked as `Passed` or not, based on the results). + + +### Querying the status of a proposal + +The status of any proposal can be queried at any time through `vm-values/query` REST API endpoints provided by the gateway/API. + +```bash +https://.multiversx.com/vm-values/query +``` + +```json +{ + "scAddress": "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqrlllsrujgla", + "funcName": "viewProposal", + "args": [""] +} +``` + +- The argument `nonce` is the proposal nonce in hex format. +- The response will contain the following json definition where all fields are base64-encoded: + +```json +{ + "returnData": [ + "", (amount locked by proposer) + "", (unique identifier of the proposal) + "", (proposal number) + "", (address of the proposer) + "", (epoch when voting starts) + "", (epoch when voting ends) + "", (current quorum stake: sum of stake that participated) + "", (total stake voting YES) + "", (total stake voting NO) + "", (total stake vetoing the proposal) + "", (total stake abstaining) + "", + "" + ] +} +``` + +Example response: +```json +{ + "returnData": [ + "Gxrk1uLvUAAA", (proposal locked amount: 500 EGLD denominated = 500 * 10^18) + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABg==", (commit hash: 0x0000...006) + "AQ==", (nonce: 1) + "aj88GtqHy9ibm5ePPQlG4aqLhpgqsQWygoTppckLa4M=", (proposer address) + "bQ==", (starting epoch: 109) + "bg==", (ending epoch: 110) + "ntGU2xmyOMAAAA==", (quorum: 750000 EGLD denominated) + "", (yes votes: 0) + "", (no votes: 0) + "ntGU2xmyOMAAAA==", (veto votes: 750000 EGLD denominated) + "", (abstain votes: 0) + "dHJ1ZQ==", (proposal closed: true) + "ZmFsc2U=" (proposal passed: false) + ] +} +``` + + + +### Querying an address voting status (direct staking or system delegation) + +The voting status of a certain address can be queried at any time by using the `vm-values/query` REST API endpoints provided by the gateway/API. + +```bash +https://gateway.multiversx.com/vm-values/query +``` + +With the following payload: + +```json +{ + "scAddress": "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqrlllsrujgla", + "funcName": "viewDelegatedVoteInfo", + "args": ["", "
"] +} +``` + +- `proposal_nonce` → the proposal identifier (nonce, hex-encoded). +- `address` → the bech32 address of the account to check. + +> **Note:** The older function `viewUserVoteHistory` (which returned lists of proposal nonces) is now considered legacy. +> Use `viewDelegatedVoteInfo` for detailed voting power and stake information. + +The response will contain the following json definition where all fields are base64-encoded: + +```json +{ + "returnData": [ + "", (voting power used by this address on the proposal) + "", (stake associated with the used power) + "", (total available voting power for the address) + "" (total stake considered in governance for the address) + ] +} +``` + +Example for an address that voted: +```json +{ + "returnData": [ + "Cg==", (used power: 100) + "ZAA=", (used stake: 100) + "A+g=", (total power: 1000) + "Gg4M=", (total stake: 1000) + ] +} +``` + +In this example, the queried address voted on this proposal with **100 stake**, which translated into **100 voting power**. The proposal overall had **1000 total stake** and **1000 total voting power** recorded. + +Example for an address that did not vote: + +```json +{ + "returnData": [ + "AA==", (used power: 0) + "AA==", (used stake: 0) + "A+g=", (total power: 1000) + "A+g=", (total stake: 1000) + ] +} +``` + +### Querying the voting power of an address + +The voting power of a certain address can be queried at any time by using the `vm-values/query` REST API endpoints provided by the gateway/API. + +```bash +https://gateway.multiversx.com/vm-values/query +``` + +With the following payload: + +```json +{ + "scAddress": "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqrlllsrujgla", + "funcName": "viewVotingPower", + "args": [""] +} +``` + +Here's an example of the response: + +```json +{ + "returnData": [ + "Q8M8GTdWSAAA" (1250000000000000000000 -> 1250 EGLD) + ] +} +``` + +### Querying the configuration of the Governance contract + +The configuration of the Governance System Smart Contract can be queried at any time by using the `vm-values/query` REST API endpoints provided by the gateway/API. + +```bash +https://gateway.multiversx.com/vm-values/query +``` + +With the following payload: + +```json +{ + "scAddress": "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqrlllsrujgla", + "funcName": "viewConfig", + "args": [] +} +``` + +The response will contain the following json definition where all fields are base64-encoded: + +```json +{ + "returnData": [ + "", (the amount of EGLD required to issue a new proposal) + "", (the amount of EGLD that the issuer loses if the proposal fails) + "", (the minimum percentage of total voting power required for a proposal to be considered valid) + "", (the minimum percentage of veto votes needed to reject a proposal and slash its fee) + "", (the minimum percentage of YES votes required for a proposal to pass) + "" (the nonce of the last created proposal) + ] +} +``` + +Here's how a response might look like: +```json +{ + "returnData": [ + "NTAwMDAwMDAwMDAwMDAwMDAwMDAw", ("500000000000000000000" -> 500 EGLD) + "MTAwMDAwMDAwMDAwMDAwMDAwMDA=", ("10000000000000000000" -> 10 EGLD) + "MC4yMDAw", ("0.2000" -> 20%) + "MC4zMzAw", ("0.3300" -> 33%) + "MC42NjY3", ("0.6667" -> 66.67%) + "MA==", ("0" -> 0) + ], +} +``` + +--- + +### Header Verifier + +# Header-Verifier + +As mentioned in the [Introduction](cross-chain-execution.md) the `Header-Verifier` smart contract is responsible to verify signatures, store the *BLS Keys* of the validators and register incoming *Operations*. + +## Registering a set of *Operations* +```rust + #[endpoint(registerBridgeOps)] + fn register_bridge_operations( + &self, + signature: BlsSignature, + bridge_operations_hash: ManagedBuffer, + operations_hashes: MultiValueEncoded, + ) +``` + +Any *Operation* before being executed has to be registered in this smart contract. The reason behind this is that the hash will be verified and it will be locked until the operation is executed by the `Mvx-ESDT-Safe` contract. + +The registering endpoint operates as follows: +1. Verifies that `bridge_operations_hash` is not found in the `hash_of_hashes_history` storage mapper, otherwise it will return an error. +2. Verifies that the hash of all `operations_hashes` matches the `bridge_operations_hash`, otherwise, the endpoint will return an error. +3. All `operations_hashes` are stored in the smart contract's storage with the status `OperationsHashStatus::NotLocked`. +4. The `bridge_operations_hash` is added to the `hash_of_hashes_history` storage mapper. + +## Locking *Operations* +```rust + #[endpoint(lockOperationHash)] + fn lock_operation_hash(&self, hash_of_hashes: ManagedBuffer, operation_hash: ManagedBuffer) +``` + +The Header-Verifier has a system in place for locking *Operation* hashes. Locking those registered hashes prevents any unwanted behaviour when executing or removing an *Operation* hash. Remember that the execution of *Operations* can only be done by the `Mvx-ESDT-Safe` smart contract. This endpoint when called will follow this flow: + +1. Check if the caller is the `Mvx-ESDT-Safe` smart contract. +2. Check if the *Operation* is registered. +3. If the hash is not locked set the status in the storage as locked or else return panic. + +:::note +The hash can be in two different states: `OperationHashStatus::NotLocked` or `OperationHashStatus::Locked` +::: + +## Removing *Operations* +```rust + #[endpoint(removeExecutedHash)] + fn remove_executed_hash(&self, hash_of_hashes: &ManagedBuffer, operation_hash: &ManagedBuffer) +``` + +After registering and executing an *Operation* the status of the hash associated to it must be removed from the Header-Verifier's internal storage. This endpoint will be called by the `Mvx-ESDT-Safe` smart contract after the execution of the *Operation* is successful. The steps are pretty clear: + +1. Check if the caller is the `Mvx-ESDT-Safe` smart contract. +2. Remove the status of the hash from storage. + +:::note +The source code for this contract can be found [here](https://github.com/multiversx/mx-sovereign-sc/blob/main/header-verifier/src/lib.rs). +::: + +--- + +### Interoperability + +# Interoperability and Modular Design + +Sovereign Chains are designed to serve as an interoperability layer between different ecosystems. Ecosystem partners and builders have the opportunity to create a set of components that can be activated or deactivated based on the specific needs of a particular Sovereign Chain. But how will sovereign chains achieve that? + +## Virtual Machine Structure + +SpaceVM has two parts, developed in GO and Rust: + +**1. One part handles**: +- OPCODES +- State management +- Transfers + +**2. The other part functions as the executor.** + +Starting from the address model, multiple VMs are enabled to run within the system. There is a clear implementation of how these VMs can call each other independently, facilitated by the blockchainHook. TODO: add information about the implementation. + +## Modular Design and Multi-VM Capabilities + +The basic idea is to utilize the modular design and multi-VM capabilities to create direct connections to existing technologies from the market. + +### Ethereum L2 + +### Bitcoin L2 + +### Solana L2 + +--- + +### Key Components + +# Key Components + +:::note + +This documentation is not complete. More content will be added once it is accepted and discussed on Agora or once it is implemented and available for production. + +::: + +Sovereign Chains mark significant progress for MultiversX in the field of appchains. While the concept is straightforward to discuss in general terms, developers, builders, or other entities looking to launch a sovereign chain need to consider several critical components. For clarity, we will divide these components into four major categories and describe the importance and role of each one. + +![img](/sovereign/sovereign-1.png) + + +### **1. Sovereign Cross-Chain Smart Contracts** + +In order to have a native *connection* with the MultiversX MainChain three smart contracts have to be deployed: + +#### **```esdt-safe``` Contract** + +The ```esdt-safe``` smart contract, which operates on both the mainchain and the sovereign chain, is the primary contract responsible for token transfers. + +Mainchain Endpoints: + +- ```deposit```: This endpoint performs a ```MultiESDTNFTTransfer``` to the contract, locking the tokens on the mainchain and initiating their transfer to the sovereign chain (tokens on the mainchain remain locked in the smart contract, while those on the sovereign chain are burned). +- ```executeBridgeOps```: Called by the bridge service wallet, this endpoint transfers tokens on the mainchain. It first verifies that these operations have been previously stored in the multisig contract before executing. +- ```registerToken```: Issues a token and maps it to a sovereign token ID, granting the mainchain contract the rights to mint/burn and requiring a payment of 0.05 EGLD for the token issuance on the mainchain. + +Sovereign Chain Endpoint: + +- ```deposit```: Burns the token being sent to the mainchain. `registerToken` must be called beforehand to enable bridging, and the contract must have the *burn right* set. The default `ESDTRoleBurnForAll` role is assigned to any issued token. If this role is deactivated, a transaction is required to assign the *burn role*, facilitated by the script `setLocalBurnRoleSovereign`. + +#### **Fee Market Contract** + +This contract allows you to specify the fee for deposit transactions. The fee structure can vary and includes: +- No fee; +- Fixed fee with a specific token; +- Fee with any token (currently, this requires a check through an xexchange contract, which is not yet operational); + +The fee is applicable either per token in the `MultiESDTNFTTransfer` action or based on the gas used in the deposit transaction. + +#### **MultiSig Verifier** + +The `multisig` smart contract, operates solely on the mainchain, and is responsible for managing and validating the multi-signature operations for bridge transactions. It registers the public keys of the validators from the sovereign chain. It also validates the multi-signature received on this endpoint which is called by the bridge service: +- `registerBridgeOps` - here, the multisig is validated, the hashes of the bridge operations are verified, and the hashes are recorded (these hashes are checked during the execution of executeBridgeOps). + +### **2. Sovereign Notifier** + +:::note +The Sovereign Notifier exports outport blocks to Sovereign Chain. It needs to be a server outport host driver. +::: + +There is only one Sovereign Notifier per Sovereign Chain, started specifically on the shard where the Sovereign's `esdt-safe` contract is deployed. It is subscribed to the address of the `esdt-safe` contract (automatically set during genesis by scripts). It listens for events with the IDs `deposit` and `execute`. When it detects an event, it sends the event to the sovereign chain as an incoming operation. The nodes then recognize the operation, process it, and the funds are credited to the account. + +### **3. Sovereign Cross-Chain TX Service** + +The Sovereign Cross-Chain TX service, connected via gRPC, manages the interaction between the mainchain and the sovereign chain for token transfers and other operations. + +For the setup described in this documentation, there is a single cross-chain tx service in operation (recommended it is for every validator from Sovereign Chain to have a running cross-chain tx service for each instance). It has a wallet attached from the mainchain and contains addresses from the cross-chain contracts (`multisig` for registration and `esdt-safe` for execution), along with various security certificates. + +#### Functionality: +- **Outgoing Operations**: The service listens for outgoing operations created from logs of deposit events in esdt-safe on the sovereign chain. +- **Validation and Signing**: These operations are validated, signed, hashed, and processed by all validators on the sovereign chain. +- **Register and Execute**: Once the service receives a validated operation, it sends the operation to the registerBridgeOps endpoint in the multisig contract and then to the executeBridgeOps endpoint in the esdt-safe contract for execution. + +### **4. Sovereign Chain** + +--- + +### Keystore files + +The MultiversX keystore is a JSON file that holds a mnemonic (seed phrase), encrypted with a password (as chosen by the user). Thus, the keystore provides users with a reliable and convenient method for managing their hot wallets and protecting their assets. + + +## MultiversX Keystore + + +### Loading a keystore into the web wallet + +Choose connecting with keystore: + +![img](/developers/keystore/connect_with_keystore.png) + +Add your keystore and introduce the password: + +![img](/developers/keystore/load_json_introd_pass.png) + +Upon loading the keystore, the user is presented with a choice of addresses, as derived from the mnemonic held by the keystore (if the keystore has `"kind":"mnemonic"`). + +![img](/developers/keystore/bech32.png) + + +### **How does a MultiversX Keystore look like?** + +Here is an example: + +```json +{ + "version": 4, + "id": "5b448dbc-5c72-4d83-8038-938b1f8dff19", + "kind": "mnemonic", + "crypto": { + "ciphertext": "6d70fbdceba874f56f15af4b1d060223799288cfc5d276d9ebb91732f5a38c3c59f83896fa7e7eb6a04c05475a6fe4d154de9b9441864c507abd0eb6987dac521b64c0c82783a3cd1e09270cd6cb5ae493f9af694b891253ac1f1ffded68b5ef39c972307e3c33a8354337540908acc795d4df72298dda1ca28ac920983e6a39a01e2bc988bd0b21f864c6de8b5356d11e4b77bc6f75ef", + "cipherparams": { + "iv": "2da5620906634972d9a623bc249d63d4" + }, + "cipher": "aes-128-ctr", + "kdf": "scrypt", + "kdfparams": { + "dklen": 32, + "salt": "aa9e0ba6b188703071a582c10e5331f2756279feb0e2768f1ba0fd38ec77f035", + "n": 4096, + "r": 8, + "p": 1 + }, + "mac": "5bc1b20b6d903b8ef3273eedf028112d65eaf85a5ef4215917c1209ec2df715a" + } +} +``` + +At first, you will see an unappealing JSON file, which appears to contain magic parameters used for numerous complex cryptographic operations with unclear and vague purpose. But if you dig a little deeper you will see that it contains: + +- **kind** - Can be `secretKey` or `mnemonic` and represents the input to be encrypted using the `cipher`; +- **ciphertext** - Your MultiversX mnemonic or secret key encrypted using the `cipher` algorithm below; +- **cipher** - The name of a symmetric AES algorithm; +- **cipherparams** - The parameters required for the `cipher` algorithm above; +- **kdf** - A key derivation function used to let you encrypt your keystore file with a password; +- **kdfparams** - The parameters required for the `kdf` algorithm above; +- **mac** - A code used to verify your password. + +Keystore files created with the first major version of the web wallet (available prior February 14th, 2023) hold the encrypted secret key, instead of the encrypted mnemonic (as the new keystore files do). Though the older files are still compatible with the new web wallet - compatibility is achieved through the aforementioned "kind" field. + +When **kind** is set (or not set at all) to `secretKey`, the `ciphertext` field will contain the encrypted secret key, as it did before. However, when **kind** is set to `mnemonic`, the `ciphertext` field will contain the encrypted mnemonic instead. + +Auxiliary reference: [ERC-2335: BLS12-381 Keystore](https://eips.ethereum.org/EIPS/eip-2335). + +--- + +### Ledger + +You can safely store your EGLD by installing the MultiversX EGLD app on your Ledger Nano S or Ledger Nano X device. We recommend using a hardware wallet for managing large amounts of EGLD. + + +## Before you begin + +1. [Set up](https://support.ledger.com/hc/en-us/articles/360000613793) your Ledger device +2. [Download](https://www.ledger.com/ledger-live/download) the Ledger Live application +3. [Upgrade](https://support.ledger.com/hc/en-us/articles/360002731113) your device with the latest firmware‌ + + +## **Install the MultiversX EGLD app on your Ledger** + +Teach your Ledger device to work with EGLD by installing the MultiversX app on it.‌ + +- Open the Ledger Live application +- Click on the “Manager” section in the app +- Connect and unlock your Ledger device +- If prompted, allow the Manager on your device by pressing the two buttons +- In the App catalog search for “MultiversX” +- Click the “Install” button next to the MultiversX app +- Your Ledger device will display “Processing” as the app installs + +![img](/wallet/ledger/ledger_live.png) + +Awesome, you did everything perfectly! + + +## Log into your MultiversX wallet using the Ledger device + +- Make sure your Ledger device is connected to your computer +- Log into your Ledger device +- Navigate to the MultiversX app & open it by pushing both buttons on your device +- In your web browser navigate to https://wallet.multiversx.com +- Select "Ledger" from the Login options + +![img](/wallet/ledger/connect_ledger.png) + +- A pop-up will notify you that your Ledger wants to connect. Allow it by clicking "Connect" + +![img](/wallet/ledger/paired.png) + +After connecting the Ledger you will be prompted to choose the address you want to connect with. Select the address then click on "Access Wallet". + +![img](/wallet/ledger/ledger_address.png) + +Your MultiversX Wallet will now open. + + +## Overview of your EGLD balance + +After logging into your wallet, your EGLD balances are immediately visible and displayed in easy to follow boxes. + +![img](/wallet/web-wallet/wallet_balance_overview.png) + +- **Available:** Freely transferable EGLD balance +- **Stake Delegation:** Amount of EGLD delegated towards a Staking Services provider +- **Legacy Delegation:** Amount of EGLD delegated towards MultiversX Community Delegation +- **Staked Nodes:** Amount of EGLD locked for your staked nodes + + +## Send a transaction using the Ledger + +In the MultiversX web wallet: + +- Click "Send" +- Input the destination address & amount +- Click Send +- In the next screen click "Confirm on Ledger"! + +![img](/wallet/ledger/confirm.png) + +On your Ledger device: + +- Review the receiver address, amount & fee on your Ledger device +- Sign the transaction by pressing both buttons + +Awesome, your transaction is now sent using your Ledger device. + +![img](/wallet/ledger/tx_completed.png) + + +## Receiving EGLD in your wallet + +After logging into your wallet, as described above, you will be able to see your wallet address and share it with others, so they can send you EGLD.‌ + +Your address is immediately visible on the top part of the wallet, as "erd1... ". You can copy the address by pressing the copy button (two overlapping squares). ‌ + +You can also click "Receive" on the right-hand side to see a QR code for the address, which can be scanned to reveal the public address. + +![img](/wallet/ledger/receive.png) + + +## Guardians + +To set up a guardian for a Ledger account, follow [these steps](/wallet/web-wallet.md#guardian). + +## **Need help?** + +- Reach out to MultiversX on your [preferred channel](https://linktr.ee/MultiversX) + +--- + +### Local Setup + +# Full Local Setup + +## Deploy local Sovereign Chain + +This guide will help you deploy a full sovereign local network connected to MultiversX network. This includes all the smart contracts and dependent services needed. Follow these steps carefully to ensure a successful deployment. + +### Step 1: Get the `mx-chain-sovereign-go` Repository + +Before proceeding, ensure that a **SSH key** for GitHub is configured on your machine. + +1. Clone the GitHub repository: + ```bash + git clone git@github.com:multiversx/mx-chain-sovereign-go.git + ``` + +2. Navigate to testnet directory: + ```bash + cd mx-chain-sovereign-go/scripts/testnet + ``` + +3. Run the prerequisites script: + ```bash + ./prerequisites.sh + ``` + + :::info + The prerequisites script verifies and downloads the necessary packages to run the nodes and clones the required repositories: + + - **mx-chain-deploy-sovereign-go**: Initializes the configuration for the chain and deployment parameters. + - **mx-chain-proxy-sovereign-go**: Repository for the sovereign proxy. + - **mx-chain-sovereign-bridge-go**: Repository for the cross-chain service. + - **mx-chain-tools-go**: Repository for updating elastic indices. + ::: + +### Step 2: Deploy Sovereign setup + +#### Sovereign chain with no main chain connection + +1. Update chain parameters in `variables.sh` file + +2. Start the chain with local scripts: +```bash +./config.sh && ./sovereignStart.sh +``` + +#### Sovereign chain with main chain connection + +Navigate to the `sovereignBridge` folder: +```bash +cd sovereignBridge +``` + +1. Install the [software dependencies](/sovereign/software-dependencies) and download the cross-chain contracts by running the sovereign bridge prerequisites script: + ```bash + ./prerequisites.sh + ``` + +2. Create a new wallet: + ```bash + mxpy wallet new --format pem --outfile ~/wallet.pem + ``` + + :::info + This wallet is the owner for cross chain smart contracts and is used by the sovereign bridge service. + ::: + + :::note + You can use any wallet of your choice, but for the purpose of this guide we are generating a new wallet. + ::: + +3. Get funds in this wallet on the chain (testnet/devnet/mainnet) you want the sovereign to be connected to. + +4. Update the configuration file `config/configs.cfg` with paths you want to use, wallet location and main chain constants. The following example shows the paths you have to use in order to connect to public MultiversX testnet: + ``` + # Sovereign Paths + SOVEREIGN_DIRECTORY="~/sovereign" + TXS_OUTFILE_DIRECTORY="${SOVEREIGN_DIRECTORY}/txsOutput" + CONTRACTS_DIRECTORY="${SOVEREIGN_DIRECTORY}/contracts" + + # Owner Configuration + WALLET="~/wallet.pem" + + # Main chain network config + MAIN_CHAIN_ELASTIC=https://testnet-index.multiversx.com + PROXY = https://testnet-gateway.multiversx.com + CHAIN_ID = T + + # Native token + NATIVE_ESDT_TICKER=SOV + NATIVE_ESDT_NAME=SovToken + ``` + + :::note + - **SOVEREIGN_DIRECTORY, OUTFILE_PATH, CONTRACTS_DIRECTORY** - represent the paths to the location where the deployment scripts will generate the outputs. + - **WALLET** - should represent the wallet generated at Step 2.2. + - **MAIN_CHAIN_ELASTIC** - represents the elasticsearch db from the main chain + - **PROXY** - in this case, for the purpose of the test, the used proxy is the testnet one. Of course that the proper proxy should be used when deploying your own set of contracts depending on the development phase of your project. + - **CHAIN_ID** - should represent the chain ID of the chain where the contracts are to be deployed. + - **"1"** for Mainnet; + - **"D"** for Devnet; + - **"T"** for Testnet; + - or use you own local network ID + - **NATIVE_ESDT_TICKER** - represents the ticker from which the native esdt will be generated, and updated in configs + - **NATIVE_ESDT_NAME** - represents the native esdt name + ::: + +5. Source the script: + ```bash + source script.sh + ``` + +6. Create and deploy main chain observer: + ```bash + createAndDeployMainChainObserver + ``` + + :::info + After running this command, one should wait until the observer is fully synchronized. + ::: + +6. Deploy all cross-chain contracts on main chain and deploy Sovereign Chain with all required services: + ```bash + deploySovereignWithCrossChainContracts sov + ``` + + :::info + `deploySovereignWithCrossChainContracts` command will: + - deploy all smart contracts on the main chain and update the sovereign chain configuration + - set up sovereign nodes and deploy the main chain observer + - use `sov` as the default prefix for ESDT tokens on the sovereign chain + (can be changed to any 1–4 character lowercase alphanumeric string and **must be unique across all deployed sovereign chains**) + - if no prefix is provided, a random one will be generated + ::: + +### Step 3: Deploy services + +You can find the documentation on how to deploy services [here](/sovereign/services). + +___ + +## Stop and clean local Sovereign Chain + +1. Navigate to `mx-chain-sovereign-go/scripts/testnet/sovereignBridge`. + Source the script: + ```bash + source script.sh + ``` + +2. Stop and clean the chain, and all sovereign services: + ```bash + stopAndCleanSovereign + ``` + +--- + +### Managing Sovereign + +# Managing Sovereign Chains + +:::note +This version of the documentation focuses solely on the essential steps required to set up and deploy a sovereign chain on either a local or remote computer. More content will be added as it is accepted and discussed on [Agora](https://agora.multiversx.com/), or once it is implemented and available for production. +::: + +--- + +### MultiversX DeFi Wallet + +Popularly referred to as the "future of money," MultiversX currently has a robust web wallet extension known as the **MultiversX DeFi Wallet**. It is a powerful browser extension for the MultiversX Wallet that effectively automates and reduces the steps and time required for users to interact with MultiversX Decentralized apps. + +The MultiversX DeFi Wallet can be installed on Firefox, Chrome, Brave, and other chromium-based browsers. This extension is free and secure, with compelling features that allow you to create a new wallet or import existing wallets, manage multiple wallets on the MultiversX mainnet, and store MultiversX tokens such as EGLD, ESDT, or NFTs on the MultiversX Network with easy accessibility. + +Let's walk through the steps on how to install and set up the MultiversX DeFi Wallet extension: + + +## Prerequisites +Wallets don't have custody of your funds, you do. Before utilizing the MultiversX DeFi Wallet Extension, certain preliminary steps must be taken to ensure its proper use. + + +### Add MultiversX DeFi Wallet to your browser + +- On the [Chrome Web Store Extension](https://chrome.google.com/webstore/category/extensions), search for **MultiversX DeFi Wallet.** and add it to your browser. + + ![MultiversX-defi-chrome](/wallet/wallet-extension/add_extension.png) + +- Confirm the action in the pop-up. + + ![wallet_extension_step2](/wallet/wallet-extension/confirm_extension.png) + +- You should receive a notification that the extension has been added successfully. + + ![wallet_extension_step3](/wallet/wallet-extension/extension_installed.png) + + +### Set up MultiversX DeFi wallet + +- Once it has been successfully installed, click on the extension to get started. + +- You will be presented with two options: you can either _Create new wallet_ or _Import existing wallet._ + + ![MultiversX-defi-example-welcome-page](/wallet/wallet-extension/extension.png) + + +### Create a new wallet + +**Step 1:** To get started, first install the [MultiversX DeFi wallet extension](https://chrome.google.com/webstore/detail/multiversx-defi-wallet/dngmlblcodfobpdpecaadgfbcggfjfnm). + +**Step 2:** Open up the extension and click on _‘’Create new wallet”_. + +**Step 3:** Next, a secret phrase consisting of a set of 24 secret words will be displayed. Safely backup these secret words. We strongly recommend you write them down or copy and store them in a safe place like a password manager. These secret words are the key to your wallet account and cannot be recovered if lost. + +![MultiversX-defi-example-secret-phrase](/wallet/wallet-extension/mnemonic_extension.png) + +**Step 4:** Before proceeding to the next step, confirm that you have safely stored your secret phrase. + +**Step 5:** For further verification, you will be prompted to input some of the secret words. + +![MultiversX-defi-example-authentication](/wallet/wallet-extension/confirm_secret_phrase.png) + +**Step 6:** Create a password that will be used to access the wallets stored in the MultiversX DeFi wallet extension. Ensure you keep this password safe as it will be needed to access your wallets regularly. Please note that this password cannot be recovered if lost. + +![MultiversX-defi-example-password](/wallet/wallet-extension/extension_password.png) + +**Step 7:** Completed! Your MultiversX DeFi Wallet has been successfully created and set to be used. + +![MultiversX-defi-example-successful](/wallet/wallet-extension/wallet_extension_created.png) + + +### Import existing wallet + +Do you already have a wallet? + +Then there is no need to create a new one. The MultiversX Wallet Extension provides an option to import your existing wallet. However, to import an existing wallet you must have access to its **secret (recovery) phrase**. + +The MultiversX wallet has a set of 24-words, which serve as your wallet’s secret phrase. Using a secret phrase to import an existing wallet does not affect your wallet in any way. + +**To get started:** + +**Step 1:** With the [MultiversX DeFi wallet extension](https://chrome.google.com/webstore/detail/multiversx-defi-wallet/dngmlblcodfobpdpecaadgfbcggfjfnm) installed. Click on ‘’Import existing wallet”. + +![MultiversX-defi-example-welcome-page](/wallet/wallet-extension/extension.png) + +**Step 2:** Next, enter your 24-word secret phrase. You can either enter these words one at a time or you can simply paste in the words using the "_paste_" icon. + +![MultiversX-defi-example-import-wallet](/wallet/wallet-extension/write_secret_phase.png) + +**Step 3:** Enter in your wallet password and confirm this password. + +![MultiversX-defi-example-password](/wallet/wallet-extension/extension_password.png) + +**Step 4:** Completed! Your MultiversX DeFi Wallet has been successfully imported and set to be used. + +![MultiversX-defi-example-successful](/wallet/wallet-extension/wallet.png) + + +## Key features + +Now you have a wallet registered in the MultiversX DeFi Wallet Extension and it's ready to use. Great! Here's what you can do with this wallet: + + +### Send to a wallet + +One of the key features of this extension is that it allows you to send funds from your wallet to another wallet. To use this feature, you will need to have some funds in your wallet before proceeding. + +**To get started** + +**Step 1:** Go to the MultiversX Wallet extension, enter your password and click on _“**Send**”_. + +![MultiversX-defi-example-step9](/wallet/wallet-extension/wallet.png) + +**Step 2:** Enter the address of the wallet you intend to send to and the amount. + +![MultiversX-defi-example-send](/wallet/wallet-extension/send.png) + +_(Optional)_ **Step 3**: Enter the data. This is a description of the transaction or any information you wish to pass through. + +**Step 4:** Click on the _“Continue”_ button to complete the transaction. + + +### Lock/unlock + +After 60 minutes of being inactive, the extension automatically locks itself. You can unlock it at any time using your password. In addition, you can lock the extension manually, by clicking the **“lock”** icon in the header. + + +### Deposit to a wallet + +A deposit can be made to your wallet using the wallet extension. This feature allows you to share your QR code or wallet address to receive a token deposit. To get started: + +- Open up your MultiversX Wallet extension. + +- Next, click on the _"*deposit*"_ and share your QR code or wallet address. + + ![MultiversX-defi-example-deposit](/wallet/wallet-extension/receive.png) + + +### Transactions history + +On the wallet extension dashboard, the wallet records all transactions sent and received in your wallet. If you are a new user, it says "_No transactions found for this wallet_" until you make your first transaction. + +![MultiversX-defi-example-step9](/wallet/wallet-extension/wallet.png) + + +### Networks + +In the settings section on your extension dashboard, you can connect to the different networks provided by MultiversX, such as the mainnet, testnet, and devnet. + +Choose either of these networks. + + +## Connecting the MultiversX DeFi Wallet to xExchange App + +You can now connect xExchange to the MultiversX DeFi wallet in real-time. With this connection, you will be able to log in to the exchange using the MultiversX DeFi wallet extension in a few steps. +Follow these steps to proceed: + +**Step 1:** To get started, go to the [MultiversX Exchange](https://xexchange.com) page on the right section of the page, click on the _“connect”_ button. + +![wallet_extension_step17](/wallet/wallet-extension/xexchange.png) + +**Step 2:** Select "**_MultiversX DeFi Wallet_**" from the options displayed. + +![wallet_extension_step15](/wallet/wallet-extension/login.png) + +**Step 3:** Lastly, enter your password and click on the wallet address you want to connect to. + +![wallet_extension_step16](/wallet/wallet-extension/extension_login.png) + +- In a split second, the MultiversX Exchange home page automatically reloads. You’ll notice your account has been added to the right section of the page. + +![wallet_extension_step18](/wallet/wallet-extension/logged.png) + +Successful 🎉 + + +## Guardian + +You can use the MultiversX DeFi wallet with [Guardians](/developers/guard-accounts). It is as simple as it was before, with the only mention that you have to introduce the 2FA code for guardian signature when requested: + +![guardian_extension1](/wallet/wallet-extension/guardian_extension1.png) + +--- + +### Mvx Esdt Safe + +# Mvx-ESDT-Safe +![To Sovereign](../../static/sovereign/to-sovereign.png) + +The ability to transfer tokens from the Main Chain to any Sovereign Chain is essential, since every Sovereign can connect to the Main MultiversX Chain. As a result, the customizable Sovereign can leverage any token available on the default network. Another great feature is the possibility of executing smart contracts inside the Sovereign Chain through this contract. + +This contract has three main modules: [`deposit`](#deposit), [`execute_operation`](#executing-an-operation) and [`register_token`](#registering-tokens). + + +## Deposit +### Main Chain deposit to Sovereign Chain transfer flow +1. User deposits the tokens he wishes to transfer in the `Mvx-ESDT-Safe` contract deployed on the Main Chain. +2. An observer is monitoring the Main Chain. +3. Sovereign network receives extended shard header. +4. Incoming transactions processor handles and processes the new transaction. + +### Deposit Endpoint +```rust + #[payable("*")] + #[endpoint] + fn deposit( + &self, + to: ManagedAddress, + optional_transfer_data: OptionalValueTransferDataTuple, + ) +``` + +One key aspect of cross chain transfers from MultiversX Main Chain to a Sovereign Chain is being able to transfer tokens and also execute a smart contract call within single transaction. The `#[payable("*")]` annotation means that the endpoint can receive any tokens that will transferred. If those tokens are from a Sovereign Chain they will be burned otherwise they will be saved in the Smart Contract`s account. The checks enabled for the transfer of tokens are the following: + +- If the token is whitelisted or not blacklisted, in that case the tokens can be transferred. +- If the fee is enabled, the smart contract assures that the fee is paid. +- If there are maximum 10 transfers in the transaction. + +If the deposit also includes the `optional_transfer_data` parameter it will also have some extra checks regarding the cross-chain execution of endpoints: + +- The gas limit must be under the specified limit. +- The endpoint that has to be executed is not blacklisted. + + +At the end of the `deposit` endpoint, all the extra tokens will be refunded to the caller and an event will be emitted since the bridging process is complete. + + +```rust +#[event("deposit")] +fn deposit_event( + &self, + #[indexed] dest_address: &ManagedAddress, + #[indexed] tokens: &MultiValueEncoded>, + event_data: OperationData, +) +``` + +This log event will emit the destination address and the tokens which will be transferred to the Sovereign Chain. + +:::note +The source code for the endpoint can be found [here](https://github.com/multiversx/mx-sovereign-sc/blob/main/mvx-esdt-safe/src/deposit.rs). +::: + +## Executing an Operation + +```rust +#[endpoint(executeBridgeOps)] + fn execute_operations( + &self, + hash_of_hashes: ManagedBuffer, + operation: Operation +) +``` +- `hash_of_hashes`: hash of all hashes of the operations that were sent in a round +- `operation`: the details of the cross-chain execution + +To ensure that the cross-chain execution is will be successful, the following checks must be passed: + +1. Calculate the hash of the *Operation* received as a parameter. +2. Verify that the given *Operation’s* hash is registered by the Header-Verifier smart contract. +3. Mint tokens or get them from the account. +4. Distribute the tokens. +5. Emit confirmation event or fail event if needed. + +As the 2nd point specifies, the `Header-Verifier` smart contract plays an important role in the cross-chain execution mechanism. In the [`Header-Verifier`](header-verifier.md) section there will also be a description for the important endpoints within this contract. + +:::note +The source code for the endpoint can be found [here](https://github.com/multiversx/mx-sovereign-sc/blob/main/mvx-esdt-safe/src/execute.rs). +::: + +## Important Endpoint Related Structures +This subsection outlines the key data structures that enable robust cross-chain operations. It details how an *Operation* is composed of its destination address, one or more token transfers defined by `OperationEsdtPayment`, and the contextual metadata provided by `OperationData`. Additionally, `TransferData` specifies the parameters needed for executing remote smart contract calls, collectively ensuring precise control over cross-chain interactions. +```rust +#[derive(TopEncode, TopDecode, NestedEncode, NestedDecode, TypeAbi, ManagedVecItem, Clone)] +pub struct Operation { + pub to: ManagedAddress, + pub tokens: ManagedVec>, + pub data: OperationData, +} +``` + +- `to`: specifies the destination of the *Operation* +- `tokens`: represents one or more token transfers associated with the operation +- `data`: encapsulates additional instructions or parameters that guide the execution of the operation + +```rust +pub struct OperationEsdtPayment { + pub token_identifier: TokenIdentifier, + pub token_nonce: u64, + pub token_data: EsdtTokenData, +} +``` + +This struct describes a single token transfer action within an *Operation*. Each Operation can have one or more of such payments, with that enabling the transfer of a variety of tokens during a cross-chain transaction. + +- `token_identifier`: used for the identification of the token +- `token_nonce`: if the token is Non-Fungible or Semi-Fungible, it will have a custom nonce, if not the value will be 0 +- `token_data`: a structure holding metadata and other token properties + +```rust +pub struct OperationData { + pub op_nonce: TxId, + pub op_sender: ManagedAddress, + pub opt_transfer_data: Option>, +} +``` + +`OperationData` encapsulates the needed information for the *Operation* that needs to be executed. This isn’t just another data definition, we’ve already seen data-related fields elsewhere. Instead, it centralizes the contextual information that *Operation* needs before, during, and after execution. + +- `op_nonce`: is used for the identification of each *Operation* +- `op_sender`: represents the original sender of the *Operation* +- `opt_transfer_data`: an optional `TransferData` field, when present, contains details about the cross-chain execution of another Smart Contract + +```rust +pub struct TransferData { + pub gas_limit: GasLimit, + pub function: ManagedBuffer, + pub args: ManagedVec>, +} +``` + +`TransferData` represents the description of the remote execution of another Smart Contract. + +- `gas_limit`: specifies the needed gas for the execution of all other endpoints. +- `function`: the name of the endpoint that will be executed. +- `args`: the arguments for the calls. + +:::note +The source code for structures can be found [here](https://github.com/multiversx/mx-sovereign-sc/blob/main/common/structs/src/operation.rs). +::: + +## Registering tokens +As mentioned at the start of this section, in the scope a Sovereign Chain, a token that already exists inside the MultiversX Mainchain can be leveraged within the custom blockchain. It has to be firstly registered inside the `Mvx-ESDT-Safe` smart contract. The `register_token` module has the role of registering any token that will be later used inside the Sovereign Chain. + +### Register any token + +```rust + #[payable("EGLD")] + #[endpoint(registerToken)] + fn register_token( + &self, + sov_token_id: TokenIdentifier, + token_type: EsdtTokenType, + token_display_name: ManagedBuffer, + token_ticker: ManagedBuffer, + num_decimals: usize, + ) +``` + +This endpoint is how an user from a Sovereign Chain registers a token on the MultiversX Mainchain. Every token registration costs 0.05 EGLD, that's why the endpoint is `payable`. +The endpoint check if the token was not registered before and if it has a prefix. + +> Every token that was created in a Sovereign Chain has a prefix. Example: `sov-TOKEN-123456`. + +If everything is in order, the `Mvx-ESDT-Safe` smart contract will initiate an asynchronous call to the `issue_and_set_all_roles` endpoint from the _ESDTSystemSC_. When the system smart contract finishes the issue transaction, the callback inside the `Mvx-ESDT-Safe` smart contract will trigger and register the mapping of token identifier inside the token mappers: + +* `sovereign_to_multiversx_token_id_mapper(sov_token_id)` -> mvx_token_id +* `multiversx_to_sovereign_token_id_mapper(mvx_token_id)` -> sov_token_id + +> After the execution of this endpoint, the `Mvx-ESDT-Safe` smart contract will have in its storage a pair of token identifiers. **Example**: `sov-TOKEN-123456` is the corresponding sovereign identifier for the `TOKEN-123456` identifier from the MultiversX Mainchain. You can view this feature as creating copies of MultiversX Mainchain tokens inside the Sovereign Chain. + +### Register the native token +Since a Sovereign Chain is a separate blockchain from the MultiversX Mainchain, it has to have a its own native token. Registering the native token is a straightforward process of just one endpoint call. + +```rust + #[payable("EGLD")] + #[only_owner] + #[endpoint(registerNativeToken)] + fn register_native_token(&self, token_ticker: ManagedBuffer, token_name: ManagedBuffer) +``` + +The owner will have to call the `register_native_token` from the `Mvx-ESDT-Safe` smart contract in order to register the token identifier that will be used inside the Sovereign Chain as the native one. There can only be one native token so the endpoint firstly checks if if was not already registered. The fee amount for registering is the same as registering any token, 0.05 EGLD. The parameters include the `token_ticker` and `token_name`. The endpoint then initiates an asynchronous call to the _ESDTSystemSC_ to `issue_and_set_all_roles`. The newly created token is always fungible and has 18 decimals. After the issue call is finished the callback inside the `Mvx-ESDT-Safe` smart contract inserts the newly issued token identifier inside its storage. + +:::note +The source code for this module can be found [here](https://github.com/multiversx/mx-sovereign-sc/blob/main/mvx-esdt-safe/src/register_token.rs). +::: + +--- + +### One Click Deployment + +# One-Click Deployment + +## One-click local sovereign deployment in Digital Ocean + +At present, the only one-click deployment option is available on the Digital Ocean marketplace. This solution sets up a droplet containing a local Sovereign Chain connected to the MultiversX public testnet, complete with all essential services such as API, wallet, explorer, and more. + +### Create Sovereign Chain droplet + +- Go to [Digital Ocean marketplace](https://marketplace.digitalocean.com/apps/multiversx-testnet-sovereign-chain) for more info and to create your Sovereign Chain droplet. + +:::note +It is recommended to choose a droplet with minimum 8 CPUs and 32GB RAM for the best performance. +::: + +:::important +The one-click deployment setup provided in this guide is designed primarily for development purposes. By default, it connects to the MultiversX public testnet and runs all services on the same machine. This setup is equivalent to the Full Local Setup but operates in the cloud, providing a convenient way to test and develop your Sovereign Chain in a hosted environment. +::: + +--- + +### Other Vm + +# Other-VM + +:::note +As the MultiversX Sovereign Chains ecosystem grows, additional VMs will be added and described here over time. SpaceVM implements every necessary interface and the new VM needs to only change the EXECUTOR part inside the SpaceVM.. +::: + +--- + +### Restaking + +# Restaking + +## Introduction + +In the blockchain space, reStaking products are emerging as a significant innovation. Eigenlayer is currently the leading platform, but new projects are continuously entering the Ethereum ecosystem with impressive valuations. Currently, over $13 billion is reStaked in Eigenlayer, despite the absence of a live product. + +## General Economics and Staking Models + +When designing the General Economics for Sovereign Chains, extensive research was conducted on various economic and staking models. This included insights from IBC (Cosmos ecosystem), the new Interchain Security model, Polygon SuperChains, Avalanche Appchains, various Ethereum Layer 2 solutions, and Eigenlayer. Each model was analyzed, incorporating the best features and discarding the less effective ones. + +## Current Proposed Economics Design + +The currently proposed economic design for Sovereign Chains incorporates a one-time reStaking model, non-custodial delegation, and allows extensive customization with native tokens. This framework enables the creation of an economic security fund that Sovereign Chains can leverage. Additionally, a general Sovereign Validator pool can be established, allowing new chains to launch using existing validators and economic resources without needing to gather new participants. + +This design allows validators to be sponsored by users, meaning validators do not need to hold EGLD; instead, users can delegate their tokens. This flexibility extends to users delegating directly to Sovereign Chains. + +## Enhanced Flexibility and Security + +The designed system aims to combine the benefits of Eigenlayer with added freedom for users, validators, and projects. Open markets and freedom encourage competition, opportunities, and innovation. The current system proposes one-time reStaking, the use of liquid staked assets, and even DeFi positions containing liquid staked assets. Key considerations include whether to allow multiple reStaking and if Sovereign Chains should be required to allocate a portion of rewards to the reStaking layer. + +## Multiple ReStaking + +Enabling multiple reStaking would allow EGLD reStakers to earn significantly higher returns, potentially doubling or tripling yields. Non-custodial reStaking ensures that users retain the base EGLD rewards, making it an attractive option for participation. +Benefits for Different Actors + +- **SovereignChain Builders**: They can utilize existing funds and validators, simplifying the process of securing their network. With pre-built contracts and a streamlined launch process, Sovereign Chains can achieve high security from the outset without distributing excessive tokens to validators. +- **Validators**: Validators can earn more by participating in multiple networks without owning EGLD, as users provide the required tokens. Validators can create their own economies by rewarding users who delegate to them, fostering a competitive environment. +- **Users**: Users face slightly higher risks of slashing but benefit from increased returns through multiple yields. They maintain a close connection with builders and validators from the beginning. +- **EGLD**: Increased staking reduces market supply, enhancing yield percentages and utility. ReStaking creates a new utility layer for EGLD, making it more appealing to investors. + +## Security Considerations + +ReStaking leverages the security of ETH, with slashing risks being the primary concern. Slashing events are rare in existing PoS networks, indicating that proper economic incentives generally ensure validator honesty. However, potential systemic risks from widespread slashing events need careful management. + +Mitigation Strategies that we are analyzing: + +- **Limit Slashing**: Set global and local limits on slashed EGLD. +- **Distribution of Slashed EGLD**: Distribute slashed tokens to honest validators and participants, incentivizing honest behavior. +- **Cooldown Period**: Implement a cooldown period before distributing slashed EGLD to mitigate immediate impacts. +- **Global Redistribution**: Distribute slashed EGLD globally rather than locally to spread the risk and reward. + +## Conclusion + +The risk/reward ratio for reStaking supports enabling multiple reStaking, offering significant benefits to all participants. By carefully managing the associated risks, the system can provide enhanced returns and foster a thriving ecosystem. + +--- + +### Security + +# Security Considerations + + +:::note +This version of the documentation focuses solely on the essential steps required to set up and deploy a sovereign chain on either a local or remote computer. More content will be added as it is accepted and discussed on [Agora](https://agora.multiversx.com/), or once it is implemented and available for production. +::: + +--- + +### Services + +# Introduction + +## Sovereign services + +### API service + +The Sovereign API is a wrapper over the Sovereign Proxy that brings a robust caching mechanism, alongside Elasticsearch historical queries support, tokens media support, delegation & staking data, and many others. + +### Lite Extras API +The Sovereign Lite Extras API includes a faucet service that allows users to obtain test tokens for their wallet. + +### Lite Wallet +The Sovereign Lite Wallet is a lightweight version of the public wallet. It supports key functionalities such as cross-chain transfers, token issuance, token transfers, and more. + +### Explorer DApp +The Explorer DApp serves as the blockchain explorer for the Sovereign Chain, providing insights into transactions, blocks, and other on-chain activity. + +## Software dependencies + +### Node.js + +[Download Node.js](https://nodejs.org/en/download) or use a version manager like [NVM](https://github.com/nvm-sh/nvm) + +### Yarn + +To install yarn, use the following command: +```bash +npm install --global yarn +``` + +### Redis + +To install and start redis, use the following commands: + +```bash +sudo apt update +sudo apt install redis +sudo systemctl start redis +sudo systemctl enable redis +``` + +## Prerequisites + +### Sovereign network deployed + +Before starting the services it is required to have a full sovereign network running, see [setup guide](/sovereign/local-setup). + +--- + +### Software Dependencies + +# Software Dependencies + +Understanding and managing software dependencies is crucial for the successful deployment and maintenance of Sovereign Chains. Dependencies ensure that all components of your blockchain network work seamlessly together. This page outlines the key dependencies required for building and operating Sovereign Chains, including software libraries, frameworks, and tools. + +:::note +Below is the list of software needed to deploy a local Sovereign Chain. All the software dependencies will be installed by scripts in [Setup Guide](/sovereign/setup). +::: + +## Core Dependencies + +### Python3 + +To install python3, use the following command: + +```bash +sudo apt install python3 +``` + +## pipx + +To install pipx, use the following command: + +```bash +sudo apt install pipx +pipx ensurepath +sudo pipx ensurepath --global +``` + +### mxpy + +Ensure you are using the latest version of mxpy. Follow the installation or upgrade instructions provided [here](/sdk-and-tools/mxpy/installing-mxpy#install-using-pipx) if you haven't done so already. + +### multiversx-sdk + +To install this dependency on Linux, use the following command: + +```bash +pip install multiversx-sdk +``` + +### tmux + +:::note +If you want to use tmux (which is the default configuration), you should install it. +::: +To install tmux, use the following command: + +```bash +sudo apt install tmux +``` + +### screen + +To install the screen utility on Linux, use the following command: + +```bash +sudo apt install screen +``` + +### wget + +To install wget, use the following command: + +```bash +apt install wget +``` + +### Docker + +While not mandatory, it is recommended to use the latest version of Docker. If you need to install it, please follow the instructions at [Docker's official documentation](https://docs.docker.com/get-docker/). + +### Golang + +Ensure you are using Go version 1.20. + +:::note +Please note that at the time of writing this documentation, the setup scripts have been tested only on Ubuntu. +::: + +--- + +### Solana L2 + +# Solana L2 + +:::note + +This documentation is not complete. More content will be added once it is accepted and discussed on Agora or once it is implemented and available for production. + +::: + +--- + +### Sov Esdt Safe + +# Sov-ESDT-Safe +![From Sovereign](../../static/sovereign/from-sovereign.png) + +Whether an External Owned Account (EOA) like an user wallet or another smart contract the procedure is simple. An address will be able to perform a multi tokens transfer with various types of tokens: +- Fungible Tokens +- (Dynamic) Non-Fungible Tokens +- (Dynamic) Semi-Fungible Tokens +- (Dynamic) Meta ESDT Tokens + +When making the deposit, the user specifies: +1. A destination address +2. `TransferData` if the execution contains a smart contract call, which contains gas, function and arguments + +## Sovereign Chain to Main Chain transfer flow +1. User deposits token to the `Sov-ESDT-Safe` smart contract. +2. Outgoing *Operations* are created at the end of the round. +3. Validators sign all the outgoing *Operations*. +4. Leader sends *Operations* to the Sovereign Bridge Service. +5. Sovereign Bridge Service sends the *Operations* to the Header-Verifier for registration and verification, and then to `Mvx-ESDT-Safe` for execution. +6. At the end of the execution success/fail, a confirmation event will be added which will be received in Sovereign through the observer and then the cross chain transfer will be completed. + +### Deposit Endpoint +```rust + #[payable("*")] + #[endpoint] + fn deposit( + &self, + to: ManagedAddress, + optional_transfer_data: OptionalValueTransferDataTuple, + ) +``` +As you can see both the `Mvx-ESDT-Safe` and `Sov-ESDT-Safe` smart contracts have the `deposit` endpoint. At a high level, the process is the same, depositing funds or calling other smart contracts. The main differences are when each payments is processed. Since the `Sov-ESDT-Safe` smart contract doesn't have the storage mapper for the native token, the payments won't be verified if they include any payment with the native token. The enabled checks are the same: + +- If the token is whitelisted or not blacklisted, in that case the tokens can be transferred. +- If the fee is enabled, the smart contract assures that the fee is paid. +- If there are maximum 10 transfers in the transaction. + +If the deposit also includes the `optional_transfer_data` parameter it will also have some extra checks regarding the cross-chain execution of endpoints: + +- The gas limit must be under the specified limit. +- The endpoint that has to be executed is not blacklisted. + + +At the end of the `deposit` endpoint, all the extra tokens will be refunded to the caller and an event will be emitted since the bridging process is complete. + + +```rust +#[event("deposit")] +fn deposit_event( + &self, + #[indexed] dest_address: &ManagedAddress, + #[indexed] tokens: &MultiValueEncoded>, + event_data: OperationData, +) +``` + +This log event will emit the destination address and the tokens which will be transferred to the Sovereign Chain. + +:::note +The source code for the endpoint can be found [here](https://github.com/multiversx/mx-sovereign-sc/blob/main/sov-esdt-safe/src/deposit.rs). +::: + +--- + +### Sovereign - Overview + +:::note + +This documentation is not complete. More content will be added once it is accepted and discussed on Agora or once it is implemented and available for production. + +::: + +This guide provides detailed instructions on setting up, deploying, and managing a sovereign chain, along with covering various other educational topics. + +## Table of Contents + +1. [Introduction](/sovereign/concept) +2. [Prerequisites](/sovereign/system-requirements) +3. [Setup Guide](/sovereign/local-setup) +4. [Custom Configurations](/sovereign/custom-configurations) +5. [Managing a Sovereign Chain](/sovereign/managing-sovereign) +6. [Economics](/sovereign/token-economics) +7. [Governance](/sovereign/governance) +8. [Testing and Validation](/sovereign/testing) +9. [Security Considerations](/sovereign/security) +10. [VMs](/sovereign/vm) +11. [Interoperability](/sovereign/interoperability) +12. [How to become a validator](/sovereign/validators) + +## Introduction + +The introduction chapter will provide an overview of what sovereign chains are and their significance in the blockchain ecosystem. It will answer questions such as: + +- What are sovereign chains? +- How do they operate within the MultiversX blockchain network? +- What are the benefits and use cases of sovereign chains? + +## Prerequisites + +The prerequisites chapter will detail the necessary preparations before setting up a sovereign chain. It will answer questions such as: + +- What are the system requirements? +- What software dependencies need to be installed? + +## Setup Guide + +The setup guide will get you through the initial steps to get your sovereign chain up and running. It will answer questions such as: + +- How do you create a new wallet? +- Where can you download the required files? +- What repositories should you use and how to prepare your environment? +- How do you deploy necessary contracts? Can you do it in an automated manner? +- What are the step-by-step instructions for manual deployment? +- How do you update sovereign configurations and manage Docker observers? + +--- + +### Sovereign Api + +# API service + +## Deploy Sovereign Proxy service + +:::info +Proxy service is automatically deployed if the sovereign chain was started with [local setup](/sovereign/local-setup) +::: + +### Step 1: Get the `mx-chain-proxy-sovereign-go` Repository + +Before proceeding, ensure that a **SSH key** for GitHub is configured on your machine. + +1. Clone the GitHub repository: + ```bash + git clone git@github.com:multiversx/mx-chain-proxy-sovereign-go.git + ``` + +2. Navigate to proxy directory: + ```bash + cd mx-chain-proxy-sovereign-go/cmd/proxy + ``` + +### Step 2: Edit Proxy `config.toml` file + +Example: +``` +[[Observers]] + ShardId = 0 + Address = "http://127.0.0.1:10000" + +[[Observers]] + ShardId = 4294967295 + Address = "http://127.0.0.1:10000" +``` + +:::note +For sovereign proxy there are 2 Observers required for `ShardId` 0 and 4294967295. The `Address` should be the same for both. +::: + +### Step 3: Start Sovereign Proxy service + +Build and run the proxy +```bash +go build +./proxy --sovereign +``` + +## Deploy Sovereign API service + +### Step 1: Get the `mx-api-service` Repository + +1. Clone the GitHub repository: + ```bash + git clone https://github.com/multiversx/mx-api-service.git + ``` + +2. Checkout the sovereign branch and navigate to testnet directory: + ```bash + cd mx-api-service && git fetch && git checkout feat/sovereign + ``` + +### Step 2: Edit API config + +1. Navigate to the `config` folder: + ```bash + cd config + ``` + +2. Update the configuration files (we are starting from testnet configuration in this example): + - `config.testnet.yaml` - enable/disable or configure the services you need + - `dapp.config.testnet.json` - dapp configuration file + +### Step 3: Start Sovereign API service + +```bash +npm install +npm run init +npm run start:testnet +``` + +## Deploy Sovereign Extras service + +The extras service only includes the `faucet` option at the moment. + +### Step 1: Get the ```mx-lite-extras-service``` Repository + +```bash +git clone https://github.com/multiversx/mx-lite-extras-service.git +``` + +### Step 2: Update extras configuration files + +- `.env.custom` - change `API_URL` and `GATEWAY_URL` with your own URLs +- `config/config.yaml` - update the faucet configuration parameters as needed + +### Step 3: Start Sovereign Extras service + +```bash +NODE_ENV=custom npm run start:faucet +``` + +Read more about deploying API service in [GitHub](https://github.com/multiversx/mx-lite-extras-service#quick-start). + +--- + +### Sovereign Explorer + +# Explorer service + +## Deploy Explorer + +### Step 1: Get the `mx-explorer-dapp` Repository + +```bash +git clone https://github.com/multiversx/mx-explorer-dapp.git +``` + +### Step 2: Update explorer configuration file + +1. Navigate to the `src/config` folder: + ```bash + cd src/config + ``` + +2. Update the parameters and URLs with your own configuration in `config.testnet.ts` file + +Example configuration: +``` +{ + default: true, + id: 'sovereign', + name: 'Sovereign', + chainId: 'S', + adapter: 'api', + theme: 'default', + egldLabel: 'SOV', + walletAddress: 'https://localhost:3000', + explorerAddress: 'https://localhost:3003', + apiAddress: 'https://localhost:3002', + hrp: 'erd', + isSovereign: true +} +``` + +### Step 3: Start Sovereign Explorer + +```bash +yarn +npm run start-testnet +``` + +Read more about deploying explorer in [GitHub](https://github.com/multiversx/mx-explorer-dapp/tree/main#quick-start). + +--- + +### Sovereign Wallet + +# Wallet service + +## Deploy Lite Wallet + +### Step 1: Get the `mx-lite-wallet-dapp` Repository + +```bash +git clone https://github.com/multiversx/mx-lite-wallet-dapp.git +``` + +### Step 2: Update sovereign configuration file + +1. Navigate to the `src/config` folder: + ```bash + cd src/config + ``` + +2. Update the `sharedNetworks.ts` file: + - for `sovereign` item + - update the URLs with your own + - update `WEGLDid` with the sovereign native token identifier from `config.toml` -> `BaseTokenID` + - update `sovereignContractAddress` with contract address from `sovereignConfig.toml` -> `SubscribedEvents` from `OutgoingSubscribedEvents` + - for `testnet` item (or the network your sovereign is connected to) + - update `sovereignContractAddress` with contract address from `sovereignConfig.toml` -> `SubscribedEvents` from `NotifierConfig` + +### Step 3: Start Sovereign Lite Wallet + +```bash +yarn install +yarn start-sovereign +``` + +Read more about deploying lite wallet in [GitHub](https://github.com/multiversx/mx-lite-wallet-dapp/tree/main#multiversx-lite-wallet-dapp). + +--- + +### Standalone Evm + +# Standalone EVM + +## EVM as example +In the early stages of the MultiversX VM development, there were already components built specifically for EVM compatibility. We are revisiting and reusing parts of that code. In **VM1.2**, for instance, there was a direct correspondence between EVM opcodes and the **BlockchainHook** interface, as well as a mechanism that wrapped MvX-style transaction data (**txData**) into EVM-specific `vmInput`. + +## 1. VMExecutionHandlerInterface +The MultiversX protocol defines a **VMExecutionHandlerInterface** with the following functions: + +```go +// RunSmartContractCreate computes how a smart contract creation should be performed +RunSmartContractCreate(input *ContractCreateInput) (*VMOutput, error) + +// RunSmartContractCall computes the result of a smart contract call and how the system must change after the execution +RunSmartContractCall(input *ContractCallInput) (*VMOutput, error) +``` +The **SCProcessor** from `mx-chain-sovereign-go` prepares the input information for these functions. We aim to avoid modifying the **SCProcessor** itself; instead, all necessary abstractions will be implemented at the EVM level. + +## 2. Input Preparation: EVMInputCreator + +When a contract creation request is made (via *ContractCreateInput), an EVMInputCreator component will: +- Convert the `ContractCreateInput` into an EVMInput. +- Invoke the actual EVM smart contract logic. + +The EVM itself is taken from the official Go implementation ([evm.go](https://github.com/ethereum/go-ethereum/blob/master/core/vm/evm.go) in go-ethereum). + +## 3. Abstraction Layer: MultiversX & EVM Interfaces + +To allow the EVM to function within MultiversX, we introduce a layer that bridges EVM interfaces with MultiversX components. The core interface it uses is the `BlockchainHookInterface`, which grants access to critical blockchain data, state, and transaction information. + +### 3.1 Reading & Writing to Storage + +- **Reading Storage**: When an EVM opcode attempts to read data from the storage (e.g., `readStorageFromTrie(key)`), it should invoke `blockchainHook.ReadFromStorage(scAddress, key)`. Internally, this call goes through the `storageContext` component, which manages reads from local cache if a key has already been accessed or modified during the current transaction. + +- **Writing to Storage**: When writing to storage, the EVM opcode should call `SetStorageToAddress(address, key)` in the `storageContext`. + +### 3.2 Finalizing State Changes + +After EVM execution finishes, we need to commit the resulting state changes to the blockchain. The EVM will use the `outputContext` component, which (together with the `storageContext`) tracks modified accounts and storages. It also creates the final `vmOutput`, which the `scProcessor` in `mx-chain-sovereign-go` will then validate and apply to the blockchain (the trie) if everything is correct. + +## 4. Gas Metering + +EVM gas metering is handled internally within the EVM code. The VMExecutionHandler can receive a new gas schedule via: + +```go +GasScheduleChange(newGasSchedule map[string]map[string]uint64) +``` + +This function provides the cost of each opcode as a map. The EVM needs the appropriate wrapper functions to load these costs into its **OPCODES** structure. + +## 5. Implementation Steps: Integrating EVM + +- Start from the SpaceVM code. +- Replace the current executor (WASMER) with the EVM executor. +- During EVM opcode interpretation, invoke the `storageContext` and `meteringContext` functions to manage state changes and track gas consumption. + +Once these steps are complete, the underlying EVM logic should effectively run on MultiversX. + +## 6. Address Conversion: 20 Bytes vs. 32 Bytes + +EVM addresses are 20-bytes long, whereas MultiversX uses 32-byte addresses. To avoid changing the broader MultiversX system, the EVM will use internal transformers: + +- **Internal EVM Calls**: Within the EVM, contracts use the last 20 bytes of the corresponding 32-byte MvX address. +- At runtime, the full 32-byte address is still known, and when a storage `read` or `write` occurs, the EVM prefixes the last 20 bytes with 10 bytes of zeros plus a 2-byte `VMType` (the standard MvX smart contract addressing scheme). + +### 6.1 Calling EVM Contracts from EVM + +When an EVM-based smart contract calls another EVM contract, it uses the 20-byte address. Internally, the system prefixes these 20 bytes with the deterministic overhead (10 bytes of zeros and 2 bytes for `VMType`) to fetch the appropriate contract code from the accounts trie before running it. + +### 6.2 Token Storage in EVM + +Token balances (like ERC20) live in the contract's own storage. The contract will use the last 20 bytes of a user’s MvX address when recording ownership or balances. If an opcode like `GetCaller` is executed, it returns only the last 20 bytes from the `ContractCallInput.Sender`. + +### 6.3 Calling WasmVM from EVM + +MultiversX WasmVM expects 32-byte addresses. If an EVM contract tries to invoke a WasmVM contract using only 20 bytes, the call will fail due to incorrect argument size. Consequently, when the EVM calls a WasmVM contract, it must supply a full 32-byte address. + +:::note +In most cases, the EVM contracts will call only other EVM contracts. However, bridging to WasmVM is still feasible, for example, when claiming ESDT tokens through an ERC wrapper contract. +::: + +## 7. WASM VM and the `ExecuteOnDestOnOtherVM` Function + +The **WASM VM** supports a public function `ExecuteOnDestOnOtherVM` via the **BlockchainHook** interface. If a new VM is fully integrated, it can be added to the `vmContainer` component with a new **baseAddress**. Below is an example table illustrating potential base addresses for different VMs: + +| VM Name | Address Suffix | Notes | +|-------------|----------------------|----------------------------------------------------------------------------------| +| **SpaceVM** | 05 | Standard base address for the WASM VM | +| **System VM** | 255 | Standard base address (example) for the System VM | +| **EVM** | To Be Decided | Will be assigned upon integration to ensure address derivation works properly | + +From the `SCAddress`, the protocol looks at bytes **10** and **11** to determine which VM should be called. Once EVM integration is complete, it will receive its own base address and will adjust how the **CreateContract** opcode calculates deployed contract addresses. + +### 7.1 Synchronous Execution + +When the EVM executes a `DelegateCall` opcode, it will invoke an internal function of the new EVM implementation that checks whether execution should occur in the EVM itself or a different VM. If it needs to run on another VM, it calls `blockchainHook.ExecuteOnDestOnOtherVM`. + +- **Returning `VMOutput`**: This function returns a `VMOutput`, which can be merged into the current `outputContext` and `storageContext` via the `PushContext`-type public functions. + +In the **WASM VM**, if a smart contract calls `ExecuteOnDest`, the VM decides where the execution should take place. For asynchronous calls, the same logic applies: + +- **Intra-Shard**: The system calls `ExecuteOnDestOnOtherVM`. +- **Cross-Shard**: On the destination shard, the **scProcessor** determines which VM to invoke and continues accordingly. + +## 8. ESDT ↔ ERC20 & ESDTNFT ↔ ERC721 + +Bridging MultiversX ESDT standards with common Ethereum-based token standards (ERC20, ERC721, etc.). This introduces several key differences in token handling, especially around **token transfers** and **approval mechanisms**. + +### 8.1 ESDT Transfer Model +On MultiversX, transfers typically use a **`transferAndExecute`** paradigm: +- The sender (token owner) explicitly initiates a transfer of tokens and, in the same operation, calls a smart contract endpoint to process further actions (e.g., swapping, staking, etc.). + +### 8.2 ERC20 Transfer Model +In the Ethereum ecosystem, the common workflow is: +1. **Approval**: A user grants a smart contract (SC) permission to spend tokens on their behalf by calling `approve(scAddress, amount)`. +2. **Transfer**: The SC (now approved) calls `transferFrom(user, destination, amount)` to pull tokens from the user’s balance. + +This design allows third-party contracts to move funds from a user’s wallet without a new, explicit approval each time. However, it also opens the door to potential exploits: a malicious dApp can trick users into granting excessive approvals, which might be exploited later to drain funds. + +### 8.3 The Wrapper/SafeESDT Contract + +Because MultiversX prohibits direct “pull” transfers of ESDTs (a fundamental security decision), bridging to ERC-like workflows requires an **intermediary contract**—often called a **wrapper** or **safeESDT** contract: + +1. **Deposit**: A user deposits their ESDT tokens into the wrapper contract. +2. **Allow**: The user can specify which addresses (e.g., other SCs) are allowed to withdraw a certain amount of these deposited tokens. +3. **Transfer**: The contract implements an ERC20-like `transferFrom()` functionality. When an external EVM-based SC tries to “pull” tokens, it actually interacts with this safeESDT contract, which checks permissions and only then completes the transfer if authorized. +4. **Withdrawal**: The user can reclaim any unspent tokens from the wrapper contract when they wish. + +In the EVM environment, an operation like `safeESDTContract.transferFrom(user, scAddress, amount)` would mimic the ERC20 approach. Under the hood, the **blockchainHook** would manage a synchronous call to the other VM. + +### 8.4 Extending to Other ERC Standards + +A similar wrapper approach can be adopted for other token types: + +- **ERC721 (NFTs)**: An **ESDTNFT** wrapper can track ownership and minted tokens, providing `approve()` and `transferFrom()` methods that mirror standard ERC721 functionality. +- **ERC1155**: This multi-token standard can likewise be “wrapped”, allowing ESDT-based multi-tokens to be interfaced with EVM-based dApps expecting ERC1155 contracts. + +By handling all "pull" transfers inside dedicated wrapper contracts, MultiversX preserves its **secure-by-design** “push” transfer model while still enabling compatibility with dApps that rely on ERC-style approvals. + +### Claiming ESDT Tokens from an ERC20 Balance + +This diagram illustrates how a user claims an ESDT token originally held in an ERC20 contract on the EVM side. The process involves burning ERC20 tokens, calling a WASM VM wrapper contract, and finally minting ESDT tokens to the user. + +```mermaid +sequenceDiagram + participant U as User + participant E as ERC20 Contract (EVM) + participant W as WASM VM Wrapper + + U->>E: 1) Call ERC20 contract to burn tokens + E->>W: 2) callContract(ERCWrapper) on WASM VM
(includes burn details) + Note over W: Registers the token under a 20-byte address
(EVM only knows 20 bytes) + U->>W: 3) User claims tokens from the WASM VM wrapper + W->>W: 3a) Checks last 20 bytes == callInput.CallerAddress[12:32] + W->>U: 4) Mints and sends ESDT tokens to OriginalCaller +``` + +--- + +### System Requirements + +# System Requirements + +:::note + This is a living document. More content will be added once it is implemented and available for production. As this documentation evolves, some sections may be updated or modified to reflect the latest developments and best practices. Community feedback and contributions are encouraged to help improve and refine this guide. Please note that the information provided is subject to change and may not always reflect the latest updates in the technology or procedures. s +::: + +This page outlines the recommended system requirements for running a Sovereign Chain node. + +The hardware requirements for running a Sovereign Chain validator node generally depend on the node configuration and may evolve as the Sovereign network undergoes upgrades. If the Sovereign Chain does not serve any special function (such as AI, DA, DePIN, etc.), the minimum requirements should align with those for running a MultiversX node. + +## Processor + +It is preferable to use a **4 x dedicated/physical** CPUs, either Intel or AMD, with ```SSE4.1``` and ```SSE4.2``` flags (use lscpu to verify). The CPUs must be ```SSE4.1``` and ```SSE4.2``` capable, otherwise the node won't be able to use the Wasmer 2 VM available through the VM 1.5 (and above) and the node will not be able to sync blocks from the network. + +:::caution +If the system chosen to host the node is a VPS, the host must have dedicated CPUs. Using shared CPUs can hinder your node's performance that will result in a decrease of node's rating and eventually the node might get jailed. +::: + +:::tip +We are promoting using processors that support the fma or fma3 instruction set since it is widely used by our VM. Displaying the available CPU instruction set can be done using the Linux shell command sudo lshw or lscpu +::: + +## Memory + +It is recommended to use at least 16GB RAM. + +## Disk space + +Disk space is usually the primary bottleneck for node operators. At the time of writing, for running a node with the **chain-sdk** binary you would need at least 200 GB SSD. +As well as storage capacity, MultiversX nodes rely on fast read and write operations. This means HDDs and cheaper SSDs can sometimes struggle to sync the blockchain. + +## Bandwidth + +It is important to have a stable and reliable internet connection, especially for running a validator because downtime can result in missed rewards or penalties. It is recommended to have at least 100 Mbit/s always-on internet connection. Running a node also requires a lot of data to be uploaded and downloaded so it is better to use an ISP that does not have a capped data allowance and if it does you would need at least 4 TB/month data plan. + +--- + +### Testing + +# Testing and Validation + +:::note + +This documentation is not complete. More content will be added once it is accepted and discussed on Agora or once it is implemented and available for production. + +::: + +--- + +### Token Economics + +# Token Economics + +## Introduction + +Sovereign Chains represent an important step forward for MultiversX ecosystem, allowing each chain to operate independently with its own set of rules, governance, and most importantly, its own token economy (tokenomics). Unlike traditional blockchain models where a single token often dominates, Sovereign Chains enable the creation and management of unique tokens tailored to the specific needs and goals of each chain. + +## The Flexibility of Token Economies + +One of the core benefits of Sovereign Chains is the flexibility in designing token economies. Each Sovereign Chain can develop a token that aligns perfectly with its specific use case, community, and economic model. This allows for a highly customized approach to incentivizing behavior, securing the network, and ensuring the long-term sustainability of the chain. + +## Key Components of Token Economics + +### Token Creation and Distribution: +- Initial Supply: Sovereign Chains can decide the total initial supply of their token, whether it’s a fixed supply or an inflationary model. +- Distribution Mechanisms: Tokens can be distributed through a variety of methods including initial coin offerings (ICOs), airdrops, or mining. + +### Utility and Functionality: +- Transaction Fees: Tokens can be used to pay for transaction fees within the chain, ensuring smooth and cost-effective operations. +- Staking and Governance: Tokens often play a crucial role in governance, allowing holders to vote on important decisions and proposals. Additionally, tokens can be staked to secure the network and earn rewards. + +### Incentive Structures: +- Reward Programs: To encourage participation and loyalty, Sovereign Chains can implement reward programs for activities such as staking, providing liquidity, or contributing to the network's development. +- Burn Mechanisms: To control inflation and increase scarcity, some chains might implement token burning mechanisms where a portion of the tokens is permanently removed from circulation. + +## Advantages of Sovereign Chain Token Economies + +- Customization: Sovereign Chains can tailor their tokenomics to fit the specific needs and goals of their ecosystem. +- Innovation: By having control over their own economic model, Sovereign Chains can experiment with innovative incentive structures and governance models. +- Independence: Each chain operates independently, reducing the risk of systemic failures and ensuring greater resilience. + +--- + +### Token Management + +# Sovereign Deposit Tokens Guide + +## Main Chain -> Sovereign Chain + +1. Navigate to `/mx-chain-sovereign-go/scripts/testnet/sovereignBridge`. + + Update the configuration file `config/configs.cfg` with the token settings you prefer. Example: + ```ini + # Issue Main Chain Token Settings + TOKEN_TICKER=TKN + TOKEN_DISPLAY_NAME=Token + NR_DECIMALS=18 + INITIAL_SUPPLY=111222333 + ``` + +3. Source the script: + ```bash + source script.sh + ``` + +4. Issue a token on the main chain: + ```bash + issueToken + ``` + +5. Deposit the token in the smart contract: + ```bash + depositTokenInSC + ``` + +## Sovereign Chain -> Main Chain + +1. Navigate to `/mx-chain-sovereign-go/scripts/testnet/sovereignBridge`. + + Update the configuration file `config/configs.cfg` with the sovereign token settings you prefer. Example: + ```ini + # Issue Sovereign Token Settings + TOKEN_TICKER_SOVEREIGN=SVN + TOKEN_DISPLAY_NAME_SOVEREIGN=SovToken + NR_DECIMALS_SOVEREIGN=18 + INITIAL_SUPPLY_SOVEREIGN=333222111 + ``` + +2. Source the script: + ```bash + source script.sh + ``` + +3. Issue a new token on the local sovereign chain: + ```bash + issueTokenSovereign + ``` + +### Steps to transfer tokens: + +:::info +- Ensure the sovereign bridge contract has the BurnRole for the token you want to bridge. All new tokens have `ESDTBurnRoleForAll` enabled. If disabled, register the burn role: + ```bash + setLocalBurnRoleSovereign + ``` +::: + +- Register the sovereign token identifier on the main chain bridge contract: + ```bash + registerSovereignToken + ``` + +- Deposit the token in the smart contract on the sovereign chain: + ```bash + depositTokenInSCSovereign + ``` + +--- + +### Token Types + +import useBaseUrl from '@docusaurus/useBaseUrl'; +import ThemedImage from '@theme/ThemedImage'; + + +# Token Types + +With the release of the bridge v3.0 & v3.1 different token types are allowed to be bridged. By token type, we refer to how the +tokens are locked/burnt or unlocked/minted along with the chain side on which they are native. The decision of how the token should +be configured in the bridge depends on the availability of the minter/burner role on the EVM-compatible chain side along with +the marketing decision of "on which chain the token should be native (a.k.a. where it was first minted)" + + +**1. Mint/Burn & Native on MultiversX < - > EVM-chain has Mint/Burn & Non-Native** + +This bridge token-type configuration has the advantage of not holding a single token in the bridge. +The swapped tokens are minted/burned on both sides. The total token quantity on both chains equals the (minted - burned) on MultiversX +added with the (minted - burned) on the EVM-compatible chain side. The initial supply & mint is done on MultiversX. + + + + + +**2. Mint/Burn & Non-Native on MultiversX < - > EVM-chain has Mint/Burn & Native** + +This has the same advantages as 1. but the initial minting is done on the EVM-compatible chain side. + + +**3. Mint/Burn & Non-Native on MultiversX < - > EVM-chain has Locked/Unlocked & Native** + +This bridge token configuration type will need to be initiated on the EVM-compatible chain side and the tokens transferred to +MultiversX will be locked in the bridge contract (no minter role is needed on the EVM-compatible chain as opposed to 1 & 2) and +will be unlocked when swaps from MultiversX to the EVM-compatible chain are done. On the MultiversX side, mint/burn +actions will be performed. The total quantity of tokens on both chains will be equal to the supply on the EVM-compatible chain. Everything that is +locked in the bridge contract will equal to what was (minted - burned) on MultiversX side. + +:::warning +This configuration will also require an intermediate token as to allow the bridging on more than one EVM-compatible chain. +It also can create discrepancies between the allowed supplies to bridge between multiple chains. +::: + +Example: if tokens are being brought to MultiversX from EVM-compatible chain 1 and then the tokens are bridged out from +MultiversX to EVM-compatible chain 2, then the bridge for EVM-compatible chain 2, at some point, will be unable to process +swaps because it will run out of its intermediary tokens. The solution here is to manually bridge in the reversed order +as described (an operation that consumes time & fees). + + + + +**Note:** The diagram above is a little bit misleading because the ERC20 contracts hold the address/balance ledgers inside +them. For the sake of simplicity, the tokens are depicted as stored inside the bridge **Safe** contracts +(just as MultiversX ESDTs). + +--- + +### Transfer Flows + +import useBaseUrl from '@docusaurus/useBaseUrl'; +import ThemedImage from '@theme/ThemedImage'; + + +# Transfer Flows + +The main functionality of the bridge is to transfer tokens from one network to another. For example, a user can transfer tokens from an EVM-compatible chain to MultiversX or from MultiversX to an EVM-compatible chain. +Besides the main functionality, there is the possibility to call a smart contract on MultiversX when doing a swap. + + +# 1. Simple token transfer functionality + +## 1.a. EVM-compatible chain to MultiversX + +Let's suppose Alice has x tokens on an EVM-compatible chain and wants to transfer them to MultiversX at the address she owns +(or it might be Bob's address on MultiversX, the bridge does not care). The steps and flow are the following: +* Alice deposits the ERC20 tokens that she wants to transfer to the MultiversX network in the EVM-compatible chain's **Safe** contract; +* The EVM-compatible chain's **Safe** contract groups multiple deposits into batches; +* After a certain period of time (defined by the finality parameters of the EVM-compatible chain), each batch becomes final and starts being processed by the relayers; +* The relayers propose, vote, and perform the transfer using MultiversX's **Bridge** contract with a consensus of a minimum of 7 out of 10 votes; +* On the MultiversX network, the same amount of ESDT tokens are minted or released, depending on the token's settings; +* The destination address receives the equivalent amount of ESDT tokens on the MultiversX network. + + +## 1.b. MultiversX chain to an EVM-compatible chain + +Now let's suppose Alice wants her x tokens on MultiversX to transfer them to an EVM-compatible chain in the address she owns +(or it might be Bob's address on the EVM-compatible chain, again, the bridge does not care). The steps and flow are the following: + +* Alice deposits the ESDT tokens that she wants to transfer to the EVM-compatible network in MultiversX's **Safe** contract; +* The MultiversX's **Safe** contract groups multiple deposits into batches; +* After a certain period of time, each batch becomes final and ready to be processed by the relayers; +* The relayers propose, vote, and perform the transfer using the EVM-compatible chain's **Bridge** contract with a consensus of exactly 7 out of 10 votes; +* The user receives the equivalent amount of ERC20 tokens on their recipient address on the EVM-compatible network minus the fee for this operation; +* On the MultiversX network, the ESDT tokens that were transferred are burned or locked, depending on the token's settings. + +# 2. Token transfer with smart-contract call on MultiversX side + +Starting with bridge v3.0, swaps from the EVM-compatible chains can invoke a smart contract on the MultiversX side. +Let's suppose Alice has x tokens on an EVM-compatible chain and wants to transfer them to MultiversX to a contract while invoking +a function on that contract. The steps and flow are the following: + +* Alice deposits the ERC20 tokens that she wants to transfer to the MultiversX network in the EVM-compatible chain's **Safe** contract, + on a special endpoint also providing the MultiversX's contract address, function, the parameters for the invoked function, and a minimum + gas-limit to be used when invoking the function; +* The EVM-compatible chain's **Safe** contract groups multiple deposits into batches, regardless of whether the deposits are of this type or 1.a. type; +* After a certain period of time (defined by the finality parameters of the EVM-compatible chain), each batch becomes final and starts being processed by the relayers; +* The relayers propose, vote, and perform the transfer using MultiversX's **Bridge** contract with a consensus of a minimum of 7 out of 10 votes; +* On the MultiversX network, the same amount of ESDT tokens are minted or released, depending on the token's settings; +* The minted or released tokens, along with the smart-contract call parameters (contract address, function, parameters, and minimum gas limit) are then moved in + the specialized contract called **BridgeProxy**; +* On the **BridgeProxy** contract there is an endpoint for executing the smart-contract call. Alice or any MultiversX entity willing to + spend the gas for execution can call the endpoint. The minimum gas limit for the execution is the one specified by Alice, or it can be higher; +* The entity triggering the execution flow will pay the gas limit and the **BridgeProxy** will handle the execution which can have 2 outcomes: + * The call was successful: in this case, the contract will be credited with the tokens sent by Alice, and the invoked function would have produced the desired effects; + * The call was unsuccessful: in this case, the **BridgeProxy** received back the tokens and will mark the transfer as failed. + * The **BridgeProxy** can then be called on another endpoint to attempt the refund mechanism. Alice or any other MultiversX entity willing + to spend gas limit can invoke that endpoint; + * Whoever calls the **BridgeProxy** refund endpoint, will pay the gas limit for the reversed transfer operation. This will also attempt to subtract the fee + as for any normal MultiversX to EVM-compatible chain transfer; + * The transfer is placed in a MultiversX batch and eventually, Alice will get her tokens back on the originating EVM-compatible chain minus the fee. + As stated, the operations on the **BridgeProxy** can be done manually, or by using the **scCallsExecutor** tool provided here https://github.com/multiversx/mx-bridge-eth-go/blob/feat/v3.1/cmd/scCallsExecutor + +The README.md file contained in this directory is a good place to start on how to manually configure the tool and run it (on a dedicated host or VM) + +## Notes regarding smart-contract invoked on MultiversX from an EVM-compatible chain: + +The next diagram explains what happens with a token transfer with a smart-contract call in the direction EVM-compatible chain -> MultiversX when the tokens are unlocked/minted on the MultiversX chain. +The transfer is stored in the **BridgeProxy** (Step 1) and then, anyone can initiate the execution (Step 2). The tokens reach the 3-rd-party smart contract along with the function required to be called +and the provided parameters. + + + + +As stated above, this is the "happy flow" in which the smart contract call succeeds on the 3-rd-party contract. But what +happens if the invoked function fails? This is described in the next diagram. Step 1 was identical to the previous diagram +and it was omitted. Step 2 got a new step 2.c in which the tokens return to the **BridgeProxy** contract and the whole transfer +is marked as failed and ready to be refunded on the original source EVM-compatible chain **to the original sender address**. +There is another Step 3 involved, in which, anyone can call the refund method. + + + + +Step 2 and Step 3 can be automatically triggered with the help of the **scCallsExecutor** tool referenced above. + +:::important +The SC call data will need to be assembled from the EVM-compatible chain side and should respect the MultiversX Rust Framework +encoding for all provided parameters. +::: + +The following resource will exemplify how the correct endpoint is to be called on the EVM-compatible chain side: +https://github.com/multiversx/mx-bridge-eth-sc-sol/blob/main/tasks/depositSC.ts + +The correct encoded data can be generated by using the `encodeCallData` function provided in this typescript implementation +https://github.com/multiversx/mx-sdk-js-bridge/blob/main/helpers/encodeCallData.ts +or, using the `EncodeCallDataStrict` function from the Golang implementation +https://github.com/multiversx/mx-bridge-eth-go/blob/feat/v3.1/parsers/multiversxCodec.go + +--- + +### Validators + +# How to become a validator + + +:::note +This version of the documentation focuses solely on the essential steps required to set up and deploy a sovereign chain on either a local or remote computer. More content will be added as it is accepted and discussed on [Agora](https://agora.multiversx.com/), or once it is implemented and available for production. +::: + +--- + +### Vm Intro + +# Introduction + +The MultiversX protocol is designed so that integrating a new executor, a new processor, or even a completely new VM is straightforward. In essence, any new VM only needs to implement the `VMExecutionHandler` interface. Currently, there are two VMs running on MultiversX: + +- **SpaceVM**: Handles general smart contracts running on WASM. +- **systemVM**: Specialized for builtin system smart contracts written in Go. + +For sovereign shards, we introduced the option for a WasmVM smart contract to call a systemVM smart contract through the `BlockchainHookInterface`, specifically via the `ExecuteOnDestOnOtherVM` endpoint. This is necessary because both VMs reside in the same shard on sovereign shards. On the mainnet, however, WasmVM contracts can interact with systemVM contracts only through an asynchronous call, since systemVM exists exclusively on the metachain. + +When considering the EVM or other VMs, on MultiversX, it’s important to note that developers will likely want to interact with WasmVM contracts as well. Put simply, a smart contract should be able to interact with both WasmVM and other VM contracts in a uniform way, and that abstraction must happen at the VM level. The WasmVM already handles this by smartly calling the `ExecuteOnDestOnOtherVM` endpoint as needed. + +If we look under the hood we have SpaceVM which can actually accommodate multiple **EXECUTORS**. In the case of current mainnet/sovereign SpaceVM the **EXECUTOR** is *WASMER2.0* with a few additions from our side. Wasmer is written in rust and SpaceVM is written in GO. The SpaceVM has a big set of **OP_CODES**, from storage handling, to memory handling to crypto operations, big floats, and more. These **OP_CODES** are represented as pointer functions, written in GO and they have an access pointer. The executor, our modified Wasmer, receives this set of functions as a LIBRARY and when a SmartContract calls one of the **OP_CODES**, this calls the internal library added to WASMER which gets executed in the GO code of SpaceVM. + +:::note +Adding a new VM means introducing a new Executor under SpaceVM architecture. +::: + +--- + +### Wallets - Overview + +Wallets give access to your funds and MultiversX applications. Only you should have access to your wallet. + + +## MultiversX Wallets + +The private key associated to an address (erd1...) needs to be stored in a specific format. In order to use that private +key to sign transactions and perform various actions, one needs a wallet. + +There are multiple ways you can store your funds. This page will present some of them. + + +## Table of contents + +| Name | Description | +| ----------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | +| [xPortal App](https://xportal.com/) | Digital wallet and global payments app that allows you to exchange and securely store money on your mobile phone. | +| [Web Wallet](/wallet/web-wallet) | MultiversX Web Wallet | +| [xAlias](/wallet/xalias) | Single sign-on solution for Web3, powered by Web2 (Google Sign-In). | +| [Web Wallet - tokens operations](/wallet/create-a-fungible-token) | Learn how to perform tokens operation inside Web Wallet. | +| [MultiversX DeFi Wallet](/wallet/wallet-extension/) | MultiversX DeFi Wallet Extension | +| [Ledger](/wallet/ledger) | Ledger Hardware Wallet | +| [Keystore](/wallet/keystore) | Learn more about how to use the **keystore** file. | + +--- + +### Web Wallet + +Use the wallet to send, receive and store EGLD in a secure manner. Includes automations for interacting with staking products and ecosystem pools. + +You can log in on the Web Wallet using a keystore file, using the xPortal App, using a Ledger device or a pem file. For now, we are going to create a keystore wallet and connect on the Web Wallet using it. + + +## **Create a new wallet** + +Go to [https://wallet.multiversx.com](https://wallet.multiversx.com/) & carefully acknowledge the instructions provided. + +Click on "Create a new wallet": + +![img](/wallet/web-wallet/wallet_landing_page.png) + +Carefully read and acknowledge the information, then click "Continue". + +![img](/wallet/web-wallet/wallet_create.png) + + +## **Save your secret phrase! This is very important.** + +The words, numbered in order, are your Secret Phrase. They are just displayed on your screen once and not saved on a server or anywhere in the world. You only get one chance to save them - please do so now. + +Click the “copy” (two rectangles) button and then paste them into a text file. If your pets don’t usually find important pieces of paper to be delicious, you could even write the words down. + +![img](/wallet/web-wallet/wallet_mnemonic.png) + +The next page is a test to see if you actually have saved the Secret Phrase. Enter the random words as indicated there and press "Continue". + +![img](/wallet/web-wallet/wallet_quiz.png) + +You are one step away from getting your Keystore File. First, encrypt it with a password. Make sure it’s the strong kind, with 8 characters, at least one UPPER-CASE letter, a $ymb@l and numb3r. + +![img](/wallet/web-wallet/wallet_password.png) + +In case you forget this password, you can get a new Keystore File with your secret phrase. Remembering it is always better. + +Congratulations, you have a new wallet! The associated Keystore File was downloaded to wherever your browser saves files by default. The file has the actual address of the wallet as default name, something like “erd….json”. You can rename it to “something.json” so it’s easier to manage, if you want. + +![img](/wallet/web-wallet/wallet_created.png) + + +## **Access a wallet** + +Go to https://wallet.multiversx.com/ and click on "Access Existing" Make sure the “Keystore file” access method is selected, click Browse and locate your Keystore File [erd1… .json], then put in your password and click "Access Wallet". + +![img](/wallet/web-wallet/wallet_login.png) + +And you’re in! Your EGLD address is on top, you can use the “copy” button (the two rectangles) to copy it to the clipboard. + +![img](/wallet/web-wallet/wallet_welcome.png) + + +## **Overview of your EGLD balance** + +After logging into your wallet, your EGLD balances are immediately visible and displayed in easy to follow boxes. + +![img](/wallet/web-wallet/wallet_balance_overview.png) + +- **Available:** Freely transferable EGLD balance +- **Stake Delegation:** Amount of EGLD delegated towards a Staking Services provider +- **Legacy Delegation:** Amount of EGLD delegated towards MultiversX Community Delegation +- **Staked Nodes:** Amount of EGLD locked for your staked nodes + + +## **Send a transaction** + +Click "Send" on the right-hand section of the wallet: + +![img](/wallet/web-wallet/wallet_send.png) + +Input the destination address & amount, and then click "Send". + +![img](/wallet/web-wallet/wallet_send_tx.png) + + Verify the destination and amount and click "Confirm". + +![img](/wallet/web-wallet/wallet_confirm_tx.png) + +After confirming the transaction you can see the progress and completion of the transaction. + +![img](/wallet/web-wallet/wallet_success_tx.png) + +You can always review your transaction history in the "Transactions" menu on the left-hand side of the wallet. + +![img](/wallet/web-wallet/wallet_transactions.png) + + +## **Receiving EGLD in your wallet** + +After logging into your wallet, as described above, you will be able to see your wallet address and share it with others, so they can send you EGLD. + +Your address is immediately visible on the top part of the wallet. You can copy the address by pressing the copy button (two overlapping squares). + +You can also click "Receive" on the right-hand side to see a QR code for the address, which can be scanned to reveal the public address. + +![img](/wallet/web-wallet/wallet_receive.png) + + +## **Testnet and Devnet faucet** + +You can request test tokens from [Testnet Wallet](https://testnet-wallet.multiversx.com) or [Devnet Wallet](https://devnet-wallet.multiversx.com) in the `Faucet` tab. + +The faucet is only available once in a given time period. Other alternatives for getting test tokens are: + +- request tokens on [Telegram - Validators chat](https://t.me/MultiversXValidators); +- use a third-party faucet, such as [https://r3d4.fr/faucet](https://r3d4.fr/faucet); +- request tokens on [Discord - NETWORKS](https://discord.gg/multiversxbuilders) category. + + +## **Guardian** + +Starting with Altair release, a new section for Guardian feature is available in the wallet interface: + +![img](/wallet/web-wallet/guardian_feature1.png) + + +### **Registering the Guardian** + +The initial step involves registering your guardian. To begin, access the wallet menu and select the Guardian feature. From there, you should have the option to register and set it up. + +![img](/wallet/web-wallet/guardian_step1.png) + +To establish a Guardian, you will need to utilize an authenticator app that is capable of generating two-factor authentication (2FA) codes. This app will enable you to set up and manage the Guardian feature effectively. + +:::note +Please use the **Authenticator app name tag** to assign a recognizable label within the authenticator app, which will help you locate it easily when searching in the app. Once you have installed the authenticator app, scan the provided QR code using the app and enter the Guardian Code into the designated fields (refer to the below image). +::: + +:::important +If this is the first time when you are doing the registration, you don't have to select the **I cannot scan a QR code, show me the manual setup key**. +::: + +![img](/wallet/web-wallet/guardian_step2.png) + +**Check & Confirm** the transaction: + +![img](/wallet/web-wallet/guardian_step3.png) + + +### **Activation period** + +After successfully completing all the previously mentioned steps, you will need to wait for the registration period to conclude. For the Mainnet, this registration period will extend over a duration of **20 epochs**. + +:::important +During the waiting period for your Guardian to become active, all interactions with the blockchain will continue to function as usual. There should be no noticeable changes or differences in the way transactions are processed. +::: + +![img](/wallet/web-wallet/guardian_step4.png) + + +### **Verify Access** + +As depicted in the above-presented picture, you have the possibility to check if you still have access to your authenticator app by clicking the *Verify Access* button. + +![img](/wallet/web-wallet/guardian_step41.png) + +If you still have access to the correct authenticator app, you will receive a success message indicating that the activation process is complete. In such a case, you will not need to repeat the entire registration process from the beginning once the activation period is over. + +![img](/wallet/web-wallet/guardian_step42.png) + +In the event that you lose access to the authenticator app, you will be required to go through the entire process again by clicking on **"Change Guardian"** and following all the aforementioned steps, starting with the registration of the Guardian. This means you will need to repeat the steps described earlier in order to set up the Guardian feature anew. + +![img](/wallet/web-wallet/guardian_step43.png) + + +### **Guarding your account** + +Once the activation period has elapsed, you should find the option to "Guard your account" enabled, as indicated in the picture provided above. By clicing it, will enable the account guarding feature for your account. + +:::important +In order to Guard your account, a normal ```GuardAccount``` self-transaction will be sent to the blockchain. +::: + +![img](/wallet/web-wallet/guardian_step6.png) + +Upon the successful completion of a transaction, your account will be guarded, signifying that the account guardian feature is active. **Congratulations! Your funds are now secure and protected.** + +![img](/wallet/web-wallet/guardian_step44.png) + +As depicted in the image, 3 new options are now available to the user. +- [Verify Access](/wallet/web-wallet#verify-access); +- [Change Guardian](/wallet/web-wallet#changing-your-guardian); +- [Unguard Account](/wallet/web-wallet#unguarding-your-account). + +:::info +New information has been added to the guardian dashboard. + +![img](/wallet/web-wallet/guardian_step46.png) + +**Your current guardian is** the **guardian address.** Once you enable the account guardian feature, any outgoing transaction initiated by this account will be considered a guarded transaction. This means that such transactions will require two signatures: one from the account owner, as before, and another from the account guardian, which is the address you can see here. + +To obtain the guardian's signature for a transaction, you don't need to take any action. The TCS (Trusted Co-Signer Service - Guardian Service) will automatically provide the valid guardian signature for the user's transaction whenever a user with a guarded account sends a transaction from their wallet and provides a valid two-factor authentication (2FA) code. The wallet manages the interaction with the TCS, so the user only needs to enter a valid 2FA code. + +::: + + +### **Sending a guarded transaction** + +Once you have set a guardian and initiated the ```GuardAccount``` transaction, all subsequent transactions must be guarded to be validated by the blockchain. This means that you must enter the 2FA code in order to proceed with these transactions when using the web-wallet. + +Let's take for example a simple eGLD transfer transaction: + +![img](/wallet/web-wallet/guardian_sendTx1.png) + +Confirm the transaction: + +![img](/wallet/web-wallet/guardian_sendTx2.png) + +In the last step, you will need to check your authenticator tool to retrieve the Guardian code (2FA code) and enter it for verification. See picture below: + +![img](/wallet/web-wallet/guardian_sendTx3.png) + + +### **Unguarding your account** + +Unguarding your account consists of sending a guarded ```UnGuardAccount``` transaction to your own address. You can do it by simply using the **Unguard Account** button from the Guardian section: + +![img](/wallet/web-wallet/guardian_step44.png) + +And confirming the transaction after checking the validity of it: + +![img](/wallet/web-wallet/guardian_unguard2.png) + +And validating it via the authenticator app: + +![img](/wallet/web-wallet/guardian_step45.png) + +Due to the transaction being guarded, there is no cooldown period required. The transaction will be processed instantly without any delay. + + +### **Changing your guardian** + +The Guardians feature allows you to change your guardian in situations where you suspect your account has been compromised or if you have lost access to your Authenticator. + +![img](/wallet/web-wallet/guardian_change7.png) + +This can be achieved by clicking th *Change Guardian* button from the Guardian's main dashboard. Afterwards you will be faced with two options: + + +#### **Changing guardian with 20 epochs pre-registration time** + +1. **If you have lost access to the authenticator app**, you can address this issue by selecting the option *"I cannot access my guardian"* from the window mentioned below. + +![img](/wallet/web-wallet/guardian_change1.png) + +2. And follow the normal guardian registration process of registering a **new guardian**: + +![img](/wallet/web-wallet/guardian_change3.png) + +3. Introduce the 2FA code provided by your Authenticator App for the **new guardian**: + +![img](/wallet/web-wallet/guardian_change4.png) + +:::note +The above described process will need 20 epochs for your Guardian to become active. If you did not lose access to the authenticator app there is no reason why you should proceed this way. If you will have a pending guardian for 20 epochs which was not registered by you it means that your account has been compromised and you must move your funds to a safe account. +::: + +It is important to be aware of certain indicators that indicate the necessity of changing your guardian. One such indicator is the presence of a red shield and frame, as depicted in the image below. This visual cue serves as a signal that prompts you to take action and investigate the situation of your account. + +![img](/wallet/web-wallet/guardian_change2.png) + +The Guardian Dashboard will signal the fact that you have an unconfirmed guardian change request: + +![img](/wallet/web-wallet/guardian_change5.png) + +Showcasing the details of the request: + +![img](/wallet/web-wallet/guardian_change6.png) + +If you were the one who initiated the request to change the guardian and you still have access to the currently active guardian, you will encounter two additional options: + +- Skip the cooldown period by clicking the **Confirm Change** button: By selecting this option, you can proceed with the change of the guardian without waiting for the cooldown period to elapse. + +- Cancel the Guardian Change and continue using your current guardian by clicking the **Cancel Change** button: Choosing this option allows you to abort the guardian change process and maintain your existing guardian without any modifications. + +Both of the actions will be done by sending a guarded ```SetGuardian``` self-transaction. + +:::important +If it was not you the one triggering the change of the guardian, it may be the case that your account has been compromised and the scammer is trying to change the guardian to a new one, that he can control. Don't worry, he has to wait 20 epochs in order to have his own guardian becoming active. By then you can: + - move the funds to a different account, that you control, by using the still active guardian. In this case never **Confirm Change.** + - instantly cancel the pending guardian by using the **Cancel Change**, and play the "owning" game with the scammer. **Not recommended.** + +We recommend the first solution. Even though you have your funds staked, the unbound period (10 epochs) is shorther than the guardian activation period so that you have enough time to unstake and safely move them to a safe account. +::: + + +#### **Instantly changing guardian** + +To instantly change your guardian you must use the Change Guardian. You must follow a different set of steps compared to the previous *Change Guardian* way: + +1. Use an authenticator app for registering the **new guardian**. You must have access to your already active guardian which means that you don't have to select **"I cannot access my guardian"** option. + +![img](/wallet/web-wallet/guardian_change8.png) + +2. Open your authenticator app, scan the QR code and register your **new guardian**. + +![img](/wallet/web-wallet/guardian_change9.png) + +3. Enter the 2FA code of the **new guardian** from the authenticator app. + +![img](/wallet/web-wallet/guardian_change10.png) + +4. Check and Confirm the transaction. + +![img](/wallet/web-wallet/guardian_change11.png) + +5. Enter the 2FA code of the guardian you want to change. + +:::note +This is the 2FA code of your previous guardian that you have to enter, **not the new guardian's one**. +::: + +![img](/wallet/web-wallet/guardian_change12.png) + +6. Awesome! Wait for your transaction to be processed. From now on your account will be guarded by the new guardian that you recently registered. + +![img](/wallet/web-wallet/guardian_change13.png) + +--- + +### Web Wallet Tokens + +## **Introduction** + +**ESDT** stands for _eStandard Digital Token_. + +MultiversX network natively supports the issuance of custom tokens, without the need for contracts such as ERC20, but addressing the same use cases. +You can create and issue an ESDT token from [MultiversX web wallet](https://wallet.multiversx.com/) in a few steps. Let's go over these steps. + + +## **Prerequisites** + +- A wallet on MultiversX Network. +- 0.05 EGLD issuance fee +- fees for transactions + + +## **Creating a fungible token from Web Wallet** + +To get started, open up the [MultiversX web wallet](https://wallet.multiversx.com/). You can create a new wallet if you do not have one or import your existing wallet. Here is a [guide](https://docs.multiversx.com/wallet/web-wallet/) to help you navigate. + +On the left sidebar, you will notice the **ISSUE** section. + +![sidebar](/wallet/wallet-tokens/sidebar.png) + +Click on **Tokens**. + +![issue-token}](/wallet/wallet-tokens/issue-token.png) + +:::note +The Web Wallet will handle the preparation of the transaction. Therefore, if you'd want a token with a supply of 10 and 2 decimals, you should simply put 10 as supply and 2 as number of decimals. +::: + +When creating a token, you are required to provide the token name, a ticker, the initial supply, and the number of decimals. +In addition to these, tokens' properties should be set. + +Useful resources: + +- [Token parameters format](/tokens/fungible-tokens#parameters-format) - constraints about length, charset and so on. +- [Token properties](/tokens/fungible-tokens#configuration-properties-of-an-esdt-token) - what the properties stand for. + +Enter the required details. Next, click on **_Continue_** button to proceed. You will have to review the transaction and sign it, if everything looks good. + +Once the transaction is processed, your token will be issued. + + +### **Finding the token identifier** + +The token identifier of a token is unique. It is composed by the token ticker, a `-` char, followed by 6 random hex characters. Example: `MTKN-c66c30`. + +Because the token identifier isn't deterministic, it can be found only after issuing it. There are 2 ways of finding it: + +1. On the Explorer page of the issue transaction, you will see a Smart Contract Result which has a data field similar to: `@4d544b4e2d373065323338@152d02c7e14af6800000`. + On the right side, choose `Smart` and you will able to see the decoded parameters. In this example, the token identifier is `MTKN-c66c30`. + +![Token issue SCR](/wallet/wallet-tokens/scr-issue-token.png) + +2. From the Web Wallet, go to `TOKENS` tab from the left sidebar, and you can see the token there, including its identifier. + +![Token view in Web Wallet](/wallet/wallet-tokens/web-wallet-token-display.png) + + +## **Transfer a token from your wallet** + +You can transfer an amount of a token to another account. To get started, open up the [MultiversX web wallet](https://wallet.multiversx.com/). + +Navigate to the `Tokens` tab, and click on `Send` for the token you want to transfer. + +![Web Wallet Tokens page](/wallet/wallet-tokens/web-wallet-tokens-page.png) + +On the pop-up, introduce the recipient and the amount you want to send. Then press `Send`. + +![Web Wallet Transfer Token](/wallet/wallet-tokens/web-wallet-transfer-token.png) + +Once the transaction is successfully executed, the recipient should receive the amount of tokens. + + +## **Managing a token from Web Wallet** + +At the time of writing, a dashboard for tokens owners is still under construction. Meanwhile, token operations have to be done +manually, by following the transaction formats described [here](/tokens/fungible-tokens/#management-operations). + +--- + +### Webhooks + +The web wallet webhooks allow you to build or setup integrations for dapps or payment flows. + +The web wallet webhooks are links that point the user of the wallet to either login or populate a "send transaction" form with the provided arguments. Once the action is performed, the user is redirected to the provided callback URL along with a success or error status. + + +## **Login hook** + +This is useful when you need to find the user's wallet address. A common use case is that, starting from this address you can query the API for the wallet's balance or recent transactions. + +### URL Parameters + +`https://wallet.multiversx.com/hook/login?callbackUrl=https://example.com/` + +| Param | Required | Description | +| ------------- | ----------------------------------------- | ----------------------------------------------------- | +| callbackUrl | REQUIRED | The URL the user should be redirected to after login. | + +Upon a successful login, the user is redirected back to the callback URL along which the user's address is appended. + +### Callback URL Parameters + +`https://example.com/?address=erd1axhx4kenjlae6sknq7zjg2g4fvzavv979r2fg425p62wkl84avtqsf7vvv` + +| Param | Description | +| ------------- | ------------------------------- | +| address | The users's Address (bech32). | + + +## **Send transaction hook** + +This is useful when you need to prepopulate a transaction required to send an EGLD amount or pre-populate the transaction's data field with a smart contract function invocation. + +### URL Parameters {#send-transaction-url-parameters} + +`https://wallet.multiversx.com/hook/transaction?receiver=erd1qqqqqqqqqqqqqpgqxwakt2g7u9atsnr03gqcgmhcv38pt7mkd94q6shuwt&value=0&gasLimit=250000000&data=claimRewards&callbackUrl=https://example.com/` + +| Param | Required | Description | +| ------------- | ----------------------------------------- | ----------------------------------------------------- | +| receiver | REQUIRED | The receiver's Address (bech32). | +| value | REQUIRED | The Value to transfer (can be zero). | +| gasLimit | OPTIONAL | The maximum amount of Gas Units to consume. | +| data | OPTIONAL | The message (data) of the Transaction. | +| callbackUrl | OPTIONAL | The URL the user should be redirected to after login. | + +### Callback URL Parameters {#send-transaction-callback-url-parameters} + +`https://example.com/?status=success&txHash=48f68a2b1ca1c3a343cbe14c8b755934eb1a4bb3a4a5f7068bc8a0b52094cc89&address=erd1axhx4kenjlae6sknq7zjg2g4fvzavv979r2fg425p62wkl84avtqsf7vvv` + +| Param | Description | +| ------------- | ----------------------------------------- | +| status | Success / failure of sending transaction. | +| txHash | The transaction's hash. | + +--- + +### Whitelist Requirements + +# Whitelist Requirements + + +Before enabling a token to be sent via the Ad-Astra bridge, the token must be whitelisted. +The whitelisting process is performed with the help of the MultiversX team. + + +## Whitelist requirements + +1. The token issuer must issue the token on the MultiversX network and submit a branding request manually or using [https://assets.multiversx.com](https://assets.multiversx.com); +2. The token issuer must assign the MINT & BURN role to the **BridgedTokensWrapper** or the **Safe** contract, depending on the +token type configuration. + +```rust +RolesAssigningTransaction { + Sender:
+ Receiver: erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u + Value: 0 + GasLimit: 60000000 + Data: "setSpecialRole" + + "@" + + + "@" + + + "@" + + + "@" + +} +``` + +Example: + +Let's suppose we want to whitelist the token `TKN-001122` on the Ethereum bridge and the token configuration type is 1. +(mint/burn tokens on both ends, native on MultiversX) + +The transaction will look like: + +```rust +RolesAssigningTransaction { + Sender:
+ Receiver: erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u + Value: 0 + GasLimit: 60000000 + Data: "setSpecialRole" + + "@544b4e2d303031313232" + + "@000000000000000005004ab1cd3d291159a38df7d2a1c498c9d7a7e3047ccc48" + + "@45534454526f6c654c6f63616c4d696e74" + + "@45534454526f6c654c6f63616c4275726e" +} +``` + +3. Depending on the token configuration (valid for 1. & 2. configuration types), the minter & burner role should be granted on +the EVM-compatible chain side (depending on the ERC20 contract variant, `addMinter` and `addBurner` endpoints can be used); +4. MultiversX team will complete the whitelisting process by issuing the required transactions. + +For reference, this is the list of the known smart contracts: +* **Wrapper** `erd1qqqqqqqqqqqqqpgq305jfaqrdxpzjgf9y5gvzh60mergh866yfkqzqjv2h` +* **Ethereum Safe** `erd1qqqqqqqqqqqqqpgqf2cu60ffz9v68r0h62sufxxf67n7xprue3yq4ap4k2` +* **BSC Safe** `erd1qqqqqqqqqqqqqpgqa89ts8s3un2tpxcml340phcgypyyr609e3yqv4d8nz` + +:::warning +To ensure the correct functioning of the bridge, as a MultiversX token owner please ensure the following points are met: +* if you make use of the transfer-role on your token, remember to grant the role also on the **Safe**, **MultiTransfer**, **BridgedTokensWrapper**, and **BridgeProxy** contracts; +* do not freeze the above-mentioned contracts; +* do not wipe tokens on the above-mentioned contracts. + +Failure to comply with these rules will force the bridge owner to blacklist the token in order to restore the correct functioning of the bridge. +::: + +--- + +### xAlias + +**xAlias** is a _single sign-on_ solution for Web3, powered by Google Sign-In (Web2). It allows new users (not yet proficient in blockchain technologies) to quickly and easily create blockchain wallets (without the need of seed phrases), then start right away and interact with MultiversX dApps. + +It's a _self-custody_ wallet, and it's _convertible_ to a conventional Web3 wallet at a later point. + +:::important +**For dApp developers:** xAlias exposes **the same [URL hooks and callbacks](/wallet/webhooks)** as the [Web Wallet](/wallet/web-wallet). Therefore, integrating xAlias is **identical to integrating the Web Wallet** (with one trivial exception: the configuration of the URL base). See [Signing Providers for dApps](/sdk-and-tools/sdk-js/sdk-js-signing-providers). +::: + + +## Before you begin + +If you don't already have a Google account, [set up one](https://accounts.google.com/signup). + + +## Sign Up with xAlias + +Navigate to **[xAlias.com](https://xalias.com)**, then click on **Get Started** to reach the **Sign Up** screen: + +![img](/wallet/xalias/xalias_signup_first.png) + +Then, click on **Authenticate**, which redirecteds you to Google Sign-In. + +![img](/wallet/xalias/xalias_signup_google_choose_account.png) + +Pick the Google account you want to use, then click on **Confirm**. + +![img](/wallet/xalias/xalias_signup_google_confirm.png) + +Next, you'll have to **Authorize** xAlias to store and access its own data on your Google Drive account: + +![img](/wallet/xalias/xalias_signup_second.png) + +Read the Google consent screen, then click on **Allow**. + +![img](/wallet/xalias/xalias_signup_authorize_google.png) + +At the end of the Sign Up flow, you will be asked to back-up your xAlias account, as a document file, which can be either received by email or downloaded directly: + +![img](/wallet/xalias/xalias_signup_backup_file.png) + +To confirm the back-up and complete the flow, enter the confirmation code from the received (or downloaded) document: + +![img](/wallet/xalias/xalias_signup_backup_code.png) + +Congratulations, you have successfully **created your xAlias account**! + + +## Sign In + +You can always sign-in to your xAlias account by navigating to **[xAlias.com](https://xalias.com)**, then clicking on **Sign In**. You will be asked to confirm the Google account, then reach the **xAlias Dashboard**. + + +## xAlias Dashboard + +Upon the initial sign-up, and each time you sign-in to xAlias, you will be presented the **xAlias Dashboard**. + +Here, you will be able to see the wallet address (the one starting with _erd1_) and share it with others, so they can send you EGLD or other tokens.‌ Additionally, you can click on **Open in Explorer** and see the all the blockchain transactions associated with your wallet address (blockchain address). + +![img](/wallet/xalias/xalias_dashboard.png) + + +## Use a MultiversX dApp with xAlias + +:::note +The screenshots below are from the [**MultiversX dApp Template**](https://devnet.template-dapp.multiversx.com). +::: + +:::important +**For dApp developers:** if your dApp doesn't yet support **xAlias** as a signing provider, **we recommend that you enable the integration, and reach a broader audicence** (wider user base for your dApp). Please follow [Signing Providers for dApps](/sdk-and-tools/sdk-js/sdk-js-signing-providers) for technical details. +::: + +If you've stumbled upon a MultiversX dApp that you'd like to use and it supports xAlias, follow the **Login** or **Connect** flow of the dApp, then pick **xAlias** (as your Web3 wallet). + +![img](/wallet/xalias/xalias_dapp_login.png) + +Then, you will reach the following consent screen: + +![img](/wallet/xalias/xalias_dapp_consent.png) + +Upon confirmation, you will be redirected to the dApp (which is informed about your blockchain address - **not your email address, of course**). + +Then, as a user of the dApp (of any dApp), you might reach a point where you need to **sign a transaction** - then, you will be redirected to xAlias: + +![img](/wallet/xalias/xalias_dapp_sign_transaction.png) + +... or you might need to sign a message: + +![img](/wallet/xalias/xalias_dapp_sign_message.png) + + +## Sign Out + +To sign out from xAlias, navigate to **[xAlias.com](https://xalias.com)**, then click on **Sign Out**. + +:::note +Note that disconnecting from a dApp doesn't sign you out from xAlias. +::: + +--- + +### xPortal + +Experience the power of convenience and security in one place. Built by MultiversX, [xPortal](https://xportal.com/) is the go-to app for all crypto needs. Securely store your cryptocurrencies and NFTs, effortlessly swap tokens, and soon, enjoy the perks of our upcoming debit cards with exciting cashback rewards. Join millions of users who trust xPortal for seamless finance management, gamified missions & AI avatar creation, global payments, and a social experience like no other. + +:::info +On this page, we will start presenting features from xPortal, beginning with the Invisible Guardians feature. Subsequently, we will introduce other features in the following sections. +::: + + +## What are Invisible Guardians? + +Invisible Guardians is a variation of the Guardians feature tailored for xPortal users. It is specifically designed to deliver the same level of security introduced by the Guardians feature while maintaining the same ease of use xPortal users are accustomed to. + +![img](/wallet/xportal/activate_guardian.jpg) + + +## How do Invisible Guardians work? + +When active, the feature enables an invisible guardian which is encrypted and stored locally on your device, silently co-signing every transaction. This acts as an additional security layer and safeguards the account in situations where, one way or another, the secret phrase has been compromised. + + +## How do I activate an Invisible Guardian on my account? +The Guardians feature can be accessed in the Security section, which can be found in the xPortal Settings. There, users can activate an Invisible Guardian on their account by going through a few simple steps: + +- Create a backup password for your guarded account +![img](/wallet/xportal/inv_guardian_step1.jpg) +- Enable the Invisible Guardian through a blockchain transaction +![img](/wallet/xportal/inv_guardian_step2.jpg) +- Wait for the 20-day cooldown period to pass +![img](/wallet/xportal/inv_guardian_step4.jpg) +- Activate the Invisible Guardian through another blockchain transaction +![img](/wallet/xportal/inv_guardian_step5.jpg) + + +## Can I deactivate the Invisible Guardian feature once it was activated? + +The Invisible Guardian can be deactivated at any time, but it requires that a transaction is sent and signed by the Guardian currently in place on that account. + + +## Can I change my Guardian? + +Yes, the Invisible Guardian can be changed, and there are two ways in which this action can be performed: + +- If the user has access to the current Guardian the change can be done immediately by signing a transaction; +- If the user doesn’t have access to the current Guardian the process implies a 20-day cooldown period before the change of Guardian takes place. + + +## Why do I have to wait 20 days to activate or change an Invisible Guardian? + +The 20-day cooldown period is designed to safeguard staked funds and relieve pressure on users. +Given that it might take longer than 10 days to safely transfer funds to a secure account, we have extended this period by an additional 10 days. +This not only allows the user ample time to react if their account is compromised, but it also provides a buffer for the user to counteract potential threats. + + +## What implications should I be aware of before activating an Invisible Guardian on my account? + +Aside from the extra security and the peace of mind that goes hand in hand with activating an Invisible Guardian on your account, there are a few things everyone should be aware of beforehand: +- You will only be able to process transactions from xPortal or by logging in with xPortal +- You won’t be able to import your secret phrase into other wallet apps + + +## What if something happens to my device or I change it? + +The Invisible Guardian is stored locally on your device, but the first step of setting it up is creating an encrypted backup containing the Guardian. This makes importing it on a different device as easy as it gets, all you need to do is to provide the password associated with the guarded backup. + + +## What happens if my secret phrase is compromised? + +If your secret phrase is compromised and the Invisible Guardian is active, whoever has access to the secret phrase will be unable to process any transactions from your account. They will be able though to request the change of the Guardian. This process lasts 20 days. + +You will be able to cancel any request of this kind, but our recommendation is to transfer all your assets (staked and unstaked) to a new wallet. + + +## How would I know if my account is compromised? + +A red warning message is displayed in your xPortal account every time a Guardian is enabled, changed or disabled. If it was you who processed this change, you can confirm that the action was performed by you and the message disappears. + +If the action was not performed by you, we strongly recommend moving all your assets to a new wallet within 20 days. + +Also, to ensure maximum protection we strongly recommend you access your account periodically. + +--- + +## Terminology +### Terminology + +**Metachain**: the blockchain that runs in a special shard, where the main responsibilities are not processing transactions, +but notarizing and finalizing the processed shard block headers. + +**Address**: the public key of a wallet. The MultiversX Address format is bech32, specified by the BIP 0173. +The public key always starts with an `erd1`. e.g.: `erd1sea63y47u569ns3x5mqjf4vnygn9whkk7p6ry4rfpqyd6rd5addqyd9lf2`. + +**Node**: a computer or server, running the MultiversX client and relaying messages received from its peers. + +**Validator**: a node on the MultiversX network that staked at least 2500 EGLD, that processes transactions and secures +the network by participating in the consensus mechanism, while earning rewards from the protocol and transaction fees. + +**Observer**: a passive member of the network that can act as a read & relay interface. + +**Validate**: the act of running a validator node and contributing to the network by relaying and +validating information. + +**Stake**: contribute to the network security by delegating 1 EGLD or more towards a staking provider that operates +validator nodes. + +**Delegate**: contribute to the network security by delegating 10 EGLD or more towards the MultiversX Community Delegation +contract. + +**Stake rewards**: locked funds for running validator nodes to secure the network and generate income in form of rewards. +Rewards can be claimed or restaked. + +**Delegate rewards**: locked funds from delegation contracts also generate income in form of rewards. +These rewards can be claimed or redelegated + +**Unstake**: the intention to unlock staked/delegated funds, that will become available after the 10 epoch unbonding period. + +**Unbond**: withdraw the funds in the original account after the 10 epoch unbonding period. + +**Staking provider**: node operator that created a staking pool and accepts funds for staking. + +**Staking pool**: a system smart contract that accepts funds for staking. + +**Delegation cap**: the maximum amount of funds accepted by a staking pool. + +**APR**: annual percentage rate. + +**Service fee**: fee that the service providers are taking from the rewards their staking pools have received. + +--- From 464cf9b3281d15c67238d72424608d9b31cf0164 Mon Sep 17 00:00:00 2001 From: bogdan-rosianu Date: Tue, 3 Feb 2026 10:43:57 +0200 Subject: [PATCH 4/4] extend pr template --- pull_request_template.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pull_request_template.md b/pull_request_template.md index 40f7539b3..69a8b5a7e 100644 --- a/pull_request_template.md +++ b/pull_request_template.md @@ -4,6 +4,10 @@ - [ ] yes - [ ] no +#### Did you regenerate static/llms.txt and static/llms-full.txt ? (happens at build time) +- [ ] yes +- [ ] no + #### Which category (categories) does this pull request belong to? - [ ] document new feature - [ ] update documentation that is not relevant anymore