Free guide
Token-2022 and token extensions, explained
Solana has two token programs. Why the second one exists, what an extension physically is, and which habits from the classic Token program still hold.
Why a second token program
The classic Token program is deliberately frozen. Every wallet, exchange, and DeFi protocol depends on its exact behavior, so adding features to it risks the whole ecosystem; its stability is its value. The feature requests were real anyway: projects kept needing transfer fees, transfer-time logic, richer metadata, confidential amounts, and kept solving them with fragile wrappers around a program that could not help.
The answer was Token-2022, also called the Token Extensions
program: a separate deployment at its own well-known address, implementing a
superset of the classic program. The core instruction set is the same — mints, token
accounts, mint_to, transfer_checked, burn all work
identically — plus an extension system for everything new. The classic
program stays frozen; innovation moved next door.
One mint, one universe
A mint belongs to exactly one of the two programs. Created under classic Token, forever classic; created under Token-2022, forever Token-2022, and every one of its token accounts lives under the same program. There is no migration instruction: "moving" a token means minting a new one. Even associated token accounts respect the split — the ATA derivation includes the token program's ID, so the same wallet-and-mint pair has different ATA addresses in the two universes. The two programs are parallel worlds that happen to share an interface.
What an extension physically is
An extension is not a separate account. It is extra bytes appended to the mint or token account itself, stored as type-length-value (TLV) entries after the base layout. The base fields keep the classic program's exact byte layout, which is what lets shared tooling read either kind of account. Three consequences follow:
- Size varies, and not by plain addition. A mint with no extensions is 82 bytes, same as classic. Enable one and the base is first padded to 165 bytes (the length of a token account) plus a 1-byte discriminator, so raw-byte readers can never mistake an extended mint for a token account — then the TLV entries follow. Every rent calculation starts by asking the extension set for the length.
- Extensions are chosen at birth. Extension data has to sit underneath the mint's initialization, so the account must be allocated large enough, and the extension written, before the mint is initialized. You cannot bolt a transfer fee onto an existing mint. Some extensions have runtime knobs (a fee rate can change); their presence is fixed at creation.
- Two hosts. Mint extensions (transfer fee, interest rate, non-transferable, permanent delegate, transfer hook, metadata, close authority, group/member) are the issuer's policy. Token-account extensions (CPI guard, required memo, immutable owner) are the holder's. Which host an extension lives on tells you who chose it.
What the size rule looks like in code
From the official default-account-state example, the first two steps of
creating a mint with an extension from inside a program:
// Calculate space required for mint and extension data
let mint_size = ExtensionType::try_calculate_account_len::<PodMint>(&[
ExtensionType::DefaultAccountState,
])?;
// Calculate minimum lamports required for size of mint account with extensions
let lamports = (Rent::get()?).minimum_balance(mint_size);
From
solana-foundation/program-examples,
MIT licensed, © Solana Foundation, pinned to commit 9389865.
The extension list goes in, the byte count comes out, and rent is computed from that.
What follows in the example is a three-CPI recipe with one iron ordering rule: the System
Program creates the account, the extension's initializer writes its TLV entry, and only
then does initialize_mint2 seal the mint. Extension first, mint second —
reverse them and the creation fails.
What Token-2022 retrains
The mental model carries over whole: mints define, token accounts hold, authorities are
stored keys, decimals scale raw amounts. What changes is at the edges. Plain
transfer is effectively retired, because Token-2022 needs the mint at transfer
time — to charge fees, consult hooks, check non-transferability — so everything speaks
transfer_checked, which carries the mint. And "create a token account" stops
being a fixed-size recipe; creation flows compute the length for the extension set first,
as above.
The deepest extension is the transfer hook: a mint that names a program of yours, which the token program calls on every transfer. That single idea covers whitelist gates, per-transfer counters, and transfer switches, and it is where reading Token-2022 code stops being optional for anyone auditing what a mint can and cannot do.