메인 콘텐츠로 건너뛰기

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.

이 페이지는 AI 자동 번역입니다. 모든 내용은 영문판을 기준으로 합니다.영문판 보기 →
CPI(“크로스프로그램 호출”)는 한 솔라나 프로그램이 다른 프로그램을 호출하는 메커니즘입니다. Raydium의 Anchor 프로그램은 호출 지점을 타입화된 함수 호출처럼 보이게 하는 CPI 래퍼 크레이트를 함께 제공합니다. 계정 구조체에는 검증된 필드 이름과 cpi::<ix>() 헬퍼가 포함됩니다. 이 페이지는 일반적인 패턴을 다루고 있으며, 제품별 코드 예제는 각 제품 챕터의 code-demos 페이지를 참조하세요.

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.
cpi 기능 플래그를 사용하면 크레이트가 전체 프로그램이 아닌 CPI 표면(계정 구조체 + 호출자)만 컴파일되므로 바이너리 크기를 작게 유지할 수 있습니다. CPI 계정 구조체를 end-to-end로 연결한 실제 예제는 raydium-io/raydium-cpi-example (AMM v4, CPMM, CLMM 포함)을 참조하세요.

계정 목록 구성

모든 Raydium CPI에는 호출 프로그램에 Accounts 구조체가 필요합니다. 필드는 프로그램의 명령어 계정 순서와 1:1로 일치하며, 필드 레벨 검증자를 포함합니다:
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>,
}
Raydium 측의 계정 대부분은 UncheckedAccount입니다. 피호출자(Raydium)가 검증을 담당하기 때문입니다. 호출 프로그램은 자신이 소유한 계정만 엄격하게 검증합니다. 사용자 ATA, 자신의 PDA 등이 이에 해당합니다. /// CHECK: 주석은 Anchor의 누락된 검증 경고를 억제합니다.

CPI 호출 구성

Anchor는 명령어당 하나의 헬퍼를 생성합니다:
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은 IDL에서 생성되며, 인수 목록은 Anchor 명령어의 인수 목록을 미러링합니다.

서명자 시드 (PDA 서명 CPI)

프로그램이 PDA를 대신하여 CPI에 서명할 때(금고, 에스크로 등에서 일반적), 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)?;
서명자 시드는 PDA의 유도와 일치해야 합니다. authority(또는 유사한 서명자 역할)로 전달된 계정의 경우, 솔라나 런타임은 PDA가 이 시드를 통해 서명하는지 확인합니다.

남은 계정들

일부 Raydium 명령어는 남은 계정들을 취합니다. 고정 계정 후 추가된 가변 길이 목록입니다. 정확한 예시:
  • CLMM SwapV2: 스왑이 통과할 수 있는 틱 배열에 해당하는 1-8개의 TickArrayState 계정을 추가합니다.
  • Farm v6 Deposit: 각 활성 보상 스트림에 대해 (reward_vault, user_reward_ata) 쌍을 추가합니다.
Anchor의 CPI 헬퍼는 남은 계정을 타입 체크하지 않습니다. .with_remaining_accounts(...)을 통해 전달합니다:
let cpi_ctx = CpiContext::new(program, accounts)
    .with_remaining_accounts(ctx.remaining_accounts.to_vec());
순서가 중요합니다. 수신자 프로그램은 전달한 순서대로 남은 계정을 반복합니다. CLMM의 경우, 틱 배열은 방향순으로 정렬되어야 합니다(스왑 방향의 첫 번째 배열이 먼저). farm v6의 경우, 보상 슬롯은 슬롯 인덱스 순서로 진행합니다.

에러 전파

Raydium의 프로그램은 자신의 에러 열거형을 반환합니다. Anchor는 이를 래핑합니다. 호출 프로그램은 Err(ProgramError::Custom(code))로 봅니다. 특정 에러를 처리하려면:
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)
    }
}
에러 코드 번호는 IDL 정책(sdk-api/anchor-idl)에 따라 안정적입니다. 숫자 값과 비교하여 특정 코드에 대해 테스트할 수 있습니다.

조합 CPI의 컴퓨트 예산

각 CPI 프레임은 오버헤드가 있으며(호출 자체 약 1,500 CU), 피호출자의 CU 소비가 당신의 것 위에 쌓입니다. 프로그램 내부에서 CPMM 스왑을 호출하는 거래는 다음을 소비합니다:
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)
스택 라우팅(프로그램 → 애그리게이터 → CPMM + CLMM + farm 수확)의 경우 ≥500k CU로 예산을 설정합니다. 항상 거래에서 명시적인 ComputeBudgetProgram::set_compute_unit_limit(...) 명령어를 설정합니다. 기본 200k CU 제한은 조용히 소진됩니다.

AMM v4 — 수동 Instruction 구성

AMM v4에는 Anchor 크레이트가 없습니다. Instruction을 수동으로 빌드합니다:
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)?;
전체 계정 목록은 products/amm-v4/code-demos를 참조하세요.

Farm v6 — 보상 쌍 남은 계정들

Farm v6의 Deposit / Withdraw / Harvest는 남은 계정의 (reward_vault_i, user_reward_ata_i) 쌍 패턴을 사용합니다. 정확한 순서:
remaining_accounts = [
    reward_vault_0, user_reward_ata_0,
    reward_vault_1, user_reward_ata_1,
    ...
]
활성(실행 중이거나 종료했지만 미청구)인 각 보상 슬롯당 한 쌍입니다. 사용되지 않은 슬롯은 생략합니다. 프로그램은 farm_state.reward_infos[i].reward_state에 따라 디스패치합니다.

CPI 흐름 테스트

로컬 개발에서는 Raydium 프로그램을 테스트 검증자에서 사용할 수 있어야 합니다. 옵션:
  1. 프로그램 복제를 사용한 anchor testAnchor.toml에서:
    [test.validator]
    clone = [
      { address = "CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK" },  # CPMM
      { address = "CLMM...program-id..." },                          # CLMM
      { address = "farm-v6-program-id..." },                         # farm v6
    ]
    
    이는 배포된 바이트코드를 메인넷에서 로컬 검증자로 끌어옵니다.
  2. Devnet — Raydium은 모든 프로그램을 메인넷과 동일한 프로그램 ID로 devnet에 배포합니다. anchor test --provider.cluster devnet을 실행하여 라이브 코드를 칩니다.
  3. 로컬 배포 — Raydium 저장소를 복제하고 anchor deploy를 로컬 검증자로 수행합니다. 테스트 주기 오버헤드를 추가하지만 디버깅을 위해 피호출자를 수정할 수 있습니다.

포인터

소스: