이 페이지는 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
인자
전제 조건
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는 선언된 계정이 아닙니다.
전제 조건 — 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에 대한permissionPDA가 존재합니다 (관리자가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입니다.
수학 — 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 경로만 계정을 동결합니다.OpenPositionV1은 동결하지 않습니다.
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
이미 열려 있는 포지션에 유동성을 추가합니다.
인자
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
포지션에서 유동성을 제거합니다.
인자
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.
- NFT 토큰 계정이 동결되어 있으면, 첫 번째 남은 계정이
personal_position.pool_id와 같은지 확인한 다음 풀 PDA로 해동합니다. - NFT를 소각합니다.
- NFT 토큰 계정과
personal_position을 닫고 임차료를nft_owner에게 환불합니다. 포지션 NFT가 Token-2022를 사용하면 NFT 민트도 닫습니다. 클래식 SPL Token 민트는 닫을 수 없으며 공급량 0으로 유지됩니다.
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년 이후 릴리스):
- 동적 수수료 할증 —
pool.dynamic_fee_info가 0이 아니면, 프로그램은 마지막 스왑 이후 순회한 틱 거리를 사용하여 변동성 누적기를 업데이트하고 (필터/감소 규칙 포함products/clmm/fees)AmmConfig.trade_fee_rate위에dynamic_fee_component를 추가합니다. 총 수수료는 10% (MAX_FEE_RATE_NUMERATOR / 1_000_000)로 제한됩니다. - 지정가 주문 매칭 — 가격 이동이 공개 지정가 주문을 보유한 틱을 지날 때, 프로그램은 먼저 해당 틱에서 사용 가능한 지정가 주문 유동성을 채웁니다 (FIFO by
order_phase), 그 다음 LP 유동성 곡선을 따라갑니다. 채워진 금액은 나중의 정산을 위해tick.unfilled_ratio_x64와tick.part_filled_orders_remaining을 업데이트합니다. 주문 자체는 소유자가SettleLimitOrder를 호출할 때까지 미지급 상태로 유지됩니다. - 단측 수수료 라우팅 —
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, 시작 인덱스가 풀의 인라인 비트맵 외부에 있는 틱 배열을 초기화할 때만 필요합니다. 그렇지 않으면 아무것도 전달하지 마세요.
계정 목록 변경 (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만 호출할 수 있습니다.
인자
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.

