Zum Hauptinhalt springen

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.

Diese Seite wurde mit KI automatisch übersetzt. Maßgeblich ist stets die englische Version.Englische Version ansehen →
CPI (»Cross-Program Invocation«) ist der Mechanismus, mit dem ein Solana-Programm ein anderes aufruft. Raydiums Anchor-Programme werden mit CPI-Wrapper-Crates ausgeliefert, die den Aufrufplatz wie einen typisierten Funktionsaufruf erscheinen lassen — Kontostrukturen mit validierten Feldnamen und cpi::<ix>()-Hilfsfunktionen. Diese Seite dokumentiert das allgemeine Muster; für produktspezifische Snippets siehe die Code-Demos-Seite jedes Produktkapitels.

Cargo-Abhängigkeiten

[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.
Das cpi-Feature-Flag sorgt dafür, dass die Crates nur zu der CPI-Oberfläche kompiliert werden (Kontostrukturen + Invoker) anstelle des vollständigen Programms, sodass Ihr Binary klein bleibt. Für funktionierende CPI-Beispiele, die die Kontostrukturen end-to-end verbinden, siehe raydium-io/raydium-cpi-example (behandelt AMM v4, CPMM und CLMM).

Kontolisten-Konstruktion

Jede Raydium-CPI erfordert eine Accounts-Struktur im aufrufenden Programm. Felder entsprechen 1:1 der Kontenreihenfolge des Programm-Befehls mit Feldvalidierungen:
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>,
}
Die meisten Raydium-seitigen Konten sind UncheckedAccount, weil der aufgerufene (Raydium) die Validierung übernimmt. Ihr aufrutendes Programm validiert nur streng Konten, die Sie besitzen — Benutzer-ATAs, Ihre eigenen PDAs. Der /// CHECK:-Doc-Kommentar unterdrückt Anchors Warnung über fehlende Checks.

Aufbau des CPI-Aufrufs

Anchor generiert eine Hilfsfunktion pro Befehl:
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 wird aus dem IDL generiert; seine Argumentliste spiegelt die Argumentliste des Anchor-Befehls.

Signierseeds (PDA-signierte CPI)

Wenn Ihr Programm die CPI im Namen einer PDA signiert (häufig bei Tresoren, Hinterlegungen usw.), verwenden Sie 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)?;
Die Signierseeds müssen mit der Ableitung der PDA übereinstimmen. Für jedes Konto, das als authority (oder ähnliche Signierrolle) weitergegeben wird, prüft die Solana-Runtime, dass die PDA durch diese Seeds signiert.

Verbleibende Konten

Einige Raydium-Befehle nutzen verbleibende Konten — eine variable Liste, die nach den festen Konten angehängt wird. Die kanonischen Beispiele:
  • CLMM SwapV2: hängt 1–8 TickArrayState-Konten an, die den Tick-Arrays entsprechen, die der Swap durchlaufen könnte.
  • Farm v6 Deposit: hängt (reward_vault, user_reward_ata)-Paare für jeden aktiven Reward-Stream an.
Anchors CPI-Hilfsfunktionen prüfen verbleibende Konten nicht typsicher. Übergeben Sie diese über .with_remaining_accounts(...):
let cpi_ctx = CpiContext::new(program, accounts)
    .with_remaining_accounts(ctx.remaining_accounts.to_vec());
Die Reihenfolge ist wichtig: das Empfängerprogramm iteriert die verbleibenden Konten in der Reihenfolge, in der Sie diese übergeben. Bei CLMM müssen Tick-Arrays direktional sortiert werden (erstes Array in Swap-Richtung zuerst). Bei Farm v6 gehen Reward-Slots in Slot-Index-Reihenfolge.

Error-Propagation

Raydiums Programme geben ihre eigenen Error-Enums zurück. Anchor umhüllt diese; Ihr aufrutendes Programm sieht sie als Err(ProgramError::Custom(code)). Um spezifische Fehler zu handhaben:
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)
    }
}
Die Error-Code-Nummer ist stabil gemäß der IDL-Richtlinie (sdk-api/anchor-idl). Sie können gegen spezifische Codes testen, indem Sie gegen den numerischen Wert vergleichen.

Compute-Budget in zusammengesetzten CPIs

Jeder CPI-Frame hat Overhead (~1.500 CU für den Aufruf selbst), und der eigene CU-Verbrauch des aufgerufenen Programms stapelt sich auf Ihrem. Eine Transaktion, die einen CPMM-Swap von innerhalb Ihres Programms aufruft, verbraucht:
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)
Für Stacking-Routing (Ihr Programm → Aggregator → CPMM + CLMM + Farm Harvest) Budget ≥500k CU. Setzen Sie immer einen expliziten ComputeBudgetProgram::set_compute_unit_limit(...)-Befehl in der Transaktion — das Standard-200k-CU-Limit wird sich stumm erschöpfen.

AMM v4 — manuelle Instruction-Konstruktion

AMM v4 hat keine Anchor-Crate. Bauen Sie die Instruction von Hand:
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)?;
Siehe products/amm-v4/code-demos für die vollständige Kontoliste.

Farm v6 — Reward-Pair-verbleibende Konten

Farm v6s Deposit / Withdraw / Harvest nutzen das (reward_vault_i, user_reward_ata_i)-Pair-Muster in verbleibenden Konten. Genaue Reihenfolge:
remaining_accounts = [
    reward_vault_0, user_reward_ata_0,
    reward_vault_1, user_reward_ata_1,
    ...
]
Ein Pair pro aktiven (laufenden oder beendeten-aber-ungesammelten) Reward-Slot. Lassen Sie ungenutzte Slots weg; das Programm dispatched von farm_state.reward_infos[i].reward_state.

Testen eines CPI-Flows

Local Dev erfordert, dass die Raydium-Programme in Ihrem Test-Validator verfügbar sind. Optionen:
  1. anchor test mit Program Clone — in Anchor.toml:
    [test.validator]
    clone = [
      { address = "CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK" },  # CPMM
      { address = "CLMM...program-id..." },                          # CLMM
      { address = "farm-v6-program-id..." },                         # farm v6
    ]
    
    Dies zieht den eingesetzten Bytecode von Mainnet in Ihren lokalen Validator.
  2. Devnet — Raydium setzt alle Programme auf Devnet mit denselben Program-IDs wie Mainnet ein. Führen Sie anchor test --provider.cluster devnet aus, um auf Live-Code zuzugreifen.
  3. Lokales Deploy — Klonen Sie die Raydium-Repos und anchor deploy auf einen lokalen Validator. Fügt Test-Zyklus-Overhead hinzu, ermöglicht aber, dass Sie den aufgerufenen Code zum Debuggen ändern.

Verweise

Quellen: