How to Approve Fungible Token and NFT Allowances on Hedera - Part 3: ERC Standard Calls
IMG 2356
Nov 29, 2022
by Abi Castro

Allowing an address to grant token allowance to another address is supported in the ERC-20 (FT) standard. In the case of ERC-721 (NFT) standard one can approve an address for a specific NFTs or approve for all assets to be operated by the designated approved address. This grants the ability to have another address (sender) move tokens from another address (owner) with a high level of autonomy. 

Let’s take an example of subscribing to a video streaming service that is built on Hedera and has their own fungible tokens: VID. You, as the owner of 100 VID tokens, would grant the video streaming service an allowance of 5 VID. This allows the video streaming service to automatically remove the allowed amount of tokens from your account without you having to personally interact and approve each transaction.  In the web2 world, this type of work flow is done without much thought and is an expected feature from a service. The key difference here is that this is a decentralized workflow that involves smart contracts, accounts, and tokens.

You can treat a Hedera Native Token, Fungible or Non-Fungible, like an ERC token by creating a smart contract that leverages the ERC standard contract calls and using the Hedera SDK. Follow along as we walk through approving an allowance for an account, transferring ownership of tokens using the allowance and removing the previously approved allowance.

Try It Yourself

  • Use this codesandbox to approve FT allowance to an account example
  • Use this codesandbox to approve NFT allowance to an account example
    • Fork the sandbox
    • Remember to provide testnet account credentials in the .env file
    • Open a new terminal and run npm run start
GitHub Repositories

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

This example guides you through the following steps:

  1. Setting up helper functions required to complete the example
  2. Creating Hedera accounts and native tokens (Treasury, Alice, Bob, the HBAR ROCKS FT, and 5 HBAR RULES NFTs)
  3. Treasury approving an allowance of tokens for Alice (50 FT / 3 NFT)
  4. Alice performing an approved token transfer from Treasury to Bob (30 FT / 2 NFT)
  5. Treasury removing the token allowance for Alice
Ftandnft full output

The diagram below depicts a high-level flow of approving token allowances.

Token allowance 1

Using the Hedera Javascript SDK we will make contract function calls to approve an allowance, transfer tokens to and from accounts, check allowance, and get approved addresses. This combines using the Hedera SDK and ERC standard calls.

Setting up our helper functions

Since we need to create a couple of accounts on top of creating FT and NFT we will first create the functions to help us achieve that. You can swap between the different helper functions by using the tabs on the upper left of the code box.

create account with an initial balance.
  • create account with an initial balance.
  • create a simple fungible token type
  • create simple non-fungible token
  • create a new nft collection and mint token
Code Snippet Background
// create an account with an initial balance
export const createAccount = async (client: Client, initialBalance: number): Promise<[AccountId, PrivateKey]> => {
  const accountPrivateKey = PrivateKey.generateED25519();

  const response = await new AccountCreateTransaction()
    .setInitialBalance(new Hbar(initialBalance))
    .setKey(accountPrivateKey)
    .execute(client);

  const receipt = await response.getReceipt(client);

  if (receipt.accountId === null) {
    throw new Error("Somehow accountId is null.");
  }

  return [receipt.accountId, accountPrivateKey];
};
export const createFungibleToken = async (
  client: Client,
  treasureyAccId: string | AccountId,
  supplyKey: PrivateKey,
  treasuryAccPvKey: PrivateKey,
  initialSupply: number,
  tokenName: string,
  tokenSymbol: string,
): Promise<{
  tokenId: TokenId,
  tokenIdInSolidityFormat: string
}> => {
  /* 
    * Create a transaction with token type fungible
    * Returns Fungible Token Id and Token Id in solidity format
  */
  const createTokenTxn = await new TokenCreateTransaction()
    .setTokenName(tokenName)
    .setTokenSymbol(tokenSymbol)
    .setTokenType(TokenType.FungibleCommon)
    .setInitialSupply(initialSupply)
    .setTreasuryAccountId(treasureyAccId)
    .setSupplyKey(supplyKey)
    .setMaxTransactionFee(new Hbar(30))
    .freezeWith(client); //freeze tx from from any further mods.

  const createTokenTxnSigned = await createTokenTxn.sign(treasuryAccPvKey);
  // submit txn to heder network
  const txnResponse = await createTokenTxnSigned.execute(client);
  // request receipt of txn
  const txnRx = await txnResponse.getReceipt(client);
  const txnStatus = txnRx.status.toString();
  const tokenId = txnRx.tokenId;
  if (tokenId === null) {
    throw new Error("Somehow tokenId is null");
  }

  const tokenIdInSolidityFormat = tokenId.toSolidityAddress();

  console.log(
    `Token Type Creation was a ${txnStatus} and was created with token id: ${tokenId}`
  );
  console.log(`Token Id in Solidity format: ${tokenIdInSolidityFormat}`);

  return {
    tokenId,
    tokenIdInSolidityFormat,
  };
};
export const createNonFungibleToken = async (
  client: Client,
  treasureyAccId: string | AccountId,
  supplyKey: PrivateKey,
  treasuryAccPvKey: PrivateKey,
  initialSupply: number,
  tokenName: string,
  tokenSymbol: string,
): Promise<[TokenId | null, string]> => {
  /* 
    * Create a transaction with token type fungible
    * Returns Fungible Token Id and Token Id in solidity format
  */
  const createTokenTxn = await new TokenCreateTransaction()
    .setTokenName(tokenName)
    .setTokenSymbol(tokenSymbol)
    .setTokenType(TokenType.NonFungibleUnique)
    .setInitialSupply(initialSupply)
    .setTreasuryAccountId(treasureyAccId)
    .setSupplyKey(supplyKey)
    .setMaxTransactionFee(new Hbar(30))
    .freezeWith(client); //freeze tx from from any further mods.

  const createTokenTxnSigned = await createTokenTxn.sign(treasuryAccPvKey);
  // submit txn to hedera network
  const txnResponse = await createTokenTxnSigned.execute(client);
  // request receipt of txn
  const txnRx = await txnResponse.getReceipt(client);
  const txnStatus = txnRx.status.toString();
  const tokenId = txnRx.tokenId;
  if (tokenId === null ) { throw new Error("Somehow tokenId is null.");}
  
  const tokenIdInSolidityFormat = tokenId.toSolidityAddress();

  console.log(
    `Token Type Creation was a ${txnStatus} and was created with token id: ${tokenId}`
  );
  console.log(`Token Id in Solidity format: ${tokenIdInSolidityFormat}`);

  return [tokenId, tokenIdInSolidityFormat];
};
export const mintToken = async (client: Client, tokenId: string | TokenId, metadatas: Uint8Array[], supplyKey: PrivateKey) => {

  const mintTokenTxn = new TokenMintTransaction()
    .setTokenId(tokenId)
    .setMetadata(metadatas)
    .freezeWith(client);

  const mintTokenTxnSigned = await mintTokenTxn.sign(supplyKey);

  // submit txn to hedera network
  const txnResponse = await mintTokenTxnSigned.execute(client);

  const mintTokenRx = await txnResponse.getReceipt(client);
  const mintTokenStatus = mintTokenRx.status.toString();

  console.log(`Token mint was a ${mintTokenStatus}`);
};

export const createNewNftCollection = async (
  client: Client,
  tokenName: string,
  tokenSymbol: string,
  metadataIPFSUrls: Buffer[], // already uploaded ipfs metadata json files
  treasuryAccountId: string | AccountId,
  treasuryAccountPrivateKey: PrivateKey,
): Promise<{
  tokenId: TokenId,
  supplyKey: PrivateKey,
}> => {
  // generate supply key
  const supplyKey = PrivateKey.generateED25519();

  const [tokenId, ] = await createNonFungibleToken(client, treasuryAccountId, supplyKey, treasuryAccountPrivateKey, 0, tokenName, tokenSymbol);
  if (tokenId === null || tokenId === undefined) {
    throw new Error("Somehow tokenId is null");
  }

  const metadatas: Uint8Array[] = metadataIPFSUrls.map(url => Buffer.from(url));

  // mint token
  await mintToken(client, tokenId, metadatas, supplyKey);
  return {
    tokenId: tokenId,
    supplyKey: supplyKey,
  };
}

export const associateToken = async (client: Client, tokenId: string | TokenId, accountId: string | AccountId, accountPrivateKey: PrivateKey) => {
  const tokenAssociateTx = new TokenAssociateTransaction()
  .setTokenIds([tokenId])
  .setAccountId(accountId)
  .freezeWith(client);
const tokenAssociateSign = await tokenAssociateTx.sign(accountPrivateKey);
const tokenAssociateSubmit = await tokenAssociateSign.execute(client);
const tokenAssociateRx = await tokenAssociateSubmit.getReceipt(client);
console.log(`- Associated with token: ${tokenAssociateRx.status}`);
} 

1. Setting up Accounts

Create the treasury’s, Alice’s, and Bob’s accounts by awaiting the createAccount helper function. Each account will be created with an initial balance of 100 HBAR.

Code Snippet Background
  // create treasury's, alice's, and bob's accounts
  const [treasuryAccId, treasuryAccPvKey] = await createAccount(client, 100);
  console.log(`- Treasury's account: https://hashscan.io/#/testnet/account/${treasuryAccId}`);
  console.log(`- Treasury's private key: ${treasuryAccPvKey}`);
  const [aliceAccId, aliceAccPvKey] = await createAccount(client, 100);
  console.log(`- Alice's account: https://hashscan.io/#/testnet/account/${aliceAccId}`);
  const ALICE_ACCOUNT_IN_SOLIDITY_FORMAT = aliceAccId.toSolidityAddress();
  console.log(`Alice, the spender, address in solidity format: ${ALICE_ACCOUNT_IN_SOLIDITY_FORMAT}`);

  const [bobAccId, bobAccPvKey] = await createAccount(client, 100);
  console.log(`- Bob's account: https://hashscan.io/#/testnet/account/${bobAccId}`);
  console.log(`- Bob's private key: ${bobAccPvKey}\n`);

2. Creating FTs and Creating NFTs

Next, we create our fungible token with an initial supply of 100. We also generate a supply key in case we want to change the supply and mint more tokens later.

Similar to seeing the helper functions, use the tabs switcher to see the code that will create a new NFT collection called HBAR RULES and mint 5 NFTs.

create fungible token
  • create fungible token
  • create non-fungible token
Code Snippet Background
const supplyKey = PrivateKey.generateED25519();
 
// create token
const { tokenId, tokenIdInSolidityFormat } = await createFungibleToken(client, treasuryAccId, supplyKey, treasuryAccPvKey, 100, 'HBAR ROCKS', 'HROCK');
// create token collection and print initial supply
const txnResponse = await createNewNftCollection(client, 'HBAR RULES', 'HRULES', metadataIPFSUrls, treasuryAccId, treasuryAccPvKey);
const TOKEN_ID_IN_SOLIDITY_FORMAT = txnResponse.tokenId.toSolidityAddress();
console.log(`Token Id in solidity format: ${TOKEN_ID_IN_SOLIDITY_FORMAT}`);

Bob will need to associate with these new tokens in order to receive it. Let’s go ahead and do that next.

Code Snippet Background
 // Bob must associate to receive token
 await associateToken(client, tokenId, bobAccId, bobAccPvKey)

3. Create our Smart Contract leveraging IERC20 and IERC721 Respectively

After we’ve generated our accounts, and have created both our FTs and NFTs, let’s create two simple smart contracts that will use openzeppelin IERC20.sol and IERC721.sol respectively. Make sure to import openzeppelin IERC20.sol if working with FTs and import IERC721.sol if working with NFTs.

IERC20 Smart Contract Set Up
  • IERC20 Smart Contract Set Up
  • IERC721 Smart Contract Set Up
Code Snippet Background

// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;
 
import "../node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol";
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

import "../node_modules/@openzeppelin/contracts/token/ERC721/IERC721.sol";

Continue to write the rest of the contracts as follows:

IERC20 Smart Contract
  • IERC20 Smart Contract
  • IERC721 Smart Contract
Code Snippet Background
contract ERC20FungibleToken {
  /// @notice Approve set amount as the allowance of spender over caller's tokens
  /// @param token address of tokens to approve
  /// @param spender address who will receive allowance
  /// @param amount that the spender is able to use
  function approve(address token, address spender, uint256 amount) public returns (bool result) {
      (bool success, ) = token.delegatecall(
          abi.encodeWithSelector(
              IERC20.approve.selector,
              spender,
              amount
          )
      );
      return success;
  }

  /// @notice Check a spender accounts allowance
  /// @param token address of token you are checking allowance on
  /// @param owner address of caller
  /// @param spender the address with an allowance
  /// @return allowance The allowance of the spender over the callers tokens
  function checkAllowance(address token, address owner, address spender) public view returns (uint256 allowance){
      return IERC20(token).allowance(owner, spender);
  }

  /// @notice Transfer the set amount of token from the sender (from) to the recipeint (to) through allowance
  /// @param token address of the token
  /// @param sender  address with allowance (the from address)
  /// @param recipient address recieving token (the to address)
  /// @return result a bool whether it was successfuly or a failure
  function transferFrom(address token, address sender, address recipient, uint256 amount) public returns (bool result) {
      (bool success, ) = token.delegatecall(
          abi.encodeWithSelector(
              IERC20.transferFrom.selector,
              sender,
              recipient,
              amount
          )
      );
      return success;
  }
  fallback () external{}
}
contract ERC721NonFungibleToken {
  /// @notice Approve spender address to spend specific NFTs on behalf of msg.sender
  /// @param token address of the token to approve
  /// @param spender address of spender
  /// @param serialNumber is the tokenId of NFT
  function approve(address token, address spender, uint256 serialNumber) public returns (bool result) {
      (bool success, ) = token.delegatecall(
          abi.encodeWithSelector(
              IERC721.approve.selector,
              spender,
              serialNumber
          )
      );
      return success;
  }

  /// @notice Get the approved address by serial number for a single NFT
  /// @param token address of token to approve
  /// @param tokenId represents the NFT serial number
  /// @return spender address approved for the NFT, or zero addres if there is none
  function getApprovedBySerialNumber(address token, uint256 tokenId) external view returns (address spender) {
    return IERC721(token).getApproved(tokenId);
  }

  /// @notice Transfer NFT to a different owner
  /// @param token address of token to transfer
  /// @param sender is the address from where the NFT is being transferred from
  /// @param recipient is the address to where the NFT is being transferred to 
  /// @return result success if transfer was successful
  function transferFrom(address token, address sender, address recipient, uint256 serialNumber) public returns (bool result) {
      (bool success, ) = token.delegatecall(
        abi.encodeWithSelector(
            IERC721.transferFrom.selector,
            sender,
            recipient,
            serialNumber
        )
    );
    return success;
  }

  fallback () external{}
}

note: It is imperative to make a delegatecall instead of a call for approve and transferFrom so the sender remains to be the treasury account which will be the one granting the allowance to Alice.

Setting up our smart contract helper functions

Once we have our smart contract created. It is time to deploy them and start making contract calls. Let’s take some time to set up two helper functions for deploying and another one for calling a contract function.

A function to deploy our smart contract
  • A function to deploy our smart contract
  • A function to execute a contract function.
Code Snippet Background
/*
* Stores the bytecode and deploys the contract to the Hedera network.
* Return an array with the contractId and contract solidity address.
*
* Note: This single call handles what FileCreateTransaction(), FileAppendTransaction() and
* ContractCreateTransaction() classes do.
*/
const deployContract = async (client, bytecode, gasLimit) => {
 const contractCreateFlowTxn = new ContractCreateFlow()
   .setBytecode(bytecode)
   .setGas(gasLimit);
 
 console.log(`- Deploying smart contract to Hedera network`)
 const txnResponse = await contractCreateFlowTxn.execute(client);
 
 const txnReceipt = await txnResponse.getReceipt(client);
 const contractId = txnReceipt.contractId;
 const contractSolidityAddress = contractId.toSolidityAddress();
 
 console.log(`- The smart contract Id is ${contractId}`);
 console.log(`- The smart contract Id in Solidity format is ${contractSolidityAddress}\n`);
 
 return [contractId, contractSolidityAddress];
}
const executeContractFunction = async (client, contractId, gasLimit, functionName, functionParameters, ownerAccPvKey) => {
 const contractCallQueryTx = new ContractExecuteTransaction()
   .setContractId(contractId)
   .setGas(gasLimit)
   .setFunction(functionName, functionParameters)
   .freezeWith(client);
 
 const contractCallQueryTxSigned = await contractCallQueryTx.sign(ownerAccPvKey);
 const txnResponse = await contractCallQueryTxSigned.execute(client);
 const txRecord = await txnResponse.getRecord(client);
 
 const recQuery = await new TransactionRecordQuery()
   .setTransactionId(txRecord.transactionId)
   .setIncludeChildren(true)
   .execute(client);
   
    return txRecord.contractFunctionResult;
}

3a. Deploy our Smart Contract

Compile our contract using solc or remix ide. Once that is done let's deploy our smart contract by calling and awaiting the helper function deployContract.

Deploy IERC20 Smart Contract
  • Deploy IERC20 Smart Contract
  • Deploy IERC721 Smart Contract
Code Snippet Background
/*
  * Read compiled byte code
  * Note: You can compile your smart contract on Remix ide or use solc
*/
const bytecode = fs.readFileSync("binaries/contracts_ERC20FungibleToken_sol_ERC20FungibleToken.bin");
 
// Deploy contract
const gasLimit = 1000000;
const [contractId, contractSolidityAddress] = await deployContract(client, bytecode, gasLimit);
  /*
    * Read compiled byte code
    * Note: You can compile your smart contract on Remix ide or use solc
  */
  const bytecode = fs.readFileSync("binaries/contracts_ERC721NonFungibleToken_sol_ERC721NonFungibleToken.bin");

  // Deploy contract
  const gasLimit = 1000000;
  const [contractId, contractSolidityAddress] = await deployContract(client, bytecode, gasLimit);

Before executing contract calls It is important that we set the operator to be the Treasury account.

Code Snippet Background
// set operator to be treasury account (treasury account is now the caller of the smart contract)
client.setOperator(treasuryAccId, treasuryAccPvKey);

4. Execute approve ERC standard contract call

Let’s build our function parameters. The approve function expects the address of the token we are granting approval for.  In our code this address is TOKEN_ID_IN_SOLIDITY_FORMAT. Next, we need to add the spender's, Alice, solidity's address. 

Lastly, when working with Fungible Tokens we specify the amount of the approved allowance that Alice will be receiving. Alice will receive an allowance of 50 HBAR ROCKS FTs.

When working with Non-Fungible tokens we grant an allowance to specific NFT serial numbers. Alice's account will be approved for the NFTs HBAR RULES with serial #s 1, 3, and 5.

Finally, we can execute a contract call function. We call our helper function executeContractFunction and pass in the client, contractId, gas limit, the function name, function parameters, and the private key that will sign the transaction.

Execute FT approve contract call
  • Execute FT approve contract call
  • Execute NFT approve contract call
Code Snippet Background
const approveParams = new ContractFunctionParameters()
  .addAddress(tokenIdInSolidityFormat)
  .addAddress(ALICE_ACCOUNT_IN_SOLIDITY_FORMAT)
  .addUint256(50);
   
 await executeContractFunction(
   client,
   contractId,
   4_000_000,
   'approve',
   approveParams,
   treasuryAccPvKey);
// NFTs with serial #1, #3, and #5 to approve
const nFTsToApprove = [1, 3, 5];
console.log(`------- Start approval of NFTs ------\n`);
for (let i = 0; i < nFTsToApprove.length; i++) {
  // Setting the necessary parameters to execute the approve contract function
  const approveParams = new ContractFunctionParameters()
   .addAddress(TOKEN_ID_IN_SOLIDITY_FORMAT)
   .addAddress(ALICE_ACCOUNT_IN_SOLIDITY_FORMAT)
   .addUint256(nFTsToApprove[i]);

  await executeContractFunction(
    client,
    contractId,
    4_000_000,
    'approve',
    approveParams,
    treasuryAccPvKey);
}

4a. Complete a checkAllowance and getApproved Contract Call

The ERC20 standard gives you the ability to check the approved allowance. We do that by calling the checkAllowance standard call which will return the remaining number of tokens that the spender, Alice, is allowed to spend on behalf of the owner.

In ERC-721, there is no allowance because every NFT is unique, the quantity is none or one. Instead, you call the getApproved standard call which will return the address approved for the specific NFT. In the NFT GetApproved tab, you notice we have an array called contractFunctionRes which will hold the result of our contract call. We then loop through each nft in the collection and call our contract function. This will return the address approved for that NFT. If there is no address, then it will return the zero address: 0x0000000000000000000000000000000000000000 

Code Snippet Background
const allowanceParams = new ContractFunctionParameters()
  .addAddress(tokenIdInSolidityFormat)
  .addAddress(treasuryAccId.toSolidityAddress())
   .addAddress(ALICE_ACCOUNT_IN_SOLIDITY_FORMAT);
    
// check the allowance
const contractFunctionResult = await executeContractFunction(
  client,
  contractId,
  4_000_000,
  'checkAllowance',
  allowanceParams,
  treasuryAccPvKey);
if (contractFunctionResult) {
  console.log(`Alice has an allowance of ${contractFunctionResult.getUint256(0)}`);
}
  
// set the client back to the operator account
client.setOperator(operatorAccountId, operatorPrivateKey);
await checkBalance(treasuryAccId, tokenId, client);
await checkBalance(bobAccId, tokenId, client);
Ft and nft check approval

5. Complete the transfer of FTs and NFTs

Make Alice the client in order to do the transferring on behalf of the treasury account.

Code Snippet Background
 // make alice the client to excute the contract call.
 client.setOperator(aliceAccId, aliceAccPvKey);

Alice will gladly use their allowance to transfer 30 fungible tokens to Bob. When building the contract function parameters for the ERC20 transferFrom contract call we make the from address the treasury accounts id in solidity format, and the to address is Bob’s account id in solidity format. The amount we want to transfer here is 30 FTs. 

Switching tabs to the Non-Fungible token code, you see that we have an array and a for loop to transfer the NFTs with serial #s 3 and 5. We also build the contract function parameters by first providing the tokenId in solidity address as the from address, and the to address is Bob's account id in solidity format. We then loop of the array nftsToTransfer which will ensure we transfer NFT serial #3, and #5 to Bob.

Once we’re ready to execute the transferFrom Contract function, Alice will be the one signing the transaction so make sure we send in Alice's account private key. We’ll also do another quick balance check of the treasury account and bob’s account once the transfer of tokens is complete.

Executing ERC20 transferFrom standard call
  • Executing ERC20 transferFrom standard call
  • Executing ERC721 transferFrom standard call
Code Snippet Background
 const transferFromParams = new ContractFunctionParameters()
 .addAddress(tokenIdInSolidityFormat)
 .addAddress(treasuryAccId.toSolidityAddress())
 .addAddress(bobAccId.toSolidityAddress())
 .addUint256(30);
 

await executeContractFunction(
  client,
  contractId,
  4_000_000,
  'transferFrom',
  transferFromParams,
  aliceAccPvKey);
  
 await checkBalance(treasuryAccId, tokenId, client);
 await checkBalance(bobAccId, tokenId, client);
  const nftsToTransfer = [3, 5];
  console.log(`------- Start transfer of ownership for NFTs ------\n`)
  for (let i = 0; i < nftsToTransfer.length; i++) {
    // Setting the necessary parameters to execute the approve contract function
    const transferFromParams = new ContractFunctionParameters()
    .addAddress(TOKEN_ID_IN_SOLIDITY_FORMAT)
    .addAddress(treasuryAccId.toSolidityAddress())
    .addAddress(bobAccId.toSolidityAddress())
    .addUint256(nftsToTransfer[i]);

    await executeContractFunction(
      client,
      contractId,
      4_000_000,
      'transferFrom',
      transferFromParams,
      treasuryAccPvKey);
  }

  await checkAccountBalance(treasuryAccId, txnResponse.tokenId, client);
  await checkAccountBalance(bobAccId, txnResponse.tokenId, client);
Ft and nft transfer

The output for FT shows that Alice transferred 30 FTs from the Treasury to Bob. Bob is shown to have 30 FTs and the Treasury had 100 FT but now is shown to have 70. As for the NFT's Alice transferred 2 NFT's to Bob from the Treasury. Therefore, the Treasury now has 3 NFTs.

6. Remove FT/NFT Allowance

The Treasury will remove the allowance that Alice has over their Hedera ROCKS fungible tokens by executing another approve contract call function. This time the amount is set to 0. Execute the allowance contract call function to check Alice's allowance.

Similarly, in order to remove the NFT allowance, you must execute an approve contract call where the sender address is the zero address. In addition, completing a transfer of the NFT will remove the approval. We have already transferred NFTs with serial numbers 3 and 5. Therefore, we will only need to remove the approval of NFT serial number 1.

Lastly, execute the getApproved contract function to ensure all the approved addresses returned are the zero address.
Be sure to set the client to be the Treasury before executing the call.

Code Snippet Background
// set operator to be treasury account (treasury account is now the caller of the smart contract)
client.setOperator(treasuryAccId, treasuryAccPvKey);
FT Delete Allowance
  • FT Delete Allowance
  • NFT Delete Allowance
Code Snippet Background
// remove Alice's allowance
const removeApproveParams = new ContractFunctionParameters()
 .addAddress(tokenIdInSolidityFormat)
 .addAddress(ALICE_ACCOUNT_IN_SOLIDITY_FORMAT)
 .addUint256(0);

await executeContractFunction(
  client,
  contractId,
  4_000_000,
  'approve',
  removeApproveParams,
  treasuryAccPvKey);
  
// check allowance after it has been removed
const checkallowanceParams = new ContractFunctionParameters()
 .addAddress(tokenIdInSolidityFormat)
 .addAddress(treasuryAccId.toSolidityAddress())
 .addAddress(ALICE_ACCOUNT_IN_SOLIDITY_FORMAT);

const contractFunctionRes = await executeContractFunction(
  client,
  contractId,
  4_000_000,
  'checkAllowance',
  checkallowanceParams,
  treasuryAccPvKey);
if (contractFunctionRes) {
  console.log(`Alice has an allowance of ${contractFunctionRes.getUint256(0)}`);
}

client.close();
/*
 * Remove NFT approval 
*/
const nftsToRemoveApproval = [1];
console.log(`------- Start removal of approval for NFTs -------\n`);
for (let i = 0; i < nftsToRemoveApproval.length; i++) {
  // Setting the necessary paramters to execute the approve contract function
  const approveParams = new ContractFunctionParameters()
   .addAddress(TOKEN_ID_IN_SOLIDITY_FORMAT)
   .addAddress('0x0000000000000000000000000000000000000000')
   .addUint256(nftsToRemoveApproval[i]);

  await executeContractFunction(
    client,
    contractId,
    4_000_000,
    'approve',
    approveParams,
    treasuryAccPvKey);
}

/*
 *  get the approved address for each NFT in the collection
 *  returns the approved address or the zero address if there is none
*/
let contractFunctionResult = [];
console.log(` - Get approved address for each NFT in collection ${txnResponse.tokenId} (should all be zero address)`)
for(let serialNum=1; serialNum < 6; serialNum++) {
  const getApprovedParams = new ContractFunctionParameters()
    .addAddress(TOKEN_ID_IN_SOLIDITY_FORMAT)
    .addUint256(serialNum);
  
   contractFunctionResult.push(await executeContractFunction(
   client,
   contractId,
   4_000_000,
   'getApprovedBySerialNumber',
   getApprovedParams,
   treasuryAccPvKey));
}

if (contractFunctionResult) {
  contractFunctionResult.forEach((result) => {
      console.log(`\n- Approved Address: ${result?.getAddress()}`);
  })
}

  client.close();
Ft and nft remove approval

Summary

That concludes the tutorial for granting an allowance to an account, completing a transfer of FT and NFT, and deleting the allowance using  ERC standard calls.

Check out the following articles for more step-by-step instructions on:


Happy Building! 👷‍♀️