How to Approve Fungible Token and NFT Allowances on Hedera - Part 2: Using Solidity Libraries
Headshot
Nov 14, 2022
by Ed Marquez
Developer Evangelist

Allowances grant another account (spender) the right to transfer HBAR, fungible tokens (FT), and non-fungible tokens (NFTs) from your account (owner).

This tutorial series shows you how to approve allowances for fungible tokens and NFTs. Part 1 showed how to approve token allowances using the Hedera JavaScript SDK. In this article, Part 2, you will learn how to approve native token allowances using Solidity libraries for tokenization (precompiles). Part 3 will show you how to do it using ERC standard functions.

To learn how to approve HBAR allowances, check out How to Approve HBAR Allowances on Hedera Using the SDK.

Try It Yourself

You Will Use These Tools

Example: Alice Spends FT and NFT on Behalf of the Treasury

This example guides you through the following steps:

  1. Creating additional Hedera accounts (Treasury, Alice, Bob), native token, and a smart contract
  2. Treasury approving an allowance of HBAR ROCKS tokens for Alice (50 FT / 1 NFT)
  3. Alice performing an approved token transfer from Treasury to Bob (10 FT / 1 NFT)
  4. Treasury deleting the token allowance for Alice (for FT only)

After completing all steps, your console should look something like this (FT/NFT):

2022 10 How to Approve Fungible and NFT Allowances Precomp Image 1

Fungible token example output (left) and NFT example output (right)

1. Create Accounts, HTS Token, and the Contract

There are six entities in this scenario: Operator, Treasury, Alice, Bob, the HBAR ROCKS token, and a smart contract. 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 15 HBAR
    • Generate and record the private key for the account. Hedera supports ED25519 and ECDSA keys
    • Use the helper function accountCreateFcn to create the new account
      • 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
Code Snippet Background
// STEP 1 ===================================
console.log(`\nSTEP 1 ===================================\n`);
console.log(`- Creating Hedera accounts, HTS token, and contract...\n`);

// Accounts
const initBalance = new Hbar(15);
const treasuryKey = PrivateKey.generateED25519();
const [treasurySt, treasuryId] = await accountCreateFcn(treasuryKey, initBalance, client);
console.log(`- Treasury's account: https://hashscan.io/#/testnet/account/${treasuryId}`);
const aliceKey = PrivateKey.generateED25519();
const [aliceSt, aliceId] = await accountCreateFcn(aliceKey, initBalance, client);
console.log(`- Alice's account: https://hashscan.io/#/testnet/account/${aliceId}`);
const bobKey = PrivateKey.generateED25519();
const [bobSt, bobId] = await accountCreateFcn(bobKey, initBalance, client);
console.log(`- Bob's account: https://hashscan.io/#/testnet/account/${bobId}`);
  • 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 NFT), token symbol (HROCKNFT), the initial supply (0), the maximum token supply (100), the treasury account for the token (treasuryId), the treasury key for transaction authorization (treasuryKey), and the client object
  • Output to the console tokenId, the token ID in Solidity format, and the token supply
Fungible Token (FT)
  • Fungible Token (FT)
  • Non-Fungible Token (NFT)
Code Snippet Background
//Token
const [tokenId, tokenInfo] = await htsTokens.createFtFcn("HBAR ROCKS", "HROCK", 100, treasuryId, treasuryKey, client);
const tokenAddressSol = tokenId.toSolidityAddress();
console.log(`\n- Token ID: ${tokenId}`);
console.log(`- Token ID in Solidity format: ${tokenAddressSol}`);
console.log(`- Initial token supply: ${tokenInfo.totalSupply.low}`);
//Token
const [tokenId, tokenInfo] = await htsTokens.createMintNftFcn("HBAR ROCKS NFT", "HROCKNFT", 0, 100, treasuryId, treasuryKey, client);
const tokenAddressSol = tokenId.toSolidityAddress();
console.log(`\n- Token ID: ${tokenId}`);
console.log(`- Token ID in Solidity format: ${tokenAddressSol}`);
console.log(`- Initial token supply: ${tokenInfo.totalSupply.low}`);
  • Deploy the contract ApproveAllowances – see the Solidity code in the second tab.
    • Set the gas limit (gasLim) to be 100,000 and define the contract bytecode
    • Deploy the contract using the helper function contracts.deployContractFcn.
      • The function returns the contractId in Hedera format and contractAddress in Solidity format
      • The inputs are the bytecode, gasLim, and client
    • Output to the console contractId and contractAddress
index.js
  • index.js
  • ApproveAllowances.sol
Code Snippet Background
// Contract
// Import the compiled contract bytecode
const bytecode = fs.readFileSync("./binaries/ApproveAllowances.bin");
let gasLim = 100000;
const [contractId, contractAddress] = await contracts.deployContractFcn(bytecode, gasLim, client);
console.log(`\n- Contract ID: ${contractId}`);
console.log(`- Contract ID in Solidity address format: ${contractAddress}`);
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.11;
pragma experimental ABIEncoderV2;

import "./hts-precompiles/HederaTokenService.sol";
import "./hts-precompiles/HederaResponseCodes.sol";
import "./hts-precompiles/ExpiryHelper.sol";
import "./hts-precompiles/FeeHelper.sol";
import "./hts-precompiles/KeyHelper.sol";

contract ApproveAllowances is HederaTokenService {
    address htsPrecompiles = address(0x167);

    function tokenAssociate(address _account, address _tokenAddress) external returns (int){
        int response = HederaTokenService.associateToken(_account, _tokenAddress);

        if (response != HederaResponseCodes.SUCCESS) {
            // revert ("allowance Failed");
            return response;
        }
        return response;
    }

    function getBalance() public view returns (uint) {
        return address(this).balance;
    }

    //============================================ 
    // FUNGIBLE TOKENS
    //============================================ 
    
    function approveFt( address _tokenAddress, address _spender, uint256 _amount) external returns (int) {
        htsPrecompiles.delegatecall(abi.encodeWithSelector(IHederaTokenService.approve.selector,_tokenAddress, _spender, _amount)); 
    }

    function ftTransferApproved(address _tokenAddress, address _owner, address _receiver, uint256 _amount) external returns (int) {
        htsPrecompiles.delegatecall(abi.encodeWithSelector(IHederaTokenService.transferFrom.selector, _tokenAddress, _owner, _receiver, _amount)); 
    }

    function getAllowance4Ft(address _tokenAddress, address _owner, address _spender) external returns (uint256) {
        (int responseCode, uint256 amount) = HederaTokenService.allowance(_tokenAddress, _owner, _spender); 
        if (responseCode != HederaResponseCodes.SUCCESS) {
            // revert ("allowance Failed");
            return amount;
        }
        return amount;
    }

    //============================================ 
    // NON-FUNGIBLE TOKENS
    //============================================ 

    function approveNft(address _tokenAddress, address _spender, uint256 _serialNumber) external returns (int) {
        htsPrecompiles.delegatecall(abi.encodeWithSelector(IHederaTokenService.approveNFT.selector,_tokenAddress, _spender, _serialNumber)); 
    }

    function approveAllNfts(address _tokenAddress, address _spender, bool _approveOrRevoke) external returns (int) {
        int responseCode  = HederaTokenService.setApprovalForAll(_tokenAddress, _spender, _approveOrRevoke); 
        if (responseCode != HederaResponseCodes.SUCCESS) {
            // revert ("allowance Failed");
            return responseCode;
        }
        return responseCode;
    }
    
    function nftTransferApproved(address _tokenAddress, address _owner, address _receiver, uint256 _serial) external returns (int) {
        htsPrecompiles.delegatecall(abi.encodeWithSelector(IHederaTokenService.transferFromNFT.selector, _tokenAddress, _owner, _receiver, _serial)); 
    }

    // Queries
    function getApprovedAddress4Nft(address _tokenAddress, uint256 _serialNumber) external returns (address) {
        (int responseCode , address approved) = HederaTokenService.getApproved(_tokenAddress, _serialNumber); 
        if (responseCode != HederaResponseCodes.SUCCESS) {
            // revert ("allowance Failed");
            return approved;
        }
        return approved;
    }
    
    function getApprovedAddress4AllNfts(address _tokenAddress, address _owner, address _spender) external returns (bool) {
        (int responseCode , bool approved) = HederaTokenService.isApprovedForAll(_tokenAddress, _owner, _spender); 
        if (responseCode != HederaResponseCodes.SUCCESS) {
            // revert ("allowance Failed");
            return approved;
        }
        return approved;
    }
}

Helper Functions

You can easily reuse the helper functions in this example in case you need to do tasks like creating more accounts, tokens, or contracts in the future. We’ll use this modular approach throughout the article.

The helper function accountCreateFcn uses the AccountCreateTransaction() class of the SDK to generate the new accounts for Treasury, Alice, and Bob. Keep in mind that on Hedera an account and a token must be associated with each other before that account can transact the token. Notice that during creation, the account is set to have 10 automatic token associations. This way, no manual association is needed for the first 10 tokens the account wishes to transact. Visit this documentation page to learn more about token associations.

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.

The function contracts.deployContractFcn uses ContractCreateFlow() to store the bytecode and deploy the contract on Hedera. This single call handles for you the operations FileCreateTransaction(), FileAppendTransaction(), and ContractCreateTransaction().

accountCreateFcn
  • accountCreateFcn
  • htsTokens.createFtFcn
  • htsTokens.createMintNftFcn
  • contracts.deployContractFcn
Code Snippet Background
async function accountCreateFcn(pvKey, iBal, client) {
	const response = await new AccountCreateTransaction()
		.setInitialBalance(iBal)
		.setKey(pvKey.publicKey)
		.setMaxAutomaticTokenAssociations(10)
		.execute(client);
	const receipt = await response.getReceipt(client);
	return [receipt.status, receipt.accountId];
}
export async function createFtFcn(tName, tSymbol, iSupply, id, pvKey, client) {
	const tokenCreateTx = new TokenCreateTransaction()
		.setTokenName(tName)
		.setTokenSymbol(tSymbol)
		.setDecimals(0)
		.setInitialSupply(iSupply)
		.setTreasuryAccountId(id)
		.setAdminKey(pvKey.publicKey)
		.setSupplyKey(pvKey.publicKey)
		.freezeWith(client);
	const tokenCreateSign = await tokenCreateTx.sign(pvKey);
	const tokenCreateSubmit = await tokenCreateSign.execute(client);
	const tokenCreateRx = await tokenCreateSubmit.getReceipt(client);
	const tokenId = tokenCreateRx.tokenId;

	const tokenInfo = await queries.tokenQueryFcn(tokenId, client);

	return [tokenId, tokenInfo];
}
export async function createMintNftFcn(tName, tSymbol, iSupply, maxSupply, id, pvKey, client) {
	const nftCreate = new TokenCreateTransaction()
		.setTokenName(tName)
		.setTokenSymbol(tSymbol)
		.setTokenType(TokenType.NonFungibleUnique)
		.setSupplyType(TokenSupplyType.Finite)
		.setDecimals(0)
		.setInitialSupply(iSupply)
		.setTreasuryAccountId(id)
		.setSupplyKey(pvKey.publicKey)
		.setMaxSupply(maxSupply)
		// .setCustomFees([nftCustomFee])
		// .setAdminKey(adminKey)
		// .setPauseKey(pauseKey)
		// .setFreezeKey(freezeKey)
		// .setWipeKey(wipeKey)
		.freezeWith(client);

	const nftCreateTxSign = await nftCreate.sign(pvKey);
	const nftCreateSubmit = await nftCreateTxSign.execute(client);
	const nftCreateRx = await nftCreateSubmit.getReceipt(client);
	const tokenId = nftCreateRx.tokenId;

	// // MINT NEW BATCH OF NFTs
	const CID = [
		Buffer.from("ipfs://QmNPCiNA3Dsu3K5FxDPMG5Q3fZRwVTg14EXA92uqEeSRXn"),
		Buffer.from("ipfs://QmZ4dgAgt8owvnULxnKxNe8YqpavtVCXmc1Lt2XajFpJs9"),
		Buffer.from("ipfs://QmPzY5GxevjyfMUF5vEAjtyRoigzWp47MiKAtLBduLMC1T"),
		Buffer.from("ipfs://Qmd3kGgSrAwwSrhesYcY7K54f3qD7MDo38r7Po2dChtQx5"),
		Buffer.from("ipfs://QmWgkKz3ozgqtnvbCLeh7EaR1H8u5Sshx3ZJzxkcrT3jbw"),
	];
	const mintTx = new TokenMintTransaction()
		.setTokenId(tokenId)
		.setMetadata(CID) //Batch minting - UP TO 10 NFTs in single tx
		.freezeWith(client);
	const mintTxSign = await mintTx.sign(pvKey);
	const mintTxSubmit = await mintTxSign.execute(client);
	const mintRx = await mintTxSubmit.getReceipt(client);
	const tokenInfo = await queries.tokenQueryFcn(tokenId, client);

	return [tokenId, tokenInfo];
}
export async function deployContractFcn(bytecode, gasLim, client) {
	const contractCreateTx = new ContractCreateFlow().setBytecode(bytecode).setGas(gasLim);
	const contractCreateSubmit = await contractCreateTx.execute(client);
	const contractCreateRx = await contractCreateSubmit.getReceipt(client);
	const contractId = contractCreateRx.contractId;
	const contractAddress = contractId.toSolidityAddress();
	return [contractId, contractAddress];
}

Console Output:

2022 10 How to Approve Fungible and NFT Allowances Precomp Image 2

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 serial #1 (nft2approve) in the case of the NFT.
    • Use ContractFunctionParameters() from the SDK to specify the parameters for the contract function that will be called. Pass the token address in Solidity format, Alice’s account IDs in Solidity format, and allowBal/nft2approve
  • For the fungible token example, use the helper function contracts.executeContractFcn to call the contract function approveFt
    • The helper function returns the record object of the transaction (allowanceApproveFtRec)
    • The inputs are contractId, approveFt, the parameters for the contract function (allowanceApproveFtParams), the gas limit (gasLim), and client
  • For the NFT example, use the same helper function contracts.executeContractFcn again to call the contract function approveNft
    • The helper function returns the record object of the transaction (allowanceApproveNftRec)
    • The inputs are contractId, approveNft, the parameters for the contract function (allowanceApproveNftParams), the gas limit (gasLim), and client
  • Note that in both cases (FT and NFT),
    • Treasury credentials are set as the operator for the client before calling the contract
    • The contract functions (see Solidity code) that approve the FT/NFT allowances use delegatecall instead of call. This is because the client (Treasury) calls the ApproveAllowances contract, which in turn calls the contract with the Hedera Solidity libraries for tokenization (precompiles). Using delegatecall ensures the allowance is approved by the correct entity – which is Treasury, not the ApproveAllowances contract
  • Use the helper function queries.mirrorTxQueryFcn to obtain transaction information from the mirror nodes
    • The function returns a mirror node REST API request about the relevant transaction (allowanceApprove[Ft/NFT]Info) and a string with a mirror explorer URL (allowanceApprove[Ft/NFT]ExpUrl)
    • The input is the ID of the relevant transaction from the record object (allowanceApprove[Ft/NFT]Rec.transactionId)
  • Output to the console:
    • The status of the contract call transaction that approves the allowance
    • The mirror node explorer URL with more details about the allowance transaction
    • Account balances using queries.balanceCheckerFcn
Fungible Token (FT)
  • Fungible Token (FT)
  • Non-Fungible Token (NFT)
Code Snippet Background
// STEP 2 ===================================
console.log(`\nSTEP 2 ===================================\n`);
console.log(`- Treasury approving fungible token allowance for Alice...\n`);

gasLim = 4000000;
let allowBal = 50;
const allowanceApproveFtParams = new ContractFunctionParameters()
	.addAddress(tokenAddressSol)
	.addAddress(aliceId.toSolidityAddress())
	.addUint256(allowBal);

client.setOperator(treasuryId, treasuryKey);
const allowanceApproveFtRec = await contracts.executeContractFcn(contractId, "approveFt", allowanceApproveFtParams, gasLim, client);
client.setOperator(operatorId, operatorKey);
console.log(`- Contract call for FT allowance approval: ${allowanceApproveFtRec.receipt.status}`);

const [allowanceApproveFtInfo, allowanceApproveFtExpUrl] = await queries.mirrorTxQueryFcn(allowanceApproveFtRec.transactionId);
console.log(`- See details: ${allowanceApproveFtExpUrl} \n`);

await queries.balanceCheckerFcn(treasuryId, tokenId, client);
await queries.balanceCheckerFcn(aliceId, tokenId, client);
await queries.balanceCheckerFcn(bobId, tokenId, client);
// STEP 2 ===================================
console.log(`\nSTEP 2 ===================================\n`);
console.log(`- Treasury approving NFT allowance for Alice...\n`);

gasLim = 4000000;
let nft2approve = 1;
const allowanceApproveNftParams = new ContractFunctionParameters()
	.addAddress(tokenAddressSol)
	.addAddress(aliceId.toSolidityAddress())
	.addUint256(nft2approve);

client.setOperator(treasuryId, treasuryKey);
const allowanceApproveNftRec = await contracts.executeContractFcn(contractId, "approveNft", allowanceApproveNftParams, gasLim, client);
client.setOperator(operatorId, operatorKey);
console.log(`- Contract call for NFT allowance approval: ${allowanceApproveNftRec.receipt.status}`);

const [allowanceApproveNftInfo, allowanceApproveNftExpUrl] = await queries.mirrorTxQueryFcn(allowanceApproveNftRec.transactionId);
console.log(`- See details: ${allowanceApproveNftExpUrl} \n`);

await queries.balanceCheckerFcn(treasuryId, tokenId, client);
await queries.balanceCheckerFcn(aliceId, tokenId, client);
await queries.balanceCheckerFcn(bobId, tokenId, client);

Helper Functions

The function contracts.executeContractFcn also uses ContractExecuteTransaction() in the SDK to call the specified contract function.

The function queries.mirrorTxQueryFcn obtains transaction information from the mirror nodes. The function introduces a delay of 10 seconds to allow for the propagation of information to the mirror nodes. It then formats the transaction ID and performs string operations to return a mirror REST API query and a mirror node explorer URL.

contracts.executeContractFcn
  • contracts.executeContractFcn
  • queries.mirrorTxQueryFcn
Code Snippet Background
export async function executeContractFcn(cId, fcnName, params, gasLim, client) {
	const contractExecuteTx = new ContractExecuteTransaction().setContractId(cId).setGas(gasLim).setFunction(fcnName, params);
	const contractExecuteSubmit = await contractExecuteTx.execute(client);
	const contractExecuteRec = await contractExecuteSubmit.getRecord(client);
	return contractExecuteRec;
}
export async function mirrorTxQueryFcn(txIdRaw) {
	// Query a mirror node for information about the transaction
	const delay = (ms) => new Promise((res) => setTimeout(res, ms));
	await delay(10000); // Wait for 10 seconds before querying a mirror node

	const txIdPretty = prettify(txIdRaw.toString());
	const mirrorNodeExplorerUrl = `https://hashscan.io/testnet/transaction/${txIdPretty}`;
	const mirrorNodeRestApi = `https://testnet.mirrornode.hedera.com/api/v1/transactions/${txIdPretty}`;
	const mQuery = await axios.get(mirrorNodeRestApi);

	return [mQuery, mirrorNodeExplorerUrl];
}

function prettify(txIdRaw) {
	const a = txIdRaw.split("@");
	const b = a[1].split(".");
	return `${a[0]}-${b[0]}-${b[1]}`;
}

Console Output:

2022 10 How to Approve Fungible and NFT Allowances Precomp Image 3

3. Perform Approved Token Transfer

In this step, Alice executes a contract function that causes the spending of tokens from the allowance granted by Treasury. Simply put, Alice transfers tokens from Treasury’s account to Bob’s by calling a contract.

  • Define the parameters for the contract functions in the variables allowanceSend[Ft/NFT]Params
    • The contract function expects the token address in Solidity format, the owner’s account IDs (Treasury) in Solidity format, the receiver’s account IDs (Bob) in Solidity format, and the FT/NFT to be sent (10/serial #1)
  • Use the helper function contracts.executeContractFcn to call the contract function [ft/nft]TransferApproved
    • The helper function returns the record object of the transaction (allowanceSend[Ft/Nft]Rec)
    • The inputs are contractId, the contract function to call, the parameters for the contract function (allowanceSend[Ft/Nft]Params), the gas limit (gasLim), and client
  • Note that in both cases (FT and NFT),
    • Alice credentials are set as the operator for the client before calling the contract
    • The contract functions (see Solidity code) that perform the FT/NFT approved transfers use delegatecall instead of call. This is because the client (Alice) calls the ApproveAllowances contract, which in turn calls the contract with the Hedera Solidity libraries for tokenization (precompiles). Using delegatecall ensures the approved transfer is performed by the correct entity – which is Alice
  • Use the helper function queries.mirrorTxQueryFcn to obtain transaction information from the mirror nodes
    • The function returns a mirror node REST API request about the relevant transaction (allowanceSend[Ft/NFT]Info) and a string with a mirror explorer URL (allowanceSend[Ft/NFT]ExpUrl)
    • The input is the ID of the relevant transaction from the record object (allowanceSend[Ft/NFT]Rec.transactionId)
  • Output to the console:
    • The status of the contract call transaction that performs the approved transfer
    • The mirror node explorer URL with more details about the transfer transaction
    • Account balances using queries.balanceCheckerFcn
Fungible Token (FT)
  • Fungible Token (FT)
  • Non-Fungible Token (NFT)
Code Snippet Background
// STEP 3 ===================================
console.log(`\nSTEP 3 ===================================\n`);
console.log(`- Alice performing allowance transfer from Treasury to Bob...\n`);

const allowanceSendFtParams = new ContractFunctionParameters()
	.addAddress(tokenAddressSol)
	.addAddress(treasuryId.toSolidityAddress())
	.addAddress(bobId.toSolidityAddress())
	.addUint256(10);

client.setOperator(aliceId, aliceKey);
const allowanceSendFtRec = await contracts.executeContractFcn(contractId, "ftTransferApproved", allowanceSendFtParams, gasLim, client);
client.setOperator(operatorId, operatorKey);
console.log(`- Contract call for approved FT transfer: ${allowanceSendFtRec.receipt.status}`);

const [allowanceSendFtInfo, allowanceSendFtExpUrl] = await queries.mirrorTxQueryFcn(allowanceSendFtRec.transactionId);
console.log(`- See details: ${allowanceSendFtExpUrl} \n`);

await queries.balanceCheckerFcn(treasuryId, tokenId, client);
await queries.balanceCheckerFcn(aliceId, tokenId, client);
await queries.balanceCheckerFcn(bobId, tokenId, client);
// STEP 3 ===================================
console.log(`\nSTEP 3 ===================================\n`);
console.log(`- Alice performing allowance transfer from Treasury to Bob...\n`);

const allowanceSendNftParams = new ContractFunctionParameters()
	.addAddress(tokenAddressSol)
	.addAddress(treasuryId.toSolidityAddress())
	.addAddress(bobId.toSolidityAddress())
	.addUint256(nft2approve);

client.setOperator(aliceId, aliceKey);
const allowanceSendNftRec = await contracts.executeContractFcn(contractId, "nftTransferApproved", allowanceSendNftParams, gasLim, client);
client.setOperator(operatorId, operatorKey);
console.log(`- Contract call for approved NFT transfer: ${allowanceSendNftRec.receipt.status}`);

const [allowanceSendNftInfo, allowanceSendNftExpUrl] = await queries.mirrorTxQueryFcn(allowanceSendNftRec.transactionId);
console.log(`- See details: ${allowanceSendNftExpUrl} \n`);

await queries.balanceCheckerFcn(treasuryId, tokenId, client);
await queries.balanceCheckerFcn(aliceId, tokenId, client);
await queries.balanceCheckerFcn(bobId, tokenId, client);

Console Output:

2022 10 How to Approve Fungible and NFT Allowances Precomp Image 4

4. Delete the Allowance (FT only)

In this step, Treasury executes a contract function to remove the fungible token allowances for Alice. Fungible token allowances are removed by simply setting the allowance value to zero. The NFT allowance won’t be removed because only one NFT serial was approved and has already been transferred out of Treasury’s account.

  • Repeat the actions from step 2 for the fungible token only, but change the allowance value (allowBal) to 0
  • Output to the console:
    • The status of the contract call transaction that deletes the allowance
    • A mirror node REST API request that shows fungible token allowances for Treasury

The last step is to join the Hedera Developer Discord!

Code Snippet Background
// STEP 4 ===================================
console.log(`\nSTEP 4 ===================================\n`);
console.log(`- Treasury deleting fungible token allowance for Alice...\n`);

allowBal = 0;
const allowanceDeleteFtParams = new ContractFunctionParameters()
	.addAddress(tokenAddressSol)
	.addAddress(aliceId.toSolidityAddress())
	.addUint256(allowBal);

client.setOperator(treasuryId, treasuryKey);
const allowanceDeleteFtRec = await contracts.executeContractFcn(contractId, "approveFt", allowanceDeleteFtParams, gasLim, client);
client.setOperator(operatorId, operatorKey);

console.log(`- Contract call for FT allowance deletion: ${allowanceDeleteFtRec.receipt.status}`);
console.log(`- See details: https://testnet.mirrornode.hedera.com/api/v1/accounts/${treasuryId}/allowances/tokens`);

console.log(`
====================================================
 THE END - NOW JOIN: https://hedera.com/discord
====================================================\n`);

Console Output:

2022 10 How to Approve Fungible and NFT Allowances Precomp Image 5

Summary

Now you know how to approve allowances for fungible tokens and NFTs on Hedera using Solidity libraries for tokenization (precompiles) and the JavaScript SDK. You can try this example with Solidity and the other officially supported SDKs for Java, Go, and Swift.

In Part 3 of this tutorial series, you will learn how to approve token allowances using ERC standard calls.

Continue Learning