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:
- CPMM:
is_supported_mintinraydium-cp-swap/programs/cp-swap/src/utils/token.rs, called fromInitializeandInitializeWithPermission. - CLMM:
is_supported_mintinraydium-clmm/programs/amm/src/util/token.rs, called fromCreatePool. - LaunchLab:
initialize_with_token_2022inraydium-launchpad/programs/launchpad/src/instructions/initialize_with_token_2022.rs(source not publicly available).
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 currentfreeze_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.
OpenPositionV2covers classic SPL position NFTs over Token-2022 pool assets.OpenPositionWithToken22Nftcovers Token-2022 position NFTs.OpenPositionV1 does not inspect vault mints and cannot serve the Token-2022 restricted assets targeted by the shipped list.- Existing positions are unchanged.
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 attachMetadataPointerand (optionally)TransferFeeConfig. Anything else returnsNoSupportExtension. A pre-existing Token-2022 mint cannot be supplied as the base at all. WhenTransferFeeConfigis attached, the mint’stransfer_fee_config_authorityis the launch authority PDA until graduation, while itswithdraw_withheld_authorityis the platform’s configuredtransfer_fee_extension_authfrom mint creation onward — the launchpad program itself has no withdraw-withheld instruction. Seeproducts/launchlab/platform-config. - Quote mint — LaunchLab does not create it and does not screen it.
CreateConfigaccepts the mint account as given, so there is nois_supported_mintequivalent on the quote side. The only gate is which mints an admin chooses to bind to aGlobalConfig.
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.
TransferFeeConfigon 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. Seeproducts/launchlab/instructions.PoolState.token_program_flagrecords both mints’ programs — bit0 for the base mint, bit1 for the quote mint. Decode it per bit; the byte is not a boolean. Seeproducts/launchlab/accounts.
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 aSupportMintAssociated 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 whosefreeze_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
TokenorToken-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.
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
Whenis_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.
- NonTransferable —
Transferalways 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
Frozenstate 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 carryingTransferFeeConfig, 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 asfee, alongsideamountIn,amountOut,minAmountOut,executionPrice,priceImpactand the rawswapResult. - The Token-2022 transfer fee comes from the mint, not the pool. It is computed by the
getTransferAmountFeehelpers in@raydium-io/raydium-sdk-v2, which return aGetTransferAmountFee:
computePairAmount returns inputAmountFee and anotherAmount as GetTransferAmountFee values.
A correct UI shows:
- the input amount plus its transfer
feeas “you send” - the output amount minus its transfer
feeas “you receive” - the pool
feeas a separate line — it is not the Token-2022 fee
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:pool.token0Vault.amount as principal.
”Token-2022 pool” definition
A pool is a Token-2022 pool if either mint hasprogramId == TokenzQdB.... The API surfaces this:
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_mintsemantics 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
Swapinstruction on a Token-2022 pool.Swappredates Token-2022. UseSwapV2whenever either mint is Token-2022. - Auto-listing new Token-2022 mints. Wallets and aggregators should check for
TransferHookandNonTransferablebefore 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.
Pointers
algorithms/token-2022-transfer-fees— fee math in swaps.products/cpmm/instructions—SwapV2,Initialize.products/clmm/instructions—SwapV2,CreatePool,CreateSupportMintAssociated.reference/error-codes—NotSupportMintnumeric codes per program.
raydium-cp-swap/programs/cp-swap/src/utils/token.rs—is_supported_mint,support_mint_associated_is_initialized.raydium-clmm/programs/amm/src/util/token.rs—is_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).

