Version: 24.3.0
FA2 Parameters
Written by Claude Barde
Formatting the parameters for FA2 entrypoints with Taquito
Based on the TZIP-12 standard, FA2 contracts are contracts that handle tokens, whether it be non-fungible or fungible tokens.
In order to be compliant with the standard, a contract must implement 3 main entrypoints (among other requirements):
transfer: an entrypoint to be called to transfer one or multiple tokens from one address to the otherbalance_of: an entrypoint meant to be called on-chain in order to get the balance of a specific accountupdate_operators: a list of parameters to give or withdraw access to users’ tokens from third-parties
Because the transfer and update_operators entrypoints require complex Michelson data, it can sometimes be complicated to find the right formatting for the parameters in JavaScript using Taquito.
Calling the entrypoint of an FA2 contract
Once you have the address of the contract you want to update, calling the transfer or the update_operators entrypoint follows the same steps as with any other contract:
import { TezosToolkit } from "@taquito/taquito";
const Tezos = new TezosToolkit(RPC_URL);
const contract = await Tezos.wallet.at(FA2_CONTRACT_ADDRESS);
const op = await contract.methodsObject.transfer(transfer_params).send();
await op.confirmation();
The transfer entrypoint
Here is the type signature for the entrypoint parameter in Michelson:
(list %transfer
(pair
(address %from_)
(list %txs
(pair
(address %to_)
(pair
(nat %token_id)
(nat %amount)
)
)
)
)
)
The %transfer entrypoint takes a list of pairs. Each pair consists of:
- Left side: The source account to deduct tokens from
- Right side: A list of transactions, where each transaction specifies the recipient address, token ID, and amount to transfer (useful when the contract manages multiple token IDs)
Incidentally, this means that the contract can process multiple transfers at the same time, with one spender sending transfers to multiple recipients for one or different token ids.
In order to format the transfer parameters properly for Taquito, there are only 2 rules to remember:
- Michelson lists are represented as arrays
- Pairs in lists are represented as objects whose properties match the field annotations of the pair
The main value of the parameters is an array. Each object in the array will be a different transaction representing the transfer of one or multiple tokens from one spender to multiple recipients. The object has 2 properties: from (the spender’s address) and txs (a list of the recipients, token ids and amounts):
const transfer_params = [
{
from_: "tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb",
txs: [...]
},
{
from_: "tz1aSkwEot3L2kmUvcoxzjMomb9mvBNuzFK6",
txs: [...]
}
]
The txs property itself contains a list of objects holding the recipient’s address, the id of the token to be transferred, and the amount to transfer:
const transfer_params = [
{
from_: "tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb",
txs: [
{
to_: "tz1aSkwEot3L2kmUvcoxzjMomb9mvBNuzFK6",
token_id: 0,
amount: 11111
},
{
to_: "tz1aSkwEot3L2kmUvcoxzjMomb9mvBNuzFK6",
token_id: 1,
amount: 22222
},
{
to_: "tz1Me1MGhK7taay748h4gPnX2cXvbgL6xsYL",
token_id: 0,
amount: 333333
}
]
}
]
You can then add as many transactions as you like to be processed by the contract (within the limits of the gas/storage fee).
The properties holding the addresses of the spender and the recipient both end with an underscore: from_ and to_.
The balance_of entrypoint
Here is the type signature for the entrypoint parameter in Michelson:
(pair %balance_of
(list %requests
(pair
(address %owner)
(nat %token_id)
)
)
(contract %callback
(list
(pair
(pair %request
(address %owner)
(nat %token_id)
)
(nat %balance)
)
)
)
)
The %balance_of entrypoint takes a pair with two components:
- Left side: A list of
%requests, where each request is a pair containing:%owner: The account address to check the balance for%token_id: The token ID to query
- Right side: A
%callbackcontract address that will receive the balance results as a list of pairs, each containing:%request: The original request (owner address and token ID)%balance: The balance amount for that owner and token ID
const balance_params = {
request: [
{
owner: 'tz1XTyqBn4xi9tkRDutpRyQwHxfF8ar4i4Wq',
token_id: '0'
}
],
callback: 'KT1KUvK6PRQdhkGnsWDmw1kjysRdMA86dvWC'
}
The update_operators entrypoint
Here is the type signature for the entrypoint parameter in Michelson:
(list %update_operators
(or
(pair %add_operator
(address %owner)
(pair
(address %operator)
(nat %token_id)
)
)
(pair %remove_operator
(address %owner)
(pair
(address %operator)
(nat %token_id)
)
)
)
)
The %update_operators entrypoint takes a list of operations. Each operation is either:
add_operator: Grants an operator permission to transfer tokens on behalf of an owner, containing:%owner: The token owner’s address%operator: The operator’s address to authorize%token_id: The token ID the operator can manage
remove_operator: Revokes an operator’s permission with the same structure (owner, operator, and token ID)
const operator_params = [
{
add_operator: {
owner: "tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb",
operator: "tz1aSkwEot3L2kmUvcoxzjMomb9mvBNuzFK6",
token_id: 0
}
},
{
remove_operator: {
owner: "tz1aSkwEot3L2kmUvcoxzjMomb9mvBNuzFK6",
operator: "tz1Me1MGhK7taay748h4gPnX2cXvbgL6xsYL",
token_id: 2
}
}
]
Just like a transfer operation, it is possible to add and remove multiple operators in the same transaction.
Batching approval and transfer operations
It can sometimes be useful or more practical to set an operator before sending a transfer transaction. If your dApp is built on a contract that will handle users’ transfer operations on their behalf, it can be more convenient for your users to approve your contract and let it transfer their tokens in one click. In this case, you can use the Batch API to first approve the contract and then call an entrypoint of the contract that will transfer the user’s tokens on his behalf:
import { TezosToolkit } from "@taquito/taquito";
const Tezos = await new TezosToolkit(RPC_URL);
const dappContract = await Tezos.wallet.at(DAPP_CONTRACT_ADDRESS);
const tokenContract = await Tezos.wallet.at(FA2_CONTRACT_ADDRESS);
const batchOp = await Tezos.wallet.batch()
.withContractCall(tokenContract.methodsObject.update_operators([
{
add_operator: {
owner: USER_ADDRESS,
operator: DAPP_CONTRACT_ADDRESS,
token_id: 0
}
}
]))
.withContractCall(dappContract.methodsObject.mint())
.send();
await batchOp.confirmation();
In the first contract call (to the token contract), the user authorizes the dApp contract to transfer his tokens on his behalf.
In the second contract call (to the dApp contract), the user calls a hypothetical mint entrypoint that sends a transaction under the hood to transfer the user’s tokens to the contract account.