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

# Stable AMM 代码示例

> 如何从 API 识别 Stable AMM 池、通过流动性模块进行交换（原生支持 version=5 池）以及使用链下稳定曲线辅助函数。

<Info>
  **本页内容由 AI 自动翻译，所有内容以英文版本为准。**

  [查看英文版 →](/products/stable/code-demos)
</Info>

<Info>
  **版本信息**

  * SDK: `@raydium-io/raydium-sdk-v2@0.2.42-alpha`
  * 集群: Solana `mainnet-beta`
  * Stable AMM 程序 ID: `5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h`（见 [`reference/program-addresses`](/zh/reference/program-addresses)）
  * 最后验证: 2026-04

  SDK 的 `liquidity` 模块原生支持 Stable AMM 池。Stable 池在 `ApiV3PoolInfoStandardItem` 上显示为 **`version: 5`**（或 `pooltype: "StablePool"`）；与 AMM v4（`version: 4`）常数乘积池相同的 `addLiquidity` / `removeLiquidity` / swap 辅助函数也适用于它们——SDK 会自动检测变体并发出正确的指令。链下稳定曲线数学位于 `src/raydium/liquidity/stable.ts`。
</Info>

## 设置

```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",
  // 可选：如果你打算直接调用 `liquidity/stable.ts` 中的报价
  // 辅助函数，加载稳定曲线模型布局。池级别的 swap / add / remove
  // 会为你延迟加载，所以大多数调用者可以跳过此步骤。
});

// 一次性：预取链上模型数据布局，供链下
// 稳定曲线辅助函数使用。仅在直接调用 getStablePrice / getDxByDyBaseIn /
// getDyByDxBaseIn 时需要。addLiquidity / removeLiquidity / swap 不需要。
await raydium.liquidity.initLayout();
```

## 识别 Stable 池

`ApiV3PoolInfoStandardItem` 上的两个等效信号：

```ts theme={null}
const isStable =
  pool.version === 5 ||
  pool.pooltype.includes("StablePool"); // SDK 内部使用此字符串检查

// 或者，通过程序 ID：
const STABLE_AMM_PROGRAM_ID = "5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h";
const isStableByProgram = pool.programId === STABLE_AMM_PROGRAM_ID;
```

AMM v4（`version: 4`，常数乘积）和 Stable AMM（`version: 5`）都通过 SDK 上相同的 `LiquidityModule` API 流动。在内部，模块分派到：

* `InstructionType.AmmV4AddLiquidity` / `AmmV4RemoveLiquidity` 用于 v4 池
* `InstructionType.AmmV5AddLiquidity` / `AmmV5RemoveLiquidity` 用于 v5（Stable）池

池的 `programId`（与池密钥一起返回）告诉 SDK 要 CPI 到哪个程序；你不需要硬编码它。

## 按代币对查找池

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

// 两个常见的代币作为示例
const mintA = new PublicKey("Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"); // USDT
const mintB = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"); // USDC

const pools = await raydium.api.fetchPoolByMintPair({
  mint1: mintA.toBase58(),
  mint2: mintB.toBase58(),
});

const stablePool = pools.find(
  (p) => p.version === 5 || p.pooltype.includes("StablePool"),
);

if (!stablePool) {
  throw new Error("此代币对不存在 Stable 池");
}

console.log("Stable 池 id:", stablePool.id);
console.log("Stable 池 programId:", stablePool.programId);
console.log("TVL:", stablePool.tvl);
```

如果代币对同时有 v4（常数乘积）池和 v5（stable）池，响应包含两者——选择你的流程需要的那个，或将它们交给 [AMM 路由](/zh/products/routing) 程序，让它选择最佳路由。

## 通过 Stable 池进行交换

`LiquidityModule.swap` 流的形状与 v4 池相同——只需将 v5 池对象交给它：

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

const inputAmount = new TokenAmount(toToken(stablePool.mintA), 1_000_000); // 1 USDT
const slippage = new Percent(50, 10_000); // 0.5%

// 使用 SDK 的稳定曲线辅助函数在内部计算预期输出。
const { amountOut, minAmountOut } = raydium.liquidity.computeAmountOut({
  poolInfo: stablePool,
  amountIn: inputAmount,
  mintIn:  stablePool.mintA.address,
  mintOut: stablePool.mintB.address,
  slippage,
});

console.log("预期输出:", amountOut.toSignificant());
console.log("最小输出:", minAmountOut.toSignificant());

// 构建并签署交换交易。
const { transaction, execute } = await raydium.liquidity.swap({
  poolInfo: stablePool,
  amountIn:    inputAmount.raw,
  amountOut:   minAmountOut.raw,
  fixedSide:   "in",
  txVersion:   TxVersion.V0,
});

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

SDK 从池密钥中读取池的 `programId` 并分派到 Stable AMM 程序。不需要特殊的 `programId` 参数。

## 添加和移除流动性

`addLiquidity` 和 `removeLiquidity` 在 v4 和 v5 池上的工作方式相同：

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

const amountInA = new TokenAmount(toToken(stablePool.mintA), 100_000_000); // 100 USDT
const slippage  = new Percent(50, 10_000); // 0.5%

// 计算曲线对此大小 A 所需的匹配 B 数量。
const { anotherAmount, minAnotherAmount } = raydium.liquidity.computePairAmount({
  poolInfo: stablePool,
  amount:   amountInA.toSignificant(),
  baseIn:   true,
  slippage,
});

const { execute } = await raydium.liquidity.addLiquidity({
  poolInfo: stablePool,
  amountInA,
  amountInB:      anotherAmount,
  otherAmountMin: minAnotherAmount,
  fixedSide:      "a",
  txVersion:      TxVersion.V0,
});

const { txId } = await execute({ sendAndConfirm: true });
console.log("添加流动性 tx:", txId);
```

在内部，SDK 发出 `InstructionType.AmmV5AddLiquidity`，因为 `pooltype.includes("StablePool")` 为真。对应的 `removeLiquidity` 流是对称的——输入 `lpAmount` 和你将接受的每一侧的最小数量。

## 链下报价辅助函数（stable.ts）

对于服务器端报价或回测，SDK 公开了底层稳定曲线数学：

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

// 在使用这些之前必须调用一次 initLayout()（将链上
// `ModelDataInfo` PDA 加载到 SDK 的 StableLayout 缓存中）。
await raydium.liquidity.initLayout();

const modelData = raydium.liquidity.stableLayout;

// 池当前储备量处的现货价格。
const price = getStablePrice(modelData, /* x */, /* y */, /* withFee */);
console.log("现货价格:", price);

// 报价：给定 dx 输入，输出多少 dy（此处不应用费用）？
const dyOut = getDyByDxBaseIn(modelData, /* x */, /* y */, /* dx */);

// 报价：给定 dy 输出目标，需要多少 dx 输入？
const dxIn  = getDxByDyBaseIn(modelData, /* x */, /* y */, /* dy */);
```

这些是纯函数——无 RPC，无签署。链上 `ModelDataInfo` 由 `initLayout()` 获取一次并缓存在 `raydium.liquidity.stableLayout` 中。传递当前储备量（`x`、`y`），辅助函数通过二分搜索查找表并在两个相邻 `DataElement` 行之间线性插值来计算。有关底层算法，见 [`products/stable/math`](/zh/products/stable/math)。

## 通过 AMM 路由进行路由（多跳 / 最佳价格）

如果你不想自己选择场所，[AMM 路由](/zh/products/routing) 程序将考虑每个 Raydium AMM（v4 / CPMM / CLMM / Stable）并通过最佳组合进行路由：

```ts theme={null}
const route = await raydium.tradeV2.fetchRoutes({
  inputMint:  mintA,
  outputMint: mintB,
  amount:     new BN(1_000_000),
  slippage,
});

// route.routes[0].poolType 告诉你最佳路由使用哪些程序；
// 每当 Stable 池是最优路径的一部分时，"Stable" 就会出现在这里。
console.log(route.routes[0]);

const { execute } = await raydium.tradeV2.swap({
  inputMint:    mintA,
  outputMint:   mintB,
  inputAmount:  new BN(1_000_000),
  swapResult:   route.routes[0],
  slippage,
  txVersion:    TxVersion.V0,
});

const { txId } = await execute({ sendAndConfirm: true });
```

这是生产交换器和聚合器的推荐路径——你永远不需要手动决定 Stable 池是否存在或它是否是今天更好的场所。

## 建议

1. **对于最终用户交换**，优先使用 [`tradeV2`](/zh/products/routing) 路由流。它处理每个 Raydium 池类型，包括 Stable。
2. **对于池特定操作**（已知 Stable 池上的 LP 添加 / 移除），直接使用 `LiquidityModule`——它自动检测 v5 池。
3. **对于链下报价 / 分析**，在 `initLayout()` 后调用 `getStablePrice` / `getDyByDxBaseIn` / `getDxByDyBaseIn`。模型数据缓存后，每个报价无 RPC 流量。
4. **不要手工编码原始 `SwapBaseIn` 指令。** [2026-06-22 升级](/zh/reference/changelog) 移除了已弃用的 OpenBook 账户，所以新的交换布局需要 **9 个账户**（旧的 18 账户布局仍然可以解析以实现向后兼容性）。`Deposit` 现在是 12 个账户（旧的 14 个兼容），`Withdraw` 12 个（旧的 21/22 个兼容），`WithdrawPnl` 10 个，**没有**兼容性路径。SDK 的预构建辅助函数为你选择正确的布局和顺序；自己编写容易出错。有关完整的账户表，见 [`products/stable/instructions`](/zh/products/stable/instructions)。

## 接下来去哪里

* [数学](/zh/products/stable/math)——查找表插值的工作原理。
* [指令](/zh/products/stable/instructions)——完整的指令参考。
* [AMM 路由](/zh/products/routing)——跨 AMM v4、CPMM、CLMM、Stable 的多池路由。

来源：

* [`raydium-sdk-V2/src/raydium/liquidity/liquidity.ts`](https://github.com/raydium-io/raydium-sdk-V2/blob/master/src/raydium/liquidity/liquidity.ts)——模块入口点；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`。
