> ## 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.

# AMM v4 코드 데모

> 기존 AMM v4 풀에 대한 스왑 및 유동성 제공을 위한 TypeScript 예제. 새 풀 생성은 범위를 벗어남 — 대신 CPMM을 사용하세요.

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

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

<Info>
  **버전 배너.** 모든 데모는 Solana mainnet-beta에 대해 `@raydium-io/raydium-sdk-v2@0.2.42-alpha`를 대상으로 하며, 2026년 4월에 확인됨. 프로그램 ID: `675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8` ([`reference/program-addresses`](/ko/reference/program-addresses) 참조).
</Info>

<Info>
  **새 풀 생성은 여기에 표시되지 않습니다.** Raydium UI는 더 이상 AMM v4 풀 생성을 제공하지 않습니다 — 새 페어는 기본적으로 [CPMM](/ko/products/cpmm/code-demos)으로 설정됩니다. AMM v4 프로그램 자체는 여전히 온체인에서 `Initialize2`를 허용하지만, 권장 경로는 아닙니다. 아래 데모는 모든 통합자가 여전히 필요로 하는 라이브 풀 작업을 다룹니다: 스왑, 입금, 출금.
</Info>

## 설정

```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" });
```

## ID로 풀 조회

```ts theme={null}
import { PublicKey } from "@solana/web3.js";

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

// SDK 정규화된 풀 객체를 가져옵니다. AMM v4의 경우 이는 명령어 빌더가
// 필요로 하는 OpenBook 계정을 포함합니다.
const data = await raydium.liquidity.getPoolInfoFromRpc({ poolId });
const { poolInfo, poolKeys, poolRpcData } = data;

console.log("Pair:", poolInfo.mintA.symbol, "/", poolInfo.mintB.symbol);
console.log("Version:", poolInfo.version);       // AMM v4의 경우 4
console.log("Market:", poolKeys.marketId.toBase58());
```

`poolKeys`는 명령어 빌더가 사용하는 구조체입니다. 프로그램이 예상하는 순서대로 모든 AMM v4 및 OpenBook 계정을 포함합니다.

## 스왑 (베이스 인)

```ts theme={null}
import BN from "bn.js";

const amountIn = new BN(1_000_000);            // 1 USDC (6자리 쿼트)
const inputMint = new PublicKey(poolInfo.mintB.address);  // USDC
const slippage  = 0.005;

const computed = raydium.liquidity.computeAmountOut({
  poolInfo,
  amountIn,
  mintIn: inputMint,
  mintOut: new PublicKey(poolInfo.mintA.address),
  slippage,
});

const { execute } = await raydium.liquidity.swap({
  poolInfo,
  poolKeys,
  amountIn,
  amountOut: computed.minAmountOut,
  fixedSide: "in",
  inputMint,
  txVersion: TxVersion.V0,
});

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

SDK는 모든 OpenBook 계정을 자동으로 추가합니다. 이를 수동으로 대체하려고 시도하지 마세요 — 프로그램이 모든 슬롯을 검증합니다.

## 스왑 (베이스 아웃)

```ts theme={null}
const amountOut = new BN(1_000_000_000);       // 1 SOL (9자리 베이스)
const slippage  = 0.005;

const computed = raydium.liquidity.computeAmountIn({
  poolInfo,
  amountOut,
  mintOut: new PublicKey(poolInfo.mintA.address),
  mintIn: new PublicKey(poolInfo.mintB.address),
  slippage,
});

const { execute } = await raydium.liquidity.swap({
  poolInfo,
  poolKeys,
  amountIn: computed.maxAmountIn,
  amountOut,
  fixedSide: "out",
  inputMint: new PublicKey(poolInfo.mintB.address),
  txVersion: TxVersion.V0,
});

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

## 유동성 추가

```ts theme={null}
const amountA = new BN(100_000_000);           // 0.1 SOL

const { anotherAmount, maxAnotherAmount } = raydium.liquidity.computePairAmount({
  poolInfo,
  amount: amountA,
  baseIn: true,
  slippage: 0.01,
});

const { execute } = await raydium.liquidity.addLiquidity({
  poolInfo,
  poolKeys,
  amountInA: amountA,
  amountInB: maxAnotherAmount,
  fixedSide: "a",
  txVersion: TxVersion.V0,
});

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

`fixedSide: "a"`는 정확한 `amountInA`를 공급했으며 `amountInB`는 최대 `maxAnotherAmount`여야 함을 SDK에 알립니다. 풀의 온체인 유동성은 비례 수학 이전에 정산되므로 예금 비율이 최신 보유량과 일치합니다.

## 유동성 제거

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

const { execute } = await raydium.liquidity.removeLiquidity({
  poolInfo,
  poolKeys,
  lpAmount,
  baseAmountMin: new BN(0),
  quoteAmountMin: new BN(0),
  txVersion: TxVersion.V0,
});

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

슬리피지 최소값은 사전 견적과 도착 시간 사이에 풀의 상태가 변하는 것으로부터 보호합니다.

## 컴퓨트 유닛 / 우선 수수료 조정

AMM v4 스왑은 모든 명령어가 전체 OpenBook 상태를 검증하기 때문에 컴퓨트 집약적입니다. 일반적인 스왑은 프로세스에서 정산해야 할 미결 주문의 수에 따라 180k–250k CU를 사용합니다. 항상 컴퓨트 유닛 한계를 전달하세요:

```ts theme={null}
import { ComputeBudgetProgram } from "@solana/web3.js";

const { execute, innerTransactions } = await raydium.liquidity.swap({
  /* ...params... */
  computeBudgetConfig: {
    units: 400_000,
    microLamports: 50_000,       // 우선 수수료
  },
});
```

`computeBudgetConfig`를 생략하면 SDK는 여전히 자체 기본값을 사용할 수 있습니다. `innerTransactions`을 검토하여 확인하세요. [`integration-guides/priority-fee-tuning`](/ko/integration-guides/priority-fee-tuning)을 참조하세요.

## 직접 Rust CPI

자신의 Anchor 프로그램에서 AMM v4로 CPI를 수행해야 하는 경우, `SwapBaseIn`의 계정 목록을 정확히 모델링해야 합니다. 최소한의 스케치:

```rust theme={null}
use anchor_lang::prelude::*;
use anchor_lang::solana_program::program::invoke_signed;
use anchor_lang::solana_program::instruction::Instruction;

const AMM_V4_PROGRAM_ID: Pubkey = pubkey!("675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8");

#[derive(Accounts)]
pub struct ProxyAmmV4Swap<'info> {
    /// CHECK:
    pub token_program: UncheckedAccount<'info>,
    #[account(mut)] /// CHECK:
    pub amm:          UncheckedAccount<'info>,
    /// CHECK:
    pub amm_authority: UncheckedAccount<'info>,
    #[account(mut)] /// CHECK:
    pub amm_open_orders: UncheckedAccount<'info>,
    #[account(mut)] /// CHECK:
    pub amm_target_orders: UncheckedAccount<'info>,
    #[account(mut)] /// CHECK:
    pub pool_coin_token_account: UncheckedAccount<'info>,
    #[account(mut)] /// CHECK:
    pub pool_pc_token_account: UncheckedAccount<'info>,
    /// CHECK:
    pub market_program: UncheckedAccount<'info>,
    #[account(mut)] /// CHECK:
    pub market: UncheckedAccount<'info>,
    #[account(mut)] /// CHECK:
    pub market_bids: UncheckedAccount<'info>,
    #[account(mut)] /// CHECK:
    pub market_asks: UncheckedAccount<'info>,
    #[account(mut)] /// CHECK:
    pub market_event_queue: UncheckedAccount<'info>,
    #[account(mut)] /// CHECK:
    pub market_coin_vault: UncheckedAccount<'info>,
    #[account(mut)] /// CHECK:
    pub market_pc_vault: UncheckedAccount<'info>,
    /// CHECK:
    pub market_vault_signer: UncheckedAccount<'info>,
    #[account(mut)] /// CHECK:
    pub user_source: UncheckedAccount<'info>,
    #[account(mut)] /// CHECK:
    pub user_dest: UncheckedAccount<'info>,
    pub user_owner: Signer<'info>,
}

pub fn proxy_swap(
    ctx: Context<ProxyAmmV4Swap>,
    amount_in: u64,
    minimum_amount_out: u64,
) -> Result<()> {
    // SwapBaseIn의 명령어 판별자는 AMM v4에서 9입니다.
    let mut data = vec![9u8];
    data.extend_from_slice(&amount_in.to_le_bytes());
    data.extend_from_slice(&minimum_amount_out.to_le_bytes());

    let ix = Instruction {
        program_id: AMM_V4_PROGRAM_ID,
        accounts: vec![
            AccountMeta::new_readonly(ctx.accounts.token_program.key(), false),
            AccountMeta::new(ctx.accounts.amm.key(), false),
            AccountMeta::new_readonly(ctx.accounts.amm_authority.key(), false),
            AccountMeta::new(ctx.accounts.amm_open_orders.key(), false),
            AccountMeta::new(ctx.accounts.amm_target_orders.key(), false),
            AccountMeta::new(ctx.accounts.pool_coin_token_account.key(), false),
            AccountMeta::new(ctx.accounts.pool_pc_token_account.key(), false),
            AccountMeta::new_readonly(ctx.accounts.market_program.key(), false),
            AccountMeta::new(ctx.accounts.market.key(), false),
            AccountMeta::new(ctx.accounts.market_bids.key(), false),
            AccountMeta::new(ctx.accounts.market_asks.key(), false),
            AccountMeta::new(ctx.accounts.market_event_queue.key(), false),
            AccountMeta::new(ctx.accounts.market_coin_vault.key(), false),
            AccountMeta::new(ctx.accounts.market_pc_vault.key(), false),
            AccountMeta::new_readonly(ctx.accounts.market_vault_signer.key(), false),
            AccountMeta::new(ctx.accounts.user_source.key(), false),
            AccountMeta::new(ctx.accounts.user_dest.key(), false),
            AccountMeta::new_readonly(ctx.accounts.user_owner.key(), true),
        ],
        data,
    };
    invoke_signed(&ix, &ctx.accounts.to_account_infos(), &[])?;
    Ok(())
}
```

AMM v4는 CPI용 Anchor 크레이트를 제공하지 않습니다. 위의 스케치는 수동으로 구성된 `Instruction`을 사용합니다.

## 함정

* **OpenBook 계정 누락.** 모든 8개의 OpenBook 계정은 모든 스왑, 입금 및 출금에 필요합니다. SDK는 이를 처리하지만 수동으로 구성된 명령어는 종종 그렇지 않습니다.
* **원본 볼트 잔액 읽기.** 온체인 예탁 금액이나 누적된 손익을 반영하지 않습니다. SDK의 견적 또는 `api-v3.raydium.io/pools/info/ids`를 사용하세요.
* **OpenBook 이벤트 큐 가득 찬 상태.** 풀이 시장의 이벤트 큐가 크랭킹되어야 할 때 `SerumOrderError`로 스왑을 되돌릴 수 있습니다. 크랭킹은 무허가(시장의 OpenBook 계정에서 `MonitorStep`)입니다.
* **Token-2022 민트.** 지원되지 않습니다. AMM v4 풀은 Token-2022 민트에 대해 생성될 수 없습니다. 모든 Token-2022 페어는 CPMM 또는 CLMM에 있어야 합니다.

## 다음으로 이동할 위치

* [`products/amm-v4/instructions`](/ko/products/amm-v4/instructions) — 이러한 데모 뒤의 명령어 수준 인터페이스.
* [`user-flows/migrate-amm-v4-to-cpmm`](/ko/user-flows/migrate-amm-v4-to-cpmm) — 마이그레이션을 고려 중인 LP의 경우.
* [`integration-guides/priority-fee-tuning`](/ko/integration-guides/priority-fee-tuning) — 무거운 AMM v4 스왑을 위한 우선 수수료 크기 조정.

출처:

* [Raydium SDK v2](https://github.com/raydium-io/raydium-sdk-V2)
* [Raydium AMM program](https://github.com/raydium-io/raydium-amm)
