> ## Documentation Index
> Fetch the complete documentation index at: https://docs.raydium.io/llms.txt
> Use this file to discover all available pages before exploring further.

# CPMM 코드 데모

> 풀 생성, 스왑, 입금, 출금, 수수료 수집 등 모든 CPMM 작업을 위한 엔드-투-엔드 TypeScript 예제 및 최소한의 Rust CPI 스켈레톤입니다.

<Info>
  **이 페이지는 AI 자동 번역입니다. 모든 내용은 영문판을 기준으로 합니다.**

  [영문판 보기 →](/products/cpmm/code-demos)
</Info>

<Info>
  **버전 배너.** 모든 TypeScript 데모는 Solana mainnet-beta에 대해 `@raydium-io/raydium-sdk-v2@0.2.42-alpha`을 대상으로 하며, 2026년 4월에 검증되었습니다. Rust CPI 스켈레톤은 `raydium-cp-swap`의 `master` 브랜치를 대상으로 하며 Anchor `0.30.x`입니다. 프로그램 ID는 [`reference/program-addresses`](/ko/reference/program-addresses)의 상수에서 가져옵니다.
</Info>

## 필수 사항

```bash theme={null}
npm install @raydium-io/raydium-sdk-v2 @solana/web3.js @solana/spl-token bn.js decimal.js
```

이 페이지의 모든 데모는 [`raydium-sdk-V2-demo/src/cpmm`](https://github.com/raydium-io/raydium-sdk-V2-demo/tree/master/src/cpmm)의 파일을 반영합니다. 각 섹션 옆에 GitHub 링크가 있습니다. 부트스트랩은 데모 저장소의 `config.ts.template` ([소스](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/config.ts.template))을 따릅니다:

```ts theme={null}
import { Connection, Keypair, clusterApiUrl } from "@solana/web3.js";
import { Raydium, TxVersion } from "@raydium-io/raydium-sdk-v2";
import fs from "node:fs";

const connection = new Connection(process.env.RPC_URL ?? clusterApiUrl("mainnet-beta"));
const owner = Keypair.fromSecretKey(
  new Uint8Array(JSON.parse(fs.readFileSync(process.env.KEYPAIR!, "utf8"))),
);

const raydium = await Raydium.load({
  owner,
  connection,
  cluster: "mainnet",
  disableFeatureCheck: true,
  blockhashCommitment: "finalized",
});
```

`Raydium` 인스턴스는 SDK의 파사드입니다. 아래의 모든 데모에서 사용됩니다. 이 인스턴스는 `api-v3.raydium.io`에서 토큰 목록과 수수료 설정을 느리게 가져옵니다. 오프라인 환경에서는 자체 데이터로 시드할 수 있습니다.

## CPMM 풀 생성

소스: [`src/cpmm/createCpmmPool.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/cpmm/createCpmmPool.ts)

```ts theme={null}
import { PublicKey } from "@solana/web3.js";
import BN from "bn.js";
import { getCpmmPdas, CREATE_CPMM_POOL_PROGRAM, CREATE_CPMM_POOL_FEE_ACC }
  from "@raydium-io/raydium-sdk-v2";

const mintA = new PublicKey("So11111111111111111111111111111111111111112"); // wSOL
const mintB = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"); // USDC

// 1. 수수료 설정을 선택합니다. index=0은 0.25% 티어입니다.
const feeConfigs = await raydium.api.getCpmmConfigs();
const feeConfig  = feeConfigs.find((c) => c.index === 0)!;

// 2. 민트 메타데이터를 가져오면 SDK가 Token-2022 확장을 처리할 수 있습니다.
const mintAInfo = await raydium.token.getTokenInfo(mintA);
const mintBInfo = await raydium.token.getTokenInfo(mintB);

// 3. 트랜잭션을 빌드합니다.
const { execute, extInfo } = await raydium.cpmm.createPool({
  programId:       CREATE_CPMM_POOL_PROGRAM,
  poolFeeAccount:  CREATE_CPMM_POOL_FEE_ACC,
  mintA:           mintAInfo,
  mintB:           mintBInfo,
  mintAAmount:     new BN(1_000_000_000),   // 1 SOL (9자리 소수 가정)
  mintBAmount:     new BN(   160_000_000),  // 160 USDC (SOL당 160)
  startTime:       new BN(0),               // 즉시 개설
  feeConfig,
  associatedOnly:  false,
  ownerInfo:       { useSOLBalance: true },
  txVersion:       TxVersion.V0,
});

const { txId } = await execute({ sendAndConfirm: true });
console.log("Pool created at", extInfo.address.poolId.toBase58());
console.log("Tx:", txId);
```

SDK가 자동으로 처리하는 몇 가지 작업:

* 풀 생성 전에 민트를 token0/token1 순서로 정렬합니다.
* 일회성 `create_pool_fee`를 `poolFeeAccount`에 지불합니다.
* 호출자의 연결된 토큰 계정이 누락된 경우 생성합니다.
* 각 쪽에 맞는 토큰 프로그램(SPL Token 또는 Token-2022)을 선택합니다.

확인 후 다음을 사용하여 실시간 풀 상태를 가져올 수 있습니다:

```ts theme={null}
const { poolKeys, poolInfo, rpcData } = await raydium.cpmm.getPoolInfoFromRpc(
  extInfo.address.poolId,
);
```

## 스왑 (기본 입력)

소스: [`src/cpmm/swap.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/cpmm/swap.ts)

```ts theme={null}
import { CurveCalculator } from "@raydium-io/raydium-sdk-v2";

const poolId = new PublicKey("<POOL_ID>");

// 1. RPC에서 직접 현재 풀 상태를 로드합니다 (API에서 로드하지 않음).
const { poolInfo, poolKeys, rpcData } = await raydium.cpmm.getPoolInfoFromRpc(poolId);

const inputMint  = new PublicKey(poolInfo.mintA.address); // A → B로 스왑
const amountIn   = new BN(100_000_000);                   // 0.1 SOL
const slippage   = 0.005;                                 // 0.5%

// 2. 로컬에서 호가를 계산합니다. SDK의 CurveCalculator는 온체인 로직을 반영하며
//    양쪽의 Token-2022 전송 수수료를 포함합니다.
const baseIn = inputMint.equals(new PublicKey(poolInfo.mintA.address));
const swapResult = CurveCalculator.swap(
  amountIn,
  baseIn ? rpcData.baseReserve : rpcData.quoteReserve,
  baseIn ? rpcData.quoteReserve : rpcData.baseReserve,
  rpcData.configInfo!.tradeFeeRate,
);
const minimumAmountOut =
  swapResult.destinationAmountSwapped.muln(1 - slippage * 100).divn(100);

// 3. 빌드 및 전송합니다.
const { execute } = await raydium.cpmm.swap({
  poolInfo,
  poolKeys,
  inputAmount: amountIn,
  swapResult,
  slippage,
  baseIn,
  txVersion: TxVersion.V0,
});

const { txId } = await execute({ sendAndConfirm: true });
console.log("Swap tx:", txId);
```

참고: SDK는 **항상** `getPoolInfoFromRpc` 내에서 RPC에서 풀 상태를 다시 가져옵니다. 서명하려는 트랜잭션에 대해 `api-v3.raydium.io`에서 호가를 제시하지 마세요. 한 블록 오래된 호가는 착지 시간에 `ExceededSlippage`로 빠질 수 있습니다.

## 스왑 (기본 출력)

소스: [`src/cpmm/swapBaseOut.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/cpmm/swapBaseOut.ts)

```ts theme={null}
const amountOutWanted = new BN(15_000_000);        // 15 USDC
const slippage        = 0.005;

const baseIn = false; // B가 입력, A가 출력? 방향에 따라 다름
const swapResult = CurveCalculator.swapBaseOutput(
  amountOutWanted,
  rpcData.baseReserve,
  rpcData.quoteReserve,
  rpcData.configInfo!.tradeFeeRate,
);
const maxAmountIn = swapResult.sourceAmountSwapped.muln(1 + slippage * 100).divn(100);

const { execute } = await raydium.cpmm.swap({
  poolInfo,
  poolKeys,
  inputAmount: maxAmountIn,
  fixedOut:    true,
  amountOut:   amountOutWanted,
  baseIn,
  slippage,
  txVersion:   TxVersion.V0,
});

await execute({ sendAndConfirm: true });
```

## 유동성 입금

소스: [`src/cpmm/deposit.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/cpmm/deposit.ts)

```ts theme={null}
const lpAmount = new BN(100_000);           // 원하는 LP 민트 수량
const slippage = 0.01;

const { execute } = await raydium.cpmm.addLiquidity({
  poolInfo,
  poolKeys,
  lpAmount,
  slippage,
  baseIn: true,           // mintA 쪽에서 호가 제시
  txVersion: TxVersion.V0,
});

await execute({ sendAndConfirm: true });
```

SDK는 풀의 현재 준비금을 사용하여 `lpAmount`를 `needed_token_0`과 `needed_token_1`로 변환하고, 명령어의 `maximum_*` 인수에 대해 각각을 `1 + slippage`로 부풀리며, 필요한 경우 ATA 생성을 빌드합니다.

## 유동성 출금

소스: [`src/cpmm/withdraw.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/cpmm/withdraw.ts)

```ts theme={null}
const lpAmount = new BN(100_000);           // 소각할 LP
const slippage = 0.01;

const { execute } = await raydium.cpmm.withdrawLiquidity({
  poolInfo,
  poolKeys,
  lpAmount,
  slippage,
  txVersion: TxVersion.V0,
});

await execute({ sendAndConfirm: true });
```

## 프로토콜/펀드/크리에이터 수수료 수집

소스: [`src/cpmm/collectCreatorFee.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/cpmm/collectCreatorFee.ts), [`src/cpmm/collectAllCreatorFee.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/cpmm/collectAllCreatorFee.ts)

이 명령어는 관리자 또는 크리에이터가 제어하며 일반적으로 Raydium 멀티시그 또는 풀 크리에이터가 보유한 서명자로부터 호출됩니다. SDK는 이를 원시 빌더로 노출합니다:

```ts theme={null}
import {
  makeCollectProtocolFeeInstruction,
  makeCollectFundFeeInstruction,
  makeCollectCreatorFeeInstruction,
} from "@raydium-io/raydium-sdk-v2";

// PDA 및 권한은 풀 생성 시 설정되었습니다. reference/program-addresses를 참조하세요
// 정규 시드의 경우. SDK는 선호할 경우 헬퍼를 노출합니다.
```

오프체인에서 `PoolState`에서 누적된 수수료를 직접 읽을 수 있습니다:

```ts theme={null}
const pool = await raydium.cpmm.getRpcPoolInfo(poolId);
console.log("Accrued protocol fee token0:", pool.protocolFeesToken0.toString());
console.log("Accrued protocol fee token1:", pool.protocolFeesToken1.toString());
```

## Rust CPI 스켈레톤

자신의 Anchor 프로그램에서 CPMM을 호출하려면 (예: 예치자를 대신하여 스왑하는 볼트), CPI 컨텍스트는 다음과 같습니다. 계정 순서는 [`products/cpmm/instructions`](/ko/products/cpmm/instructions)를 따릅니다.

```rust theme={null}
// Cargo.toml
// raydium-cp-swap = { git = "https://github.com/raydium-io/raydium-cp-swap" }
// anchor-spl       = "0.30"

use anchor_lang::prelude::*;
use anchor_spl::token_interface::{TokenAccount, TokenInterface, Mint};
use raydium_cp_swap::cpi::accounts::Swap;
use raydium_cp_swap::cpi;
use raydium_cp_swap::program::RaydiumCpSwap;

#[derive(Accounts)]
pub struct ProxySwap<'info> {
    #[account(mut)]
    pub payer: Signer<'info>,

    /// CHECK: validated by the CPMM program
    pub authority:     UncheckedAccount<'info>,
    /// CHECK:
    pub amm_config:    UncheckedAccount<'info>,
    #[account(mut)]
    /// CHECK:
    pub pool_state:    UncheckedAccount<'info>,

    #[account(mut)]
    pub input_token_account:  InterfaceAccount<'info, TokenAccount>,
    #[account(mut)]
    pub output_token_account: InterfaceAccount<'info, TokenAccount>,

    #[account(mut)]
    pub input_vault:  InterfaceAccount<'info, TokenAccount>,
    #[account(mut)]
    pub output_vault: InterfaceAccount<'info, TokenAccount>,

    pub input_token_program:  Interface<'info, TokenInterface>,
    pub output_token_program: Interface<'info, TokenInterface>,

    pub input_token_mint:  InterfaceAccount<'info, Mint>,
    pub output_token_mint: InterfaceAccount<'info, Mint>,

    #[account(mut)]
    /// CHECK: ring buffer
    pub observation_state: UncheckedAccount<'info>,

    pub cpmm_program: Program<'info, RaydiumCpSwap>,
}

pub fn proxy_swap_base_input(
    ctx: Context<ProxySwap>,
    amount_in: u64,
    minimum_amount_out: u64,
) -> Result<()> {
    let cpi_accounts = Swap {
        payer:                ctx.accounts.payer.to_account_info(),
        authority:            ctx.accounts.authority.to_account_info(),
        amm_config:           ctx.accounts.amm_config.to_account_info(),
        pool_state:           ctx.accounts.pool_state.to_account_info(),
        input_token_account:  ctx.accounts.input_token_account.to_account_info(),
        output_token_account: ctx.accounts.output_token_account.to_account_info(),
        input_vault:          ctx.accounts.input_vault.to_account_info(),
        output_vault:         ctx.accounts.output_vault.to_account_info(),
        input_token_program:  ctx.accounts.input_token_program.to_account_info(),
        output_token_program: ctx.accounts.output_token_program.to_account_info(),
        input_token_mint:     ctx.accounts.input_token_mint.to_account_info(),
        output_token_mint:    ctx.accounts.output_token_mint.to_account_info(),
        observation_state:    ctx.accounts.observation_state.to_account_info(),
    };
    let cpi_ctx = CpiContext::new(
        ctx.accounts.cpmm_program.to_account_info(),
        cpi_accounts,
    );
    cpi::swap_base_input(cpi_ctx, amount_in, minimum_amount_out)
}
```

CPI가 PDA로 서명하는 경우 (예: 예치자를 대신하여 볼트를 관리하는 경우), `CpiContext::new`를 `CpiContext::new_with_signer`로 바꾸고 시드를 전달합니다.

## 일반적인 함정

지원 티켓을 열기 전에 확인할 짧은 체크리스트:

* **정렬된 민트.** 파생된 `poolState` PDA가 온체인 풀과 일치하지 않으면 민트를 정렬하는 것을 잊었을 가능성이 높습니다.
* **오래된 API 호가.** `api-v3.raydium.io`에서의 준비금 값을 `CurveCalculator.swap`에 절대 전달하지 마세요. RPC에서 가져오세요.
* **잘못된 토큰 프로그램.** Token-2022 민트의 금고는 SPL Token이 아닌 Token-2022 프로그램이 소유합니다. 항상 풀의 `token_0_program` / `token_1_program` 필드를 사용하세요.
* **전송 수수료 민트에 대해 과소 계산된 슬리피지.** 풀의 어느 쪽이든 Token-2022 전송 수수료 민트인 경우, `minimum_amount_out`은 금고가 보내는 것이 아니라 사용자가 실제로 받는 것으로 계산되어야 합니다.
* **스왑에서 `NotApproved`.** `PoolState.status`를 확인하세요. 관리자가 해당 풀에서 스왑을 일시 중지했을 수 있습니다. 상태 비트마스크는 [`products/cpmm/instructions`](/ko/products/cpmm/instructions)를 참조하세요.

## 다음으로 이동

* [`sdk-api/typescript-sdk`](/ko/sdk-api/typescript-sdk) — 전체 SDK 참조입니다.
* [`sdk-api/rest-api`](/ko/sdk-api/rest-api) — 호가 및 풀 메타데이터 엔드포인트입니다.
* [`user-flows/create-cpmm-pool`](/ko/user-flows/create-cpmm-pool) — 동일한 흐름의 비코드 워크스루입니다.
* [`integration-guides/aggregator`](/ko/integration-guides/aggregator) — CPMM을 다중 홉 경로의 일부로 라우팅하는 경우입니다.

소스:

* [Raydium SDK v2 — `@raydium-io/raydium-sdk-v2`](https://github.com/raydium-io/raydium-sdk-V2)
* [Raydium SDK v2 데모 — `raydium-sdk-v2-demo`](https://github.com/raydium-io/raydium-sdk-V2-demo)
* [`raydium-io/raydium-cp-swap`](https://github.com/raydium-io/raydium-cp-swap)
