Advance

 

Smart Contract API Functions

Welcome to the tutorial on Smart Contract API Functions in MultiversX! In this tutorial, you will learn about the various API functions available for interaction with smart contracts in MultiversX. These functions are essential for building decentralized applications that leverage the power of smart contracts.

MultiversX is a platform for decentralized application development that is built on top of the Tezos blockchain. It offers a suite of tools and services to simplify the process of developing and deploying smart contracts on the Tezos network. One of these tools is the API functions, which allow developers to interact with smart contracts in a simple and intuitive manner.

In this tutorial, we will cover the different types of API functions available in MultiversX, including read-only functions, transactions, and contract management functions. We will also go through examples of how to use these functions in your code.

By the end of this tutorial, you will have a solid understanding of the Smart Contract API Functions in MultiversX and be able to start building your own decentralized applications with ease.

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

  • 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 source code for the APIs can be found here:

Blockchain API

This API is accessible through self.blockchain(). Available functions:

get_sc_addressget_sc_address() -> ManagedAddress

get_sc_address() -> ManagedAddress
get_owner_address() -> ManagedAddress

Returns the owner’s address.

check_caller_is_owner

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

get_shard_of_address(address: &ManagedAddress) -> u32

Returns the shard of the address passed as argument.

is_smart_contract

is_smart_contract(address: &ManagedAddress) -> bool

Returns true if the address passed as a parameter is a Smart Contract address, false for simple accounts.

get_caller

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 behavior.

get_balance

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

get_sc_balance(token: &EgldOrEsdtTokenIdentifier, nonce: u64) -> BigUint

Returns the EGLD/ESDT/NFT balance of the smart contract.

For fungible ESDT, the nonce should be 0. To get the EGLD balance, you can simply pass EgldOrEsdtTokenIdentifier::egld() as a parameter.

get_tx_hash

get_tx_hash() -> ManagedByteArray<Self::Api, 32>

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

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

get_block_timestamp() -> u64

get_block_nonce

get_block_nonce() -> u64

get_block_round

get_block_round() -> u64

get_block_epoch

get_block_epoch() -> u64

These functions are mostly used for setting up deadlines, so they’ve been grouped together.

get_block_random_seed

get_block_random_seed() -> ManagedByteArray<Self::Api, 48>

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

get_prev_block_timestamp() -> u64

get_prev_block_nonce

get_prev_block_nonce() -> u64

get_prev_block_round

get_prev_block_round() -> u64

get_prev_block_epoch

get_prev_block_epoch() -> u64

get_prev_block_random_seed

get_prev_block_random_seed() -> ManagedByteArray<Self::Api, 48>

The same as the functions above, but for the previous block instead of the current block.

get_current_esdt_nft_nonce

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

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, the nonce should be 0. For EGLD balance, use the get_balance instead.

get_esdt_token_data

get_esdt_token_data(address: &ManagedAddress, token_id: &TokenIdentifier, nonce: u64) -> EsdtTokenData<Self::Api>

Gets the ESDT token properties for the specific token type, owned by the specified address.

EsdtTokenData has the following format:

pub struct EsdtTokenData<M: ManagedTypeApi> {
    pub token_type: EsdtTokenType,
    pub amount: BigUint<M>,
    pub frozen: bool,
    pub hash: ManagedBuffer<M>,
    pub name: ManagedBuffer<M>,
    pub attributes: ManagedBuffer<M>,
    pub creator: ManagedAddress<M>,
    pub royalties: BigUint<M>,
    pub uris: ManagedVec<M, ManagedBuffer<M>>,
}

token_type is an enum, which can have one of the following values:

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 a 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 the 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

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

egld_value() -> BigUint

Returns the amount of EGLD transferred in the current transaction. Will return 0 for ESDT transfers.

all_esdt_transfers

all_esdt_transfers() -> ManagedVec<EsdtTokenPayment<Self::Api>>

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:

pub struct EsdtTokenPayment<M: ManagedTypeApi> {
    pub token_identifier: TokenIdentifier<M>,
    pub token_nonce: u64,
    pub amount: BigUint<M>,
}

multi_esdt

multi_esdt<const N: usize>() -> [EsdtTokenPayment<Self::Api>; 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

single_esdt() -> EsdtTokenPayment<Self::Api>

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

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

egld_or_single_fungible_esdt(&self) -> (EgldOrEsdtTokenIdentifier<Self::Api>, BigUint)

Same as the function above, but also allows EGLD to be received.

egld_or_single_esdt

egld_or_single_esdt() -> EgldOrEsdtTokenPayment<Self::Api>

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

sha256(data: &ManagedBuffer) -> ManagedByteArray<Self::Api, 32>

keccak256

keccak256(data: &ManagedBuffer) -> ManagedByteArray<Self::Api, 32>

ripemd160

ripemd160(data: &ManagedBuffer) -> ManagedByteArray<Self::Api, 20>

Signature verification functions:

verify_ed25519_legacy_managed

verify_ed25519_legacy_managed<const MAX_MESSAGE_LEN: usize>(key: &ManagedByteArray<Self::Api, 32>, message: &ManagedBuffer, signature: &ManagedByteArray<Self::Api, 64>) -> bool

verify_bls

verify_bls(key: &[u8], message: &[u8], signature: &[u8]) -> bool

verify_secp256k1

verify_secp256k1(key: &[u8], message: &[u8], signature: &[u8]) -> bool

verify_custom_secp256k1

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”.

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:

let hash = self.crypto().sha256_legacy_managed::<200>(&data);

Where 200 is the max expected byte length of data.

encode_secp256k1_der_signature

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 section.

Without further ado, let’s take a look at the available functions:

direct

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

direct_egld(to: &ManagedAddress, amount: &BigUint)

The EGLD-transfer version for the direct function.

direct_esdt

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

direct_multi(to: &ManagedAddress, payments: &ManagedVec<EsdtTokenPayment<Self::Api>>)

The multi-transfer version for the direct_esdt function. Keep in mind you cannot transfer EGLD with this function, only ESDTs.

change_owner_address

change_owner_address(child_sc_address: &ManagedAddress, new_owner: &ManagedAddress)

Changes the ownership of the 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

esdt_local_mint(token: &TokenIdentifier, nonce: u64, amount: &BigUint)

Allows synchronous minting of ESDT/SFT (depending on nonce). Execution is resumed afterward. 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 an additional quantity.

This function cannot be used for NFTs.

esdt_local_burn

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

esdt_nft_create<T: TopEncode>(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 the 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. The 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

esdt_nft_create_compact<T: TopEncode>(token: &TokenIdentifier, amount: &BigUint, attributes: &T) -> u64

sell_nft

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 the 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

nft_add_uri(token_id: &TokenIdentifier, nft_nonce: u64, new_uri: ManagedBuffer)

Adds a 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<ManagedBuffer> as an argument instead.

nft_update_attributes

nft_update_attributes<T: TopEncode>(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.

With this, you complete this workshop successfully!!