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

# Mã demo Stable AMM

> Cách xác định pool Stable AMM từ API, swap qua nó bằng module Liquidity (nó hỗ trợ pool version=5 một cách tự nhiên), và sử dụng các trợ giúp stable-curve ngoài chuỗi.

<Info>
  **Trang này được dịch tự động bằng AI. Phiên bản tiếng Anh là bản chính thức.**

  [Xem bản tiếng Anh →](/products/stable/code-demos)
</Info>

<Info>
  **Banner phiên bản.**

  * SDK: `@raydium-io/raydium-sdk-v2@0.2.64-alpha`
  * Cluster: Solana `mainnet-beta`
  * ID chương trình Stable AMM: `5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h` (xem [`reference/program-addresses`](/vi/reference/program-addresses))
  * Xác minh lần cuối: **2026-09-09** với SDK `0.2.64-alpha` source và các demo amm/trade `raydium-sdk-V2-demo`

  Module `liquidity` của SDK xử lý pool Stable AMM một cách tự nhiên. Pool Stable xuất hiện dưới dạng **`version: 5`** (hoặc `pooltype: "StablePool"`) trên `ApiV3PoolInfoStandardItem`; các trợ giúp `addLiquidity` / `removeLiquidity` / swap giống nhau cho chúng như cho pool constant-product AMM v4 (`version: 4`) — SDK phát hiện biến thể và phát ra các instruction chính xác một cách tự động. Toán học stable-curve ngoài chuỗi nằm trong `src/raydium/liquidity/stable.ts`.
</Info>

## Thiết lập

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

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

const connection = new Connection(clusterApiUrl("mainnet-beta"));
const owner = Keypair.fromSecretKey(bs58.decode(process.env.PRIVATE_KEY!));

const raydium = await Raydium.load({ connection, owner, cluster: "mainnet" });

// BẮT BUỘC trước bất kỳ quoting pool Stable nào. initLayout() tìm nạp bảng tra cứu
// ModelDataInfo trên chuỗi vào raydium.liquidity.stableLayout.
await raydium.liquidity.initLayout();
```

<Warning>
  **`initLayout()` không phải là tùy chọn cho pool Stable.** `liquidity.computeAmountOut` phân nhánh trên `poolInfo.version`: với version 4 nó sử dụng công thức constant-product, và với bất kỳ cái nào khác nó đọc `this.stableLayout.stableModelData` và nội suy bảng tra cứu. Bộ nhớ cache đó trống cho đến khi `initLayout()` chạy, vì vậy quoting pool v5 mà không có nó sẽ tạo ra kết quả sai thay vì lỗi. Nó là idempotent và rẻ — gọi nó một lần khi khởi động.

  Các transaction *builders* (`swap`, `addLiquidity`, `removeLiquidity`) không cần nó: chúng phân phối trên `pooltype.includes("StablePool")` và phát ra các instruction `AmmV5*`, và các giới hạn số lượng bạn truyền vào đã được tính toán.
</Warning>

<Warning>
  **`liquidity.computeAmountIn` không hỗ trợ pool Stable.** Không giống như `computeAmountOut`, nó không có nhánh version — nó luôn áp dụng constant-product inverse, vì vậy trên pool v5 nó trả về `maxAmountIn` sai. Đối với swap exact-output chống lại pool Stable, hãy tự đảo ngược bằng `getDxByDyBaseIn` (xem [Trợ giúp quote ngoài chuỗi](#off-chain-quote-helpers-stablets) dưới đây) và truyền kết quả cho `swap` với `fixedSide: "out"`.
</Warning>

## Xác định pool Stable

Hai tín hiệu tương đương trên `ApiV3PoolInfoStandardItem`:

```ts theme={null}
const isStable =
  pool.version === 5 ||
  pool.pooltype.includes("StablePool"); // SDK sử dụng kiểm tra chuỗi này ở bên trong

// Hoặc, theo ID chương trình:
const STABLE_AMM_PROGRAM_ID = "5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h";
const isStableByProgram = pool.programId === STABLE_AMM_PROGRAM_ID;
```

Cả AMM v4 (`version: 4`, constant-product) và Stable AMM (`version: 5`) đều chảy qua cùng một API `LiquidityModule` trên SDK. Ở bên trong, module phân phối tới:

* `InstructionType.AmmV4AddLiquidity` / `AmmV4RemoveLiquidity` cho pool v4
* `InstructionType.AmmV5AddLiquidity` / `AmmV5RemoveLiquidity` cho pool v5 (Stable)

`programId` của pool (được trả về cùng với các khóa pool) cho SDK biết chương trình nào để CPI vào; bạn không cần hardcode nó.

## Tìm pool theo cặp mint

```ts theme={null}
import { PublicKey } from "@solana/web3.js";
import type { ApiV3PoolInfoStandardItem } from "@raydium-io/raydium-sdk-v2";

// Hai mint phổ biến để sử dụng làm ví dụ
const mintA = new PublicKey("Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"); // USDT — xem reference/program-addresses
const mintB = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"); // USDC

// fetchPoolByMints trả về một phong bì phân trang: { count, data, hasNextPage }
const res = await raydium.api.fetchPoolByMints({
  mint1: mintA.toBase58(),
  mint2: mintB.toBase58(),
});

const stablePool = res.data.find(
  (p) => p.version === 5 || p.pooltype.includes("StablePool"),
) as ApiV3PoolInfoStandardItem | undefined;

if (!stablePool) {
  throw new Error("Không tồn tại pool Stable cho cặp mint này");
}

console.log("ID pool Stable:", stablePool.id);
console.log("ID chương trình pool Stable:", stablePool.programId);
console.log("TVL:", stablePool.tvl);
```

Nếu cặp mint có cả pool v4 (constant-product) và pool v5 (stable), phản hồi bao gồm cả hai — chọn cái mà luồng của bạn cần, hoặc chuyển chúng tới chương trình [AMM Routing](/vi/products/routing) và để nó chọn tuyến đường tốt nhất.

## Swap qua pool Stable

Luồng `LiquidityModule.swap` có cùng hình dạng như cho pool v4 — chỉ cần chuyển nó một đối tượng pool v5. Hai chi tiết dễ sai: `computeAmountOut` cần **live reserves** của pool được ghép vào thông tin pool (mục API một mình không mang chúng), và `slippage` của nó là một **số** thuần túy, không phải `Percent`:

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

const amountIn = new BN(1_000_000); // 1 USDT (6 decimals), raw units

// Live reserves + status. Trên mainnet bạn cũng có thể lấy poolKeys từ
// getAmmPoolKeys(poolId) và reserves từ getRpcPoolInfo(poolId).
const rpcData  = await raydium.liquidity.getRpcPoolInfo(stablePool.id);
const poolKeys = await raydium.liquidity.getAmmPoolKeys(stablePool.id);

const out = raydium.liquidity.computeAmountOut({
  poolInfo: {
    ...stablePool,
    baseReserve:  rpcData.baseReserve,
    quoteReserve: rpcData.quoteReserve,
    status:       rpcData.status.toNumber(),
    version:      5, // 5 = Stable; nhánh curve được chọn trên cái này
  },
  amountIn,
  mintIn:  stablePool.mintA.address,
  mintOut: stablePool.mintB.address,
  slippage: 0.005, // số thuần túy: 0.005 = 0.5% (phạm vi 1 ~ 0.0001)
});

// amountOut / minAmountOut là BN trong raw units, không phải TokenAmount.
console.log("Expected out:", out.amountOut.toString());
console.log("Minimum out:", out.minAmountOut.toString());

// Xây dựng & ký transaction swap. `inputMint` là bắt buộc — mà không có nó SDK
// không thể biết bạn đang bán phía nào của pool.
const { transaction, execute } = await raydium.liquidity.swap({
  poolInfo:  stablePool,
  poolKeys,
  amountIn,
  amountOut: out.minAmountOut, // out.amountOut là con số trước slippage
  inputMint: stablePool.mintA.address,
  fixedSide: "in",
  txVersion: TxVersion.V0,
});

const { txId } = await execute({ sendAndConfirm: true });
console.log("Tx swap Stable:", txId);
```

SDK đọc `programId` của pool từ các khóa pool và phân phối vào chương trình Stable AMM. Không cần đối số `programId` đặc biệt.

## Thêm và loại bỏ liquidity

`addLiquidity` và `removeLiquidity` hoạt động giống hệt nhau trên pool v4 và v5:

```ts theme={null}
import { Percent, TokenAmount, toToken } from "@raydium-io/raydium-sdk-v2";
import Decimal from "decimal.js";

const inputAmount = "100"; // 100 USDT, trong human units — computePairAmount lấy một chuỗi decimal

// Tính toán số lượng B phù hợp mà curve yêu cầu cho kích thước A này.
// Lưu ý: slippage của computePairAmount LÀ một Percent (không giống computeAmountOut).
const r = raydium.liquidity.computePairAmount({
  poolInfo: stablePool,
  amount:   inputAmount,
  baseIn:   true,
  slippage: new Percent(50, 10_000), // 0.5%
});

const { execute } = await raydium.liquidity.addLiquidity({
  poolInfo: stablePool,
  poolKeys,
  amountInA: new TokenAmount(
    toToken(stablePool.mintA),
    new Decimal(inputAmount).mul(10 ** stablePool.mintA.decimals).toFixed(0),
  ),
  amountInB: new TokenAmount(
    toToken(stablePool.mintB),
    new Decimal(r.maxAnotherAmount.toExact()).mul(10 ** stablePool.mintB.decimals).toFixed(0),
  ),
  otherAmountMin: r.minAnotherAmount,
  fixedSide:      "a",
  txVersion:      TxVersion.V0,
});

const { txId } = await execute({ sendAndConfirm: true });
console.log("Tx add-liquidity:", txId);
```

Ở bên trong, SDK phát ra `InstructionType.AmmV5AddLiquidity` vì `pooltype.includes("StablePool")` là true. Luồng `removeLiquidity` tương ứng là đối xứng — cung cấp `lpAmount` và số lượng tối thiểu bạn sẽ chấp nhận ở mỗi bên.

## Trợ giúp quote ngoài chuỗi (stable.ts)

Để quoting phía máy chủ hoặc backtesting, SDK công khai toán học stable-curve cơ bản:

```ts theme={null}
import {
  getStablePrice,
  getDxByDyBaseIn,
  getDyByDxBaseIn,
} from "@raydium-io/raydium-sdk-v2";

// Bạn phải gọi initLayout() một lần trước khi sử dụng những cái này (tải
// PDA `ModelDataInfo` trên chuỗi vào bộ nhớ cache StableLayout của SDK).
await raydium.liquidity.initLayout();

// initLayout() điền vào instance StableLayout; đối tượng dữ liệu thuần túy mà
// ba trợ giúp dưới đây mong đợi là getter `stableModelData` của nó, KHÔNG phải instance.
const modelData = raydium.liquidity.stableLayout.stableModelData;

// Reserves được truyền dưới dạng số thuần túy trong human units (không phải BN, không phải raw units).
const coinReserve = 1_200_000; // reserve phía coin của pool
const pcReserve   = 1_150_000; // reserve phía pc của pool

// Giá spot tại reserves hiện tại của pool. Đối số thứ 4 là `baseCoin`:
// true trả về coin-per-pc, false trả về reciprocal.
const price = getStablePrice(modelData, coinReserve, pcReserve, true);
console.log("Giá spot:", price);

// Quote: cho dx vào, bao nhiêu dy ra (không áp dụng phí ở đây)?
const dyOut = getDyByDxBaseIn(modelData, coinReserve, pcReserve, 1_000);

// Quote: cho mục tiêu dy ra, cần bao nhiêu dx vào?
const dxIn  = getDxByDyBaseIn(modelData, coinReserve, pcReserve, 1_000);
```

Đây là các hàm thuần túy — không RPC, không ký. `ModelDataInfo` trên chuỗi được tìm nạp một lần bởi `initLayout()` và được lưu trong bộ nhớ cache bên trong `raydium.liquidity.stableLayout`, có thể đọc được dưới dạng `.stableModelData`. `initStableModelLayout()` là idempotent: nó chỉ tìm nạp lại trong khi `validDataCount === 0`, vì vậy gọi `initLayout()` lại là miễn phí. Truyền reserves hiện tại (`x`, `y`) và các trợ giúp tính toán bằng cách tìm kiếm nhị phân bảng tra cứu và nội suy tuyến tính giữa hai hàng `DataElement` xung quanh. Xem [`products/stable/math`](/vi/products/stable/math) để biết thuật toán cơ bản.

## Định tuyến qua AMM Routing (multi-hop / best-price)

Nếu bạn không muốn tự chọn một địa điểm, chương trình [AMM Routing](/vi/products/routing) sẽ xem xét mọi Raydium AMM (v4 / CPMM / CLMM / Stable) và định tuyến qua bất kỳ kết hợp nào tốt nhất:

Router là một pipeline năm lệnh gọi thay vì một `swap()` duy nhất, và nó được viết ra đầy đủ — với lời khuyên lưu trữ giúp nó có thể sử dụng được trong sản xuất — trên [`products/routing/code-demos`](/vi/products/routing/code-demos). Hình dạng là:

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

const poolData = await raydium.tradeV2.fetchRoutePoolBasicInfo(); // lưu vào bộ nhớ cache cái này
const routes   = raydium.tradeV2.getAllRoute({ inputMint: mintA, outputMint: mintB, ...poolData });
const ctx      = await raydium.tradeV2.fetchSwapRoutesData({ routes, inputMint: mintA, outputMint: mintB });
const quotes   = raydium.tradeV2.getAllRouteComputeAmountOut({ /* ...ctx, amount, slippage, ... */ });

const best = quotes[0]; // sắp xếp tốt nhất trước
if (!best) throw new Error("không tìm thấy tuyến đường swap nào");

// Các mục `best.poolInfoList` mang `version` — 5 đánh dấu pool Stable trong đường dẫn.
console.log(best.routeType, best.poolInfoList.map((p) => p.version));

const poolKeys = await raydium.tradeV2.computePoolToPoolKeys({
  pools: best.poolInfoList,
  ammRpcData:  ctx.ammPoolsRpcInfo,
  clmmRpcData: ctx.clmmPoolsRpcInfo,
});

const { execute } = await raydium.tradeV2.swap({
  routeProgram: Router,
  swapInfo:     best,
  swapPoolKeys: poolKeys,
  ownerInfo:    { associatedOnly: true, checkCreateATAOwner: true },
  txVersion:    TxVersion.V0,
});

// tradeV2.swap có thể trả về nhiều transaction; cái đầu tiên tạo ATA trung gian.
const { txIds } = await execute({ sequentially: true });
```

<Warning>
  Không có `raydium.tradeV2.fetchRoutes`, và `tradeV2.swap` **không** chấp nhận `inputMint` / `outputMint` / `slippage` — nó lấy `swapInfo` đã được tính toán từ `getAllRouteComputeAmountOut`. Đối với một pool **duy nhất** đã biết, hãy sử dụng builder của loại pool đó (`raydium.liquidity.swap` cho AMM v4 và Stable, `raydium.cpmm.swap`, `raydium.clmm.swap`) như được hiển thị trước đó trên trang này.
</Warning>

Đây là đường dẫn được khuyến nghị cho swapper sản xuất và aggregator — bạn không bao giờ cần phải quyết định thủ công liệu pool Stable có tồn tại hay liệu nó có phải là địa điểm tốt hơn ngày hôm nay hay không.

## Khuyến nghị

1. **Đối với swap người dùng cuối**, ưu tiên luồng định tuyến [`tradeV2`](/vi/products/routing). Nó xử lý mọi loại pool Raydium bao gồm Stable.
2. **Đối với các hoạt động dành riêng cho pool** (thêm / loại bỏ LP trên pool Stable đã biết), hãy sử dụng `LiquidityModule` trực tiếp — nó tự động phát hiện pool v5.
3. **Đối với quoting / phân tích ngoài chuỗi**, gọi `getStablePrice` / `getDyByDxBaseIn` / `getDxByDyBaseIn` sau `initLayout()`. Không có lưu lượng RPC cho mỗi quote sau khi dữ liệu mô hình được lưu vào bộ nhớ cache.
4. **Không mã hóa tay các instruction `SwapBaseIn` thô.** Nâng cấp [2026-06-22](/vi/reference/changelog) đã loại bỏ các tài khoản OpenBook chết, vì vậy bố cục swap mới lấy **9 tài khoản** (bố cục 18 tài khoản cũ vẫn phân tích cho khả năng tương thích ngược). `Deposit` hiện là 12 tài khoản (cũ 14 tương thích), `Withdraw` 12 (cũ 21/22 tương thích), và `WithdrawPnl` 10 với **không** đường dẫn tương thích. Các trợ giúp được xây dựng sẵn của SDK chọn bố cục và thứ tự chính xác cho bạn; tự viết của bạn dễ bị lỗi. Xem [`products/stable/instructions`](/vi/products/stable/instructions) để biết bảng tài khoản đầy đủ.

## Tiếp theo đi đâu

* [Math](/vi/products/stable/math) — cách nội suy bảng tra cứu hoạt động.
* [Instructions](/vi/products/stable/instructions) — tham chiếu instruction đầy đủ.
* [AMM Routing](/vi/products/routing) — định tuyến multi-pool trên AMM v4, CPMM, CLMM, Stable.

Nguồn:

* [`raydium-sdk-V2/src/raydium/liquidity/liquidity.ts`](https://github.com/raydium-io/raydium-sdk-V2/blob/master/src/raydium/liquidity/liquidity.ts) — điểm vào module; phân phối v4 / v5.
* [`raydium-sdk-V2/src/raydium/liquidity/stable.ts`](https://github.com/raydium-io/raydium-sdk-V2/blob/master/src/raydium/liquidity/stable.ts) — `StableLayout`, `getStablePrice`, `getDxByDyBaseIn`, `getDyByDxBaseIn`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.