blog

How to Exempt Hedera Accounts from Custom Token Fees

October 28, 2022
Hedera Team
Hedera
Hedera provides secure, scalable infrastructure for real-world decentralized applications in finance, AI, and sustainability, governed by global enterprises.

The implementation of HIP-573 in mainnet release v0.31 (November 10th, 2022) enables token creators whose tokenomics require custom fees and different collection accounts to exempt fee collectors from paying custom fees when exchanging token units.

This example guides you through the following steps:

  1. Creating a Helper Function and defining three new accounts (Account 1, 2, 3)
  2. Initializing Fractional Fees for each new account and creating a Fungible Token
  3. Transferring 10.000 units from the Treasury Account to Account 2 and then from Account 2 to Account 1

Try It Yourself

  • Get a Hedera testnet account
  • Use this Codesandbox to try the following example
    • Fork the sandbox
    • Remember to provide credentials in the .env file
    • Open a new terminal to execute index.js
  • Get the complete code of this example on GitHub

1. Set Up Helper Functions

To simplify creating accounts, you need to create a helper function. In this example, the function is called accountCreator, and the parameters you need to specify are an initial balance and a private key. Note that the token association is automatic to further simplify the tutorial, as you can see below.


code window background

const accountCreator =  async (initialBalance, privateKey) => {
   const createAccount = new AccountCreateTransaction()
       .setInitialBalance(initialBalance)
       .setKey(privateKey.publicKey)
       .setAlias(privateKey.publicKey.toEvmAddress())
       .setMaxAutomaticTokenAssociations(10)
       .freezeWith(client);
   const createAccountTx = await createAccount.execute(client);
   const createAccountRx = await createAccountTx.getReceipt(client);
   return createAccountRx.accountId;
}

2. Define Hedera Accounts

Now, you can use the helper function to create three different accounts that will be the collectors of the token fees.


code window background

// STEP 1: Create three accounts using the helper function
const accountKey1 = PrivateKey.generateECDSA();
const accountId1 = await accountCreator(50, accountKey1);
 
const accountKey2 = PrivateKey.generateECDSA();
const accountId2 = await accountCreator(50, accountKey2);
 
const accountKey3 = PrivateKey.generateECDSA();
const accountId3 = await accountCreator(50, accountKey3);
 
console.log(`STEP 1 - Three accounts created: n ${accountId1} n ${accountId2} n ${accountId3}n`);

Console Output:

3. Create Fractional Fees and Fungible Token

Let’s define three fractional fees for the fungible token we are about to create. During a token transfer, the first account will receive 1% of the amount, the second 2%, and the third 3%.

Note that the critical factor of this tutorial is using the setAllCollectorsAreExempt() extension method on fractional fee creation, as you can see below.


code window background

const fee1 = new CustomFractionalFee()
       .setFeeCollectorAccountId(accountId1)
       .setNumerator(1)
       .setDenominator(100)
       .setAllCollectorsAreExempt(true);
 
const fee2 = new CustomFractionalFee()
       .setFeeCollectorAccountId(accountId2)
       .setNumerator(2)
       .setDenominator(100)
       .setAllCollectorsAreExempt(true);
 
const fee3 = new CustomFractionalFee()
       .setFeeCollectorAccountId(accountId3)
       .setNumerator(3)
       .setDenominator(100)
       .setAllCollectorsAreExempt(true);

Now, you can create the fungible token and specify the fees you just defined. Remember to sign the TokenCreateTransaction() using all the accounts (fee collectors included).


code window background

const createToken = new TokenCreateTransaction()
       .setTokenName("HIP-573 Token")
       .setTokenSymbol("H573")
       .setTokenType(TokenType.FungibleCommon)
       .setTreasuryAccountId(operatorId)
       .setAutoRenewAccountId(operatorId)
       .setAdminKey(operatorKey)
       .setFreezeKey(operatorKey)
       .setWipeKey(operatorKey)
       .setInitialSupply(100000000) // Total supply = 100000000 / 10 ^ 2
       .setDecimals(2)
       .setCustomFees([fee1, fee2, fee3])
       .setMaxTransactionFee(new Hbar(40))
       .freezeWith(client);
 
const createTokenSigned1 = await createToken.sign(accountKey1);
const createTokenSigned2 = await createTokenSigned1.sign(accountKey2);
const createTokenSigned3 = await createTokenSigned2.sign(accountKey3);
 
const createTokenTx = await createTokenSigned3.execute(client);
const createTokenRx = await createTokenTx.getReceipt(client);
 
const tokenId = createTokenRx.tokenId;
 
console.log(`STEP 2 - Token with custom fees created: ${tokenId}n`);

Console Output:

4. Transfer Tokens between Fee Collectors

Before transferring a token from one collection account to another, you first need to transfer some tokens from the treasury account (in this example, the operator) to one of the collection accounts.


code window background

// STEP 3: Send token from treasury to one account and from one account to another
const transferFromTreasuryTx = await new TransferTransaction()
       .addTokenTransfer(tokenId, operatorId, -10000)
       .addTokenTransfer(tokenId, accountId2, 10000)
       .freezeWith(client)
       .execute(client);
 
const transferFromTreasuryRx = await transferFromTreasuryTx.getReceipt(client);
const transferFromTreasuryStatus = transferFromTreasuryRx.status.toString();
 
console.log(`STEP 3 nToken transfer from treasury to account 2: ${transferFromTreasuryStatus}`);

Now that Account 2 has 10.000 fungible tokens, you can try to transfer the tokens from Account 2 to Account 1.


code window background


const transferFromAccount2 = await new TransferTransaction()
       .addTokenTransfer(tokenId, accountId2, -10000)
       .addTokenTransfer(tokenId, accountId1, 10000)
       .freezeWith(client)
       .sign(accountKey2);
  
const transferFromAccount2Tx = await transferFromAccount2.execute(client);
const transferFromAccount2Rx = await transferFromAccount2Tx.getReceipt(client);
 
console.log(`Transfer from account 2 to account 1: ${transferFromAccount2Rx.status.toString()}n`);
 

Console Output:

Done! Now you need to check each account balance to verify if they are actually exempt from token fees.


code window background

// Check collectors account balance (methods will be deprecated soon, use axios and mirror node api)
const checkBalance1 = await new AccountBalanceQuery()
    .setAccountId(accountId1)
    .execute(client);
const balance1 = checkBalance1.tokens._map.get(tokenId.toString());
 
const checkBalance2 = await new AccountBalanceQuery()
       .setAccountId(accountId2)
       .execute(client);
 
const balance2 = checkBalance2.tokens._map.get(tokenId.toString());
 
const checkBalance3 = await new AccountBalanceQuery()
   .setAccountId(accountId3)
   .execute(client);
 
const balance3 = checkBalance3.tokens._map.get(tokenId.toString());
 
console.log(`Accounts Balance: nAccount 1: ${balance1} nAccount 2: ${balance2} nAccount 3: ${balance3} n`);

Console Output:



<p><strong>v0.30</strong></p>
<p>“/><figcaption class=

v0.30



<p><strong>v0.31</strong></p>
<p>“/><figcaption class=

v0.31

As you can see in v0.30, the third collector receives 3% of the 10.000 transferred from the second to the first collector. While in v0.31, fee collectors are exempt from fees.

For further questions, don’t hesitate to get in touch with us on the Hedera Discord Server.

Check out the Code

Check out this code example on GitHub

Continue Learning

Hedera Improvement Proposals

Custom Token Fees (Documentation)

Back to Blog

discover

See more articles

January 22, 2026

McLaren Racing and Hedera Partner to Expand Digital Fan Engagement

McLaren Racing today announced a multi-year partnership with Hedera Foundation to leverage Hedera, the trusted public network for building fast, secure, and compliant decentralised applications. Hedera will become an Official
Read More
January 20, 2026

Real-world applications of protocol-level smart contract automation on Hedera

Smart contracts have always been described as “self-executing code,” but until now, they’ve relied on external triggers to run. Every time-based action – whether processing payments, rebalancing portfolios, or executing
Read More
January 14, 2026

HIP-1249: Enhanced smart contracts on Hedera with precise throttling

HIP-1249 introduces a fundamental shift in how Hedera throttles smart contract execution, replacing the conservative 15 million gas-per-second limit with a precise operations-per-second model that unlocks significantly more throughput. This
Read More