Skip to main content
이 페이지는 AI 자동 번역입니다. 모든 내용은 영문판을 기준으로 합니다.영문판 보기 →
버전 안내. 모든 TypeScript 데모는 @raydium-io/raydium-sdk-v2@0.2.64-alpha를 기준으로 작성되었습니다. 마지막으로 0.2.42-alpha(2026-04)에서 Solana mainnet-beta를 대상으로 실행했고, 2026-09-09에 호출 시그니처를 0.2.64-alpha 소스와 대조해 다시 확인했습니다. 끝부분의 Rust CPI 골격은 master 브랜치의 raydium-clmm과 Anchor 1.0.2를 대상으로 합니다. CPMM 페이지와 같은 버전 고정이라 두 코드를 하나의 crate에 함께 둘 수 있습니다. 프로그램 ID는 SDK를 통해 reference/program-addresses에서 가져옵니다.

설정

이 페이지의 모든 데모는 raydium-sdk-V2-demo/src/clmm의 파일과 대응합니다. 각 섹션 옆에 GitHub 링크가 있습니다. 초기 설정은 데모 저장소의 config.ts.template (소스)을 따릅니다. 규모가 있는 통합이라면 disableFeatureCheck: true를 설정하는 것을 권장합니다.

CLMM 풀 생성

출처: src/clmm/createPool.ts
SDK는 다음 작업을 수행합니다.
  • 주소를 유도하기 전에 mint1과 mint2를 바이트 순서로 정렬합니다.
  • sqrt_price_x64 = floor(sqrt(initialPrice × 10^(dB−dA)) × 2^64)를 계산합니다.
  • observation 계정과 tick_array_bitmap_extension 계정을 생성합니다.
  • ammConfig에 정의된 풀 생성 수수료를 지불합니다.

원하는 범위에 포지션 열기

출처: src/clmm/createPosition.ts
SDK는 범위가 걸치는 tick array를 계산해 계정으로 전달합니다. 별도의 초기화 명령을 묶을 필요는 없습니다. tick array를 초기화하는 명령은 따로 없고, OpenPosition*이 없는 tick array를 호출자(payer)의 비용으로 직접 할당합니다.

기존 포지션의 유동성 늘리기

출처: src/clmm/increaseLiquidity.ts

유동성 줄이기 (수수료 동시 수령)

출처: src/clmm/decreaseLiquidity.ts 및 src/clmm/closePosition.ts
수수료와 보상만 수령하려면 liquidity = new BN(0)으로 decreaseLiquidity를 호출합니다. 이 명령은 token_fees_owed_{0,1}과 reward_amount_owed를 정산하고 그 금액을 밖으로 전송합니다. 두 항목을 수령하는 방법은 이것이 유일합니다. 유동성과 수수료를 모두 0으로 만든 뒤 포지션을 완전히 닫으려면 마지막 decreaseLiquidity 호출에 ownerInfo: { closePosition: true }를 전달합니다. SDK가 ClosePosition을 추가하고 NFT를 소각합니다.
제한된 발행자(restricted-issuer) 포지션에는 호환되는 close 빌더가 필요합니다. 이런 포지션은 NFT 토큰 계정이 동결되어 있습니다. CLMM이 계정을 소각하기 전에 동결을 해제할 수 있도록, ClosePosition은 포지션의 풀 ID를 첫 번째 remaining account로 추가해야 합니다. 프로그램 소스 브랜치에는 SDK 변경 사항이 포함되어 있지 않습니다. 제한된 발행자 포지션 생성을 활성화하기 전에, 사용 중인 SDK 버전이 동결된 상태의 close 경로를 명시적으로 지원하는지 확인하세요.
Anchor 클라이언트를 직접 사용한다면 선언된 계정은 그대로 두고 풀 계정을 추가합니다.
모든 close 호출에 poolId를 전달해도 됩니다. CLMM은 positionNftAccount가 동결된 경우에만 이 값을 읽습니다. 따라서 하나의 클라이언트 경로로 기존 포지션과 새 포지션을 모두 처리할 수 있습니다.

보상 수령

출처: src/clmm/harvestAllRewards.ts
harvestAllRewards는 전달된 모든 풀의 모든 포지션을 순회합니다. 수수료와 보상을 정산하는 유동성 0짜리 DecreaseLiquidity 호출(필요하면 UpdateRewardInfos도 포함)을 묶고, 필요에 따라 여러 트랜잭션으로 나눕니다.

스왑

출처: src/clmm/swap.ts 이 예제는 src/clmm/swap.ts와 같은 방식으로 작성되었으며, PoolUtils.computeAmountOutFormat 대신 swapInternal로 시뮬레이션합니다.
시뮬레이션은 온체인 프로그램과 동일한 로직으로 틱 맵을 오프체인에서 순회하며, 출력 금액(amountCalculated)과 스왑이 접근할 정확한 계정 목록(accounts)을 반환합니다. 시뮬레이션이 반환한 remainingAccounts는 반드시 그대로 전달하세요. 개수가 부족하면 순회 도중 NotEnoughTickArrayAccount 오류로 스왑이 되돌아가고, 오래된 계정을 전달하면 컴퓨트만 낭비됩니다.
PoolUtils.computeAmountOutFormat도 여전히 존재하지만, ComputeClmmPoolInfo가 필요합니다. 즉 API 풀 객체가 아니라 getPoolInfoFromRpc에서 얻은 computePoolInfo를 넘겨야 하며, 추가로 필수 인자 두 개(tickarrayBitmapExtension, blockTimestamp)가 더 필요합니다. 또한 raydium.clmm.fetchTickArrays 메서드는 없습니다. fetchTickArrays는 독립 함수이고, 모듈 수준 헬퍼는 PoolUtils.fetchMultiplePoolTickArrays와 getPoolInfoFromRpc가 반환하는 tickData / tickArrays입니다.

커스터마이즈 가능한 CLMM 풀 생성

createCustomizablePool은 풀 생성 시점에 동적 수수료와 단일 자산 수수료 설정을 노출하는 진입점입니다. 인자 구조는 createPool과 같고, 여기에 두 가지 항목이 추가됩니다.
enableDynamicFee, dynamicFeeConfigId, startTime 파라미터는 존재하지 않습니다. 동적 수수료를 활성화하는 것은 dynamicFeeConfig를 전달하는 것입니다. 이 값을 생략하면 오류 없이 고정 수수료 풀이 생성됩니다. 또한 SDK의 열거형 멤버는 TokenOnlyA / TokenOnlyB이지만, 온체인 Rust 열거형에서는 Token0Only / Token1Only로 적혀 있습니다. 숫자 값은 같으며(FromInput = 0), 이를 주의하세요.
기본 수수료를 쓰고 동적 수수료가 필요 없는 경우에는 createPool을 계속 사용할 수 있습니다. 두 가지 설정 중 하나라도 필요하다면 createCustomizablePool을 사용하세요. 온체인 계정 목록은 products/clmm/instructions에서 확인할 수 있습니다.

지정가 주문

지정가 주문은 사용자의 입력 자산을 특정 틱 하나에 예치해 두고, 스왑이 해당 틱을 통과할 때 선입선출(FIFO) 순서로 체결합니다. 출력 자산은 정산 시점에 주문자의 ATA로 전송되므로, 체결을 위해 주문자가 온라인 상태일 필요는 없습니다.

지정가 주문 열기

풀마다 지정가 주문 설정 계정이 따로 있지 않으며, getClmmLimitOrderConfigs() 헬퍼도 없습니다. 주문은 틱 단위로 구분됩니다.
SDK는 (owner, nonce PDA, order nonce)로부터 LimitOrderState PDA를 도출하고, 지갑별 LimitOrderNonce를 증가시킨 뒤, 해당 틱의 FIFO 코호트에 주문을 삽입합니다.

열린 주문 수량 늘리기 / 줄이기

decreaseLimitOrder는 주문 중 아직 체결되지 않은 부분에서만 수량을 뺄 수 있습니다. 이미 체결된 부분은 정산이 끝날 때까지 잠겨 있습니다. 주문이 전부 체결된 상태에서 두 명령을 실행하면 모두 InvalidOrderPhase 오류로 되돌아갑니다.

체결된 주문 정산하기

settleLimitOrder는 코호트 트래커를 기준으로 주문의 unfilled_ratio_x64를 읽고, 체결된 출력 금액을 계산해 주문자의 ATA로 전송합니다. 주문자가 직접 호출할 수 있으며, 오프체인 운영 키퍼인 limit_order_admin도 주문자를 대신해 호출할 수 있습니다. 이때도 출력 자산은 주문자에게 전달됩니다. 전부 정산된 주문을 닫고 렌트를 회수하려면 단일 주문에는 closeLimitOrder를, 여러 주문에는 closeAllLimitOrder를 사용하세요. 여러 주문을 한 번에 정산하려면 settleAllLimitOrder를 사용하면 되며, 이 함수는 v0 트랜잭션에 들어갈 만큼의 SettleLimitOrder 호출을 묶어 보냅니다.

지갑의 대기 중인 주문 조회 (오프체인)

활성 주문 엔드포인트는 미체결 주문과 부분 체결 주문을 하나의 응답에 함께 반환합니다. totalAmount, filledAmount, pendingSettle 필드로 각 단계를 구분할 수 있습니다. 종료된 주문의 이력은 /limit-order/history/order/list-by-user?wallet=…(지갑별, nextPageId로 페이지네이션)에서 조회하고, 특정 주문의 전체 이벤트 로그는 /limit-order/history/event/list-by-pda?pda=…에서 조회하세요.

Rust CPI 스켈레톤

SwapV2의 remaining account 순서는 다음과 같습니다.
스왑에 bitmap extension이 필요 없다면 생략하세요. 필요한 경우에는 첫 번째 remaining account로 전달합니다.

자주 하는 실수

  • Tick 끝점이 간격에 맞지 않음 → TickAndSpacingNotMatch. 항상 TickUtil.getPriceAndTick으로 스냅하세요 (단수형 TickUtil입니다).
  • SwapV2에 tick array를 충분히 전달하지 않음 → NotEnoughTickArrayAccount. 목록은 swapInternal(...).accounts에서 가져오세요.
  • 비트맵 확장 없이 전체 범위 포지션을 만듦 → 확장 PDA는 쓰기 가능(writable)해야 합니다. SDK가 이 부분을 자동으로 처리합니다.
  • sqrt_price_x64를 price로 착각함 → 여기서 2배 차이를 혼동하면 특히 골치 아픕니다. 확신이 없다면 사람이 읽을 수 있는 가격을 넘겨서 SDK가 계산하도록 하세요.
  • 보상을 너무 자주 수령함 → 수령할 때마다 유동성이 0인 DecreaseLiquidity가 실행되어 트랜잭션이 하나씩 추가됩니다. 여러 포지션의 보상은 harvestAllRewards로 한꺼번에 묶어서 처리하세요. 이때 execute에는 { sequentially: true }가 필요하다는 점을 기억하세요.
  • NFT 계정을 직접 닫음 → ClosePosition은 NFT를 소각하고 해당 ATA도 닫습니다. Token-2022 NFT 민트도 함께 닫히지만, 기존 SPL Token 민트는 해당 프로그램이 민트를 닫을 수 없으므로 공급량 0 상태로 남습니다. 지원되는 계정을 따로 닫지 마세요. 그렇게 하면 명령이 실패(revert)합니다.
  • 간격에 맞지 않는 tick에 지정가 주문을 엶 → TickAndSpacingNotMatch. 내보내진(exported) getOrderTick 헬퍼로 항상 tick을 맞추세요.
  • 이미 완전히 체결된 주문에 decreaseLimitOrder를 호출함 → InvalidOrderPhase. 대신 settleLimitOrder를 호출한 뒤 closeLimitOrder를 사용하세요.
  • enableDynamicFee 플래그가 있다고 기대함 → 그런 플래그는 없습니다. dynamicFeeConfig를 생략하면 오류 없이 조용히 고정 수수료 풀이 만들어집니다. 동적 수수료를 사용하려면 /main/clmm-dynamic-config에서 고른 설정 계정의 PublicKey를 전달하세요.

다음 단계

출처: