Pools support both plain SPL Token mints and Token-2022 mints, including the two extensions
that actually change swap/deposit math: transfer fees and transfer hooks.
Supported mint extensions
Pool creation validates every mint extension against a fixed allowlist — an extension the
program doesn’t recognize causes pool creation to fail rather than being silently ignored:
TransferFeeConfig, InterestBearingConfig, TokenMetadata, MetadataPointer,
ScaledUiAmount, ConfidentialTransferMint, ConfidentialTransferFeeConfig,
PermanentDelegate, MintCloseAuthority, DefaultAccountState, Pausable, TransferHook.
Native SOL’s mint is explicitly rejected as a pool token — use the wrapped SOL mint instead.
Transfer fees
If either mint carries TransferFeeConfig, the amount that actually moves on a transfer
differs from the amount named in the instruction. The program converts between gross and net
correctly at every boundary, using the mint’s real fee config for the current epoch (a
transfer-fee config can have an “older” and “newer” rate that takes effect at a specific epoch
— the program picks the right one):
- Bins and pool math always operate on net amounts. A bin never sees a gross,
fee-inclusive value — the conversion happens once, outside the bin fold.
- Swaps: exact-in nets the input down before traversal, then nets the raw output down
before the slippage check. Exact-out grosses the output target up before traversal, then
grosses the raw input up before the slippage check. Slippage protection always applies to
the actually-realized net/gross amount, never an intermediate value.
- Deposits: net amounts are computed before distribution across bins, then grossed back up
for the actual wallet-to-vault transfer.
- Withdraw / claim fee: the token program itself deducts the fee on the outbound transfer —
the DLMM program transfers gross bin-derived amounts, and separately reports
transferFeeX/transferFeeY on the relevant event so clients can see what was actually lost
to the fee.
Pool.getTransferFees() (SDK) resolves each mint’s active fee config from cached mint data.
undefined for a side means that mint carries no TransferFeeConfig at all — not that the fee
is zero.
Transfer hooks
If either mint carries an active TransferHook extension, every instruction that moves that
mint (swap, deposit, withdraw, claim fee) resolves and appends the hook’s extra accounts
automatically as part of the transfer CPI. This works the same way regardless of which side of
the pool carries the hook, and regardless of which instruction is moving it.
@solana/kit has no built-in ExtraAccountMetaList resolver for Token-2022 transfer hooks —
the SDK hand-rolls this resolution itself
(Pool.resolveTransferHookAccountsX/resolveTransferHookAccountsY, exposed publicly if you
need to build a custom instruction that bypasses the SDK’s own instruction builders).
Why pool creation is admin-gated
A transfer hook is arbitrary program logic the pool itself doesn’t control — from the pool’s
perspective, it’s the same trust boundary as any other external CPI. Rather than accept
transfer-hook mints permissionlessly (where anyone could point a pool at a malicious hook),
pool creation is gated to the admin authority specifically so the set of hook-carrying
mints in play is curated. This is the main reason pool creation isn’t permissionless in Picon
DLMM the way it is in some other DLMM designs — see Governance.