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

# 部署 CLMM 集合池

> 複製即用的 Node 指令碼，建立新的 CLMM 集合池，然後開設初始集中流動性部位。遵循官方 raydium-sdk-V2-demo 流程。

<Info>
  **本頁內容由 AI 自動翻譯，所有內容以英文版本為準。**

  [查看英文版 →](/quick-start/deploy-clmm-pool)
</Info>

<Info>
  **此功能的作用。** 按你選擇的費率級別建立新的 CLMM 集合池，然後開設初始集中流動性部位。兩筆交易，一個指令碼。代碼源自官方示例 [`raydium-sdk-V2-demo/src/clmm`](https://github.com/raydium-io/raydium-sdk-V2-demo/tree/master/src/clmm)，並改編為單一 Node 可執行檔案。
</Info>

## 設定

確保你已閱讀 [快速入門前置要求](/zh-Hant/quick-start)，並已安裝 `RPC_URL`、`KEYPAIR` 和相關依賴。

CLMM 集合池建立需要一次性費用，加上初始部位的每個 tick 陣列租賃費。你還需要在錢包中持有兩個種子代幣 — 當價格位於所選範圍內時開設部位，需要雙邊流動性。

## 第 1 步 — `config.ts`

另存為 `config.ts`。形式與示例倉庫的 [`src/config.ts.template`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/config.ts.template) 相同 — `disableFeatureCheck` 被強制設為 `true`（建議用於任何非平凡的整合，以防 SDK 在啟動時被功能檢測呼叫阻擋）：

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

export const owner: Keypair = Keypair.fromSecretKey(
  // accept either a JSON-array keypair file (same shape Solana CLI writes) or a bs58 secret in env
  process.env.KEYPAIR_BS58
    ? bs58.decode(process.env.KEYPAIR_BS58)
    : new Uint8Array(JSON.parse(fs.readFileSync(process.env.KEYPAIR!, "utf8"))),
);

export const connection = new Connection(
  process.env.RPC_URL ?? clusterApiUrl("mainnet-beta"),
  "confirmed",
);
export const txVersion = TxVersion.V0;
const cluster = "mainnet" as "mainnet" | "devnet";

let raydium: Raydium | undefined;
export const initSdk = async (params?: { loadToken?: boolean }) => {
  if (raydium) return raydium;
  raydium = await Raydium.load({
    owner,
    connection,
    cluster,
    disableFeatureCheck: true,
    disableLoadToken: !params?.loadToken,
    blockhashCommitment: "finalized",
  });
  return raydium;
};

export const fetchTokenAccountData = async () => {
  const solAccountResp = await connection.getAccountInfo(owner.publicKey);
  const tokenAccountResp = await connection.getTokenAccountsByOwner(owner.publicKey, {
    programId: TOKEN_PROGRAM_ID,
  });
  const token2022Req = await connection.getTokenAccountsByOwner(owner.publicKey, {
    programId: TOKEN_2022_PROGRAM_ID,
  });
  return parseTokenAccountResp({
    owner: owner.publicKey,
    solAccountResp,
    tokenAccountResp: {
      context: tokenAccountResp.context,
      value: [...tokenAccountResp.value, ...token2022Req.value],
    },
  });
};
```

## 第 2 步 — `createPool.ts`

與 `config.ts` 一起保存。來源：[`src/clmm/createPool.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/clmm/createPool.ts)。

```ts theme={null}
// createPool.ts
import { CLMM_PROGRAM_ID, DEVNET_PROGRAM_ID } from "@raydium-io/raydium-sdk-v2";
import { PublicKey } from "@solana/web3.js";
import Decimal from "decimal.js";
import { initSdk, txVersion } from "./config";

export const createPool = async () => {
  const raydium = await initSdk({ loadToken: true });

  // RAY
  const mint1 = await raydium.token.getTokenInfo("4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R");
  // USDT
  const mint2 = await raydium.token.getTokenInfo("Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB");

  // Fee tiers come from the live API. On devnet the published `id` field is wrong;
  // re-derive the PDA before passing it to the SDK.
  const clmmConfigs = await raydium.api.getClmmConfigs();

  const { execute } = await raydium.clmm.createPool({
    programId: CLMM_PROGRAM_ID,
    // programId: DEVNET_PROGRAM_ID.CLMM_PROGRAM_ID,
    mint1,
    mint2,
    ammConfig: {
      ...clmmConfigs[0],
      id: new PublicKey(clmmConfigs[0].id),
      fundOwner: "",
      description: "",
    },
    initialPrice: new Decimal(1),
    txVersion,
    // optional: set up priority fee here
    // computeBudgetConfig: { units: 600000, microLamports: 46591500 },
  });

  const { txId } = await execute({ sendAndConfirm: true });
  console.log("clmm pool created:", { txId: `https://explorer.solana.com/tx/${txId}` });
  process.exit();
};

createPool();
```

## 第 3 步 — `createPosition.ts`

來源：[`src/clmm/createPosition.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/clmm/createPosition.ts)。

```ts theme={null}
// createPosition.ts
import {
  ApiV3PoolInfoConcentratedItem,
  TickUtils,
  PoolUtils,
  ClmmKeys,
} from "@raydium-io/raydium-sdk-v2";
import BN from "bn.js";
import Decimal from "decimal.js";
import { initSdk, txVersion } from "./config";
import { isValidClmm } from "./utils";

export const createPosition = async () => {
  const raydium = await initSdk();

  let poolInfo: ApiV3PoolInfoConcentratedItem;
  // RAY-USDC pool
  const poolId = "61R1ndXxvsWXXkWSyNkCxnzwd3zUNB8Q2ibmkiLPC8ht";
  let poolKeys: ClmmKeys | undefined;

  if (raydium.cluster === "mainnet") {
    const data = await raydium.api.fetchPoolById({ ids: poolId });
    poolInfo = data[0] as ApiV3PoolInfoConcentratedItem;
    if (!isValidClmm(poolInfo.programId)) throw new Error("target pool is not CLMM pool");
  } else {
    const data = await raydium.clmm.getPoolInfoFromRpc(poolId);
    poolInfo = data.poolInfo;
    poolKeys = data.poolKeys;
  }

  // Optional: pull on-chain real-time price to avoid slippage errors from a stale API quote.
  // const rpcData = await raydium.clmm.getRpcClmmPoolInfo({ poolId: poolInfo.id });
  // poolInfo.price = rpcData.currentPrice;

  const inputAmount = 0.000001; // RAY amount
  const [startPrice, endPrice] = [0.000001, 100000];

  const { tick: lowerTick } = TickUtils.getPriceAndTick({
    poolInfo,
    price: new Decimal(startPrice),
    baseIn: true,
  });
  const { tick: upperTick } = TickUtils.getPriceAndTick({
    poolInfo,
    price: new Decimal(endPrice),
    baseIn: true,
  });

  const epochInfo = await raydium.fetchEpochInfo();
  const res = await PoolUtils.getLiquidityAmountOutFromAmountIn({
    poolInfo,
    slippage: 0,
    inputA: true,
    tickUpper: Math.max(lowerTick, upperTick),
    tickLower: Math.min(lowerTick, upperTick),
    amount: new BN(new Decimal(inputAmount || "0").mul(10 ** poolInfo.mintA.decimals).toFixed(0)),
    add: true,
    amountHasFee: true,
    epochInfo,
  });

  const { execute, extInfo } = await raydium.clmm.openPositionFromBase({
    poolInfo,
    poolKeys,
    tickUpper: Math.max(lowerTick, upperTick),
    tickLower: Math.min(lowerTick, upperTick),
    base: "MintA",
    ownerInfo: { useSOLBalance: true },
    baseAmount: new BN(new Decimal(inputAmount || "0").mul(10 ** poolInfo.mintA.decimals).toFixed(0)),
    otherAmountMax: res.amountSlippageB.amount,
    txVersion,
    computeBudgetConfig: { units: 600000, microLamports: 100000 },
  });

  const { txId } = await execute({ sendAndConfirm: true });
  console.log("clmm position opened:", { txId, nft: extInfo.nftMint.toBase58() });
  process.exit();
};

createPosition();
```

## 第 4 步 — `utils.ts`

來源：[`src/clmm/utils.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/clmm/utils.ts)。

```ts theme={null}
// utils.ts
import { CLMM_PROGRAM_ID, DEVNET_PROGRAM_ID } from "@raydium-io/raydium-sdk-v2";

const VALID_PROGRAM_IDS = new Set<string>([
  CLMM_PROGRAM_ID.toBase58(),
  DEVNET_PROGRAM_ID.CLMM_PROGRAM_ID.toBase58(),
]);

export const isValidClmm = (programId: string) => VALID_PROGRAM_IDS.has(programId);
```

## 執行

```bash theme={null}
# create the pool first
RPC_URL="https://api.mainnet-beta.solana.com" \
KEYPAIR="$HOME/.config/solana/id.json" \
npx tsx createPool.ts

# then open an initial position against the new pool id
# (edit poolId at the top of createPosition.ts to point at your new pool)
RPC_URL="https://api.mainnet-beta.solana.com" \
KEYPAIR="$HOME/.config/solana/id.json" \
npx tsx createPosition.ts
```

## 剛才發生了什麼

**交易 1 — `raydium.clmm.createPool`** 初始化了：

* 位於 `(mint1, mint2, ammConfig)` 規範 PDA 的集合池狀態，
* `token_0_vault` 和 `token_1_vault`（按 mint 位元組順序排序），
* `observation` 環形緩衝區，
* 內聯 tick 陣列位圖，

並從你的 `initialPrice` 設定初始 `sqrt_price_x64`。

**交易 2 — `raydium.clmm.openPositionFromBase`** 開設了集中流動性部位：

* 向你的錢包鑄造位置 NFT（NFT *就是*部位；轉移它就是轉移部位），
* 在下限和上限邊界分配 tick 陣列（如果是該範圍內的第一個部位，則為一次性租賃；tick 陣列永遠不會被程式關閉，所以同一陣列中的後續部位不需額外租賃），
* 存入 `inputAmount` 的 `mint1` 以及匹配的 `mint2` 數量（由 `PoolUtils.getLiquidityAmountOutFromAmountIn` 計算），
* 根據範圍寬度給部位增加流動性。

範圍越窄，每美元 TVL 的資本效率越高 — 以及當價格漂出範圍時無常損失越痛苦。上面使用的範圍（`[0.000001, 100000]`）實際上是全範圍；縮小它以集中費用在當前現貨附近。

## 選擇費率級別

`clmmConfigs[0]` 是最低費率級別。完整集合發佈在 `GET https://api-v3.raydium.io/main/clmm-config`：

| 索引 | `tradeFeeRate`  | Tick 間距 | 適用於                     |
| -- | --------------- | ------- | ----------------------- |
| 0  | `100`（1bp）      | 1       | 穩定幣/穩定幣，預期無常損失極低        |
| 1  | `500`（5bp）      | 10      | 高度相關資產（例如流動性質押 vs 基礎資產） |
| 2  | `2_500`（25bp）   | 60      | 標準代幣對、藍籌 + 穩定幣          |
| 3  | `10_000`（1.00%） | 120     | 波動大或流動性薄弱的對，無常損失風險高     |

查看 [`user-flows/choosing-a-pool-type`](/zh-Hant/user-flows/choosing-a-pool-type) 了解完整決策矩陣。

## 常見錯誤

* **`Pool already exists for this config`** — 此 `(mint1, mint2, ammConfig)` 三元組已存在 CLMM 集合池。查詢現有集合池 ID 並跳過第 2 步。
* **`Insufficient funds for amount B`** — 你的錢包有請求的 `mintA` 數量，但沒有匹配的 `mintB`。當價格位於範圍內時開設部位需要雙邊流動性。
* **`Tick out of range`** — 你的 `lowerPrice` 或 `upperPrice` 超出可代表的價格範圍。相對於當前價格使用更合理的範圍。
* **價格過時** — API 的報價可能已過時 5–60 秒。如果 `executePosition` 因滑點失敗，在 `createPosition.ts` 中取消註釋 `getRpcClmmPoolInfo` 區塊，以在簽署前重新獲取即時價格。

## 注意事項

* **部位 NFT 是你唯一的句柄。** 丟失 NFT 或轉移它，就會失去對部位的訪問權限。像對待鑰匙一樣對待它。
* **超出範圍的部位不賺取費用。** 如果價格移動到 `[lowerPrice, upperPrice]` 之外，你的部位完全駐扎在一個資產中，在你重新平衡之前不賺取任何東西。
* **Tick 陣列租賃是單向的。** 首個接觸從未初始化的 tick 陣列的部位支付其租賃；程式不公開關閉 tick 陣列的路徑，所以該租賃是永久的。同一陣列中的後續部位是免費的。

## 下一步

* [`products/clmm/overview`](/zh-Hant/products/clmm/overview) — 完整 CLMM 機制。
* [`products/clmm/ticks-and-positions`](/zh-Hant/products/clmm/ticks-and-positions) — tick 背後的數學。
* [`algorithms/impermanent-loss`](/zh-Hant/algorithms/impermanent-loss) — 量化 CLMM 無常損失放大。
* [`user-flows/create-clmm-pool`](/zh-Hant/user-flows/create-clmm-pool) — 通過 Raydium UI 進行相同流程。

來源：

* [`raydium-sdk-V2-demo/src/clmm/createPool.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/clmm/createPool.ts)
* [`raydium-sdk-V2-demo/src/clmm/createPosition.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/clmm/createPosition.ts)
* [`raydium-sdk-V2-demo/src/clmm/utils.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/clmm/utils.ts)
* [`raydium-sdk-V2-demo/src/config.ts.template`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/config.ts.template)
