Documentation Index
Fetch the complete documentation index at: https://docs.raydium.io/llms.txt
Use this file to discover all available pages before exploring further.
CPI (“invocação entre programas”) é o mecanismo por meio do qual um programa Solana chama outro. Os programas Anchor do Raydium vêm com crates wrapper CPI que tornam o local da chamada semelhante a uma chamada de função tipada — structs de conta com nomes de campo validados e helpers cpi::<ix>(). Esta página documenta o padrão geral; para trechos específicos de cada produto, consulte a página code-demos de cada capítulo de produto.
Dependências Cargo
[dependencies]
anchor-lang = "0.29"
anchor-spl = "0.29"
raydium_cp_swap = { git = "https://github.com/raydium-io/raydium-cp-swap", features = ["cpi"] }
raydium_amm_v3 = { git = "https://github.com/raydium-io/raydium-clmm", features = ["cpi"] }
# AMM v4 e farm v6: nenhum crate CPI Anchor publicado. Veja "AMM v4 / farm v6" abaixo.
A flag de feature cpi faz os crates compilarem apenas a superfície CPI (structs de conta + invocadores) em vez do programa completo, mantendo seu binário pequeno.
Para exemplos CPI funcionais que conectam os structs de conta de ponta a ponta, veja raydium-io/raydium-cpi-example (cobre AMM v4, CPMM e CLMM).
Construção de lista de contas
Cada CPI do Raydium requer um struct Accounts no programa chamador. Os campos correspondem à ordem de contas da instrução do programa 1-para-1, com validadores em nível de campo:
use anchor_lang::prelude::*;
use anchor_spl::token::{Token, TokenAccount, Mint};
#[derive(Accounts)]
pub struct MyProxySwap<'info> {
#[account(mut)]
pub user: Signer<'info>,
/// CHECK: validated by CPMM
#[account(mut)]
pub pool_state: UncheckedAccount<'info>,
/// CHECK: ditto
pub amm_config: UncheckedAccount<'info>,
/// CHECK: ditto
pub pool_authority: UncheckedAccount<'info>,
#[account(mut)]
pub input_vault: Account<'info, TokenAccount>,
#[account(mut)]
pub output_vault: Account<'info, TokenAccount>,
pub input_mint: Account<'info, Mint>,
pub output_mint: Account<'info, Mint>,
#[account(mut)]
pub user_input_ata: Account<'info, TokenAccount>,
#[account(mut)]
pub user_output_ata: Account<'info, TokenAccount>,
pub cpmm_program: Program<'info, raydium_cp_swap::program::RaydiumCpSwap>,
pub token_program: Program<'info, Token>,
pub token_program_2022: Program<'info, anchor_spl::token_2022::Token2022>,
/// CHECK: observation PDA
#[account(mut)]
pub observation_state: UncheckedAccount<'info>,
}
A maioria das contas do lado Raydium são UncheckedAccount porque a função chamada (Raydium) é responsável pela validação. Seu programa chamador apenas valida estritamente contas que você possui — ATAs de usuário, seus próprios PDAs. O comentário /// CHECK: suprime o aviso do Anchor sobre verificações ausentes.
Construindo a chamada CPI
Anchor gera um helper por instrução:
use raydium_cp_swap::cpi::{self, accounts::Swap as CpmmSwap};
pub fn my_proxy_swap(
ctx: Context<MyProxySwap>,
amount_in: u64,
minimum_amount_out: u64,
) -> Result<()> {
let cpi_accounts = CpmmSwap {
payer: ctx.accounts.user.to_account_info(),
authority: ctx.accounts.user.to_account_info(),
amm_config: ctx.accounts.amm_config.to_account_info(),
pool_state: ctx.accounts.pool_state.to_account_info(),
input_token_account: ctx.accounts.user_input_ata.to_account_info(),
output_token_account: ctx.accounts.user_output_ata.to_account_info(),
input_vault: ctx.accounts.input_vault.to_account_info(),
output_vault: ctx.accounts.output_vault.to_account_info(),
input_token_program: ctx.accounts.token_program.to_account_info(),
output_token_program: ctx.accounts.token_program.to_account_info(),
input_token_mint: ctx.accounts.input_mint.to_account_info(),
output_token_mint: ctx.accounts.output_mint.to_account_info(),
observation_state: ctx.accounts.observation_state.to_account_info(),
};
let cpi_ctx = CpiContext::new(
ctx.accounts.cpmm_program.to_account_info(),
cpi_accounts,
);
cpi::swap_base_input(cpi_ctx, amount_in, minimum_amount_out)?;
Ok(())
}
cpi::swap_base_input é gerado a partir do IDL; sua lista de argumentos espelha a lista de argumentos da instrução Anchor.
Seeds de assinante (CPI assinado por PDA)
Quando seu programa assina a CPI em nome de um PDA (comum para vaults, escrows, etc.), use CpiContext::new_with_signer:
let bump = ctx.accounts.my_authority_bump;
let signer_seeds: &[&[&[u8]]] = &[&[b"my_authority", &[bump]]];
let cpi_ctx = CpiContext::new_with_signer(
ctx.accounts.cpmm_program.to_account_info(),
cpi_accounts,
signer_seeds,
);
cpi::swap_base_input(cpi_ctx, amount_in, minimum_amount_out)?;
As seeds de assinante devem corresponder à derivação do PDA. Para qualquer conta passada como authority (ou função de assinante similar), o runtime Solana verifica que o PDA assina por meio destas seeds.
Contas restantes
Algumas instruções Raydium usam contas restantes — uma lista de comprimento variável anexada após as contas fixas. Os exemplos canônicos:
- CLMM
SwapV2: anexa 1–8 contas TickArrayState correspondentes aos tick arrays que o swap pode atravessar.
- Farm v6
Deposit: anexa pares (reward_vault, user_reward_ata) para cada stream de recompensa ativa.
Os helpers CPI do Anchor não fazem verificação de tipo de contas restantes. Passe-as via .with_remaining_accounts(...):
let cpi_ctx = CpiContext::new(program, accounts)
.with_remaining_accounts(ctx.remaining_accounts.to_vec());
A ordem importa: o programa receptor itera as contas restantes na ordem que você as passa. Para CLMM, os tick arrays devem estar ordenados direcionalmente (primeiro array na direção de swap primeiro). Para farm v6, os slots de recompensa vão em ordem de índice de slot.
Propagação de erros
Os programas do Raydium retornam seus próprios enums de erro. Anchor os envolve; seu programa chamador os vê como Err(ProgramError::Custom(code)). Para tratar erros específicos:
use raydium_cp_swap::error::ErrorCode as CpmmErr;
match cpi::swap_base_input(cpi_ctx, amount_in, minimum_amount_out) {
Ok(_) => Ok(()),
Err(e) => {
msg!("CPMM swap failed: {:?}", e);
// Re-raise, ou converter para seu próprio tipo de erro.
Err(e)
}
}
O número do código de erro é estável conforme a política IDL (sdk-api/anchor-idl). Você pode testar contra códigos específicos comparando com o valor numérico.
Orçamento de computação em CPIs compostos
Cada frame CPI tem overhead (~1.500 CU pela chamada em si), e o consumo CU próprio da função chamada se acumula sobre o seu. Uma transação que chama CPMM swap de dentro do seu programa gasta:
your_program_cu
+ ~1_500 (CPI overhead)
+ ~150_000 (CPMM swap, variante SPL-token)
+ ~200_000 (se Token-2022 com taxa de transferência)
+ ~10_000 (observation update)
Para roteamento empilhado (seu programa → agregador → CPMM + CLMM + farm harvest), orçamento ≥500k CU. Sempre defina uma instrução explícita ComputeBudgetProgram::set_compute_unit_limit(...) na transação — o limite padrão de 200k CU se esgotará silenciosamente.
AMM v4 — construção manual de Instruction
AMM v4 não possui crate Anchor. Construa a Instruction manualmente:
use anchor_lang::solana_program::program::invoke_signed;
use anchor_lang::solana_program::instruction::{Instruction, AccountMeta};
const AMM_V4_PROGRAM_ID: Pubkey = pubkey!("675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8");
// SwapBaseIn discriminator is 9.
let mut data = vec![9u8];
data.extend_from_slice(&amount_in.to_le_bytes());
data.extend_from_slice(&minimum_amount_out.to_le_bytes());
let ix = Instruction {
program_id: AMM_V4_PROGRAM_ID,
accounts: vec![
AccountMeta::new_readonly(token_program_id, false),
AccountMeta::new(amm_id, false),
AccountMeta::new_readonly(amm_authority, false),
// ... remaining accounts per products/amm-v4/instructions ...
],
data,
};
invoke_signed(&ix, &account_infos, signer_seeds)?;
Veja products/amm-v4/code-demos para a lista completa de contas.
Farm v6 — contas restantes de pares de recompensa
Farm v6’s Deposit / Withdraw / Harvest usam o padrão de par (reward_vault_i, user_reward_ata_i) em contas restantes. Sequência exata:
remaining_accounts = [
reward_vault_0, user_reward_ata_0,
reward_vault_1, user_reward_ata_1,
...
]
Um par por slot de recompensa ativo (em execução ou encerrado mas não reivindicado). Omita slots não utilizados; o programa despacha fora de farm_state.reward_infos[i].reward_state.
Testando um fluxo CPI
O desenvolvimento local requer que os programas Raydium estejam disponíveis no seu test validator. Opções:
-
anchor test com program clone — em Anchor.toml:
[test.validator]
clone = [
{ address = "CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK" }, # CPMM
{ address = "CLMM...program-id..." }, # CLMM
{ address = "farm-v6-program-id..." }, # farm v6
]
Isso extrai o bytecode implantado da mainnet para seu validador local.
-
Devnet — Raydium implanta todos os programas na devnet com os mesmos IDs de programa que a mainnet. Execute
anchor test --provider.cluster devnet para acessar o código ao vivo.
-
Deploy local — clone os repos Raydium e
anchor deploy para um validador local. Adiciona overhead de ciclo de teste mas permite modificar a função chamada para debugging.
Referências
Fontes: