Token Extensions: Transfer Fees

·

The Solana blockchain continues to evolve with powerful token standard upgrades, and one of the most impactful innovations is the Token Extensions framework. Among these, the Transfer Fee extension stands out as a game-changer for token creators who want to automatically collect fees on every transfer—directly at the protocol level.

Unlike traditional models requiring third-party contracts or manual enforcement, this extension embeds fee logic directly into the mint configuration. Every time tokens are transferred, a predefined fee is automatically withheld and stored securely until claimed by the designated authority.

This guide walks you through how to set up and manage transfer fees using Solana’s Token 2022 program in a devnet environment, with practical code examples and best practices.


Getting Started with Transfer Fees

Before diving into implementation, it’s important to understand the core benefits:

👉 Learn how to integrate advanced token features like transfer fees into your project.

To follow along, open the Solana Playground and use the starter template. If you're new to the playground:

  1. Create a Playground wallet.
  2. Fund it with devnet SOL using the airdrop command:

    solana airdrop 5
  3. Run the initial script to confirm connectivity.

Once funded, you're ready to build.


Add Required Dependencies

Start by importing essential libraries from @solana/web3.js and @solana/spl-token. These provide access to Solana's core transaction tools and Token 2022 functionality.

import {
  Connection,
  Keypair,
  SystemProgram,
  Transaction,
  clusterApiUrl,
  sendAndConfirmTransaction,
} from "@solana/web3.js";

import {
  ExtensionType,
  TOKEN_2022_PROGRAM_ID,
  createAccount,
  createInitializeMintInstruction,
  createInitializeTransferFeeConfigInstruction,
  getMintLen,
  getTransferFeeAmount,
  harvestWithheldTokensToMint,
  mintTo,
  transferCheckedWithFee,
  unpackAccount,
  withdrawWithheldTokensFromAccounts,
  withdrawWithheldTokensFromMint,
} from "@solana/spl-token";

Set up your connection to devnet and initialize the payer (your playground wallet):

const connection = new Connection(clusterApiUrl("devnet"), "confirmed");
const payer = pg.wallet.keypair;
let transactionSignature: string;

Configure the Mint with Transfer Fee Settings

Now define key parameters for your token mint:

const mintKeypair = Keypair.generate();
const mint = mintKeypair.publicKey;
const decimals = 2;
const mintAuthority = pg.wallet.publicKey;
const transferFeeConfigAuthority = pg.wallet.keypair;
const withdrawWithheldAuthority = pg.wallet.keypair;
const feeBasisPoints = 100; // 1% fee
const maxFee = BigInt(100); // Max fee in token base units

Calculate rent-exempt minimums based on the extended mint size:

const mintLen = getMintLen([ExtensionType.TransferFeeConfig]);
const lamports = await connection.getMinimumBalanceForRentExemption(mintLen);
🔍 Note: Enabling extensions increases the required account size. Always use getMintLen() to ensure correct allocation.

Build Initialization Instructions

Three instructions are needed to create a fee-enabled mint:

1. Create the Account

Use the System Program to allocate space and assign ownership to the Token 2022 program:

const createAccountInstruction = SystemProgram.createAccount({
  fromPubkey: payer.publicKey,
  newAccountPubkey: mint,
  space: mintLen,
  lamports,
  programId: TOKEN_2022_PROGRAM_ID,
});

2. Initialize Transfer Fee Extension

Configure fee rules and authorities:

const initializeTransferFeeConfig = createInitializeTransferFeeConfigInstruction(
  mint,
  transferFeeConfigAuthority.publicKey,
  withdrawWithheldAuthority.publicKey,
  feeBasisPoints,
  maxFee,
  TOKEN_2022_PROGRAM_ID
);

3. Initialize Mint Data

Set basic mint properties (decimals, authority):

const initializeMintInstruction = createInitializeMintInstruction(
  mint,
  decimals,
  mintAuthority,
  null,
  TOKEN_2022_PROGRAM_ID
);

Send the Transaction

Bundle and send all instructions:

const transaction = new Transaction().add(
  createAccountInstruction,
  initializeTransferFeeConfig,
  initializeMintInstruction
);

transactionSignature = await sendAndConfirmTransaction(
  connection,
  transaction,
  [payer, mintKeypair]
);

console.log("Create Mint Account:", `https://solana.fm/tx/${transactionSignature}?cluster=devnet-solana`);

After execution, your mint now enforces transfer fees automatically.


Create and Fund Token Accounts

To test transfers, create two token accounts:

const sourceTokenAccount = await createAccount(
  connection,
  payer,
  mint,
  payer.publicKey,
  undefined,
  TOKEN_2022_PROGRAM_ID
);

const randomKeypair = new Keypair();
const destinationTokenAccount = await createAccount(
  connection,
  payer,
  mint,
  randomKeypair.publicKey,
  undefined,
  TOKEN_2022_PROGRAM_ID
);

Mint tokens to the source account:

transactionSignature = await mintTo(
  connection,
  payer,
  mint,
  sourceTokenAccount,
  mintAuthority,
  2000_00,
  undefined,
  undefined,
  TOKEN_2022_PROGRAM_ID
);

Execute a Transfer with Fee

Use transferCheckedWithFee to move tokens while applying the configured fee:

const transferAmount = BigInt(1000_00);
const fee = (transferAmount * BigInt(feeBasisPoints)) / BigInt(10_000);
const feeCharged = fee > maxFee ? maxFee : fee;

transactionSignature = await transferCheckedWithFee(
  connection,
  payer,
  sourceTokenAccount,
  mint,
  destinationTokenAccount,
  payer.publicKey,
  transferAmount,
  decimals,
  feeCharged,
  undefined,
  undefined,
  TOKEN_2022_PROGRAM_ID
);

The fee is deducted from the sender and held in the recipient’s account—untouchable by them but claimable by the Withdraw Authority.

👉 Discover how platforms use transfer fees for sustainable tokenomics.


Withdraw Fees from Token Accounts

Fees accumulate in recipient token accounts. To collect them:

  1. Fetch all token accounts for the mint:

    const allAccounts = await connection.getProgramAccounts(TOKEN_2022_PROGRAM_ID, { ... });
  2. Filter those with withheld fees:

    const accountsToWithdrawFrom = [];
    for (const accountInfo of allAccounts) {
      const account = unpackAccount(...);
      const transferFeeAmount = getTransferFeeAmount(account);
      if (transferFeeAmount?.withheldAmount > 0) {
        accountsToWithdrawFrom.push(accountInfo.pubkey);
      }
    }
  3. Withdraw to a destination account:

    transactionSignature = await withdrawWithheldTokensFromAccounts(
      connection,
      payer,
      mint,
      destinationTokenAccount,
      withdrawWithheldAuthority,
      undefined,
      accountsToWithdrawFrom,
      undefined,
      TOKEN_2022_PROGRAM_ID
    );

Harvest Fees to Mint Account

Users may wish to close token accounts that hold withheld fees. Since non-empty accounts can’t be closed, others can harvest fees to the mint account permissionlessly:

transactionSignature = await harvestWithheldTokensToMint(
  connection,
  payer,
  mint,
  [destinationTokenAccount],
  undefined,
  TOKEN_2022_PROGRAM_ID
);

This moves fees from individual token accounts into the central mint account.


Withdraw Fees from Mint Account

Finally, the Withdraw Authority can pull harvested fees from the mint:

transactionSignature = await withdrawWithheldTokensFromMint(
  connection,
  payer,
  mint,
  destinationTokenAccount,
  withdrawWithheldAuthority,
  undefined,
  undefined,
  TOKEN_2022_PROGRAM_ID
);

Frequently Asked Questions (FAQ)

Q: Can transfer fees be paid in a different token?
A: No. The Transfer Fee extension only supports fees in the same token being transferred. For cross-token fees, consider using the Transfer Hook extension instead.

Q: Who can harvest fees to the mint account?
A: Anyone can call harvestWithheldTokensToMint. It's a permissionless operation designed to help users clean up accounts with withheld fees.

Q: Why are fees stored in recipient accounts instead of a central wallet?
A: To maximize parallelization. Storing fees locally avoids write-locking a single account during high-volume transfers, preserving network throughput.

Q: Can I change the fee rate after deployment?
A: Yes—but only if you control the transferFeeConfigAuthority. This allows dynamic updates without redeploying the token.

Q: Are there gas implications for users sending tokens?
A: Slightly higher compute costs due to additional checks, but these are minimal and handled within the SPL Token program.

👉 See how leading projects implement automated fee collection securely.


Conclusion

The Transfer Fee extension brings enterprise-grade financial tooling directly into Solana’s token standard. By enabling automatic, protocol-enforced fees, it simplifies revenue models for NFT platforms, governance tokens, and community coins alike.

With flexible configuration, efficient architecture, and seamless integration via Token 2022, it’s an essential feature for modern token design.

Whether you're building a decentralized exchange, social token, or DeFi protocol, leveraging native transfer fees reduces complexity and enhances user experience—all without sacrificing performance.

Core keywords naturally integrated: transfer fee extension, Solana, Token 2022, mint account, withdraw authority, token extensions, SPL Token, protocol-level fees.