Skip to main content
이 페이지는 AI 자동 번역입니다. 모든 내용은 영문판을 기준으로 합니다.영문판 보기 →
CPI(“크로스 프로그램 호출”)는 한 Solana 프로그램이 다른 프로그램을 호출하는 메커니즘입니다. Raydium의 대부분의 프로그램은 Anchor CPI 래퍼 크레이트를 제공하여 호출 지점이 타입이 지정된 함수 호출처럼 보이게 하며, 검증된 필드 이름을 가진 계정 구조체와 cpi::<ix>() 헬퍼를 포함합니다. 이 페이지는 일반적인 패턴을 한 번 문서화한 후 프로그램별 차이점을 설명합니다. 실행 가능한 TypeScript의 경우 각 제품 장의 code-demos 페이지를 참조하세요.

어느 패턴이 어느 프로그램에 적용되는가

CPMM, CLMM 또는 LaunchLab을 통합하는 경우 먼저 일반 패턴을 읽은 후 계정 목록과 차이점에 대해 프로그램의 섹션으로 이동하세요. Farm v6과 AMM v4는 충분히 다르므로 해당 섹션을 독립적으로 읽을 가치가 있습니다.

Cargo 의존성

의존성 키는 대상 저장소의 [package] name과 정확히 일치해야 하며, 하이픈을 포함합니다. Cargo는 git 의존성을 해결할 때 raydium_cp_swap을 raydium-cp-swap과 동등한 것으로 취급하지 않습니다.
branch = "master"는 최신 게시된 소스를 추적합니다; 재현 가능한 빌드가 필요한 경우 특정 rev = "<commit>"으로 고정하세요. 프로토타이핑을 지난 후에는 권장됩니다. 업스트림 계정 레이아웃 변경이 master에서 경고 없이 빌드를 깨뜨릴 수 있기 때문입니다. cpi 기능 플래그는 크레이트가 전체 프로그램이 아닌 CPI 표면(계정 구조체 + 호출자)만 컴파일하도록 하므로 바이너리가 작게 유지됩니다. anchor-lang / anchor-spl은 대상 크레이트가 고정한 것과 일치해야 합니다:
두 크레이트를 동일한 Anchor 라인에서 가져오세요. 두 master 브랜치 모두 =1.0.2를 고정하므로 한 프로그램이 단일 크레이트에서 CPMM과 CLMM으로 CPI할 수 있습니다. 라인을 혼합하면 빌드가 깨집니다. 예를 들어, master의 raydium-cp-swap과 Anchor 1.0 업그레이드 이전의 rev로 고정된 raydium-clmm. Cargo는 한 바이너리에 Anchor의 특성의 두 개의 호환되지 않는 복사본을 연결해야 합니다. 혼합 쌍에 갇혀 있다면 프로그램을 둘로 나누거나 한쪽에 대해 타입이 지정된 CPI 크레이트를 버리고 해당 명령어를 수동으로 인코딩하세요(AMM v4에 표시된 패턴은 모든 프로그램에 작동합니다).
Anchor 1.0은 모든 CPI 호출 지점이 건드리는 두 가지를 변경했습니다. 0.3x에서 작동하는 통합을 이동하는 경우:
  • CpiContext::new는 AccountInfo가 아닌 Pubkey를 사용합니다. CpiContext::new(ctx.accounts.cpmm_program.to_account_info(), accts)는 CpiContext::new(*ctx.accounts.cpmm_program.key, accts)가 됩니다. new_with_signer도 동일합니다. 구조체 필드는 이제 program_id: Pubkey입니다.
  • Context는 하나의 수명을 가집니다. 네 개가 아닙니다. Context<'_, '_, 'info, 'info, MyProxySwap<'info>>는 Context<'info, MyProxySwap<'info>>가 됩니다.
클라이언트 측에서 anchor-client의 RequestBuilder::instructions()은 이제 Result<Vec<Instruction>>이 아닌 Vec<Instruction>을 반환합니다(? 제거). CommitmentConfig는 solana-sdk에서 이동했습니다 — anchor_client에서 가져오세요. spl-associated-token-account 8.0은 새로운 spl-associated-token-account-interface 크레이트에서 헬퍼를 다시 내보냅니다. get_associated_token_address와 ID는 여전히 크레이트 루트(spl_associated_token_account::{get_associated_token_address, ID})에서 도달할 수 있지만, 주소 헬퍼는 그곳에서 더 이상 사용되지 않습니다 — spl-associated-token-account-interface에 직접 의존하고 spl_associated_token_account_interface::address::get_associated_token_address와 spl_associated_token_account_interface::program::ID를 가져오는 것을 선호합니다. ::address와 ::program은 인터페이스 크레이트의 모듈입니다; spl_associated_token_account::address::…는 해결되지 않습니다.
계정 구조체를 끝에서 끝까지 연결하는 작동하는 CPI 예제는 raydium-io/raydium-cpi-example을 참조하세요(AMM v4, CPMM, CLMM을 다룹니다). 최신 브랜치는 anchor-0.31.0입니다 — 아직 Anchor 1.x 브랜치가 없으므로 해당 저장소를 이 페이지가 요구하는 버전 핀이 아닌 계정 구조체 연결의 참조로 취급하세요.

일반적인 Anchor CPI 패턴

이 섹션에서는 CPMM을 처음부터 끝까지 실제 예제로 설명합니다: Accounts 구조체, CpiContext, cpi::<ix>(). CLMM은 동일한 구조를 따르되 계정 목록과 남은 계정(remaining-accounts) 요구사항이 다릅니다. LaunchLab은 동일한 메커니즘을 따르지만 계정 목록에 CPMM/CLMM과 동등한 것이 없는 여러 계정(global_config, platform_config, event_authority, program)을 포함하므로, 이를 동일한 패턴으로 취급하되 동일한 형태로는 취급하지 마세요. 이 설명의 계정 목록이 직접 전이된다고 가정하지 말고 각 프로그램의 해당 섹션을 참조하세요.

계정 목록 구성

모든 Raydium CPI는 호출 프로그램에서 Accounts 구조체를 필요로 합니다. 필드는 당신의 명령어가 필요로 하는 계정이며, 필드 수준의 검증자를 포함합니다. 선언 순서는 Raydium의 명령어 계정 순서와 일치할 필요가 없습니다. 당신의 IDL 생성 클라이언트가 위치가 아닌 이름으로 주소를 지정하기 때문입니다:
Raydium 측 계정 대부분은 UncheckedAccount입니다. 호출 대상(Raydium)이 검증을 담당하기 때문입니다. 당신의 호출 프로그램은 사용자 ATA 및 당신의 PDA와 같이 당신이 소유한 계정만 엄격히 검증합니다. /// CHECK: 문서 주석은 누락된 검사에 대한 Anchor의 경고를 억제합니다. Raydium 측의 유일한 예외는 cpmm_program 자체입니다: Raydium이 내부적으로 검증하는 데이터 계정이 아니라 호출되는 프로그램이므로, Program<T>로 입력되고 수동 /// CHECK: 대신 Anchor의 자동 주소 검사를 받습니다. 이 대부분 UncheckedAccount 형태(Raydium이 자신의 계정을 검증함)는 CLMM과 LaunchLab에서도 동일합니다. 이 예제는 두 민트가 모두 클래식 SPL Token이라고 가정합니다. 어느 쪽이든 Token-2022 민트일 수 있다면, token_program_2022: Program<'info, anchor_spl::token_2022::Token2022> 필드를 추가하고 아래 CPI 호출에서 token_program 대신 해당 쪽의 input_token_program/output_token_program으로 전달하세요.

CPI 호출 구성

Anchor는 명령어당 하나의 헬퍼를 생성하며, CPI 계정 구조체(cpi::accounts::Swap, 아래에서 CpmmSwap으로 별칭)도 함께 생성합니다. 위의 당신의 MyProxySwap 구조체와 달리, 이 구조체의 필드 이름과 순서는 raydium-cp-swap의 IDL에 의해 고정되며 정확히 일치해야 합니다:
cpi::swap_base_input은 IDL에서 생성되며, 인수 목록은 Anchor 명령어의 인수 목록을 반영합니다. 확인된 모든 Anchor 기반 Raydium 프로그램(CPMM, CLMM, LaunchLab)은 동일한 방식으로 cpi::<ix>() 헬퍼를 생성하며, 함수 이름은 스네이크_케이스의 명령어 이름과 일치합니다. 이것이 Farm v6으로 확장되는지는 확인되지 않았습니다. 해당 섹션을 참조하세요.

서명자 시드(PDA 서명 CPI)

당신의 프로그램이 PDA를 대신하여 CPI에 서명할 때(금고, 에스크로 등에서 일반적), CpiContext::new_with_signer를 사용하세요:
서명자 시드는 PDA의 파생과 일치해야 합니다. authority(또는 유사한 서명자 역할)로 전달되는 모든 계정에 대해 Solana 런타임은 PDA가 이 시드를 통해 서명하는지 확인합니다.

남은 계정

일부 Raydium 명령어는 고정 계정 뒤에 추가되는 가변 길이 목록인 남은 계정을 사용합니다. Anchor의 CPI 헬퍼는 남은 계정을 타입 검사하지 않습니다. .with_remaining_accounts(...)를 통해 전달하세요:
순서는 항상 중요합니다. 수신 프로그램이 전달한 순서대로 남은 계정을 반복하기 때문입니다. 확인된 두 가지 순서:
  • CLMM SwapV2: 틱 배열, 방향별로 정렬됨.
  • Farm v6: (reward_vault, user_reward_ata) 쌍, 하지만 두 번째 보상 스트림부터만. 실제 트랜잭션 디코딩이 보여주는 내용은 Farm v6을 참조하세요.

패턴 적용: CLMM

SwapV2는 위의 일반 패턴을 따르되 다른 계정 목록과 틱 배열에 대한 남은 계정 요구사항이 있습니다. 크레이트의 #[program] 모듈은 raydium_clmm으로 명명되며, 이는 Rust use 경로이기도 합니다.
CPI 계정 구조체는 SwapV2가 아니라 SwapSingleV2로 명명됩니다. SwapV2는 온체인 명령어 이름입니다.
SDK가 하는 방식과 동일하게 현재 풀 상태에 대한 견적을 통해 틱 배열 목록을 계산하세요. 고정 개수를 추측하지 마세요. 전달한 배열을 초과하는 스왑은 TickArrayNotFound로 되돌아갑니다(products/clmm/instructions에서 전체 계정 테이블 및 오류 목록 참조). 가격 이동 방향으로 전달하세요: 스왑 방향의 첫 배열을 먼저.

패턴 적용: LaunchLab

LaunchLab은 Anchor 기반이며 IDL이 공개되어 있습니다: 공개 raydium-idl 저장소의 raydium_launchpad/raydium_launchpad.json. 이 IDL의 내부 메타데이터 식별자는 raydium_launchpad로, 기본 프로그램의 기술적 이름이며 제품의 다른 이름이 아닙니다. CPMM 및 CLMM과 달리, 프로그램의 소스 코드는 공개되지 않습니다 (reference/program-addresses 참조). Cargo가 가리킬 git = "..." 의존성이 없으며, 실제 크레이트의 Rust use 경로가 무엇인지 확인할 소스도 없습니다. Anchor의 declare_program! 매크로를 사용하여 공개된 IDL에서 바인딩을 생성합니다. IDL JSON을 크레이트의 idls/raydium_launchpad.json으로 저장하고 (CARGO_MANIFEST_DIR을 기준으로 Cargo가 idls/ 디렉토리를 찾음), declare_program!(raydium_launchpad);를 실행하면 프로그램 소스 없이도 IDL에서 직접 raydium_launchpad::cpi::accounts::<Ix> 구조체와 cpi::<ix>() 함수를 생성합니다. 생성된 계정 구조체 이름은 항상 명령어 이름을 PascalCase로 변환한 것입니다 (buy_exact_in → BuyExactIn). 필드 이름은 IDL의 계정 이름과 정확히 일치하며, 아래 MyProxyBuy에서 이미 사용된 계정 목록과 동일합니다. CPI 형태는 일반적인 패턴을 따릅니다. 아래의 계정 목록과 인수는 products/launchlab/instructions.mdx가 아닌 온체인 IDL의 buy_exact_in 명령어에서 가져옵니다:
졸업 후 대상 프로그램은 pool_state.migrate_type에 따라 CPMM 또는 AMM v4입니다. products/launchlab/accounts.mdx에 따르면 이는 Initialize 시점에 설정됩니다. CPI 계정 목록을 둘 다 준비하거나, 먼저 PoolState에서 migrate_type을 읽고 분기해야 합니다.

오류 전파

각 Anchor 기반 Raydium 프로그램은 자체 오류 열거형을 반환합니다. Anchor가 이를 래핑하므로 호출 프로그램은 Err(ProgramError::Custom(code))로 표시됩니다. 특정 오류를 처리하려면:
호출하는 프로그램에 맞는 오류 타입으로 바꿉니다 (CLMM의 경우 raydium_clmm::error::ErrorCode 등). 오류 코드 번호는 IDL 정책에 따라 안정적입니다 (sdk-api/anchor-idl). 따라서 숫자 값과 비교하여 특정 코드를 테스트할 수 있습니다. 전체 오류 표: CPMM, CLMM, AMM v4, Farm v6, LaunchLab.

합성 CPI의 컴퓨트 예산

각 CPI 프레임에는 오버헤드가 있으며, 피호출자의 CU 소비가 당신의 것 위에 쌓이므로, 프로그램 내부에서 Raydium으로 호출하는 트랜잭션은 200k CU 기본값에 의존하지 않고 명시적인 컴퓨트 예산이 필요합니다.
측정값이지 추정값이 아닙니다. 메인넷의 CPMM swap_base_input은 CPMM 프로그램 자체에서 약 23,000 CU를 소비합니다 — 2026-09-09에 고거래량 풀의 8개 라이브 스왑에서 샘플링됨 (22,721–23,052). Program CPMMoo8… consumed N of M compute units 로그 라인에서 읽음. 비교: AMM v4 스왑 약 26,000; CLMM swap 약 41,000; CLMM swap_v2 약 48,000 (43,838–52,887). 각 틱 교차마다 증가합니다.이 페이지의 이전 버전은 프록시 스왑 CPI에 약 47,700 CU를 보고했습니다. 그 수치는 전체 트랜잭션 (computeUnitsConsumed)이었으며, 호출자의 프로그램, CPI 프레임 및 모든 ATA 설정을 포함합니다 — 피호출자의 비용이 아닙니다. 둘 다 유용하지만 같은 숫자가 아니므로 같은 것끼리 비교하세요. 문서에서 복사한 숫자가 아닌 자신의 트랜잭션을 측정하세요.
CLMM 및 LaunchLab CPI는 더 많은 비용이 듭니다 (CLMM은 특히 remaining_accounts를 통해 추가 틱 배열을 탐색하여 배열당 CU를 추가함). 하지만 위의 CPMM 수치만 측정값입니다. 항상 자신의 측정에서 크기를 조정한 명시적 ComputeBudgetProgram::set_compute_unit_limit(...) 명령어를 설정하세요. 문서에서 복사한 숫자가 아닙니다. 기본 200k CU 제한은 조용히 소진되고 프로그램이 업그레이드되면서 명령어당 비용이 변하기 때문입니다.

AMM v4: 수동 Instruction 구성

AMM v4는 Anchor 이전에 만들어졌으며 CPI 크레이트가 없어서, 이 문서에서 위의 일반적인 패턴을 따르지 않는 유일한 프로그램입니다. Instruction을 수동으로 구성합니다:
전체 계정 목록은 products/amm-v4/code-demos를 참조하세요.

Farm v6

통합에 TS SDK가 옵션이라면 TS SDK를 사용하세요. raydium.farm.deposit(...) (products/farm-staking/code-demos 참조)는 실제 데모로 검증되었으며 이 프로그램에 대한 Rust Anchor 크레이트의 존재 여부에 의존하지 않습니다.
Farm v6은 Anchor CPI 경로를 제공하지 않습니다. crates.io에 raydium_farm_v6 크레이트가 없고, 공개 소스 저장소가 없으며, 온체인 IDL도 없습니다 — 프로그램에는 레거시 anchor:idl 계정도 없고 Program Metadata 프로그램의 항목도 없습니다 (sdk-api/anchor-idl 참조). 이를 비-Anchor 프로그램으로 취급하고 아래와 같이 명령어를 수동으로 구성하세요.
Rust CPI가 필요한 경우, 예를 들어 다른 온체인 프로그램에서 합성하는 경우, AMM v4와 동일한 방식으로 Instruction을 수동으로 구성합니다: 실제 계정 목록과 명령어 판별자를 독립적으로 파생시킵니다. 예를 들어 SDK의 TypeScript 레이아웃 디코딩 (raydium-sdk-V2의 farm 모듈), 실제 트랜잭션 직접 디코딩 (아래 참조), 또는 배포된 프로그램 덤프 및 역어셈블링을 통해. 인수가 없는 명령어 형태로 수확 또는 청구 호출과 일치하는 경우, 실제 계정 순서는 고정 접두사 (token_program, 팜의 상태 계정, 볼트 권한 PDA, 해당 PDA의 첫 번째 보상 볼트, 두 번째 PDA, 호출자, 호출자의 첫 번째 보상 민트에 대한 ATA)이며, 첫 번째 이후의 모든 보상 스트림에 대해 remaining_accounts의 (reward_vault_i, user_reward_ata_i) 쌍이 뒤따릅니다. 페어링 규칙은 실제이지만, 두 번째 보상 스트림부터만 시작됩니다: 첫 번째 스트림의 볼트와 ATA는 고정 계정이며, 서로 인접하지 않으며, remaining_accounts의 일부가 아닙니다.

CPI 플로우 테스트

로컬 개발 환경에서는 테스트 검증자에 Raydium 프로그램이 필요합니다. 세 가지 옵션이 있습니다:
  1. 프로그램 클론을 사용한 anchor test. 배포된 메인넷 바이트코드를 로컬 검증자로 가져옵니다. 아래 로컬 검증자에 프로그램 클론하기에서 Anchor.toml 설정과 풀 생성 테스트에서 주의할 두 가지 사항을 확인하세요.
  2. Devnet. Raydium은 대부분의 프로그램을 devnet에 배포하지만, 모든 프로그램마다 메인넷과 다른 프로그램 ID를 사용합니다 (CPMM, CLMM, AMM v4, Stable AMM, LaunchLab은 각각 고유한 devnet 주소를 가집니다. reference/program-addresses의 Devnet 표를 참조하세요). Farm v3/v5/v6은 devnet에 안정적으로 게시되지 않습니다. 라이브 API (https://api-v3-devnet.raydium.io/main/info)에서 현재 상황을 확인할 수 있습니다. raydium_clmm의 번들된 DEVNET_PROGRAM_ID 상수 (또는 다른 크레이트의 동등한 상수)를 사용하는 경우, 메인넷 ID가 devnet에서도 작동한다고 가정하지 마세요. 올바른 주소를 확보한 후 anchor test --provider.cluster devnet을 실행하여 라이브 코드를 테스트하세요.
  3. 로컬 배포. Raydium 저장소 (CPMM, CLMM; LaunchLab의 소스는 이 옵션에서 사용할 수 없음)를 클론하고 로컬 검증자에 anchor deploy합니다. 테스트 사이클 오버헤드가 추가되지만 디버깅을 위해 호출 대상을 수정할 수 있습니다.
anchor test으로 실행하거나, 프로그램을 변경하지 않고 테스트 파일을 반복할 때는 먼저 anchor build를 실행한 후 anchor test --skip-build을 실행하세요.

로컬 검증자에 프로그램 클론하기

이 방식은 프로그램의 소스가 공개되어 있는지 여부와 관계없이 프로그램 ID로 작동하므로, LaunchLab의 소스를 사용할 수 없더라도 CPMM 및 CLMM과 동일한 방식으로 클론됩니다. reference/program-addresses는 여기의 모든 주소에 대한 신뢰할 수 있는 출처입니다.
프로그램을 클론하는 것만으로는 테스트가 풀을 생성하는 경우 (이미 존재하는 풀에 대해 스왑하는 것이 아닌) 충분하지 않습니다. CPMM의 initialize 명령어는 amm_config 및 create_pool_fee 계정을 실제 온체인 데이터에 대해 검증하므로, 이들도 클론해야 하거나 initialize가 완전히 실패합니다. CPMM의 경우: 원하는 수수료 계층 AmmConfig를 클론하세요 (주소는 GET https://api-v3.raydium.io/main/cpmm-config에서 가져오고, 인덱스 0은 0.25% 계층입니다) 그리고 수수료 수신자 토큰 계정을 클론하세요. 이는 정확한 주소로 검증되며 즉시 생성되지 않으므로 이미 존재해야 합니다.
테스트가 방금 생성한 풀은 같은 순간에 스왑 가능하지 않습니다. CPMM의 initialize는 미래가 아닌 요청된 open_time을 자동으로 무시합니다 (if open_time <= block_timestamp { open_time = block_timestamp + 1 }). 따라서 startTime: 0 (SDK에 따르면 “즉시 열기”)도 풀이 스왑을 수락하기 전에 실제로 ≥1초의 간격을 남깁니다. 풀을 생성하고 지연 없이 스왑하는 테스트는 NotApproved에 도달합니다. 풀 생성과 첫 번째 스왑 사이에 짧은 await (1–2초)가 충분합니다. 이는 테스트에만 해당됩니다. 사람이 두 개의 별도 수동 명령어를 실행하는 경우 일반적으로 입력 및 프로세스 시작이 이미 1초 이상을 소비하므로 알아차리지 못할 것입니다.

참고 자료

출처: