Allowances grant another account (spender) the right to transfer HBAR, fungible tokens (FT), and non-fungible tokens (NFTs) from your account (owner). The ability to approve allowances is important because it enables applications like exchanges and wallets to perform transfers on behalf of their customers without requiring a customer to sign every single transaction in advance. You can approve allowances and perform approved transfers on Hedera as you build things like NFT exchanges, marketplaces for carbon assets, games, and more.
This tutorial series shows you how to approve allowances for fungible tokens and NFTs. Part 1 focuses on approving token allowances using the Hedera JavaScript SDK. Part 2 and Part 3 show how to approve token allowances using Solidity HTS precompiles and ERC standard calls, respectively.
Example: Alice Spends FT and NFT on Behalf of the Treasury
This example guides you through the following steps:
Creating additional Hedera accounts and native tokens (Treasury, Alice, Bob, and the HBAR ROCKS token)
Treasury approving an allowance of HBAR ROCKS tokens for Alice (50 FT / 3 NFT)
Alice performing an approved token transfer from Treasury to Bob (45 FT / 1 NFT)
Treasury deleting the token allowance for Alice
After completing all steps, your console should look something like this (for FT/NFT):
Fungible token example output (left) and NFT example output (right)
1. Create Accounts and HTS Token
There are five entities in this scenario: Operator, Treasury, Alice, Bob, and the HBAR ROCKS token. Your testnet credentials from the Hedera portal should be used for the operator variables, which are used to initialize the Hedera client that submits transactions to the network and gets confirmations.
Create new accounts for Treasury, Alice, and Bob. Start by specifying the initial balance of each new account (initBalance) to be 10 HBAR
Generate and record the private key for each account. Hedera supports ED25519
and ECDSA
keys
Use the function accountCreateFcn to create the new accounts
The function returns the status of the transaction (treasurySt) and the new account ID (treasuryId
The inputs are the newly generated private key (treasuryKey), initBalance, and the client object
Output to the console a link to the mirror node explorer, HashScan, showing information about the new accounts
The code tabs below show the functions called to create the FT (htsTokens.createFtFcn) and NFT (htsTokens.createMintNftFcn).
Both functions return the ID of the new token (tokenId), and token information from a query (tokenInfo)
For creation of the FT, the inputs are the token name (HBAR ROCKS), token symbol (HROCK), the initial supply (100), the treasury account for the token (treasuryId), the treasury key for transaction authorization (treasuryKey), and the client object
For the NFT creation, the inputs are the token name (HBAR ROCKS), token symbol (HROCK), the initial supply (0), the maximum token supply (1000), the treasury account for the token (treasuryId), the treasury key for transaction authorization (treasuryKey), and the client
object
Output to the console tokenId and the token supply
The helper function accountCreateFcn uses the AccountCreateTransaction()
class of the SDK to generate the new accounts for Treasury, Alice, and Bob.
The functions htsTokens.createFtFcn and htsTokens.createMintNftFcn
both use the TokenCreateTransaction()
class of the SDK. However, each instance is customized appropriately to create a fungible or non-fungible token. In the NFT case, the helper function also uses the TokenMintTransaction()
class to mint a batch of 5 NFTs in a single transaction. For additional details on using the Hedera Token Service (HTS) for fungible and non-fungible tokens, be sure to read the tutorial series Get Started with the Hedera Token Service.
You can easily reuse the helper functions in this example in case you need to do tasks like creating more accounts or tokens in the future. We’ll use this modular approach throughout the article.
export async function tokenQueryFcn(tkId, client) {
let info = await new TokenInfoQuery().setTokenId(tkId).execute(client);
return info;
}
Console Output (FT/NFT):
2. Approve FT / NFT Allowance
From the token creation and minting in the previous step, Treasury has an HBAR ROCK balance of 100 FT / 5 NFT.
The allowance amount approved for Alice to spend on behalf of Treasury (allowBal) is 50 in the case of the fungible token - and serials #1, #2, and #3 in the case of the NFT
For the fungible token example, use the function approvals.ftAllowanceFcn to approve the allowance
The function returns the receipt object of the transaction (allowanceApproveFtRx)
The inputs are tokenId, the owner account ID (treasuryId), the spender account ID (aliceId), allowBal, the private key of the owner for transaction authorization (treasuryKey), and client
For the NFT example, use the function approvals.nftAllowanceFcn
to approve the allowance
Serials #1, #2, and #3 of the NFT are specified using he NftId
class of the SDK
The function returns the receipt object of the transaction (allowanceApproveNftRx)
The inputs are tokenId, treasuryId, aliceId, the array of NFTs to approve (nft2approve), treasuryKey, and client
Output to the console:
The status of the allowance approval transaction
A mirror node REST API request that shows fungible token allowances approved by Treasury
The helper functions approvals.ftAllowanceFcn and approvals.nftAllowanceFcn both use AccountAllowanceApproveTransaction()
from the SDK to grant the token allowances for the spender from an owner’s account balance. The difference is the methods used - .approveTokenAllowance()
is used to approve fungible token allowances, whereas .approveTokenNftAllowance()
is used to approve NFT allowances by serial number. Note that for NFTs you also have the option to approve all serial numbers by using the .approveTokenNftAllowanceAllSerials()
method.
The function queries.balanceCheckerFcn uses AccountBalanceQuery() to check and display the HBAR and token balances for a given account ID or contract ID.
export async function nftAllowanceFcn(tId, owner, spender, nft2Approve, pvKey, client) {
const allowanceTx = new AccountAllowanceApproveTransaction()
// .approveTokenNftAllowanceAllSerials(tId, owner, spender) // Can approve all serials under a NFT collection
.approveTokenNftAllowance(nft2Approve[0], owner, spender) // Or can approve individual serials under a NFT collection
.approveTokenNftAllowance(nft2Approve[1], owner, spender)
.approveTokenNftAllowance(nft2Approve[2], owner, spender)
.freezeWith(client);
const allowanceSign = await allowanceTx.sign(pvKey);
const allowanceSubmit = await allowanceSign.execute(client);
const allowanceRx = await allowanceSubmit.getReceipt(client);
return allowanceRx;
}
export async function balanceCheckerFcn(acId, tkId, client) {
let balanceCheckTx = [];
try {
balanceCheckTx = await new AccountBalanceQuery().setAccountId(acId).execute(client);
console.log(
`- Balance of account ${acId}: ${balanceCheckTx.hbars.toString()} + ${balanceCheckTx.tokens._map.get(
tkId.toString()
)} unit(s) of token ${tkId}`
);
} catch {
balanceCheckTx = await new AccountBalanceQuery().setContractId(acId).execute(client);
console.log(
`- Balance of contract ${acId}: ${balanceCheckTx.hbars.toString()} + ${balanceCheckTx.tokens._map.get(
tkId.toString()
)} unit(s) of token ${tkId}`
);
}
}
Console Output (FT/NFT):
3. Perform Approved Token Transfer
In this step, Alice spends tokens from the allowance granted by Treasury. This means that Alice transfers tokens from Treasury’s account to Bob’s.
To perform an approved transfer of 45 fungible tokens (sendBal), use the function transfers.ftAllowanceFcn
The function returns the receipt object of the transaction (allowanceSendFtRx)
The inputs are tokenId, the owner account ID (treasuryId), the receiver account ID (bobId), sendBal, the spender account ID (aliceId), the private key of the spender for transaction authorization (aliceKey), and client
To perform an approved transfer of NFT serial #3 (nft3), use the function transfers.nftAllowanceFcn
The function returns the receipt object of the transaction (allowanceSendNftRx)
The inputs are treasuryId, bobId, nft3, aliceId, aliceKey, and client
For both cases, output to the console:
The status of the approved transfer transaction
Account balances using queries.balanceCheckerFcn
Fungible Token (FT)
Fungible Token (FT)
Non-Fungible Token (NFT)
console.log(`\nSTEP 3 ===================================\n`);
console.log(`- Alice performing allowance transfer from Treasury to Bob...\n`);
const sendBal = 45; // Spender must generate the TX ID or be the client
const allowanceSendFtRx = await transfers.ftAllowanceFcn(tokenId, treasuryId, bobId, sendBal, aliceId, aliceKey, client);
console.log(`- Allowance transfer status: ${allowanceSendFtRx.status} \n`);
console.log(`\nSTEP 3 ===================================\n`);
console.log(`- Alice performing allowance transfer from Treasury to Bob...\n`);
const allowanceSendNftRx = await transfers.nftAllowanceFcn(treasuryId, bobId, nft3, aliceId, aliceKey, client);
console.log(`- Allowance transfer status: ${allowanceSendNftRx.status} \n`);
The functions transfers.ftAllowanceFcn and transfers.nftAllowanceFcn
both use TransferTransaction()
from the SDK to enable a spender to use an allowance approved by an owner. Notice the following:
The methods .addApprovedTokenTransfer()
and .addApprovedNftTransfer() are used to specify the amount of fungible token or NFT serial # coming out of the owner’s account, respectively
The spender must either generate the transaction ID or be the client submitting the transaction for the approved transfer to be successful
In this case, .setTransactionId(TransactionId.generate(spender))
generates the transaction ID with the spender and sets it for the transfer transaction
The transaction must be signed with the spender’s private key
transfers.ftAllowanceFcn
transfers.ftAllowanceFcn
transfers.nftAllowanceFcn
export async function ftAllowanceFcn(tId, owner, receiver, sendBal, spender, spenderPvKey, client) {
const approvedSendTx = new TransferTransaction()
.addApprovedTokenTransfer(tId, owner, -sendBal)
.addTokenTransfer(tId, receiver, sendBal)
.setTransactionId(TransactionId.generate(spender)) // Spender must generate the TX ID or be the client
.freezeWith(client);
const approvedSendSign = await approvedSendTx.sign(spenderPvKey);
const approvedSendSubmit = await approvedSendSign.execute(client);
const approvedSendRx = await approvedSendSubmit.getReceipt(client);
return approvedSendRx;
}
export async function nftAllowanceFcn(owner, receiver, nft2Send, spender, spenderPvKey, client) {
const approvedSendTx = new TransferTransaction()
.addApprovedNftTransfer(nft2Send, owner, receiver)
.setTransactionId(TransactionId.generate(spender)) // Spender must generate the TX ID or be the client
.freezeWith(client);
const approvedSendSign = await approvedSendTx.sign(spenderPvKey);
const approvedSendSubmit = await approvedSendSign.execute(client);
const approvedSendRx = await approvedSendSubmit.getReceipt(client);
return approvedSendRx;
}
Console Output (FT/NFT):
4. Delete FT/NFT Allowance
In this step, Treasury removes the token allowances for Alice. Fungible token allowances are removed by simply setting the allowance value to zero. On the other hand, NFT allowances are removed by specifying the NFT serials that should be disallowed.
The function approvals.ftAllowanceFcn
from before is used again for the fungible token, but now passing a value of 0 HBAR (allowBal)
For the NFT scenario, call the function approvals.nftAllowanceDeleteFcn
The function returns the receipt object of the transaction (allowanceDeleteNftRx)
The inputs are treasuryId, an array of NFTs to disallow (nft2disallow), the private key of the owner for transaction authorization (treasuryKey), and client
Output to the console:
Status of the allowance deletion transaction
A mirror node REST API request that shows fungible token allowances for Treasury
console.log(`
====================================================
THE END - NOW JOIN: https://hedera.com/discord
====================================================\n`);
Helper functions
The function approvals.nftAllowanceDeleteFcn uses AccountAllowanceDeleteTransaction() from the SDK to remove an allowance previously approved by an owner. Keep in mind that 20 is the maximum number of NFT serials that can be disallowed in a single transaction.
Now you know how to approve allowances for fungible tokens and NFTs on Hedera using the JavaScript SDK. You can try this example with the other officially supported SDKs for Java, Go, and Swift.
In Part 2 and Part 3 of this tutorial series, you will learn how to approve token allowances using Solidity HTS precompiles and ERC standard call.