Sign in

Token-2022 and token extensions

The classic SPL Token Program covers about ninety-five percent of what most projects need. The remaining five percent led to a second token program, deployed alongside the original, that supports the same conceptual model with optional behaviors layered on top. Token-2022 lets a mint opt into features like transfer fees, transfer hooks, frozen-by-default accounts, interest-bearing balances, and on-mint metadata. The base model is unchanged. The new capabilities sit in a tagged area on each mint and each token account, activated only when the mint creator chooses them. Knowing which extensions exist, when to reach for them, and what they cost in ecosystem compatibility is the whole job of this lecture.

Token-2022 is the same model with an extensions slot

Token-2022 is a separate program from classic SPL Token. It has its own program ID (TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb), its own on-chain accounts, and its own deployed bytecode. It does not supersede classic SPL Token. Both programs run side by side on mainnet. Each new mint is created under one program or the other, and the choice is permanent.

What makes Token-2022 worth understanding is the relationship between the two programs. The Mint and TokenAccount struct shapes are the same. The four core instructions, Transfer, MintTo, Burn, and CloseAccount, work identically. The three authority concepts, mint authority, freeze authority, and token account owner, mean the same thing. If you sit down to do a routine token operation against a Token-2022 mint, the code looks almost indistinguishable from the same operation against a classic SPL mint.

The difference shows up in two places. Token-2022 mints and token accounts can carry a tail of extension data after the standard fields, in a format called TLV, short for type-length-value, that lets several extensions coexist on one account. And Token-2022 exposes extension-specific instructions on top of the standard ones, for things like setting transfer fee rates or withdrawing accumulated fees.

Classic SPL Token vs Token-2022: same model, extensions added Classic SPL Token program ID: TokenkegQfeZyiNw... Mint fields: mint_authority supply decimals freeze_authority TokenAccount fields: mint, owner, amount delegate, state, ... instructions: Transfer, MintTo, Burn, CloseAccount, FreezeAccount, ... extensibility: none. fields are fixed. universal support every wallet, DEX, indexer handles it correctly Token-2022 program ID: TokenzQdBNbLqP5VE... Mint fields: mint_authority supply decimals freeze_authority + extensions (TLV) TokenAccount fields: mint, owner, amount delegate, state, ... + extensions (TLV) instructions: all the classic ones, PLUS extension-specific calls extensibility: opt into extensions at mint creation time growing support most tools handle base case, extensions are spottier Same Mint/TokenAccount shape, same instructions, same authorities. Extensions are appended.

You can think of Token-2022 as the same library with optional feature flags. The base case is identical to classic SPL. The flags add capabilities, each one a small piece of behavior baked into the mint at creation time. Once a flag is set, it's set for the life of the mint. There's no upgrade path that adds extensions to an existing classic SPL mint. You have to mint under Token-2022 from the start to have any extensions at all.

Five extensions worth knowing

Token-2022 has more than a dozen extensions, and new ones land in the program over time. Five of them cover most of the cases real protocols reach for.

Five extensions worth knowing Transfer Fee a percentage of each transfer is automatically diverted to accumulated fees on the mint controlled by: withdraw_withheld_authority use case: royalties, protocol fees Transfer Hook every transfer triggers a CPI to a designated program before the transfer completes controlled by: hook program's own logic use case: compliance, custom rules Interest-Bearing displayed balance grows over time at a set rate. raw amount in the account doesn't change. controlled by: rate_authority on the mint use case: yield-bearing stablecoins Default Account State new token accounts start frozen. freeze authority must explicitly unfreeze them. controlled by: freeze_authority on the mint use case: KYC-gated tokens, RWAs Metadata (on-mint) name, symbol, URI live directly on the mint, with no separate metadata account. use case: simpler than Metaplex Token Metadata Extensions are chosen at mint creation. Some are immutable once set, others have a configured authority.

The transfer fee extension takes a basis-point cut on every transfer and routes it into a hidden balance on each token account. The mint's withdraw-withheld authority can periodically harvest the accumulated fees from many accounts at once and consolidate them into a single destination. This is the on-chain equivalent of a sales tax: it applies to every transfer regardless of which dApp or wallet initiated it, with no way for senders or receivers to avoid it. Royalty tokens and protocol-fee tokens are the obvious uses. Less obviously, projects that wanted to fund a treasury automatically without trusting any single dApp to remit fees now have a way to do it at the token layer.

The transfer hook extension is the most powerful and the most disruptive. When a transfer happens, the Token-2022 program performs a CPI into a program of the mint creator's choice, passing details about the transfer. The hook program can run arbitrary validation logic before the transfer is allowed to complete. This enables compliance gates, such as allowing transfers only between accounts on an approved list. It enables custom royalty enforcement, like requiring a royalty payment alongside any NFT-like transfer. It enables audit trails, where every transfer is logged to an off-chain analytics PDA. The catch is that every protocol downstream of the token needs to know how to invoke the hook with the right accounts, which makes transfer-hooked tokens incompatible with most existing infrastructure.

The interest-bearing extension changes how balances are displayed, while keeping the underlying account data static. The raw amount field in a token account never changes due to interest. But when a client or program calls the program's amount_to_ui_amount instruction, the result includes accrued interest at a configurable rate stored on the mint. The clean way to think about this: the mint exposes a function from raw amount and current time to display amount, and clients are expected to use that function. A yield-bearing stablecoin can be implemented without ever actually moving balances around. Holders see their displayed balance increase, and the mint's supply function computes the implied total.

The default account state extension makes new token accounts start in the frozen state automatically. Anyone can call InitializeAccount to create the token account, but the resulting account can't send or receive tokens until the freeze authority explicitly unfreezes it. This gives token issuers a built-in onboarding gate. Real-world-asset tokens often need to verify KYC before letting a wallet hold the asset. Without this extension, you'd have to track approval off-chain and rely on every distribution path to check it. With the extension, the chain enforces the gate.

The metadata extension lets a mint carry name, symbol, and URI directly, removing the need for a separate account in the Metaplex Token Metadata program. The shape is simpler, the lookup is faster, and the cost is lower. For new projects starting on Token-2022, this is usually preferable to setting up a metadata account in another program. For existing tokens already using Metaplex, the cost of migration usually outweighs the simplification.

Extensions can stack. A single mint can carry a transfer fee, an interest-bearing rate, and metadata, all at once. The decision is per-extension at mint creation. Most extensions are immutable once set. Others let you change parameters later through a configured authority.

Choosing between classic SPL and Token-2022

The decision rule is simpler than the menu of options suggests.

Which program should your mint use? Do you need one of these behaviors? transfer fees, transfer hooks, interest-bearing, default-frozen accounts, on-mint metadata yes no Token-2022 is your only option enable just the extensions you need at mint creation. base behavior is identical to classic SPL. Use classic SPL Token simpler, universally supported, battle-tested. the default for almost every new token. before shipping a Token-2022 mint, check this → does the major DEX you're targeting handle the extensions you picked? → does the wallet your users will use display Token-2022 balances correctly? → does your indexer or analytics pipeline decode Token-2022 accounts? → if you use transfer hooks, are downstream protocols ready to call them? transfer fees and transfer hooks break naive integrations the most. test against every system your token will touch. Pick Token-2022 for capability. Pick classic SPL for compatibility. Most projects pick the latter.

The ecosystem-support caveat deserves a paragraph of its own. Classic SPL Token has been the standard for years. Every wallet, every DEX, every aggregator, every indexer, every analytics dashboard assumes it. Token-2022 has been growing in support since 2023, but the picture is uneven. Most tools handle the base case: a Token-2022 mint with no extensions behaves identically to classic SPL, and most software gets that right. Extensions are where the breakage happens.

Transfer fees are the most common source of integration bugs because a transfer that withholds a fee changes the amount that lands at the destination. Naive code that says "send 100 USDC and assume 100 USDC arrives" breaks. Transfer hooks are the most disruptive because every program downstream of a hooked token needs to know how to invoke the hook with the right extra accounts, and many production protocols simply refuse hooked tokens to keep their integration surface small. Default-frozen accounts cause initialization flows to fail at the unfreeze step if the dApp doesn't know to wait. Interest-bearing balances confuse code that compares raw amounts versus display amounts inconsistently.

The cure is the same in all cases: test against every system your token is going to touch. If you're issuing a Token-2022 mint, verify the major wallets show it correctly, verify the DEXes you're targeting will list it, verify your own indexers decode the right fields. The capabilities are real and valuable. The compatibility cost is also real.

token_interface: writing programs that handle both

When you're writing a program that needs to work with either classic SPL or Token-2022 mints, Anchor's anchor_spl::token_interface module provides a unified type that abstracts over the program ID. Instead of Account<'info, TokenAccount> from anchor_spl::token, you use InterfaceAccount<'info, TokenAccount> from anchor_spl::token_interface. The type accepts either flavor of token account.

rust
use anchor_spl::token_interface::{
    Mint, TokenAccount, TokenInterface, transfer_checked, TransferChecked,
};

#[derive(Accounts)]
pub struct Deposit<'info> {
    #[account(mut)]
    pub user: Signer<'info>,

    pub mint: InterfaceAccount<'info, Mint>,

    #[account(mut, token::mint = mint, token::authority = user)]
    pub user_ata: InterfaceAccount<'info, TokenAccount>,

    pub token_program: Interface<'info, TokenInterface>,
}

The Interface<'info, TokenInterface> accepts either the classic SPL Token Program or the Token-2022 program. The runtime resolves which one the mint belongs to and routes the CPI accordingly. Your handler doesn't need to branch.

A meaningful change in the API is that token_interface exposes transfer_checked rather than transfer. The "checked" version requires you to pass the mint and the expected decimals as part of the call, and the Token Program verifies the values match. This protects against bugs where the wrong mint or wrong decimals got plumbed through, and it's the recommended pattern for any new code regardless of which token program you're targeting. Classic SPL also has transfer_checked. The reason older code tends to use plain transfer is just historical inertia.

For a program author choosing between token and token_interface: pick token_interface for any new program that might encounter Token-2022 mints, which is most new programs. Pick token only if you're certain you'll never need to work with Token-2022, which usually means you're targeting a specific known mint that's classic SPL.

The summary

Token-2022 is the same program with a feature-flag layer for behaviors classic SPL can't express. The conceptual model from your earlier work with classic SPL carries over directly. The new things are the extensions, the slightly different program ID, and the unified token_interface shim for writing programs that handle both. For most projects most of the time, classic SPL remains the right default. For projects that need a specific extension, Token-2022 is the only option, and the right next step is checking that the ecosystem support is sufficient before committing. Once you've shipped a mint under either program, the day-to-day operations of moving tokens around, signing as PDAs, creating ATAs, and burning supply, all work the same way.