Skip to main content
이 페이지는 AI 자동 번역입니다. 모든 내용은 영문판을 기준으로 합니다.영문판 보기 →
이 페이지는 products/clmm/accounts (계정이 무엇인지)와 products/clmm/math (수학이 무엇인지)와 함께 사용됩니다. 인자와 계정 순서에 대해 권위 있는 참조이며, 특정 바이트 레이아웃은 IDL에서 나옵니다.2026-09 프로그램 업그레이드는 CLMM을 Anchor 1.0.2 / Solana 3.1.10으로 재구축했으며, 관리자 명령어 CollectExcessLamports를 추가했고, CreateAmmConfig가 owner / fund_owner에 쓰는 내용을 변경했습니다. 사용자 대면 명령어는 계정, 인자 또는 수학을 변경하지 않았습니다. 2026-09-30 변경 로그 항목을 참조하세요.

명령어 목록

초기화 틱 배열 명령어는 없으며 필요하지 않습니다. 틱 배열은 OpenPosition* / IncreaseLiquidity* 내에서 TickArrayState::get_or_create_tick_array에 의해 생성되며, payer가 비용을 지불합니다. (아직 초기화되지 않았을 수 있는 시스템 소유) 틱 배열 PDA를 tick_array_lower / tick_array_upper로 전달하면 프로그램이 누락된 경우 할당합니다. OpenLimitOrder는 단일 tick_array에 대해 동일하게 수행합니다.
대부분의 관리자 전용 명령어 (CreateAmmConfig, UpdateAmmConfig, UpdatePoolStatus, CreateOperationAccount, UpdateOperationAccount, CloseProtocolPosition)는 프로그램의 하드코딩된 admin 공개 키로 제어됩니다. CreatePermissionPda / ClosePermissionPda는 admin 공개 키 또는 전용 permission_pda_admin 키를 허용합니다. CreateSupportMintAssociated / CloseSupportMintAssociated는 admin 공개 키 또는 전용 지원 민트 소유자 키를 허용합니다. CollectExcessLamports는 admin 공개 키 또는 전용 수집 램포트 지갑을 허용합니다. 보상 스트림 관리자 명령어 (TransferRewardOwner, CollectRemainingRewards)는 프로그램 관리자가 아닌 보상 펀더로 제어됩니다. V2 접미사는 “볼트 / NFT에서 Token-2022를 지원하며, 비트맵 확장 슬롯이 필요함”을 의미합니다. SDK는 새 풀에 대해 기본적으로 V2를 선택합니다.

CreatePool

인자
계정 (축약)
슬롯 10과 11은 민트별이며, “SPL Token 다음 Token-2022”가 아닙니다. 각각은 일치하는 민트의 mint::token_program으로 제약되므로, token_mint_0이 Token-2022이고 token_mint_1이 클래식 SPL인 풀의 경우 슬롯 10에 Token-2022를 전달하고 슬롯 11에 SPL Token을 전달해야 합니다. CreateCustomizablePool과 CreatePermissionedPool은 동일한 두 슬롯을 사용합니다.
전제 조건
  • token_mint_0 < token_mint_1 (바이트 순서).
  • amm_config.disable_create_pool == false.
  • 민트는 Token-2022 확장 허용 목록에 의해 거부되지 않습니다.
사후 조건
  • pool_state.sqrt_price_x64 = sqrt_price_x64, tick_current = floor(log_{1.0001}(price)).
  • pool_state.liquidity = 0 (아직 포지션 없음).
  • pool_state.fee_on = FromInput (레거시 기본값).
  • pool_state.dynamic_fee_info는 0으로 설정됨 (동적 수수료 비활성화).

CreateCustomizablePool

새 풀에 권장됩니다. CreatePool과 동일한 효과에 풀별 수수료 수집 모드와 선택적 동적 수수료 옵트인을 추가합니다. 인자
계정 — CreatePool의 정확히 13개 선언된 계정. dynamic_fee_config는 선언된 계정이 아닙니다.
enable_dynamic_fee = true일 때, 스냅샷할 DynamicFeeConfig는 마지막 remaining_account로 제공되어야 합니다. 핸들러는 ctx.remaining_accounts.last()를 읽습니다. 모든 SupportMintAssociated 바이패스 PDA는 따라서 그 앞에 와야 하며, 그렇지 않으면 프로그램이 잘못된 계정을 스냅샷하고 역직렬화에 실패합니다. 플래그가 설정되어 있는데 생략하면 AccountLack으로 실패합니다.
전제 조건 — CreatePool과 동일합니다. enable_dynamic_fee = false이면 remaining_account가 필요하지 않으며 전달된 모든 것은 지원 민트 레코드만 스캔됩니다. 사후 조건
  • pool_state.fee_on이 선택된 CollectFeeOn 변형으로 설정됨.
  • 동적 수수료가 활성화된 경우: pool_state.dynamic_fee_info는 제공된 DynamicFeeConfig에서 초기화됨 (5개 보정 매개변수 복사됨, 상태 필드는 0으로 설정됨).
  • 그렇지 않으면: pool_state.dynamic_fee_info는 0으로 설정됨 (= 이 풀에 대해 영구적으로 동적 수수료 비활성화).
fee_on과 동적 수수료 활성화 비트는 풀 생성 시에만 설정됩니다. 제자리 업그레이드는 없습니다. 레거시 CreatePool을 통해 생성된 풀은 동적 수수료나 단측 수수료를 소급하여 얻을 수 없습니다. 새 배포는 이 명령어를 기본값으로 사용해야 합니다.

CreatePermissionedPool

CreatePool과 CreateCustomizablePool 모두 ["pool", amm_config, token_mint_0, token_mint_1]에서 풀 PDA를 파생하므로, (config, mint0, mint1) 트리플당 정확히 하나의 정규 풀 주소가 있습니다. 동일한 시드에서 두 번째 init은 실패합니다. CreatePermissionedPool은 클라이언트가 제공한 seed_index: u16을 풀 PDA 시드에 포함하여 이 제한을 해제하므로, 동일한 쌍 및 수수료 계층에 대해 여러 풀을 만들 수 있습니다. 각각 자신의 주소에서. 임의의 풀 주소는 특권 기능이므로, 지불자는 이를 승인하는 Permission PDA를 보유해야 합니다. 풀에 대한 다른 모든 것은 CreateCustomizablePool과 동일합니다. 동일한 CreateCustomizableParams를 사용하고 단측 수수료 및 동적 수수료 옵트인을 지원합니다. 인자
계정 (축약) — CreateCustomizablePool과 동일하며, 앞에 추가: pool_state PDA는 ["pool", amm_config, token_mint_0, token_mint_1, seed_index.to_le_bytes()]에서 파생됩니다. 전제 조건
  • seed_index != 0. seed_index가 0이면 레거시 풀용으로 예약되어 있으며 여기서 거부됩니다. [0, 0] 시드 구성요소는 레거시 풀 주소를 클래식 4시드 형태로 축소하는 것입니다.
  • payer에 대한 permission PDA가 존재합니다 (관리자가 CreatePermissionPda를 통해 생성).
  • CreatePool과 동일한 민트 / 허용 목록 규칙.
사후 조건
  • 새로운 pool_state가 seed_index 파생 주소에 존재하며, pool_state.seed_index = seed_index.
  • 다른 모든 사후 상태는 CreateCustomizablePool과 일치합니다 (수수료 모드, 선택적 동적 수수료).
이 명령어는 일반 풀 생성 액세스를 확대하지 않습니다. 권한 없는 생성은 CreatePool / CreateCustomizablePool을 통해 계속되며, 이는 쌍당 하나의 풀로 유지됩니다. CreatePermissionedPool은 화이트리스트된 운영자가 동일한 쌍에 대해 여러 풀이 필요한 경우 (예: 다른 초기 가격 또는 출시 코호트)와 관리자가 부여한 Permission PDA를 보유하는 특정 경우에 존재합니다.

OpenPositionV2 / OpenPositionWithToken22Nft

기존 풀 내에 새로운 포지션을 만듭니다. 인자
계정 — 두 변형은 다른 계정 목록을 가집니다. OpenPositionV2는 22개를 선언합니다. OpenPositionWithToken22Nft는 20개를 선언합니다. OpenPositionV2, 순서대로: OpenPositionWithToken22Nft는 metadata_account (5)와 metadata_program (19)이 제거된 동일한 목록입니다. 대신 NFT 민트의 Token-2022 메타데이터 확장을 통해 포지션의 메타데이터를 작성하므로 20개 계정을 제공합니다. 그 position_nft_mint는 베어 Signer이고 position_nft_account는 UncheckedAccount입니다.
tick_array_bitmap_extension은 두 변형 모두에서 선언된 계정이 아닙니다. 포지션의 범위가 풀의 인라인 tick_array_bitmap (±512 틱 배열) 외부에 있으면, TickArrayBitmapExtension PDA를 시드 ["pool_tick_array_bitmap_extension", pool_state]에서 remaining_accounts[0]으로 추가합니다.
수학 — products/clmm/math를 참조하세요. base_flag가 주어지면, 프로그램은 liquidity 또는 (amount_0_max, amount_1_max) 중 하나를 실제 L과 소비된 실제 토큰 금액으로 해결합니다. 전제 조건
  • tick_lower < tick_upper, 모두 pool.tick_spacing의 배수, [MIN_TICK, MAX_TICK] 내.
  • 두 개의 틱 배열 PDA가 전달됩니다. 이미 존재할 필요는 없습니다. get_or_create_tick_array는 이 명령어 내에서 payer의 비용으로 누락된 것을 할당합니다. 별도의 초기화 틱 배열 명령어는 없습니다.
  • 사용자는 소스 ATA에 최소 amount_0_max와 amount_1_max를 가지고 있습니다.
사후 조건
  • personal_position이 존재하고, liquidity가 설정되고, fee_growth_inside_last가 스냅샷됨.
  • tick_lower와 tick_upper의 틱 배열 항목이 업데이트됨 (liquidity_gross += L, liquidity_net ± L, 수수료 성장 스냅샷 유지).
  • 포지션이 범위 내에 있으면 pool_state.liquidity += L (tick_lower ≤ tick_current < tick_upper).
  • 포지션 NFT 민트는 pool_state를 동결 권한으로 기록합니다. 민트 권한은 단일 NFT가 발행된 후 제거됩니다. 동결 권한을 기록하는 것은 NFT 토큰 계정의 상태를 변경하지 않습니다.
  • NFT 토큰 계정은 명령어가 OpenPositionV2 또는 OpenPositionWithToken22Nft 이고 볼트 민트의 동결 권한이 CLMM의 제한된 발행자 목록과 일치하는 경우를 제외하고는 동결되지 않은 상태로 유지됩니다. 일치하는 V2 경로만 계정을 동결합니다. OpenPosition V1은 동결하지 않습니다.
일반적인 오류 — TickInvalidOrder (tick_lower >= tick_upper), TickAndSpacingNotMatch (끝점이 tick_spacing의 배수가 아님), InvalidTickIndex ([MIN_TICK, MAX_TICK] 외부), MissingTickArrayBitmapExtensionAccount (범위가 인라인 비트맵 외부이고 확장이 추가되지 않음), NotApproved (pool_state.status가 열기를 차단), ZeroAmountSpecified.
포지션 동결은 선언된 명령어 계정이나 인자를 추가하지 않습니다. 클라이언트는 기존 V2 레이아웃으로 이러한 포지션을 열 수 있습니다. 동작은 vault_0_mint와 vault_1_mint에서 온체인으로 선택됩니다.

IncreaseLiquidityV2

이미 열려 있는 포지션에 유동성을 추가합니다. 인자
계정 — 15개이며, OpenPosition에서 파생 불가능합니다. rent, system_program, associated_token_program 및 메타데이터 계정이 없습니다. 포지션의 범위가 인라인 비트맵 외부에 있으면 TickArrayBitmapExtension PDA를 remaining_accounts[0]으로 앞에 추가합니다. 효과
  • 사용자 → 볼트에서 amount_0_actual / amount_1_actual을 전송합니다.
  • personal_position.liquidity와 pool_state.liquidity (범위 내인 경우)를 증가시키고, 끝점 틱 liquidity_gross / liquidity_net을 그에 따라 증가시킵니다.
  • 마지막 터치 이후 미지급 수수료 및 보상을 수집하고 token_fees_owed_{0,1} / reward_amount_owed에 입금합니다. 이들은 DecreaseLiquidity / DecreaseLiquidityV2에서만 지급되며, 증가에서는 지급되지 않습니다. 독립형 수집 명령어는 없습니다.

DecreaseLiquidityV2

포지션에서 유동성을 제거합니다. 인자
계정 — 16개이며, IncreaseLiquidityV2와 동일한 형태나 순서가 아닙니다. personal_position과 pool_state가 교환되고, 볼트가 틱 배열 앞에 오고, 사용자 측 계정의 이름이 recipient_token_account_*이며, 추가 memo_program이 있습니다. 남은 계정 — 수집 중인 활성 보상당 3개씩, 순서대로 reward_token_vault(W), recipient_token_account(W), reward_vault_mint. 포지션의 범위가 인라인 비트맵 외부에 있으면 TickArrayBitmapExtension PDA를 앞에 추가합니다.
이것은 또한 수수료와 보상을 수집하는 유일한 방법입니다. 포지션을 변경하지 않고 수집하려면 liquidity = 0, amount_0_min = 0, amount_1_min = 0으로 호출합니다.
효과
  • 현재 sqrt_price_x64가 주어진 제거된 L에 대해 (amount_0, amount_1)을 계산합니다.
  • 마지막 터치 이후 발생한 수수료/보상을 정산합니다. IncreaseLiquidity와 동일합니다.
  • amount_0 + fees_owed_0과 amount_1 + fees_owed_1을 볼트에서 사용자에게 전송합니다.
  • 유동성 카운터를 감소시킵니다. 새로운 personal_position.liquidity == 0이면 포지션은 ClosePosition에 적합합니다.
슬리피지 — amount_0_min과 amount_1_min은 출력 측의 Token-2022 전송 수수료를 차감한 사용자가 수락하는 최소값입니다.

ClosePosition

포지션 NFT를 소각하고 PersonalPositionState를 닫습니다. 선언된 계정 남은 계정
  • 동결되지 않은 NFT: 필요 없음. 추가 풀 계정은 핸들러가 읽지 않으므로 무해합니다.
  • 동결된 NFT: personal_position.pool_id를 첫 번째 남은 계정으로 추가합니다. 프로그램은 이를 PoolState로 로드하고 해동에 서명하기 위해 PDA 시드를 사용합니다.
전제 조건
  • personal_position.liquidity == 0.
  • tokens_fees_owed_{0,1} == 0.
  • 모든 보상 카운터 reward_amount_owed == 0.
(즉, 먼저 모든 것을 수집하고 0으로 감소시킵니다.) 효과
  • NFT 토큰 계정이 동결되어 있으면, 첫 번째 남은 계정이 personal_position.pool_id와 같은지 확인한 다음 풀 PDA로 해동합니다.
  • NFT를 소각합니다.
  • NFT 토큰 계정과 personal_position을 닫고 임차료를 nft_owner에게 환불합니다. 포지션 NFT가 Token-2022를 사용하면 NFT 민트도 닫습니다. 클래식 SPL Token 민트는 닫을 수 없으며 공급량 0으로 유지됩니다.
해동, 소각 및 닫기는 원자적입니다. NFT는 이 단계 사이에 양도 가능해질 수 없습니다. 조건부 클라이언트 중단 — 선언된 IDL 레이아웃은 변경되지 않으므로 레거시 클라이언트는 기존 및 동결되지 않은 포지션을 계속 닫습니다. 풀 남은 계정을 생략하는 레거시 빌더는 동결된 포지션을 닫을 때 AccountLack으로 실패합니다. 모든 닫기에 대해 풀을 전달하는 것이 가장 간단한 호환 전략입니다.

SwapV2

유동성 곡선을 따라갑니다. is_base_input에 따라 정확한 입력 또는 정확한 출력. 인자
계정 (축약) 호출자는 예상 스왑 이동을 포함하는 순위가 지정된 틱 배열 목록을 전달합니다. 프로그램은 필요한 만큼 사용합니다. SDK는 PoolUtils.computeAmountOutFormat 또는 API의 견적 끝점을 통해 이 목록을 계산합니다. 전제 조건
  • pool_state.status가 스왑을 허용합니다.
  • now >= open_time.
  • sqrt_price_limit_x64는 방향에 대해 sqrt_price_x64의 올바른 쪽에 있습니다.
일반적인 오류 — TooLittleOutputReceived (정확한 입력 슬리피지), TooMuchInputPaid (정확한 출력 슬리피지), SqrtPriceLimitOverflow, NotEnoughTickArrayAccount, InvalidFirstTickArrayAccount, MissingTickArrayBitmapExtensionAccount, LiquidityInsufficient, NotApproved (스왑 비트가 pool_state.status에 설정됨). CLMM에는 ExceededSlippage 변형이 없습니다. 그것은 CPMM의 이름이며, TickArrayNotFound도 없습니다. SwapV2가 호출자가 알아야 할 내부적으로 수행하는 것 (2025년 이후 릴리스):
  1. 동적 수수료 할증 — pool.dynamic_fee_info가 0이 아니면, 프로그램은 마지막 스왑 이후 순회한 틱 거리를 사용하여 변동성 누적기를 업데이트하고 (필터/감소 규칙 포함 products/clmm/fees) AmmConfig.trade_fee_rate 위에 dynamic_fee_component를 추가합니다. 총 수수료는 10% (MAX_FEE_RATE_NUMERATOR / 1_000_000)로 제한됩니다.
  2. 지정가 주문 매칭 — 가격 이동이 공개 지정가 주문을 보유한 틱을 지날 때, 프로그램은 먼저 해당 틱에서 사용 가능한 지정가 주문 유동성을 채웁니다 (FIFO by order_phase), 그 다음 LP 유동성 곡선을 따라갑니다. 채워진 금액은 나중의 정산을 위해 tick.unfilled_ratio_x64와 tick.part_filled_orders_remaining을 업데이트합니다. 주문 자체는 소유자가 SettleLimitOrder를 호출할 때까지 미지급 상태로 유지됩니다.
  3. 단측 수수료 라우팅 — pool.fee_on = Token0Only 또는 Token1Only일 때, 스왑 단계는 여전히 동일한 입력 출력 거래를 계산합니다. 수수료는 구성된 쪽으로 라우팅됩니다. 구성된 수수료 쪽이 출력인 방향의 경우, 수수료는 스왑 출력에서 공제됩니다 (사용자는 out − fee를 받음). 입력인 방향의 경우, 동작은 FromInput과 일치합니다. PoolState의 is_fee_on_input(zero_for_one)과 is_fee_on_token0(zero_for_one)을 참조하세요.
Swap (V1)은 SwapV2와 동일한 동적 수수료, 단측 수수료 라우팅 및 지정가 주문 매칭을 구현합니다. 유일하게 부족한 기능은 Token-2022 지원입니다. 두 볼트 모두 클래식 SPL Token이어야 합니다. Token-2022 민트가 있는 풀은 SwapV2를 통해 스왑해야 합니다. 애그리게이터와 SDK는 이미 모든 CLMM 레그에 대해 V2를 선호하므로 호출자는 민트 유형에 따라 분기할 필요가 없습니다.

OpenLimitOrder

특정 틱에서 판매 주문을 배치합니다. 주문은 틱별 FIFO 코호트에 앉아 있으며 가격이 지나갈 때 채워집니다. 인자
계정 (축약) 남은 계정 — [0] tick_array_bitmap_extension, 시작 인덱스가 풀의 인라인 비트맵 외부에 있는 틱 배열을 초기화할 때만 필요합니다. 그렇지 않으면 아무것도 전달하지 마세요.
rent 계정이 없습니다. 구조는 system_program에서 끝나며 13개의 선언된 계정입니다. 위치 14의 잘못된 rent는 선택적 비트맵 확장이 읽히는 정확한 위치에 착지하므로 주문은 인라인 비트맵 외부의 틱 배열을 처음 초기화해야 할 때까지 작동하는 것처럼 보입니다. 그 다음 실패합니다.
계정 목록 변경 (2026-07 릴리스). OpenLimitOrder는 이제 입력 측에 추가로 출력 측 계정 — output_token_account, output_vault, output_vault_mint —도 사용합니다. 이들은 검증에만 사용됩니다. 프로그램은 소유자의 입력 또는 출력 토큰 계정이 동결되어 있으면 주문을 거부합니다. 이는 채우기가 실제로 소유자의 출력 ATA로 정산될 수 있음을 보장하며, 이는 허용 목록 / 기본 동결 Token-2022 민트 (예: 권한 있는 토큰)에 중요합니다. 여기서 계정이 아직 해동되지 않았을 수 있습니다. 이전 단측 계정 목록에 대해 구축된 클라이언트는 3개의 출력 계정을 추가해야 합니다.
전제 조건
  • input_token_account와 output_token_account 모두 동결되지 않음 (그렇지 않으면 NotApproved).
  • pool_state.status가 스왑 (비트 4)과 지정가 주문 (비트 5) 작업을 모두 허용함 (그렇지 않으면 NotApproved).
  • tick_index % pool.tick_spacing == 0이고 [MIN_TICK, MAX_TICK] 내.
  • tick_index는 선택된 방향에 대해 pool.tick_current의 올바른 쪽에 있습니다 (토큰0 판매 → 틱은 현재 위에 있어야 하며, 그 반대도 마찬가지). 이미 지나간 틱에서 판매하면 즉시 매칭되며 거부됩니다.
사후 조건
  • limit_order가 존재하고, 열기 시간에 tick.order_phase와 tick.unfilled_ratio_x64를 스냅샷합니다.
  • tick.orders_amount += amount (현재 코호트에서).
  • limit_order_nonce.order_nonce += 1.
  • OpenLimitOrderEvent 발행됨.
일반적인 오류 — NotApproved (입력 또는 출력 토큰 계정 동결, 또는 풀이 스왑 / 지정가 주문 비활성화), ZeroAmountSpecified (입력 측 전송 수수료 후 amount == 0), InvalidLimitOrderAmount (금액이 해당 틱에서 1 기본 단위 미만의 출력을 생성하거나 u64 오버플로우), InvalidTickIndex ([MIN_TICK, MAX_TICK] 외부, 또는 선택된 방향에 대해 tick_current의 잘못된 쪽), TickAndSpacingNotMatch (tick_index % pool.tick_spacing != 0), OrderPhaseSaturated.

IncreaseLimitOrder

기존 공개 주문에 추가합니다. 주문의 owner만 호출할 수 있습니다. 인자
계정 — 8개, 입력 측만. 논스 계정, 3개의 출력 측 계정 및 system_program을 모두 제거합니다. 전제 조건
  • limit_order.owner == signer.
  • 주문은 여전히 동일한 코호트에 있습니다 (tick.order_phase == limit_order.order_phase). 코호트가 이미 채우기를 시작했으면 주문이 부분적으로 정산됩니다. 호출자는 먼저 DecreaseLimitOrder 또는 SettleLimitOrder를 호출하여 앞으로 이동해야 합니다.
효과
  • 소유자 ATA에서 input_vault로 amount를 전송합니다.
  • limit_order.total_amount += amount. tick.orders_amount += amount.

DecreaseLimitOrder

공개 주문을 줄이거나 완전히 취소합니다. 미체결 나머지를 소유자에게 지급하고, 과거 부분 채우기로 이미 정산된 출력도 지급합니다. 인자
계정 — 입력 및 출력 토큰 측: