All articles
solanatoken-2022splstablecoins

Token-2022 extensions: a field guide for builders

Every Token-2022 extension explained — transfer hooks, confidential transfers, interest-bearing, permanent delegate, and the rest. When to use which.

The original SPL Token program was deliberately minimal: mint, burn, transfer, freeze. Any behavior beyond that — KYC checks, fees on transfer, interest accrual, confidential balances — meant writing your own program and forking the wallet ecosystem to recognize it. Token-2022 (program ID TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb) is the new token program that ships those behaviors as composable extensions attached to the mint or token account itself.

Every major stablecoin issuer that launched on Solana in 2024-2026 picked Token-2022. So did Ondo (USDY), Paxos (USDG, PYUSD), Agora (AUSD), and a long list of NFT and RWA projects. If you're designing any token primitive today, you start with Token-2022 and opt out only if you have a specific reason.

How extensions work

Extensions are bytes appended to a mint or token account, each marked by a discriminator. The program reads the discriminators to know which extensions to enforce. There's no fork, no proxy contract — extensions are part of the runtime semantics.

Two scopes: mint extensions apply to the whole token (every account behaves the same) and token account extensions apply to individual holders.

The mint extensions

  • TransferHook — every transfer calls into a program you choose. The hook can deny, log, or update state. The basis for KYC, allowlists, royalty enforcement, and on-chain compliance.
  • TransferFee — fixed bps fee withheld on every transfer, claimable by a withdraw authority. Used by tokens that want a built-in tax (royalty, burn, treasury).
  • ConfidentialTransfer — balances and amounts hidden using ElGamal encryption + ZK proofs. The wallet can prove its balance is sufficient without revealing it.
  • InterestBearing — the displayed balance compounds at a configurable rate. The base "principal" is what's actually stored. Used by yieldcoins (USDY-style).
  • PermanentDelegate — a single address that can transfer or burn any balance, ever. The compliance escape hatch for regulated stables.
  • NonTransferable — soul-bound tokens. Mintable, burnable, never transferable. Useful for credentials, badges, KYC tier markers.
  • MetadataPointer + TokenMetadata — metadata stored on the mint itself. No Metaplex hop, no metadata PDA. The new default for NFT and fungible metadata.
  • GroupPointer + MemberPointer — collection membership at the protocol level. The native way to group a set of mints (think NFT collections without Metaplex).
  • MintCloseAuthority — the mint can be closed (rent reclaimed) once supply hits zero. Useful for ephemeral or escrow-style tokens.
  • DefaultAccountState — newly created token accounts start as Frozen by default. Combined with a freeze authority, gates token holding behind your unfreeze logic.
  • ConfidentialMintBurn — extends ConfidentialTransfer to mints and burns. Required if your token wants end-to-end confidentiality including issuance.

The token account extensions

  • ImmutableOwner — the owner of this token account cannot be changed. Always enabled on Associated Token Accounts. Set it on direct accounts to protect against ownership-change attacks.
  • CpiGuard — blocks operations on this account when invoked via CPI. The user's opt-in protection against malicious programs taking actions on their behalf.
  • MemoTransfer — incoming transfers must include a memo. Useful for exchanges or apps that need a reference ID with every deposit.

Creating a mint with extensions

typescript
import { Connection, Keypair, sendAndConfirmTransaction, SystemProgram, Transaction } from "@solana/web3.js"
import {
  createInitializeMintInstruction,
  createInitializeTransferFeeConfigInstruction,
  createInitializeMetadataPointerInstruction,
  getMintLen,
  ExtensionType,
  TOKEN_2022_PROGRAM_ID,
} from "@solana/spl-token"

const extensions = [ExtensionType.TransferFeeConfig, ExtensionType.MetadataPointer]
const mintLen = getMintLen(extensions)
const lamports = await connection.getMinimumBalanceForRentExemption(mintLen)

const tx = new Transaction().add(
  SystemProgram.createAccount({
    fromPubkey: payer.publicKey,
    newAccountPubkey: mint.publicKey,
    space: mintLen,
    lamports,
    programId: TOKEN_2022_PROGRAM_ID,
  }),
  createInitializeTransferFeeConfigInstruction(
    mint.publicKey,
    feeAuthority,
    withdrawAuthority,
    /* fee basis points */ 100,        // 1%
    /* max fee */ BigInt(1_000_000_000),
    TOKEN_2022_PROGRAM_ID,
  ),
  createInitializeMetadataPointerInstruction(
    mint.publicKey,
    payer.publicKey,
    /* metadata address — point at the mint itself */ mint.publicKey,
    TOKEN_2022_PROGRAM_ID,
  ),
  createInitializeMintInstruction(mint.publicKey, 9, mintAuthority, null, TOKEN_2022_PROGRAM_ID),
)

await sendAndConfirmTransaction(connection, tx, [payer, mint])

Important: extensions are immutable once the mint is initialized. Pick your set carefully, because you can't add or remove an extension later.

Use-when / avoid-when

Stablecoin / RWA issuer: TransferHook + PermanentDelegate + DefaultAccountState=Frozen + ConfidentialTransfer (optional). Avoid InterestBearing unless you're shipping a yieldcoin.

NFT collection: MetadataPointer + GroupPointer + TransferHook (royalty enforcement). Avoid TransferFee — Token-2022 fees compound oddly with NFT marketplace flows.

Yieldcoin: InterestBearing + TransferHook + ConfidentialTransfer (optional). Avoid TransferFee — your fee accounting will fight the interest accrual.

Loyalty / credential: NonTransferable + MetadataPointer. Avoid TransferHook — there are no transfers to hook.

Plain SPL-equivalent: no extensions. Token-2022 still works — you just get the new program ID and forward compatibility.

What you give up

Token-2022 mints use a different program ID, which means every wallet, SDK, and indexer has to know about both programs. Most major wallets and SDKs support Token-2022 now, but if you're integrating against a long tail of older tools (an obscure CEX, a legacy explorer, an unmaintained DEX aggregator), the surface area is wider. Check the consumers of your token before you ship.

Also: extensions cost rent. A mint with five extensions costs meaningfully more than a plain SPL mint. Negligible for most projects, worth knowing about if you're minting millions of accounts.

References

If you're designing a new token in 2026, the default answer is Token-2022, and the only real question is which extensions belong on the mint.