Logo
新用户订阅 RPC,首月享 6.5 折优惠查看优惠
OnFinality Learn
集成与开发阅读约 11 分钟

Raydium Swap:Solana 代币兑换如何运作以及如何构建

一份关于 Solana 上 Raydium 兑换的实用指南,涵盖 AMM 与 CLMM 池模型、报价-构建-签名-发送工作流、滑点与优先费,以及如何端到端验证一次兑换。

TL;DR

Raydium 兑换是一笔链上 Solana 交易,它针对 Raydium 池中持有的流动性,将一种 SPL 代币兑换为另一种,路由会跨 AMM v4、CPMM 和 CLMM 池类型进行计算。你可以通过托管版 Raydium 界面、Raydium Trade API,或直接使用 SDK 构建指令来执行兑换。本文解释了池的机制,逐步演示了报价-构建-签名-发送的实现,展示了如何在链上验证结果,并涵盖了最常导致兑换失败的故障模式:过期报价、滑点、优先费和 RPC 可靠性。它将有文档记录的协议行为与你应自行运行的测量区分开来。

Raydium 兑换实际执行了什么

Raydium 兑换并不是对单个池的一次合约调用。它是一笔 Solana 交易,包含一条或多条兑换指令,每条指令针对特定的池程序,外加关联代币账户的设置以及 SOL 的包装或解包。Raydium 的路由层决定要触及哪些池以及以什么顺序,以便输入代币在其可见的流动性范围内以最佳可用价格到达输出代币。

你会遇到三种池系列。传统 AMM v4 池使用恒定乘积曲线,并带有 Raydium 早期架构中接近订单簿的设计。CPMM 池是恒定乘积池,具有更简单的无许可创建路径。CLMM 池是集中流动性池,流动性被放置在价格区间内,这可以产生更窄的价差,但在区间边界附近也会带来不同的价格影响行为。兑换路由可能跨越这些类型的任意组合。

实际后果是,你很少需要知道某个交易对在哪个池中交易。你真正需要控制的是你接受的报价、你签名时使用的滑点容忍度,以及决定交易是否能在报价过期前落地的计算预算和优先费。

  • 输入和输出代币的 mint,以及钱包的关联代币账户
  • 跨一个或多个池的路由,每个池都有自己的程序
  • 根据你的滑点设置得出的最小输出约束
  • 用于落地的计算单元限制和优先费
  • 一个大约在 60-90 秒内过期的近期 blockhash

开始前的假设与前提条件

本指南假设你基于 Solana 主网构建,拥有一个已充值的钱包和一个你控制的专用 RPC 端点。公共端点会积极限流,并会导致间歇性失败,这些失败看起来像兑换 bug,但实际上是传输问题。如果你还没有选择端点,RPC Assistant 的 Solana RPC 提供商比较涵盖了权衡取舍,而 Solana devnet 指南是在不花费真实资金的情况下演练流程的正确场所。

你需要 Node.js 18 或更高版本以使用全局 fetch API、一个 Solana 密钥对,以及少量 SOL 余额来支付交易费、优先费和关联代币账户的任何租金。对于程序化兑换,你将使用 Raydium Trade API(它会为你报价并构建交易),或者如果你需要直接的链上控制,则使用当前的 Raydium SDK v2。已归档的 v1 SDK 早于 CPMM 和 CLMM 支持,不应用于新工作。

  • 一个你控制的 Solana RPC_URL,最好带有备用端点
  • 用于签名的 PRIVATE_KEY 或钱包适配器
  • 用于费用的 SOL,以及如果从 SOL 兑换,用于包装的 SOL
  • 以基点表示的滑点策略
  • 适合当前网络状况的优先费策略

报价、构建、签名、发送的心智模型

每一次 Raydium 兑换,无论你是在界面中点击按钮还是调用 API,都遵循相同的四阶段生命周期。首先,根据当前池状态计算报价:给定输入金额和 mint,可实现多少输出金额以及沿哪条路由。其次,构建一笔交易,将该路由编码,以你的钱包作为付款人,并以你的滑点作为最小输出保护。第三,你在本地签名,使私钥永远不会离开你的环境。第四,你将其发送到 RPC 节点,由节点广播到集群。

关键洞察是,第一阶段和第二阶段是建议性的,而第四阶段是权威性的。报价反映的是计算那一刻的池状态。在报价和确认之间,其他交易者会移动池子,因此链上结果可能与报价不同。你的滑点容忍度是两者之间的契约:它告诉程序在交易回滚之前,你愿意接受比报价输出差多少。设置得太紧会在波动市场中导致失败;设置得太松会使你暴露于三明治式价格影响。

使用 Trade API 获取报价并构建兑换

Raydium Trade API 暴露了一个计算端点,可在一次调用中返回报价和序列化交易。下面的示例使用 RPC 端点和密钥对的占位符;请替换为你自己的值。它将固定 lamport 数量的 SOL 兑换为 USDC,设置 50 个基点的滑点容忍度,并请求优先费。将返回的交易视为不透明的:签名、发送并确认它,而不是试图解析其指令。

import { Connection, Keypair, VersionedTransaction } from '@solana/web3.js';
import bs58 from 'bs58';

const RPC_URL = process.env.RPC_URL!;
const wallet = Keypair.fromSecretKey(bs58.decode(process.env.PRIVATE_KEY!));
const connection = new Connection(RPC_URL, 'confirmed');

const WSOL = 'So11111111111111111111111111111111111111112';
const USDC = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v';

async function swapSolToUsdc(lamportsIn: number) {
  const url = new URL('https://transaction-v1.raydium.io/compute/swap-base-in');
  url.searchParams.set('inputMint', WSOL);
  url.searchParams.set('outputMint', USDC);
  url.searchParams.set('amount', String(lamportsIn));
  url.searchParams.set('slippageBps', '50');
  url.searchParams.set('txVersion', 'V0');
  url.searchParams.set('wallet', wallet.publicKey.toBase58());

  const quote = await (await fetch(url)).json();
  if (!quote.success) throw new Error(JSON.stringify(quote));

  const tx = VersionedTransaction.deserialize(
    Buffer.from(quote.data.transaction, 'base64')
  );
  tx.sign([wallet]);

  const signature = await connection.sendTransaction(tx, {
    maxRetries: 3,
    skipPreflight: false,
  });
  const latest = await connection.getLatestBlockhash('confirmed');
  const result = await connection.confirmTransaction(
    { signature, ...latest },
    'confirmed'
  );
  return { signature, result, quote: quote.data };
}

swapSolToUsdc(10_000_000).then(console.log).catch(console.error);

在链上验证兑换结果

已确认的签名并不能证明你收到了预期金额。始终读取交易元数据,并将实际代币余额变化与报价进行比较。下面的代码片段获取解析后的交易,并打印钱包代币账户的前后余额,这是实际变动情况的真实依据。

async function inspectSwap(signature: string) {
  const tx = await connection.getParsedTransaction(signature, {
    maxSupportedTransactionVersion: 0,
    commitment: 'confirmed',
  });
  if (!tx) throw new Error('transaction not found');

  const pre = tx.meta!.preTokenBalances ?? [];
  const post = tx.meta!.postTokenBalances ?? [];
  const owner = wallet.publicKey.toBase58();

  const rows = post
    .filter((b) => b.owner === owner)
    .map((b) => {
      const before = pre.find((p) => p.accountIndex === b.accountIndex);
      const delta =
        Number(b.uiTokenAmount.uiAmountString) -
        Number(before?.uiTokenAmount.uiAmountString ?? 0);
      return { mint: b.mint, delta };
    });

  console.table(rows);
  console.log('fee lamports:', tx.meta!.fee);
  console.log('compute units:', tx.meta!.computeUnitsConsumed);
}

inspectSwap('<SIGNATURE>');

记录可比较的兑换测量数据

由于池状态和网络条件不断变化,任何单次兑换都说明不了什么。要评估路由策略、优先费策略或 RPC 提供商,请运行受控的系列测试,并每次记录相同的字段。下表是一个模板;请用你自己观察到的值填充它,而不是依赖他人的数字。

运行输入 mint输出 mint报价输出实际输出滑点 bps优先费 (µLamports/CU)消耗的 CU落地时间 (ms)状态
1SOLUSDC50
2SOLUSDC50
3USDCRAY100

每种配置至少运行十次迭代,并报告中位数和失败率,而不是最佳情况。落地时间应使用单调时钟从发送到确认承诺进行测量。如果你在比较 RPC 端点,请保持钱包、金额和滑点不变,只改变端点,这样比较才有意义。

排查失败或交付不足的兑换

大多数兑换失败属于少数几类,每一类在交易日志中都有独特的特征。在更改任何内容之前阅读日志,可以让你避免追逐错误的修复方法。

滑点超出是最常见的。程序返回一个错误,表明最小输出约束被违反,这意味着池在报价和执行之间移动超出了你的容忍度。适度放宽滑点、减少交易规模,或在发送前立即重新报价。找不到 blockhash 意味着你的交易在落地前已过期;获取新的 blockhash 并重试,并调查发送缓慢的原因。租金资金不足出现在钱包缺乏 SOL 来为其从未持有的代币创建关联代币账户时。计算预算超出意味着路由需要的计算单元超过了限制;提高限制而不是优先费。

来自 RPC 层的超时和 429 响应是传输失败,而不是兑换失败。它们聚集在共享公共端点上,当你迁移到专用节点时就会消失。如果你反复看到它们,修复方法是基础设施,而不是交易参数。

  • 滑点超出:重新报价、放宽容忍度或减少规模
  • 找不到 blockhash:刷新 blockhash 并及时重试
  • 租金资金不足:为钱包充值 SOL
  • 计算预算超出:提高 CU 限制
  • 429 或超时:迁移到专用 RPC 端点

每种兑换方法的局限性与权衡

托管界面是人类交易者最快的路径,但不提供程序化控制,并且依赖于你不运营的前端。Trade API 是最简单的程序化路径,并为你处理跨池类型的路由,但它引入了对托管服务的依赖,并且对路由构建的可见性有限。SDK v2 让你直接控制指令和池选择,这对做市、套利和自定义费用逻辑很重要,代价是显著更多的代码和路由选择的责任。

CLMM 池值得单独考虑。集中流动性可以在区间内提供更好的价格,但区间边缘附近的价格影响可能是不连续的,报价时看起来便宜的路由可能迅速恶化。对于大额兑换,跨路由拆分或使用限价式方法可能比单一市价订单更可取。这些方法都无法消除 MEV 暴露;优先费和滑点设置改变经济性,但不会消除风险。

生产级兑换服务的运维加固

如果你将兑换作为服务运行而不是一次性操作,RPC 层的可靠性将主导一切。专用 Solana 节点消除了共享端点的嘈杂邻居问题,并在负载下为你提供一致的 sendTransaction 行为。将其与备用端点以及区分可重试传输错误和终端程序错误的重试策略配对。使用测量表中的字段对每次兑换进行插桩,以便在用户之前检测到落地时间或失败率的回归。

对于大规模运营的团队,具有可预测吞吐量的专用节点通常比在共享基础设施上调试间歇性故障所花费的工程时间更便宜。查看专用节点选项和 RPC 定价模型,根据预期交易量来确定承诺规模。

从可用的兑换到可靠的兑换的后续步骤

一旦单次兑换端到端成功,工作就从正确性转向可靠性和成本。在 devnet 上演练完整流程,然后在主网上用小额运行测量系列,再扩大规模。在至少两种 RPC 配置之间比较你观察到的落地时间和失败率,这样你就知道哪个变量真正重要。如果你仍在选择端点,请从 Solana RPC 提供商比较和更广泛的 RPC 端点指南开始,然后用你自己的测量而不是营销声明来验证你的选择。

从那里开始,浏览网络目录以了解你可能需要支持的其他链,并考虑你的兑换服务是否应使用 SDK 在本地报价和构建以降低延迟。一致的主题是,Raydium 兑换性能是池状态、交易参数和 RPC 质量共同作用的结果;只优化三者之一会使另外两个成为你的瓶颈。

永远不用担心基础设施

OnFinality 消除了 DevOps 的繁重工作,让您能够更聪明、更快地构建。

开始