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 라인에서 가져오세요. 두 업그레이드 브랜치 모두 =1.0.2를 고정하므로 한 프로그램이 단일 크레이트에서 CPMM과 CLMM으로 CPI할 수 있습니다. 라인을 혼합하면 빌드가 깨집니다. 예를 들어 chore/upgrade-anchor의 raydium-cp-swap과 master의 raydium-clmm. Cargo는 호환되지 않는 두 개의 Anchor 특성 복사본을 하나의 바이너리에 연결해야 합니다. 혼합된 쌍에 갇혀 있다면 프로그램을 둘로 나누거나 한쪽에 대해 타입이 지정된 CPI 크레이트를 버리고 해당 명령어를 수동으로 인코딩하세요(AMM v4에 표시된 패턴은 모든 프로그램에 작동합니다). 시작하기 전에 두 Cargo.toml 파일을 다시 확인하세요. 브랜치는 결국 master로 이동할 것입니다.
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은 동일한 형태를 따르며 다른 계정 목록과 남은 계정 요구사항이 있습니다. 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_launchpad/raydium_launchpad.json 공개 raydium-idl 저장소에서. 해당 IDL의 내부 메타데이터 식별자는 raydium_launchpad입니다. 제품 이름이 아닌 기본 프로그램의 기술적 이름입니다. CPMM과 CLMM과 달리 프로그램의 자체 소스는 공개적으로 사용할 수 없습니다(reference/program-addresses 참조). Cargo가 가리킬 git = "..." 의존성이 없으며, 실제 크레이트의 Rust use 경로가 무엇일지 확인할 소스가 없습니다. Anchor의 declare_program! 매크로를 사용하여 게시된 IDL에서 바인딩을 생성하세요. IDL JSON을 크레이트의 idls/raydium_launchpad.json으로 저장하세요(Cargo는 CARGO_MANIFEST_DIR에 상대적인 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))로 봅니다. 특정 오류를 처리하려면:
호출하는 프로그램에 대한 관련 오류 타입으로 스왑하세요(raydium_clmm::error::ErrorCode CLMM의 경우 등). 오류 코드 번호는 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를 보고했습니다. 해당 수치는 호출자의 자체 프로그램, CPI 프레임 및 모든 ATA 설정을 포함하는 전체 거래(computeUnitsConsumed)였습니다 — 호출자의 비용이 아닙니다. 둘 다 유용하지만 동일한 숫자가 아니므로 같은 것과 비교하세요. 자신의 거래를 측정하세요.
CLMM과 LaunchLab CPI는 더 많은 비용이 듭니다(CLMM은 특히 remaining_accounts를 통해 추가 틱 배열을 걷고 배열당 CU를 추가함). 하지만 위의 CPMM 수치만 측정된 값입니다. 항상 자신의 측정에서 크기를 조정한 명시적 ComputeBudgetProgram::set_compute_unit_limit(...) 명령어를 설정하세요. 문서에서 복사한 숫자가 아닙니다. 기본 200k CU 제한이 조용히 소진되고 프로그램이 업그레이드되면서 명령어당 비용이 변하기 때문입니다.

AMM v4: 수동 명령어 구성

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

Farm v6

통합에 대한 옵션이라면 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 모듈)을 디코딩하거나 실제 거래를 직접 디코딩하거나 배포된 프로그램을 덤프하고 분해합니다. 0 인수 명령어 형태(수확 또는 청구 호출과 일치)의 경우 실제 계정 순서는 고정 접두사(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에 배포하지만 모든 프로그램(CPMM, CLMM, AMM v4, Stable AMM, LaunchLab 각각 고유한 devnet 주소)에 대해 메인넷과 다른 프로그램 ID에서 배포합니다(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 }). 따라서 SDK의 “즉시 열기”인 startTime: 0도 풀이 스왑을 수락하기 전에 실제 ≥1초 간격을 남깁니다. 풀을 생성하고 지연 없이 스왑하는 테스트는 NotApproved를 적중시킵니다. 풀 생성과 첫 번째 스왑 사이의 짧은 await(1–2초)로 충분합니다. 이것은 테스트에만 해당됩니다. 인간이 두 개의 별도 수동 명령어를 실행하면 입력 및 프로세스 시작이 이미 1초 이상을 먹기 때문에 일반적으로 알아차리지 못합니다.

포인터

소스: