跳转到主要内容

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.

本页内容由 AI 自动翻译,所有内容以英文版本为准。查看英文版 →

前置要求

npm install @raydium-io/raydium-sdk-v2 @solana/web3.js @solana/spl-token bn.js decimal.js
本页的每个示例都对应 raydium-sdk-V2-demo/src/cpmm 中的一个文件;GitHub 链接位于每个部分旁边。初始化配置遵循演示仓库的 config.ts.template源代码):
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
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. 获取 mint 元数据,以便 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(按 160/SOL)
  startTime:       new BN(0),               // 立即开放
  feeConfig,
  associatedOnly:  false,
  ownerInfo:       { useSOLBalance: true },
  txVersion:       TxVersion.V0,
});

const { txId } = await execute({ sendAndConfirm: true });
console.log("池创建于", extInfo.address.poolId.toBase58());
console.log("交易:", txId);
SDK 会默默处理几件事:
  • 在派生 PDA 之前将 mint 排序为 token0/token1 顺序。
  • poolFeeAccount 支付一次性的 create_pool_fee
  • 如果缺少调用者的关联代币账户,则创建它们。
  • 根据每一侧选择正确的代币程序(SPL Token 还是 Token-2022)。
确认后,你可以使用以下方式获取实时池状态:
const { poolKeys, poolInfo, rpcData } = await raydium.cpmm.getPoolInfoFromRpc(
  extInfo.address.poolId,
);

交换(基础输入)

源代码:src/cpmm/swap.ts
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("交换交易:", txId);
注意:SDK 总是getPoolInfoFromRpc 内从 RPC 重新获取池状态。不要使用来自 api-v3.raydium.io 的报价进行你即将签署的交易——一个过期一个区块的报价在落地时可能滑入 ExceededSlippage

交换(基础输出)

源代码:src/cpmm/swapBaseOut.ts
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
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_0needed_token_1,将每个增加 1 + slippage 作为指令的 maximum_* 参数,并在必要时构建 ATA 创建。

提出流动性

源代码:src/cpmm/withdraw.ts
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.tssrc/cpmm/collectAllCreatorFee.ts 这些指令是管理员或创建者控制的,通常从由 Raydium 多签或池创建者持有的签署者调用。SDK 将它们作为原始构建器表现:
import {
  makeCollectProtocolFeeInstruction,
  makeCollectFundFeeInstruction,
  makeCollectCreatorFeeInstruction,
} from "@raydium-io/raydium-sdk-v2";

// PDA 和权限在池创建时设置;详见 reference/program-addresses
// 获取规范种子。SDK 如果你愿意,可提供帮助。
离线,你可以直接从 PoolState 读取应计费用:
const pool = await raydium.cpmm.getRpcPoolInfo(poolId);
console.log("应计协议费 token0:", pool.protocolFeesToken0.toString());
console.log("应计协议费 token1:", pool.protocolFeesToken1.toString());

Rust CPI 框架

如果你想从自己的 Anchor 程序调用 CPMM——例如,一个代表其存款人交换的金库——CPI 上下文如下。账户顺序遵循 products/cpmm/instructions
// 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 并传入你的种子。

常见陷阱

在提交支持工单前的简短检查清单:
  • 排序的 mint。 如果你派生的 poolState PDA 与链上池不匹配,你可能忘记了排序 mint。
  • 过时的 API 报价。 不要将来自 api-v3.raydium.io 的准备金值传入 CurveCalculator.swap。从 RPC 获取。
  • 错误的代币程序。 Token-2022 mint 的金库由 Token-2022 程序所有,而不是由 SPL Token 所有。始终使用池的 token_0_program / token_1_program 字段。
  • 转账费 mint 的滑点分母不足。 如果池的任一侧是 Token-2022 转账费 mint,你的 minimum_amount_out 必须按用户实际接收的金额计值,而不是金库发送的金额。
  • 交换时出现 NotApproved 检查 PoolState.status——管理员可能已暂停该池上的交换。详见 products/cpmm/instructions 的状态位掩码。

后续步骤

源代码: