Skip to main content
Raydium does not accept arbitrary Token-2022 mints. CPMM and CLMM both run a strict allow-list mode: only a small set of extensions pass by default; everything else is rejected at pool creation. Each program admits an individual mint through one mechanism — an admin-managed per-mint registry PDA. Both programs also carried hardcoded bypasses at various points; all of those have now been deleted. This page is the single-page reference for what’s enforced and where, with citations into the program source.

Program-level support

The allow-list checks live in: There is no swap-time mint check on CPMM or CLMM — the gate fires only at pool creation. Once a pool exists, swaps just trust that the mints didn’t change, which is correct for the immutable parts of Token-2022 mint state.

CLMM position NFT freezing for restricted issuers

Position NFT accounts remain unfrozen by default. CLMM freezes one only when the position uses a V2 open path and at least one vault mint’s current freeze_authority appears in the hardcoded frozen_position_nft_authorities::IDS list. That list is what replaced CLMM’s earlier Superstate-token detection, and it carries the same issuer authority the old heuristic matched on. Setting the pool PDA as the position NFT mint’s freeze authority is not itself a freeze. This is a position-custody rule, not a pool-creation allow-list:
  • The pool can already exist and remain swappable.
  • The position NFT mint uses the CLMM pool PDA as its own freeze authority; the underlying issuer does not control the position NFT.
  • OpenPositionV2 covers classic SPL position NFTs over Token-2022 pool assets. OpenPositionWithToken22Nft covers Token-2022 position NFTs.
  • OpenPosition V1 does not inspect vault mints and cannot serve the Token-2022 restricted assets targeted by the shipped list.
  • Existing positions are unchanged.
Frozen positions remain manageable by their owner but cannot be transferred. ClosePosition thaws and burns atomically when the client passes the pool as the first remaining account. See products/clmm/ticks-and-positions.

LaunchLab quote mints

LaunchLab’s two mints are gated very differently, and the asymmetry is easy to miss.
  • Base mint — LaunchLab creates it. A Token-2022 base mint is only reachable through initialize_with_token_2022, and the program will only attach MetadataPointer and (optionally) TransferFeeConfig. Anything else returns NoSupportExtension. A pre-existing Token-2022 mint cannot be supplied as the base at all. When TransferFeeConfig is attached, the mint’s transfer_fee_config_authority is the launch authority PDA until graduation, while its withdraw_withheld_authority is the platform’s configured transfer_fee_extension_auth from mint creation onward — the launchpad program itself has no withdraw-withheld instruction. See products/launchlab/platform-config.
  • Quote mint — LaunchLab does not create it and does not screen it. CreateConfig accepts the mint account as given, so there is no is_supported_mint equivalent on the quote side. The only gate is which mints an admin chooses to bind to a GlobalConfig.
That makes binding a Token-2022 quote mint a high-trust action, for the same reason registering a mint below is: a TransferHook quote mint would run its hook on every buy, sell, and fee claim in every pool quoted in it, and a PermanentDelegate quote mint would let the delegate sweep those pools’ quote vaults. Neither is blocked by the program. What LaunchLab does handle correctly once a quote mint is bound:
  • The quote vault, both fee vaults, and the share-fee receiver’s token account are created on the quote mint’s own program.
  • TransferFeeConfig on the quote side is priced into all four trade instructions, and the slippage bound is checked against the payer’s net amount rather than the vault’s gross movement. See products/launchlab/instructions.
  • PoolState.token_program_flag records both mints’ programs — bit0 for the base mint, bit1 for the quote mint. Decode it per bit; the byte is not a boolean. See products/launchlab/accounts.
The deprecated Initialize instruction still takes a legacy-only quote program, so a config with a Token-2022 quote mint is reachable only through InitializeV2 and InitializeWithToken2022.

CPMM and CLMM extension allow-list

After the two short-circuits covered below, the program iterates the mint’s extensions and rejects the mint if it carries any extension other than these five: Anything not in this list — TransferHook, NonTransferable, ConfidentialTransferMint, PermanentDelegate, MintCloseAuthority, DefaultAccountState, GroupPointer, GroupMemberPointer, MemberPointer, Pausable, etc. — causes is_supported_mint to return false and pool creation to revert. The relevant lines (CPMM, identical shape in CLMM):
cp-swap/src/utils/token.rs

Bypass paths

A Token-2022 mint that doesn’t fit the allow-list can still be admitted, through one mechanism in each program. Both programs also carried hardcoded bypasses in the past; none of them survive. is_supported_mint is now byte-for-byte the same function in both programs: legacy SPL Token mints pass, a mint with a registry PDA passes, and everything else must carry only allow-listed extensions.

The one bypass: the per-mint registry

Both programs consult a SupportMintAssociated PDA at seed [b"support_mint", mint]. If that PDA exists for the mint, the mint is admitted regardless of its extension set. Each program has its own copy of the PDA (they derive under different program IDs), its own CreateSupportMintAssociated / CloseSupportMintAssociated pair, and its own dedicated authority alongside the shared admin: In both programs the instruction accepts either crate::admin::ID or that program’s dedicated authority, and requires the mint to be owned by Token-2022. Effect: a specific Token-2022 mint can be opted into pool creation without a program upgrade — which is why the hardcoded lists could go. Each program consults the registry from every pool-creation path it has: CPMM from Initialize and InitializeWithPermission (the latter is what LaunchLab graduations use, so a registered mint graduates as well as it creates), CLMM from CreatePool, CreateCustomizablePool and CreatePermissionedPool.

The removed bypasses

Both hardcoded mechanisms are gone from the deployed programs. They are documented here only because integrations written against the older behaviour may still assume them.

Static MINT_WHITELIST — removed

A constant array of base58 mint addresses used to short-circuit is_supported_mint before the extension iteration. CLMM’s held six addresses and was deleted on 2026-07-24; CPMM’s held the first four of the same set and was deleted in the 2026-09-09 upgrade. A pool that already exists for one of these mints keeps trading — the mint check runs only at pool creation. Creating a new pool for one now requires a registry PDA for it instead.

Superstate authority-shape detection — removed

CLMM briefly identified Superstate’s tokenized assets by their authority shape rather than by address: a Token-2022 mint whose freeze_authority and permanent delegate both equalled superstate_allowlist::ID, with DefaultAccountState set to Frozen, was admitted. It was a heuristic, so any future mint with the same shape would have been admitted automatically. It was deleted on 2026-07-31, together with the superstate_allowlist module. What replaced it is narrower and serves a different purpose: frozen_position_nft_authorities::IDS, which does not admit anything — it decides whether a position NFT gets frozen, and is described above. The one issuer authority the old heuristic matched is the one entry in that list.

What the bypasses do not waive

The bypasses skip the extension allow-list, but the program still enforces:
  • The mint is owned by either Token or Token-2022. A custom token program is rejected upstream.
  • The pool vaults are created with the right ATA extensions for Token-2022 pools (ImmutableOwner, etc.).
  • All transfers go through transfer_checked — fee-bearing mints land the right amount in vault.
A whitelisted or PDA-registered mint that, e.g., adds a TransferHook later does not gain a swap-time check; the hook would simply run on every transfer and could brick swaps. Registering a mint is therefore a high-trust action.

”Blocked” semantics

When is_supported_mint returns false, pool creation reverts with ErrorCode::NotSupportMint (CPMM) / ErrorCode::NotSupportMint (CLMM). See reference/error-codes for the numeric codes. Existing pools cannot retroactively fail this check — the gate runs only at creation. Mint extensions are immutable for the categories Raydium rejects (transfer hook, non-transferable, confidential transfer can’t be added post-creation), so the static check is sufficient.

Why each excluded extension is excluded

  • TransferHook — invokes a custom program on every transfer, with arbitrary CU consumption, arbitrary failure conditions, and the ability to reenter the calling program. No safe sandbox exists. Some DEXes maintain hook allow-lists; Raydium does not.
  • NonTransferableTransfer always fails. A pool cannot take custody.
  • ConfidentialTransfer — transfer amounts are encrypted; the curve cannot price the swap.
  • PermanentDelegate — a holder of the delegate can sweep any token account, including the pool vault. Admissible only by registering the mint, which is how a trusted issuer (e.g. a regulated stablecoin) is onboarded case by case.
  • MintCloseAuthority — the mint can be closed; existing pools become unusable. Disallowed by default.
  • DefaultAccountState (Frozen) — pool ATAs would land in Frozen state and require thawing per account. Admissible only by registering the mint, which assumes the issuer thaws institutional accounts on enrollment.
  • Group/Member pointers — not actively harmful, but unreviewed. Disallowed by default to keep the surface narrow.

Transfer-fee accounting

For mints carrying TransferFeeConfig, every swap, deposit, and withdraw moves less than the nominal amount. Two separate numbers are involved, and the SDK keeps them apart:
  • The pool fee (LP + protocol + fund + creator) comes from the curve. raydium.cpmm.computeSwapAmount({ ... }) returns it as fee, alongside amountIn, amountOut, minAmountOut, executionPrice, priceImpact and the raw swapResult.
  • The Token-2022 transfer fee comes from the mint, not the pool. It is computed by the getTransferAmountFee helpers in @raydium-io/raydium-sdk-v2, which return a GetTransferAmountFee:
Deposit and withdraw surface it directly: computePairAmount returns inputAmountFee and anotherAmount as GetTransferAmountFee values. A correct UI shows:
  • the input amount plus its transfer fee as “you send”
  • the output amount minus its transfer fee as “you receive”
  • the pool fee as a separate line — it is not the Token-2022 fee
A naive UI that shows only amountIn → amountOut understates costs. Pass epochInfo from the cluster into these helpers rather than caching it; the epoch is what selects between a mint’s older and newer fee config.

maximumFee cap

Token-2022 transfer fees are capped per transfer. For a 1 % mint with a 10,000-token cap, a 100,000,000-token transfer pays only 10,000 in fee. The SDK’s computeSwapAmount applies the cap; direct program callers must replicate it.

Epoch transition

A mint authority can schedule a fee-rate change that activates at the next epoch. During the transition window, two configs (older, newer) live on the mint at once and TransferChecked selects by current epoch. CPMM SwapV2 and CLMM SwapV2 both pass the full mint account in accounts, so the program reads the right config without an extra lookup. If you quote more than one epoch in advance via the Trade API or SDK, executed fee can differ from quoted fee — bounded by the older config’s maximum_fee_basis_points.

Interest-bearing and ScaledUiAmount

The pool holds the principal amount; the “UI amount” is the principal multiplied by a time-dependent or admin-set scale factor. Swap math operates on principal:
The SDK converts automatically. Direct RPC readers should treat pool.token0Vault.amount as principal.

”Token-2022 pool” definition

A pool is a Token-2022 pool if either mint has programId == TokenzQdB.... The API surfaces this:
Use programId to dispatch, and hasTransferFee to surface a UI warning.

SDK helpers

Common integration mistakes

  • Pre-flighting only the program ID. A mint can be Token-2022 and unsupported. Walk the extension list against the allow-list, and check for the mint’s registry PDA, before allowing pool creation.
  • Trusting the SDK’s quote when the mint isn’t accepted at all. The quote API doesn’t refuse to quote — pool creation is what reverts. Confirm is_supported_mint semantics off-chain before exposing pool creation in your UI.
  • Quoting without the transfer-fee haircut. A 1% transfer-fee mint on both sides of a 0.25% CPMM pool has an effective fee around 2.25%, not 0.25%. Use the SDK quote or Trade API quote — never compute fee manually from the pool’s fee tier alone.
  • Calling the legacy Swap instruction on a Token-2022 pool. Swap predates Token-2022. Use SwapV2 whenever either mint is Token-2022.
  • Auto-listing new Token-2022 mints. Wallets and aggregators should check for TransferHook and NonTransferable before surfacing a mint to users; both are Raydium-hostile.

Future work

Solana ecosystem and protocol roadmap items that would change this matrix:
  • Allow-listed transfer-hook programs at the Solana level (ecosystem convention evolving).
  • Confidential-transfer-compatible AMMs (research stage).
  • Broader CPMM per-mint registry (parity with CLMM).
  • A public read path for the registry, so a UI can tell “unsupported” from “unsupported but registered” without deriving the PDA itself.
This page will be updated when any land.

Pointers

Sources:
  • raydium-cp-swap/programs/cp-swap/src/utils/token.rsis_supported_mint, support_mint_associated_is_initialized.
  • raydium-clmm/programs/amm/src/util/token.rsis_supported_mint, support_mint_associated_is_initialized, frozen_position_nft_authorities, position_nft_must_freeze.
  • raydium-clmm/programs/amm/src/instructions/admin/create_support_mint_associated.rs — per-mint registry instruction.
  • raydium-launchpad/programs/launchpad/src/instructions/initialize_with_token_2022.rs — LaunchLab Token-2022 base-mint creation.
  • raydium-launchpad/programs/launchpad/src/instructions/admin/create_config.rs — LaunchLab quote-mint binding (no extension check).