sdk-api/rust-cpi covers the low-level mechanics of invoking each Raydium program. This page is the higher-level companion: why you would compose Raydium into your own program, which pattern fits your use case, and the full glue you need end-to-end.When CPI is the right tool
A custom program makes sense when the trade needs to happen atomically with other on-chain state changes that only your program can make. Common cases:- Escrow / limit-order programs — user deposits a mint into your escrow, your program watches for a price condition, and when it triggers, your program atomically swaps through Raydium and credits the user’s account.
- Aggregator proxies — a single instruction that routes a swap through Raydium + one or more other DEXes, with all hops under a single slippage check owned by your program.
- Auto-compounding vaults — deposit LP or farm stake into your vault, vault harvests rewards on a schedule, re-supplies liquidity, issues share tokens.
- Strategy vaults — leveraged LP positions that rebalance by swapping through CLMM; liquidators that close positions and swap collateral in one transaction.
- Token-launch platforms with custom vesting — your program holds vesting tokens and releases into a Raydium pool on a schedule.
Composition patterns
Pattern 1: Thin proxy
Your program exposes a single instruction that validates some policy (e.g. whitelisted mint pairs, fee discount for verified users) and then forwards to Raydium.Pattern 2: Escrow
Your program owns a PDA that holds the user’s input mint. On trigger, the PDA signs a CPI to Raydium to swap its own balance.CpiContext::new_with_signer. See Signer seeds.
Pattern 3: Composed multi-hop
Your program issues multiple CPIs in one instruction, enforcing a single slippage bound across all of them. The Raydium swap instructions each have their ownminimum_amount_out, but you set those to 0 (or a very loose floor) and enforce a strict final minimum yourself after the last hop.
Pattern 4: Vault / strategy
Your program holds LP tokens or farm stake in a PDA. A keeper (or the user) callscompound(), which:
- Harvests rewards from the farm.
- Swaps rewards for pool tokens (CPI into CPMM or CLMM).
- Deposits the proceeds back into the LP (another CPI).
- Stakes the new LP (another CPI).
Account list construction
The calling program’sAccounts struct mirrors the Raydium program’s account order, but most Raydium-side accounts are UncheckedAccount because Raydium validates them itself. You only add constraints on accounts you own:
UncheckedAccount on Raydium’s — is not laziness. The receiver validates its own; double-validating at the caller just burns CU and risks going out of sync when Raydium ships a new struct layout field.
The CPI call itself
PDA signer seeds
The CPI succeeds only if the PDA passed asauthority matches the derivation the caller claims. The two must agree on:
- The seed byte sequence (here
[b"escrow", user.key().as_ref()]). - The bump.
- The calling program ID (your program, not Raydium’s).
authority slot is its own vault PDA — a fixed, program-wide account it derives and signs with itself, and which your program neither controls nor substitutes. The account your PDA seeds have to line up with is payer: the check happens inside CPMM’s own transfer_from_user_to_pool_vault helper, which requires the account passed as payer to be the owner of input_token_account.
Common bug: passing user as payer while escrow_input_ata is owned by the escrow PDA. The SPL Token program rejects with owner mismatch. Always make payer the ATA’s owner — and sign for it with new_with_signer when that owner is a PDA.
Remaining accounts
Several Raydium instructions take a variable-length list of accounts appended after the fixed ones — remaining accounts.- CLMM
SwapV2: 1–8TickArrayStateaccounts for the tick arrays the swap may traverse, in swap direction. - Farm v6
Deposit/Harvest/Withdraw:(reward_vault, user_reward_ata)pairs, one pair per live reward slot. - Token-2022 transfer-hook mints: the transfer-hook program plus any accounts the hook needs.
Compute budget for composed calls
A CPI costs ~1,500 CU for the call frame itself; the callee’s own CU use stacks on top. The callee figures below are measured from live mainnet transactions on high-volume pools on 2026-09-09, read from theProgram <id> consumed N of M compute units log line for the Raydium program’s own invocation (so they include its inner token-program CPIs):
Add ~1,500 for each CPI frame and your own program’s overhead on top. CLMM swap cost scales with tick crossings, so treat its figure as a floor. Token-2022 mints add the extension-handling cost of the transfer itself; measure it for your own mints rather than applying a flat multiplier.
Always set an explicit
ComputeBudgetProgram::set_compute_unit_limit:
Error propagation
Raydium’s programs return Anchor errors with stable error codes. Your calling program sees them asErr(ProgramError::Custom(code)). Bubble through by default:
ERROR_CODE_OFFSET term: #[error_code] variants are emitted starting at 6000, so comparing against the bare enum discriminant never matches. (There is no is_err helper in anchor-lang or in raydium_cp_swap — earlier revisions of this page used one that does not exist.)
The error code-to-meaning mapping is stable per the IDL policy (sdk-api/anchor-idl); new codes append at the end, existing codes never change meaning.
Full worked example: limit-order escrow
Flow:open_order— user depositsamount_inofinput_mintinto escrow PDA; record targetmin_amount_outand expiry.execute_order— anyone (keeper) calls with the current pool accounts. Program checks the current quote ≥min_amount_out, then CPIs Raydium swap and keeps the output in escrow.claim— user withdraws the output mint from escrow.
order PDA signs the CPI as payer, because it owns the escrow’s input ATA; ExecuteOrder therefore also needs a pool_authority: UncheckedAccount<'info> field for CPMM’s own vault PDA. Both the Raydium-side slippage check and the escrow’s own delta check enforce the floor — belt and braces.
Testing
Pulling Raydium programs into a local validator for integration tests (fromAnchor.toml):
anchor test fetches them from mainnet at startup. See sdk-api/rust-cpi.
Pitfalls specific to composition
Reentrancy
Solana has no true reentrancy — a CPI can’t call back into the originating program in the same invocation. But you can still build yourself into a logical reentrancy: a CPI that reads your state, then your code reads it again assuming the CPI didn’t change it. For Raydium, the CPIs don’t touch your state, so this is less a concern than e.g. flash-loan contexts. But if you compose Raydium with a lending protocol, be aware.Account mutability drift
If your program passes an account asmut but Raydium expects it read-only (or vice versa), the runtime rejects the invocation with InvalidAccountData. Always check Raydium’s instruction’s expected mutability in the IDL; raydium_cp_swap::cpi::accounts::Swap sets each account’s mutability for you, from the #[account(mut)] markers on CPMM’s own Swap struct — the generated fields are all plain AccountInfo<'info>, so it is the derived ToAccountMetas impl, not the field types, that carries the flags.
Token-2022 program field
Input and output mints may be under different token programs — one SPL Token, one Token-2022. The CPI has separateinput_token_program and output_token_program fields for this reason. Always check each mint’s owner field and route the correct program into each slot.
Versioned transactions
A composed tx that does 2+ Raydium CPIs plus an ATA creation rarely fits in a legacy (v0-without-LUT) transaction. Use V0 with address lookup tables; pull Raydium’s public LUTs viaraydium.getRaydiumLutAddresses().
Pointers
sdk-api/rust-cpi— low-level CPI mechanics.integration-guides/priority-fee-tuning— sizing compute budget.products/cpmm/code-demos,products/clmm/code-demos,products/farm-staking/code-demos— per-product CPI snippets.

