Saltar para o conteúdo principal

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.

Esta página foi traduzida automaticamente por IA. A versão em inglês é a fonte oficial.Ver versão em inglês →
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:
  1. 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.
  2. 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.
  3. 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: