Table of Contents
- 1. What Is a Token Tax on Solana?
- 2. Token-2022 Transfer Fee Extension Explained
- 3. Key Parameters: Basis Points, Max Fee, Authorities
- 4. Solana Token Tax vs. EVM Buy/Sell Tax — Key Differences
- 5. How to Set Token Tax Without Code (CreateMyCoin)
- 6. How to Set Token Tax Using the SPL CLI
- 7. How to Withdraw Collected Transfer Fees
- 8. DEX Compatibility: Where Token-2022 Tokens Trade
- 9. Best Practices & Common Mistakes
- 10. FAQ
What Is a Token Tax on Solana?
A token tax on Solana — also called a transfer fee or SPL token tax — is an automatic deduction applied every time your token changes hands. Whenever someone sends, buys, or sells your token, a percentage of the transferred amount is withheld and stored in the recipient's token account. The token project can then withdraw those withheld fees to their wallet at any time.
This is the Solana-native equivalent of what EVM chains call a "buy/sell tax" or "reflection tax." The key difference: on Solana, the fee mechanism lives inside the token program itself, not in a separate smart contract. This makes it cheaper, more reliable, and harder to circumvent than EVM tax implementations.
- Common use cases: Auto-buyback treasury funding, community reward pools, team revenue, burn mechanisms via fee redirection
- Who controls it: The wallet holding transfer fee config authority — set when the token is created
- When fees are collected: On every SPL token transfer, automatically, at the protocol level
- Where fees go: Withheld in the recipient's token account until the fee authority harvests them
Token-2022 Transfer Fee Extension Explained
Solana has two token programs: the original SPL Token program (also called Token), and the newer Token-2022 program (also written as Token Extensions or Token22). Transfer fees are a feature of Token-2022 — they are not available on the original SPL Token program.
| Feature | SPL Token (Original) | Token-2022 (Extensions) |
|---|---|---|
| Transfer fee / token tax | ❌ Not supported | ✅ Built-in extension |
| Confidential transfers | ❌ Not supported | ✅ Available |
| Non-transferable tokens | ❌ Not supported | ✅ Available |
| Interest-bearing tokens | ❌ Not supported | ✅ Available |
| Mint close authority | ❌ Not supported | ✅ Available |
| DEX support (Raydium, Orca) | ✅ Full support | ✅ Growing (check version) |
| Wallet support (Phantom, Solflare) | ✅ Universal | ✅ Supported |
The Transfer Fee extension works by embedding fee logic directly into the token mint account. When you create a Token-2022 token with the transfer fee extension enabled, every transfer instruction checks the fee parameters and withholds the correct amount — before the tokens reach the destination.
Important: The transfer fee extension must be added at token creation time. You cannot add a transfer fee to an existing SPL token or an existing Token-2022 token after deployment. This decision is made once, permanently, at mint creation.
Key Parameters: Basis Points, Max Fee, Authorities
When setting a token tax on Solana, you configure four critical parameters. Understanding each one before you deploy is essential — these values define how your token behaves economically.
| Parameter | Type | Description | Example |
|---|---|---|---|
feeBasisPoints |
u16 (0–10000) | The fee as basis points. 100 basis points = 1%. 500 = 5%. | 300 = 3% tax |
maxFee |
u64 | Maximum fee per transfer in raw token units (with decimals). Caps the fee for very large transfers. | 1000000 = 1 token (if 6 decimals) |
transferFeeConfigAuthority |
Pubkey | null | The wallet that can update the fee rate and max fee in the future. Set to null to lock forever. | Your project wallet or null |
withdrawWithheldAuthority |
Pubkey | null | The wallet that can harvest withheld fees from token accounts. Should be a secure treasury wallet. | Your treasury wallet |
Understanding Basis Points
Basis points (bps) are the standard unit for expressing fee percentages in financial systems. One basis point equals 0.01%, so:
- 50 bps = 0.5% tax per transfer
- 100 bps = 1% tax per transfer
- 300 bps = 3% tax per transfer
- 500 bps = 5% tax per transfer
- 1000 bps = 10% tax per transfer
- 10000 bps = 100% (every token withheld — effectively non-transferable)
Understanding maxFee
The maxFee parameter caps the withheld amount on any single transfer. This prevents your tax from becoming punitive on massive whale transactions. For example, if you set 3% tax but a whale transfers 10,000,000 tokens, without a cap they'd pay 300,000 tokens in fees. A maxFee of, say, 10,000 tokens (with 6 decimals: 10000000000) limits the maximum fee on any single transfer.
Setting maxFee to the maximum value (18446744073709551615, a u64 max) effectively means no cap — the percentage always applies. Most projects set a reasonable cap to avoid deterring large holders.
Transfer Fee Config Authority vs. Withdraw Withheld Authority
These are two separate authorities, each with distinct powers:
- Transfer Fee Config Authority — Can update
feeBasisPointsandmaxFeeat any time via asetTransferFeeinstruction. This is powerful: if you retain this authority, you can change the tax rate from 3% to 10% without token holders' consent. Projects that want to build trust should either renounce this authority (set to null) or publicly commit to a fee governance process. - Withdraw Withheld Authority — Can harvest the fees accumulated in token accounts and the mint account. This authority cannot change the fee rate — it only collects already-earned fees. This should always point to a secure wallet (ideally a multisig).
Solana Token Tax vs. EVM Buy/Sell Tax — Key Differences
If you've built tokens on Ethereum, BNB Chain, or other EVM chains, the concept of a buy/sell tax is familiar — but Solana implements it very differently. Here's what changes:
| Aspect | EVM Buy/Sell Tax | Solana Transfer Fee (Token-2022) |
|---|---|---|
| Implementation | Custom Solidity contract logic (e.g., ERC-20 override) | Built-in Token-2022 program extension |
| Applies to | Usually only DEX buys/sells (via swap detection) | Every transfer, including wallet-to-wallet |
| Circumventable? | Sometimes (direct contract calls can skip tax logic) | No — enforced at the runtime level |
| Fee destination | Typically auto-distributed (reflection) or sent to contract | Withheld in recipient account, harvested manually |
| Fee update flexibility | Requires re-deploying or calling owner functions | setTransferFee instruction (if authority retained) |
| Audit requirement | Custom code must be audited for hidden backdoors | Standard program — no custom audit needed for fee logic |
| Gas cost | Higher (extra logic per transfer) | Minimal (few extra compute units per transfer) |
The most important difference: Solana's transfer fee applies to all transfers, not just DEX swaps. This means wallet-to-wallet transfers, airdrops, and internal team movements also incur the fee. Factor this into your tokenomics — frequent internal operations will eat into your own treasury if you're not careful.
How to Set Token Tax Using the SPL CLI
For developers who want full control, the spl-token CLI and @solana/spl-token TypeScript library both support creating Token-2022 tokens with transfer fees. Here's how to do it via CLI and code.
Prerequisites
- Solana CLI installed (
solana --version) - spl-token CLI updated to a version supporting Token-2022 (
spl-token --version≥ 3.0) - A funded Solana keypair (mainnet or devnet)
- Node.js ≥ 18 if using TypeScript
Method 1: SPL Token CLI
Create a Token-2022 mint with the transfer fee extension in a single command:
spl-token create-token \ --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb \ --transfer-fee 300 1000000 \ --decimals 6
Breaking down the flags:
--program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb— This is the Token-2022 program address. Required to create a Token-2022 token.--transfer-fee 300 1000000— Sets the fee to 300 basis points (3%) with a max fee of 1,000,000 raw units. With 6 decimals, that equals 1 token max.--decimals 6— Standard for most Solana tokens (same as USDC).
The CLI will output your new mint address. The current keypair (your ~/.config/solana/id.json) becomes both the transfer fee config authority and the withdraw withheld authority by default.
Method 2: TypeScript (@solana/spl-token)
For programmatic creation — useful for scripts, backends, or dApps:
import {
createInitializeMintInstruction,
createInitializeTransferFeeConfigInstruction,
ExtensionType,
getMintLen,
TOKEN_2022_PROGRAM_ID,
} from "@solana/spl-token";
import {
Connection, Keypair, SystemProgram,
Transaction, sendAndConfirmTransaction, clusterApiUrl
} from "@solana/web3.js";
const connection = new Connection(clusterApiUrl("mainnet-beta"), "confirmed");
const payer = Keypair.fromSecretKey(/* your secret key */);
const mintKeypair = Keypair.generate();
// Fee config
const feeBasisPoints = 300; // 3%
const maxFee = BigInt(1_000_000); // 1 token (6 decimals)
const feeConfigAuthority = payer.publicKey;
const withdrawAuthority = payer.publicKey;
const decimals = 6;
// Calculate space needed for the mint + extension
const extensions = [ExtensionType.TransferFeeConfig];
const mintLen = getMintLen(extensions);
const lamports = await connection.getMinimumBalanceForRentExemption(mintLen);
const transaction = new Transaction().add(
SystemProgram.createAccount({
fromPubkey: payer.publicKey,
newAccountPubkey: mintKeypair.publicKey,
space: mintLen,
lamports,
programId: TOKEN_2022_PROGRAM_ID,
}),
createInitializeTransferFeeConfigInstruction(
mintKeypair.publicKey,
feeConfigAuthority,
withdrawAuthority,
feeBasisPoints,
maxFee,
TOKEN_2022_PROGRAM_ID
),
createInitializeMintInstruction(
mintKeypair.publicKey,
decimals,
payer.publicKey, // mint authority
null, // freeze authority (null = none)
TOKEN_2022_PROGRAM_ID
)
);
await sendAndConfirmTransaction(connection, transaction, [payer, mintKeypair]);
console.log("Mint address:", mintKeypair.publicKey.toBase58());
Order matters: ThecreateInitializeTransferFeeConfigInstructioninstruction must come beforecreateInitializeMintInstructionin the transaction. Extension initialization must happen before the mint is initialized. Getting this order wrong will cause a transaction error.
How to Withdraw Collected Transfer Fees
After your token is live and transferring, fees accumulate in two places:
- Recipient token accounts — Each account that receives tokens with a withheld fee stores that fee internally.
- The mint account itself — Fees can optionally be swept from individual accounts into the mint account first.
To collect these fees, the withdraw withheld authority executes a two-step process:
-
Harvest withheld tokens to the mint. Call
harvestWithheldTokensToMintagainst the list of token accounts holding withheld fees. This sweeps all withheld amounts from individual accounts into the mint account's fee escrow. -
Withdraw from the mint to your wallet. Call
withdrawWithheldTokensFromMintwith your treasury token account as the destination. This moves the fees from the mint's fee escrow into your token account.
CLI Withdrawal
# Step 1: Harvest fees from all token accounts into the mint spl-token harvest-withheld-tokens YOUR_MINT_ADDRESS \ --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb # Step 2: Withdraw fees from the mint to your token account spl-token withdraw-withheld-tokens YOUR_MINT_ADDRESS YOUR_TOKEN_ACCOUNT \ --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb
The wallet executing these commands must be the withdraw withheld authority set at mint creation. Anyone else calling these instructions will get an unauthorized error.
DEX Compatibility: Where Token-2022 Tokens Trade
One of the most important practical questions for a Solana token with a transfer fee: which DEXes support Token-2022? Not all AMMs updated their pools to handle the withheld fee accounting that Token-2022 requires.
| DEX / Platform | Token-2022 Support | Transfer Fee Handling | Notes |
|---|---|---|---|
| Raydium | ✅ Yes | Supported in CPMM pools | Use the CPMM pool type for Token-2022 tokens |
| Orca | ✅ Yes | Supported (Whirlpools) | Whirlpool v2 supports Token-2022 extensions |
| Jupiter | ✅ Yes | Aggregator routes through compatible pools | Jupiter routes to Raydium/Orca pools automatically |
| Meteora | ✅ Yes | DLMM and dynamic pools | Check pool type compatibility before creating |
| DexScreener | ✅ Yes (display) | Shows Token-2022 pools and price | Price charts and data fully supported |
| Phantom Wallet | ✅ Yes | Displays tokens and shows fee info | Shows transfer fee badge on Token-2022 tokens |
| Solflare | ✅ Yes | Full Token-2022 display support | Shows withheld fee balance in account details |
For new projects, the practical path is: create your Token-2022 token → add liquidity on Raydium CPMM → your token appears on DexScreener and routes through Jupiter automatically. This gives you full market access without any extra configuration.
Best Practices & Common Mistakes
Best Practices
- Keep the fee reasonable. Taxes above 5% dramatically reduce liquidity depth and trader interest. Most successful Solana projects with transfer fees use 1–3%. Anything above 5% gets labeled "honeypot" by rug-check tools.
- Use a hardware wallet or multisig as authorities. Both the fee config authority and withdraw withheld authority should be protected. Squads multisig is the standard for Solana project teams.
- Disclose the fee publicly. Add a transfer fee disclaimer to your website, Telegram, and any token listing pages. Informed traders are less likely to feel scammed when they see the fee on-chain.
- Set a sensible maxFee cap. A reasonable cap prevents large holders from paying disproportionate fees and keeps your token attractive to bigger investors.
- Test on devnet first. Deploy your token on Solana devnet, execute some test transfers, and confirm the fee math is correct before mainnet launch.
- Document your fee usage. Publish where the collected fees go (buyback, team, staking rewards). Transparency builds trust and reduces the "rug" perception.
Common Mistakes to Avoid
- Setting feeBasisPoints incorrectly. Entering 3 instead of 300 results in a 0.03% fee instead of 3%. Always double-check your basis points value.
- Forgetting maxFee units. The
maxFeeis in raw token units, not human-readable amounts. For a 6-decimal token, 1 token = 1,000,000 raw units. AmaxFeeof1caps the fee at 0.000001 tokens — essentially zero. - Using the same wallet for both authorities. If your hot wallet is compromised, you lose control of both fee rate and fee collection in one shot.
- Not accounting for fees in internal transfers. If you airdrop your own token or move tokens between team wallets, the transfer fee applies. Budget accordingly.
- Trying to add the extension after deployment. The transfer fee extension cannot be retrofitted onto an already-deployed mint. If you forgot to enable it, you must create a new token.
- Creating a Raydium v4 (legacy) pool. Legacy Raydium pools don't support Token-2022. Use CPMM.
FAQ
No. The transfer fee extension must be added at mint creation. You cannot add extensions to an already-deployed token mint. If you want a transfer fee, you must create a new Token-2022 token from scratch.
Technically, up to 10,000 basis points (100%) — meaning all transferred tokens are withheld. In practice, anything above 5–10% makes your token illiquid and will be flagged by scanners. Most successful projects use 1–3%. A 100% fee effectively creates a non-transferable token.
Yes, the fee applies to every token transfer — including DEX swaps. When a buyer purchases your token on Raydium, the swap internally transfers tokens from the pool to the buyer's wallet. That transfer triggers the fee. Same for sells. The fee applies regardless of whether the transfer is a direct send or a DEX swap.
If you retained the transfer fee config authority, you can call setTransferFee with new feeBasisPoints and maxFee values at any time. Via CLI: spl-token set-transfer-fee YOUR_MINT NEW_RATE NEW_MAX_FEE --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb. If you renounced (set to null) the authority at creation, the fee rate is permanently locked.
DexScreener shows Token-2022 extension badges, including transfer fee information, directly on the token page. CoinGecko listing pages may also display token extension data. Phantom and Solflare wallets show a transfer fee indicator when users view your token — so buyers see the fee before transacting.
Withheld fees are the amounts automatically deducted from transfers and stored inside individual recipient token accounts. They're held in escrow and not yet available to the project. Collecting fees is the manual process of running harvestWithheldTokensToMint followed by withdrawWithheldTokensFromMint — this moves withheld fees into your treasury wallet. You must actively collect fees; they don't arrive automatically.
No. A reflection token (common on BNB Chain) automatically redistributes fees to all existing holders proportionally. Solana's transfer fee mechanism does not do this natively — fees are withheld and go to whoever controls the withdraw withheld authority. You can simulate reflection by manually distributing collected fees to holders, but it requires extra logic and is not automatic.
The fee is deducted from the transferred amount. If you send 1000 tokens with a 3% fee, the recipient gets 970 tokens and 30 tokens are withheld. The sender does not pay extra — the recipient receives less. This is different from some EVM tax implementations where the fee is added on top of the sent amount.