Chuyển đến nội dung chính

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.

Trang này được dịch tự động bằng AI. Phiên bản tiếng Anh là bản chính thức.Xem bản tiếng Anh →
CPI (“cross-program invocation”) là cơ chế mà một chương trình Solana gọi một chương trình khác. Các chương trình Anchor của Raydium đi kèm với các crate CPI wrapper giúp call site trông như một hàm được gõ — các struct tài khoản với tên trường được xác thực và cpi::<ix>() helpers. Trang này nêu ra mẫu chung; để xem các đoạn mã theo từng sản phẩm, hãy xem trang code-demos của từng chương sản phẩm.

Các phụ thuộc 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 and farm v6: no published Anchor CPI crate. See "AMM v4 / farm v6" below.
Flag tính năng cpi khiến các crate được biên dịch chỉ thành bề mặt CPI (account structs + invokers) thay vì toàn bộ chương trình, vì vậy file nhị phân của bạn vẫn nhỏ. Để xem các ví dụ CPI hoạt động nối tiếp các struct tài khoản từ đầu đến cuối, hãy xem raydium-io/raydium-cpi-example (bao gồm AMM v4, CPMM, và CLMM).

Xây dựng danh sách tài khoản

Mỗi CPI của Raydium yêu cầu một struct Accounts trong chương trình gọi. Các trường khớp với thứ tự tài khoản của lệnh chương trình 1-1, với các bộ xác thực cấp trường:
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>,
}
Hầu hết các tài khoản Raydium là UncheckedAccount vì chương trình được gọi (Raydium) sở hữu xác thực. Chương trình gọi của bạn chỉ xác thực nghiêm ngặt các tài khoản bạn sở hữu — user ATAs, PDAs của riêng bạn. Comment doc /// CHECK: che giấu cảnh báo của Anchor về các kiểm tra bị thiếu.

Xây dựng CPI call

Anchor tạo một helper cho mỗi lệnh:
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 được tạo từ IDL; danh sách đối số của nó phản ánh danh sách đối số của lệnh Anchor.

Signer seeds (PDA-signed CPI)

Khi chương trình của bạn ký CPI thay mặt một PDA (phổ biến đối với vaults, escrows, v.v.), hãy sử dụng 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)?;
Signer seeds phải khớp với derivation của PDA. Đối với bất kỳ tài khoản nào được truyền làm authority (hoặc vai trò signer tương tự), runtime Solana kiểm tra rằng PDA ký qua các seeds này.

Remaining accounts

Một số lệnh Raydium nhận remaining accounts — danh sách có độ dài thay đổi được nối sau các tài khoản cố định. Các ví dụ chuẩn:
  • CLMM SwapV2: nối 1–8 tài khoản TickArrayState tương ứng với các mảng tick mà swap có thể đi qua.
  • Farm v6 Deposit: nối các cặp (reward_vault, user_reward_ata) cho mỗi luồng reward đang hoạt động.
Các helper CPI của Anchor không type-check remaining accounts. Truyền chúng qua .with_remaining_accounts(...):
let cpi_ctx = CpiContext::new(program, accounts)
    .with_remaining_accounts(ctx.remaining_accounts.to_vec());
Thứ tự có quan trọng: chương trình nhận lặp lại các remaining accounts theo thứ tự bạn truyền chúng. Đối với CLMM, các tick arrays phải được sắp xếp theo hướng (mảng đầu tiên theo hướng swap trước). Đối với farm v6, các reward slots theo thứ tự chỉ số slot.

Xử lý lỗi

Các chương trình của Raydium trả về các enum lỗi riêng của chúng. Anchor bao bọc chúng; chương trình gọi của bạn thấy chúng dưới dạng Err(ProgramError::Custom(code)). Để xử lý các lỗi cụ thể:
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, or convert to your own error type.
        Err(e)
    }
}
Mã lỗi là ổn định theo chính sách IDL (sdk-api/anchor-idl). Bạn có thể kiểm tra các mã cụ thể bằng cách so sánh với giá trị số.

Compute budget trong composed CPIs

Mỗi khung CPI có overhead (~1.500 CU cho chính call), và tiêu thụ CU của callee xếp chồng lên của bạn. Một giao dịch gọi CPMM swap từ bên trong chương trình của bạn chi tiêu:
your_program_cu
+ ~1_500       (CPI overhead)
+ ~150_000     (CPMM swap, SPL-token variant)
+ ~200_000     (if Token-2022 with transfer fee)
+ ~10_000      (observation update)
Đối với định tuyến xếp chồng (chương trình của bạn → aggregator → CPMM + CLMM + farm harvest), budget ≥500k CU. Luôn đặt một lệnh ComputeBudgetProgram::set_compute_unit_limit(...) rõ ràng trong giao dịch — giới hạn CU mặc định 200k sẽ cạn kiệt im lặng.

AMM v4 — xây dựng Instruction thủ công

AMM v4 không có crate Anchor. Xây dựng Instruction bằng tay:
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)?;
Xem products/amm-v4/code-demos để xem danh sách tài khoản đầy đủ.

Farm v6 — remaining accounts cặp reward

Farm v6 Deposit / Withdraw / Harvest sử dụng mẫu cặp (reward_vault_i, user_reward_ata_i) trong remaining accounts. Trình tự chính xác:
remaining_accounts = [
    reward_vault_0, user_reward_ata_0,
    reward_vault_1, user_reward_ata_1,
    ...
]
Một cặp cho mỗi reward slot live (đang chạy hoặc kết thúc nhưng chưa nhận). Bỏ qua các slot không sử dụng; chương trình dispatcher từ farm_state.reward_infos[i].reward_state.

Kiểm tra luồng CPI

Dev cục bộ yêu cầu các chương trình Raydium có sẵn trong test validator của bạn. Các tùy chọn:
  1. anchor test với program clone — trong Anchor.toml:
    [test.validator]
    clone = [
      { address = "CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK" },  # CPMM
      { address = "CLMM...program-id..." },                          # CLMM
      { address = "farm-v6-program-id..." },                         # farm v6
    ]
    
    Điều này kéo bytecode được triển khai từ mainnet vào validator cục bộ của bạn.
  2. Devnet — Raydium triển khai tất cả các chương trình vào devnet với cùng một program ID như mainnet. Chạy anchor test --provider.cluster devnet để truy cập code trực tiếp.
  3. Local deploy — clone các repo Raydium và anchor deploy vào một validator cục bộ. Thêm overhead vòng lặp kiểm tra nhưng cho phép bạn sửa đổi callee để gỡ lỗi.

Con trỏ

Nguồn: