发行类型
联合曲线兑换集成
Last updated April 9, 2026
使用 Genesis SDK 读取联合曲线状态、计算兑换报价、在链上执行买入和卖出交易、处理滑点以及认领创作者费。
本指南涵盖内容
本指南涵盖:
- 获取并解析
BondingCurveBucketV2账户状态 - 使用
isSwappable、isSoldOut和isGraduated检查生命周期状态 - 使用
getSwapResult获取精确的兑换报价 - 使用
applySlippage保护用户免受滑点影响 - 使用
swapBondingCurveV2构建买入和卖出交易 - 认领曲线和毕业后 Raydium 池中的创作者费
摘要
联合曲线兑换使用 Genesis SDK 与链上 BondingCurveBucketV2 账户交互——这是一个恒积 AMM,接受 SOL 并返回代币(买入),或接受代币并返回 SOL(卖出)。有关定价数学基础,请参阅运作原理。
- 发送前先获取报价 — 调用
getSwapResult获取精确的含手续费输入和输出金额 - 滑点保护 — 使用
applySlippage推导minAmountOutScaled并将其传给指令 - wSOL 需手动处理 — 兑换指令不会自动包装或解包原生 SOL;调用方必须自行管理 wSOL ATA
- Program ID — Solana 主网上为
GNS1S5J5AspKXgpjz6SvKL66kPaKWAhaGRhCqPRxii2B
快速开始
跳转至: 安装 · 配置 · 获取曲线 · 生命周期辅助函数 · 报价 · 滑点 · 执行兑换 · 创作者费 · 错误处理 · API 参考
- 安装依赖包并使用
genesis()插件配置 Umi 实例 - 推导
BondingCurveBucketV2Pda并获取账户 - 检查
isSwappable(bucket)— 若返回 false 则中止操作 - 调用
getSwapResult(bucket, amountIn, SwapDirection.Buy)获取含手续费的报价 - 使用
applySlippage(quote.amountOut, slippageBps)获得minAmountOutScaled - 手动处理 wSOL 包装,然后发送
swapBondingCurveV2并确认
前置条件
- Node.js 18+ — 原生 BigInt 支持所必需
- Solana 钱包 — 已充值 SOL,用于支付交易费用和兑换输入
- Solana RPC 端点(mainnet-beta 或 devnet)
- 熟悉 Umi 框架 和 async/await 模式
测试配置
| 工具 | 版本 |
|---|---|
@metaplex-foundation/genesis | 1.x |
@metaplex-foundation/umi | 1.x |
@metaplex-foundation/umi-bundle-defaults | 1.x |
| Node.js | 18+ |
安装
npm install @metaplex-foundation/genesis \
@metaplex-foundation/umi \
@metaplex-foundation/umi-bundle-defaults
Umi 和 Genesis 插件配置
在调用任何 SDK 函数前,先配置 Umi 实例并注册 genesis() 插件。
1import { createUmi } from '@metaplex-foundation/umi-bundle-defaults';
2import { genesis } from '@metaplex-foundation/genesis';
3import { keypairIdentity } from '@metaplex-foundation/umi';
4import { readFileSync } from 'fs';
5
6const keypairFile = JSON.parse(readFileSync('/path/to/keypair.json', 'utf-8'));
7
8const umi = createUmi('https://api.mainnet-beta.solana.com')
9 .use(genesis());
10
11const keypair = umi.eddsa.createKeypairFromSecretKey(Uint8Array.from(keypairFile));
12umi.use(keypairIdentity(keypair));
获取 Bonding Curve BucketV2
根据已掌握的信息,可选用三种发现策略。
从已知 Genesis 账户获取
1import {
2 findBondingCurveBucketV2Pda,
3 fetchBondingCurveBucketV2,
4 genesis,
5} from '@metaplex-foundation/genesis';
6import { publicKey } from '@metaplex-foundation/umi';
7import { createUmi } from '@metaplex-foundation/umi-bundle-defaults';
8
9const umi = createUmi('https://api.mainnet-beta.solana.com').use(genesis());
10
11const genesisAccount = publicKey('YOUR_GENESIS_ACCOUNT_PUBKEY');
12
13const [bucketPda] = findBondingCurveBucketV2Pda(umi, {
14 genesisAccount,
15 bucketIndex: 0,
16});
17
18const bucket = await fetchBondingCurveBucketV2(umi, bucketPda);
1mplx genesis bucket fetch <GENESIS_ACCOUNT> --type bonding-curve
从代币 Mint 获取
1import {
2 findGenesisAccountV2Pda,
3 findBondingCurveBucketV2Pda,
4 fetchBondingCurveBucketV2,
5} from '@metaplex-foundation/genesis';
6import { publicKey } from '@metaplex-foundation/umi';
7
8const baseMint = publicKey('TOKEN_MINT_PUBKEY');
9
10const [genesisAccount] = findGenesisAccountV2Pda(umi, {
11 baseMint,
12 genesisIndex: 0,
13});
14
15const [bucketPda] = findBondingCurveBucketV2Pda(umi, {
16 genesisAccount,
17 bucketIndex: 0,
18});
19
20const bucket = await fetchBondingCurveBucketV2(umi, bucketPda);
读取 Bonding Curve BucketV2 状态
| 字段 | 类型 | 描述 |
|---|---|---|
baseTokenBalance | bigint | 曲线上剩余的代币数量。零表示已售罄。 |
baseTokenAllocation | bigint | 创建时分配给此曲线的代币总量。 |
quoteTokenDepositTotal | bigint | 买家存入的真实 SOL 金额(lamports)。初始为 0。 |
virtualSol | bigint | 初始化时添加的虚拟 SOL 储备(仅用于定价)。 |
virtualTokens | bigint | 初始化时添加的虚拟代币储备(仅用于定价)。 |
depositFee | number | 适用于每次兑换 SOL 侧的协议手续费率。 |
withdrawFee | number | 适用于卖出 SOL 输出侧的协议手续费率。 |
creatorFeeAccrued | bigint | 自上次认领以来累积的创作者费(lamports)。 |
creatorFeeClaimed | bigint | 迄今累计认领的创作者费(lamports)。 |
swapStartCondition | object | 允许交易前必须满足的条件。 |
swapEndCondition | object | 触发时结束交易的条件。 |
virtualSol 和 virtualTokens 仅存在于定价数学中——它们从未作为真实资产存入链上。请参阅运作原理了解虚拟储备如何塑造恒积曲线。
联合曲线生命周期辅助函数
五个辅助函数可在无需额外 RPC 调用的情况下检查曲线状态(isGraduated 除外)。
1import {
2 isSwappable,
3 isFirstBuyPending,
4 isSoldOut,
5 getFillPercentage,
6 isGraduated,
7} from '@metaplex-foundation/genesis';
8
9const canSwap = isSwappable(bucket);
10const firstBuyPending = isFirstBuyPending(bucket);
11const soldOut = isSoldOut(bucket);
12const fillPercent = getFillPercentage(bucket);
13const graduated = await isGraduated(umi, bucket); // async RPC call
| 辅助函数 | 异步 | 返回值 | 描述 |
|---|---|---|---|
isSwappable(bucket) | 否 | boolean | 接受公开交易时为 true |
isFirstBuyPending(bucket) | 否 | boolean | 指定首次购买尚未完成时为 true |
isSoldOut(bucket) | 否 | boolean | baseTokenBalance === 0n 时为 true |
getFillPercentage(bucket) | 否 | number | 已售出分配量的 0–100 百分比 |
isGraduated(umi, bucket) | 是 | boolean | Raydium CPMM 池链上存在时为 true |
获取兑换报价
getSwapResult(bucket, amountIn, swapDirection, isFirstBuy?) 无需发送任何交易即可计算兑换的精确含手续费金额。
返回 { amountIn, fee, creatorFee, amountOut }:
amountIn— 经调整后的实际输入金额fee— 收取的协议手续费,以 lamports 计creatorFee— 收取的创作者费,以 lamports 计(未配置创作者费时为 0)amountOut— 收到的代币(买入)或 SOL(卖出)
买入报价(SOL 换代币)
1import {
2 genesis,
3 findBondingCurveBucketV2Pda,
4 fetchBondingCurveBucketV2,
5 getSwapResult,
6} from '@metaplex-foundation/genesis';
7import { publicKey } from '@metaplex-foundation/umi';
8import { createUmi } from '@metaplex-foundation/umi-bundle-defaults';
9
10const umi = createUmi('https://api.mainnet-beta.solana.com').use(genesis());
11
12const genesisAccount = publicKey('YOUR_GENESIS_ACCOUNT_PUBKEY');
13const [bucketPda] = findBondingCurveBucketV2Pda(umi, { genesisAccount, bucketIndex: 0 });
14const bucket = await fetchBondingCurveBucketV2(umi, bucketPda);
15
16const SOL_IN = 1_000_000_000n; // 1 SOL in lamports
17
18const buyQuote = getSwapResult(bucket, SOL_IN, 'buy');
19
20console.log('SOL input: ', buyQuote.amountIn.toString(), 'lamports');
21console.log('Total fee: ', buyQuote.fee.toString(), 'lamports');
22console.log('Tokens out: ', buyQuote.amountOut.toString());
23
24// SOL input: 1000000000 lamports
25// Total fee: 10000000 lamports
26// Tokens out: <calculated token amount>
1# Get a buy quote without executing a swap (--info flag)
2mplx genesis swap <GENESIS_ACCOUNT> --info --buyAmount 1000000000
卖出报价(代币换 SOL)
1import {
2 genesis,
3 findBondingCurveBucketV2Pda,
4 fetchBondingCurveBucketV2,
5 getSwapResult,
6} from '@metaplex-foundation/genesis';
7import { publicKey } from '@metaplex-foundation/umi';
8import { createUmi } from '@metaplex-foundation/umi-bundle-defaults';
9
10const umi = createUmi('https://api.mainnet-beta.solana.com').use(genesis());
11
12const genesisAccount = publicKey('YOUR_GENESIS_ACCOUNT_PUBKEY');
13const [bucketPda] = findBondingCurveBucketV2Pda(umi, { genesisAccount, bucketIndex: 0 });
14const bucket = await fetchBondingCurveBucketV2(umi, bucketPda);
15
16const TOKENS_IN = 500_000_000_000n; // 500 tokens (9 decimals)
17
18const sellQuote = getSwapResult(bucket, TOKENS_IN, 'sell');
19
20console.log('Tokens input: ', sellQuote.amountIn.toString());
21console.log('Total fee: ', sellQuote.fee.toString(), 'lamports');
22console.log('SOL out: ', sellQuote.amountOut.toString(), 'lamports');
23
24// Tokens input: 500000000000
25// Total fee: <fee in lamports>
26// SOL out: <calculated SOL amount>
1# Get a sell quote without executing a swap (--info flag)
2mplx genesis swap <GENESIS_ACCOUNT> --info --sellAmount 500000000000
首次购买手续费豁免
将 true 作为第四个参数传入,可以模拟免手续费的首次购买报价:
1const firstBuyQuote = getSwapResult(bucket, SOL_IN, SwapDirection.Buy, true);
2console.log('Fee (waived): ', firstBuyQuote.fee.toString()); // 0n
当前价格辅助函数
1import {
2 getCurrentPrice,
3 getCurrentPriceQuotePerBase,
4 getCurrentPriceComponents,
5} from '@metaplex-foundation/genesis';
6
7const tokensPerSol = getCurrentPrice(bucket); // bigint
8const lamportsPerToken = getCurrentPriceQuotePerBase(bucket); // bigint
9const { baseReserves, quoteReserves } = getCurrentPriceComponents(bucket);
滑点保护
applySlippage(expectedAmountOut, slippageBps) 将预期输出减去滑点容忍度。将结果作为 minAmountOutScaled 传给兑换指令——若实际输出低于此值,链上程序将拒绝交易。
1import { getSwapResult, applySlippage, SwapDirection } from '@metaplex-foundation/genesis';
2
3const quote = getSwapResult(bucket, 1_000_000_000n, SwapDirection.Buy);
4const minAmountOutScaled = applySlippage(quote.amountOut, 100); // 1% slippage
切勿在未通过 applySlippage 推导 minAmountOutScaled 的情况下发送兑换交易。联合曲线的价格随每次交易变化;若不加滑点保护,用户可能收到远少于报价的代币。
常用值:稳定行情下 50 bps(0.5%),波动性发行时 200 bps(2%)。
构建兑换交易
swapBondingCurveV2(umi, accounts) 构建兑换指令。调用方负责在交易前后处理包装 SOL(wSOL)。
买入交易(SOL 换代币)
1import {
2 genesis,
3 findBondingCurveBucketV2Pda,
4 fetchBondingCurveBucketV2,
5 getSwapResult,
6 applySlippage,
7 swapBondingCurveV2,
8 isSwappable,
9} from '@metaplex-foundation/genesis';
10import { findAssociatedTokenPda } from '@metaplex-foundation/mpl-toolbox';
11import { publicKey } from '@metaplex-foundation/umi';
12import { createUmi } from '@metaplex-foundation/umi-bundle-defaults';
13
14const umi = createUmi('https://api.mainnet-beta.solana.com').use(genesis());
15
16const genesisAccount = publicKey('YOUR_GENESIS_ACCOUNT_PUBKEY');
17const baseMint = publicKey('TOKEN_MINT_PUBKEY');
18const quoteMint = publicKey('So11111111111111111111111111111111111111112'); // wSOL
19
20const [bucketPda] = findBondingCurveBucketV2Pda(umi, { genesisAccount, bucketIndex: 0 });
21const bucket = await fetchBondingCurveBucketV2(umi, bucketPda);
22
23if (!isSwappable(bucket)) throw new Error('Curve is not currently accepting swaps');
24
25const SOL_IN = 1_000_000_000n; // 1 SOL in lamports
26const quote = getSwapResult(bucket, SOL_IN, 'buy');
27const minAmountOut = applySlippage(quote.amountOut, 100); // 1% slippage
28
29const [userBaseTokenAccount] = findAssociatedTokenPda(umi, { mint: baseMint, owner: umi.identity.publicKey });
30const [userQuoteTokenAccount] = findAssociatedTokenPda(umi, { mint: quoteMint, owner: umi.identity.publicKey });
31
32// NOTE: Fund the wSOL ATA before this call — see the wSOL Wrapping Note on this page.
33const result = await swapBondingCurveV2(umi, {
34 genesisAccount,
35 bucketPda,
36 baseMint,
37 quoteMint,
38 userBaseTokenAccount,
39 userQuoteTokenAccount,
40 amountIn: quote.amountIn,
41 minAmountOut,
42 direction: 'buy',
43}).sendAndConfirm(umi);
44
45console.log('Buy confirmed:', result.signature);
46
47// Buy confirmed: <base58 transaction signature>
1# Buy tokens — SOL is wrapped automatically
2# --buyAmount is in lamports (1 SOL = 1000000000)
3mplx genesis swap <GENESIS_ACCOUNT> --buyAmount 1000000000
4
5# With custom slippage (100 bps = 1%)
6mplx genesis swap <GENESIS_ACCOUNT> --buyAmount 1000000000 --slippage 100
卖出交易(代币换 SOL)
1import {
2 genesis,
3 findBondingCurveBucketV2Pda,
4 fetchBondingCurveBucketV2,
5 getSwapResult,
6 applySlippage,
7 swapBondingCurveV2,
8} from '@metaplex-foundation/genesis';
9import { findAssociatedTokenPda } from '@metaplex-foundation/mpl-toolbox';
10import { publicKey } from '@metaplex-foundation/umi';
11import { createUmi } from '@metaplex-foundation/umi-bundle-defaults';
12
13const umi = createUmi('https://api.mainnet-beta.solana.com').use(genesis());
14
15const genesisAccount = publicKey('YOUR_GENESIS_ACCOUNT_PUBKEY');
16const baseMint = publicKey('TOKEN_MINT_PUBKEY');
17const quoteMint = publicKey('So11111111111111111111111111111111111111112'); // wSOL
18
19const [bucketPda] = findBondingCurveBucketV2Pda(umi, { genesisAccount, bucketIndex: 0 });
20const bucket = await fetchBondingCurveBucketV2(umi, bucketPda);
21
22const [userBaseTokenAccount] = findAssociatedTokenPda(umi, { mint: baseMint, owner: umi.identity.publicKey });
23const [userQuoteTokenAccount] = findAssociatedTokenPda(umi, { mint: quoteMint, owner: umi.identity.publicKey });
24
25const TOKENS_IN = 500_000_000_000n; // 500 tokens (9 decimals)
26const quote = getSwapResult(bucket, TOKENS_IN, 'sell');
27const minAmountOut = applySlippage(quote.amountOut, 100); // 1% slippage
28
29const result = await swapBondingCurveV2(umi, {
30 genesisAccount,
31 bucketPda,
32 baseMint,
33 quoteMint,
34 userBaseTokenAccount,
35 userQuoteTokenAccount,
36 amountIn: quote.amountIn,
37 minAmountOut,
38 direction: 'sell',
39}).sendAndConfirm(umi);
40
41// NOTE: Close the wSOL ATA after confirming to unwrap back to native SOL.
42console.log('Sell confirmed:', result.signature);
43
44// Sell confirmed: <base58 transaction signature>
1# Sell tokens — SOL is unwrapped automatically
2# --sellAmount is in base token units (with decimals)
3mplx genesis swap <GENESIS_ACCOUNT> --sellAmount 500000000000
4
5# With custom slippage (100 bps = 1%)
6mplx genesis swap <GENESIS_ACCOUNT> --sellAmount 500000000000 --slippage 100
wSOL 包装说明
需手动处理 wSOL
swapBondingCurveV2 使用包装 SOL(wSOL)作为报价代币,不会自动包装或解包原生 SOL。
买入时: 创建 wSOL ATA,将所需 lamports 转入其中,并在发送兑换前调用 syncNative。
卖出时: 兑换确认后关闭 wSOL ATA,将 wSOL 解包回原生 SOL。
当前版本仅接受 wSOL 作为报价代币。
1import {
2 findAssociatedTokenPda,
3 createAssociatedTokenAccountIdempotentInstruction,
4 syncNative,
5 closeToken,
6} from '@metaplex-foundation/mpl-toolbox';
7import { transactionBuilder, sol, publicKey } from '@metaplex-foundation/umi';
8
9const wSOL = publicKey('So11111111111111111111111111111111111111112');
10const [wSolAta] = findAssociatedTokenPda(umi, { mint: wSOL, owner: umi.identity.publicKey });
11
12// --- Wrap SOL before a buy ---
13const wrapBuilder = transactionBuilder()
14 .add(createAssociatedTokenAccountIdempotentInstruction(umi, {
15 mint: wSOL,
16 owner: umi.identity.publicKey,
17 }))
18 .add(syncNative(umi, { account: wSolAta }));
19
20await wrapBuilder.sendAndConfirm(umi);
21
22// --- Unwrap SOL after a sell ---
23const unwrapBuilder = closeToken(umi, {
24 account: wSolAta,
25 destination: umi.identity.publicKey,
26 authority: umi.identity,
27});
28
29await unwrapBuilder.sendAndConfirm(umi);
认领创作者费
创作者费在每次兑换时累积到 bucket(creatorFeeAccrued),而非直接转账。通过无需许可的 claimBondingCurveCreatorFeeV2 指令在曲线活跃时收取,并通过 claimRaydiumCreatorFeeV2 在毕业后收取。
完整认领流程——包括如何检查累积余额以及处理毕业后 Raydium LP 费用——请参阅创作者费。
错误处理
| 错误 | 原因 | 解决方法 |
|---|---|---|
BondingCurveInsufficientFunds | 曲线持有的代币(买入)或 SOL(卖出)不足 | 重新获取 bucket 并重新报价;曲线可能即将售罄 |
InsufficientOutputAmount | 实际输出低于 minAmountOutScaled | 增大 slippageBps 或立即重试 |
InvalidSwapDirection | swapDirection 值无效 | 从 @metaplex-foundation/genesis 导入传入 SwapDirection.Buy 或 SwapDirection.Sell |
BondingCurveNotStarted | swapStartCondition 尚未满足 | 检查 bucket.swapStartCondition 并等待 |
BondingCurveEnded | 曲线已售罄或已毕业 | 将用户引导至 Raydium CPMM 池 |
1async function executeBuy(bucket, amountIn: bigint, slippageBps: number) {
2 if (!isSwappable(bucket)) {
3 if (isSoldOut(bucket)) throw new Error('Token sold out. Trade on Raydium.');
4 throw new Error('Curve not yet active. Check the start time.');
5 }
6
7 const quote = getSwapResult(bucket, amountIn, SwapDirection.Buy);
8 const minAmountOutScaled = applySlippage(quote.amountOut, slippageBps);
9
10 try {
11 return await swapBondingCurveV2(umi, {
12 amount: quote.amountIn,
13 minAmountOutScaled,
14 swapDirection: SwapDirection.Buy,
15 // ... accounts
16 }).sendAndConfirm(umi);
17 } catch (err: any) {
18 if (err.message?.includes('InsufficientOutputAmount'))
19 throw new Error('Price moved. Try again with higher slippage.');
20 if (err.message?.includes('BondingCurveInsufficientFunds'))
21 throw new Error('Not enough tokens remaining. Reduce amount.');
22 throw err;
23 }
24}
注意事项
- 在生产环境中,每次兑换前都应重新获取 bucket——价格随每位用户的每次交易而变化
virtualSol和virtualTokens在曲线创建后不可更改——可缓存;每次兑换只有真实储备字段变化isGraduated每次调用都会发起一次 RPC 请求——请在索引器中缓存结果- 在
isSoldOut返回true与isGraduated返回true之间,曲线已售罄但 Raydium 尚未注入资金;在isGraduated确认前不要将用户引导至 Raydium - 事件解码和生命周期索引,请参阅索引与事件
- 所有手续费金额以 lamports 计(SOL 侧);当前费率请参阅协议费用
API 参考
报价和价格函数
| 函数 | 异步 | 返回值 | 描述 |
|---|---|---|---|
getSwapResult(bucket, amountIn, swapDirection, isFirstBuy?) | 否 | { amountIn, fee, creatorFee, amountOut } | 含手续费的兑换报价 |
getCurrentPrice(bucket) | 否 | bigint | 每 SOL 单位可换基础代币数(整数除法) |
getCurrentPriceQuotePerBase(bucket) | 否 | bigint | 每个基础代币单位所需 lamports(整数除法) |
getCurrentPriceComponents(bucket) | 否 | { baseReserves, quoteReserves } | 虚拟+真实组合储备(bigint) |
生命周期函数
| 函数 | 异步 | 返回值 | 描述 |
|---|---|---|---|
isSwappable(bucket) | 否 | boolean | 接受公开交易时为 true |
isFirstBuyPending(bucket) | 否 | boolean | 指定首次购买尚未完成时为 true |
isSoldOut(bucket) | 否 | boolean | baseTokenBalance === 0n 时为 true |
getFillPercentage(bucket) | 否 | number | 已售出分配量的 0–100 百分比 |
isGraduated(umi, bucket) | 是 | boolean | Raydium CPMM 池链上存在时为 true |
滑点
| 函数 | 返回值 | 描述 |
|---|---|---|
applySlippage(amountOut, slippageBps) | bigint | 将 amountOut 减少 slippageBps / 10_000 |
兑换指令账户
| 账户 | 可写 | 签名 | 描述 |
|---|---|---|---|
genesisAccount | 是 | 否 | Genesis 协调 PDA |
bucket | 是 | 否 | BondingCurveBucketV2 PDA |
baseMint | 否 | 否 | SPL 代币 mint |
quoteMint | 否 | 否 | wSOL mint |
baseTokenAccount | 是 | 否 | 用户的基础代币 ATA |
quoteTokenAccount | 是 | 否 | 用户的 wSOL ATA |
payer | 是 | 是 | 交易手续费付款方 |
账户发现
| 函数 | 返回值 | 描述 |
|---|---|---|
findBondingCurveBucketV2Pda(umi, { genesisAccount, bucketIndex }) | [PublicKey, bump] | 推导 bucket PDA |
findGenesisAccountV2Pda(umi, { baseMint, genesisIndex }) | [PublicKey, bump] | 推导 genesis 账户 PDA |
fetchBondingCurveBucketV2(umi, pda) | BondingCurveBucketV2 | 获取并反序列化账户 |
FAQ
isSwappable 和 isSoldOut 有什么区别?
isSwappable 仅在曲线正在接受公开交易时返回 true。isSoldOut 在 baseTokenBalance 降至零的瞬间返回 true,此时交易结束并触发毕业流程。曲线可以已售罄但尚未毕业。
调用 swapBondingCurveV2 之前需要先包装 SOL 吗?
是的。联合曲线使用 wSOL 作为报价代币,swapBondingCurveV2 不会自动包装或解包原生 SOL。详见 wSOL 包装说明。
getSwapResult 返回什么,它如何处理手续费?
getSwapResult 返回 { amountIn, fee, creatorFee, amountOut }。买入时,手续费在 AMM 公式运算前从 SOL 输入中扣除。卖出时,手续费在 AMM 公式运算后从 SOL 输出中扣除。将 true 作为第四个参数传入以模拟首次购买手续费豁免(所有手续费归零)。
如何防止滑点?
调用 applySlippage(quote.amountOut, slippageBps) 推导 minAmountOutScaled,然后将其传给 swapBondingCurveV2 作为 minAmountOutScaled 字段。若实际输出低于此值,链上程序将拒绝交易。
