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

# LaunchLab 代码示例

> 端到端 TypeScript：创建 LaunchLab 启动、在绑定曲线上买卖、毕业进入 CPMM 池、收取创建者费用。

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

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

<Info>
  **版本信息**

  * SDK：`@raydium-io/raydium-sdk-v2@0.2.64-alpha`
  * 集群：Solana `mainnet-beta`
  * 程序 ID：见 [`reference/program-addresses`](/zh/reference/program-addresses)
  * 最后验证：**2026-09-09**，针对 SDK `0.2.64-alpha` 源代码、[`raydium-sdk-V2-demo/src/launchpad`](https://github.com/raydium-io/raydium-sdk-V2-demo/tree/master/src/launchpad) 和 `raydium-launch` 程序源代码

  在 `package.json` 中固定 SDK 版本。绑定曲线接口在小版本之间有所变化。
</Info>

## 设置

此处的示例镜像 [`raydium-sdk-V2-demo/src/launchpad`](https://github.com/raydium-io/raydium-sdk-V2-demo/tree/master/src/launchpad) 中的文件。引导程序遵循演示仓库的 [`config.ts.template`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/config.ts.template)：

```ts theme={null}
import { Connection, Keypair, clusterApiUrl, PublicKey } from "@solana/web3.js";
import { Raydium, TxVersion } from "@raydium-io/raydium-sdk-v2";
import BN from "bn.js";
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",
});
export const txVersion = TxVersion.V0;
```

## 创建启动

源代码：[`src/launchpad/createMint.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/launchpad/createMint.ts)（以及 [`createBonkMintApi.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/launchpad/createBonkMintApi.ts) 用于 API 驱动的 Bonk 变体）

启动的大部分经济学参数**不是**参数——它们来自你通过 `configId` 绑定的 `GlobalConfig`，SDK 从 API 的默认参数填充其余部分。你传递代币的身份、要绑定的配置，以及（可选）首次购买：

```ts theme={null}
import {
  LAUNCHPAD_PROGRAM,
  DEVNET_PROGRAM_ID,
  LaunchpadConfig,
  getPdaLaunchpadConfigId,
} from "@raydium-io/raydium-sdk-v2";
import { Keypair, PublicKey } from "@solana/web3.js";
import { NATIVE_MINT } from "@solana/spl-token";

const programId = LAUNCHPAD_PROGRAM;          // devnet: DEVNET_PROGRAM_ID.LAUNCHPAD_PROGRAM

// 基础代币是你生成并签署的密钥对。
const pair  = Keypair.generate();
const mintA = pair.publicKey;

// 绑定 GlobalConfig：(报价代币、curve_type、索引)。
const configId = getPdaLaunchpadConfigId(programId, NATIVE_MINT, 0, 0).publicKey;
const configData = await raydium.connection.getAccountInfo(configId);
if (!configData) throw new Error("config not found");
const configInfo = LaunchpadConfig.decode(configData.data);
const mintBInfo = await raydium.token.getTokenInfo(configInfo.mintB);

const { execute, transactions, extInfo } = await raydium.launchpad.createLaunchpad({
  programId,
  mintA,
  decimals: 6,
  name:   "Example Token",
  symbol: "EXMPL",                 // 必须为 10 个字符或更少
  uri:    "https://example.com/metadata.json",   // 必须非空
  migrateType: "amm",              // "amm" | "cpmm"

  configId,
  configInfo,                      // 可选；SDK 在省略时重新读取 configId
  mintBDecimals: mintBInfo.decimals,
  mintBProgram:  new PublicKey(mintBInfo.programId),

  // 可选的同一捆绑中的首次购买。
  createOnly: true,                // true = 仅创建代币
  buyAmount:  new BN(1000),        // 当 createOnly 为 true 时忽略
  slippage:   new BN(100),         // 100 = 1%

  extraSigners: [pair],            // 基础代币密钥对必须签署
  txVersion: TxVersion.V0,

  // 默认值来自绑定配置的 API 默认值；仅在必要时覆盖：
  // supply:            new BN(1_000_000_000_000_000),
  // totalSellA:        new BN(793_100_000_000_000),
  // totalFundRaisingB: new BN(85_000_000_000),   // 当 mintB 为 SOL 时为 85 SOL
  // totalLockedAmount: new BN(0),
  // cliffPeriod:       new BN(0),                // 秒
  // unlockPeriod:      new BN(0),                // 秒
  // platformId:        new PublicKey("4Bu96XjU84XjPDSpveTVf6LYGCkfW5FK7SNkREWcEfV4"), // Raydium 的，默认值
});

// createLaunchpad 返回多个交易 - 按顺序执行它们。
const { txIds } = await execute({ sequentially: true });
console.log("Pool:", extInfo.address.poolId.toBase58());
console.log("Base mint:", mintA.toBase58());
console.log("Create txs:", txIds);
```

注意：

* **没有** `curveType`、`graduationFractionBps`、`initialK`、`quoteMint`、`openTime`、`fees` 或 `postGraduationLpPolicy` 参数。曲线形状和报价代币是你绑定的 `GlobalConfig` 的属性；费率存在于 `GlobalConfig` 和 `PlatformConfig` 上；毕业时的 LP 处置是 `PlatformConfig` 设置。有关 `InitializeV2` 接受的内容，请参阅 [`products/launchlab/instructions`](/zh/products/launchlab/instructions#initialize)。
* 长于 10 个字符的 `symbol` 和空 `uri` 都在任何 RPC 之前被客户端拒绝。
* 传递 `transferFeeExtensionParams` 会将构建器切换到 `InitializeWithToken2022` 并强制 `migrateType` 为 `"cpmm"`。
* 构建器返回 `transactions` 数组；`createOnly: false` 添加首次购买交易。始终 `execute({ sequentially: true })` ——代币必须在购买到达之前存在。

## 获取启动状态

源代码：[`src/launchpad/poolInfo.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/launchpad/poolInfo.ts)

没有 `getLaunchById`。池 ID 是 `(programId, mintA, mintB)` 的 PDA，`getRpcPoolInfo` 返回解码的 `PoolState` 及其绑定的 `configInfo`：

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

const poolId = getPdaLaunchpadPoolId(programId, mintA, NATIVE_MINT).publicKey;
const poolInfo = await raydium.launchpad.getRpcPoolInfo({ poolId });

console.log("status:", poolInfo.status);
console.log("base sold:", poolInfo.realA.toString(), "/", poolInfo.totalSellA.toString());
console.log("quote raised:", poolInfo.realB.toString(), "/", poolInfo.totalFundRaisingB.toString());

// 曲线上的当前现货价格。
const price = Curve.getPrice({
  poolInfo,
  curveType: poolInfo.configInfo.curveType,
  decimalA: poolInfo.mintDecimalsA,
  decimalB: poolInfo.mintDecimalsB,
});
console.log("price:", price.toString());
```

`getRpcPoolsInfo({ poolIdList })` 是批处理形式，返回按池 ID 键入的 `poolInfoMap` ——用于投资组合视图而不是循环。如果你想自己解码原始账户，`LaunchpadPool.decode` 和 `LaunchpadConfig.decode` 已导出。

## 购买——精确报价输入

源代码：[`src/launchpad/buy.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/launchpad/buy.ts)

离链报价助手是 **`Curve.buyExactIn`**，是导出的 `Curve` 类上的静态方法——不是 `raydium.launchpad` 上的方法。交易构建器是 **`buyToken`**。两者都需要平台的费率，这些费率存在于单独的 `PlatformConfig` 账户上：

```ts theme={null}
import { Curve, PlatformConfig, toTransferFeeConfig } from "@raydium-io/raydium-sdk-v2";

const inAmount = new BN(1).mul(new BN(10).pow(new BN(9)));   // 1 SOL
const slippage = new BN(100);                                 // 100 = 1%

const poolInfo = await raydium.launchpad.getRpcPoolInfo({ poolId });
const platformData = await raydium.connection.getAccountInfo(poolInfo.platformId);
const platformInfo = PlatformConfig.decode(platformData!.data);

const mintInfo  = await raydium.token.getTokenInfo(mintA);
const mintBInfo = await raydium.token.getTokenInfo(poolInfo.mintB);
const epochInfo = await raydium.connection.getEpochInfo();

// 离链预览——镜像链上曲线数学。
const res = Curve.buyExactIn({
  poolInfo,
  amountB: inAmount,
  protocolFeeRate: poolInfo.configInfo.tradeFeeRate,
  platformFeeRate: platformInfo.feeRate,
  creatorFeeRate:  platformInfo.creatorFeeRate,
  curveType:       poolInfo.configInfo.curveType,
  shareFeeRate:    new BN(0),
  transferFeeConfigA: mintInfo.extensions.feeConfig  ? toTransferFeeConfig(mintInfo,  epochInfo.epoch) : undefined,
  transferFeeConfigB: mintBInfo.extensions.feeConfig ? toTransferFeeConfig(mintBInfo, epochInfo.epoch) : undefined,
  slot: await raydium.connection.getSlot(),
});
console.log("expected out:", res.amountA.amount.sub(res.amountA.fee ?? new BN(0)).toString());

const { execute, extInfo } = await raydium.launchpad.buyToken({
  programId,
  mintA,
  mintAProgram: new PublicKey(mintInfo.programId),
  mintB:        poolInfo.mintB,
  mintBProgram: new PublicKey(mintBInfo.programId),
  transferFeeConfigB: toTransferFeeConfig(mintBInfo, epochInfo.epoch),
  poolInfo,
  configInfo:      poolInfo.configInfo,
  platformFeeRate: platformInfo.feeRate,
  buyAmount:       inAmount,
  slippage,                       // BN in bps; SDK 从中推导 minMintAAmount
  txVersion: TxVersion.V0,
});

console.log("expected receive amount:", extInfo.decimalOutAmount.toString());
const { txId } = await execute({ sendAndConfirm: true });
console.log("Buy tx:", txId);
```

你可以显式传递 `minMintAAmount` 而不是 `slippage`；省略两者，SDK 从实时 RPC 数据计算界限。`shareFeeRate` 不得超过 `poolInfo.configInfo.maxShareFeeRate`，仅当你也传递 `shareFeeReceiver` 时才适用。

## 购买——精确基础输出

构建器是 **`buyTokenExactOut`**，金额参数是 `outAmount`（你想要的基础）和 `maxBuyAmount`（你最多花费的报价）。`Curve.buyExactOut({ poolInfo, amountA, ... })` 是匹配的离链预览。

```ts theme={null}
const { execute, extInfo } = await raydium.launchpad.buyTokenExactOut({
  programId,
  mintA,
  mintAProgram: new PublicKey(mintInfo.programId),
  mintB:        poolInfo.mintB,
  mintBProgram: new PublicKey(mintBInfo.programId),
  poolInfo,
  configInfo:      poolInfo.configInfo,
  platformFeeRate: platformInfo.feeRate,

  outAmount:    new BN(1_000_000).mul(new BN(10).pow(new BN(6))),  // 1M 基础
  maxBuyAmount: new BN(2).mul(new BN(10).pow(new BN(9))),          // 上限 2 SOL
  slippage:     new BN(100),

  txVersion: TxVersion.V0,
});

console.log("out:", extInfo.outAmount.toString(), "max spent:", extInfo.maxSpentAmount.toString());
await execute({ sendAndConfirm: true });
```

对于"精确购买 X 个代币"UI 很有用。构建器在客户端拒绝零 `outAmount`，如果曲线移动足够使报价要求超过 `maxBuyAmount`，程序会还原。

## 出售

源代码：[`src/launchpad/sell.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/launchpad/sell.ts)

构建器是 **`sellToken`**（精确报价输出变体为 `sellTokenExactOut`），带有 `sellAmount` 和可选的 `minAmountB`。`Curve.sellExactIn({ poolInfo, amountA, ... })` 预览它。

```ts theme={null}
const inAmount = new BN(500_000).mul(new BN(10).pow(new BN(6)));  // 0.5M 基础

const res = Curve.sellExactIn({
  poolInfo,
  amountA: inAmount,
  protocolFeeRate: poolInfo.configInfo.tradeFeeRate,
  platformFeeRate: platformInfo.feeRate,
  creatorFeeRate:  platformInfo.creatorFeeRate,
  curveType:       poolInfo.configInfo.curveType,
  shareFeeRate:    new BN(0),
  transferFeeConfigA: mintInfo.extensions.feeConfig  ? toTransferFeeConfig(mintInfo,  epochInfo.epoch) : undefined,
  transferFeeConfigB: mintBInfo.extensions.feeConfig ? toTransferFeeConfig(mintBInfo, epochInfo.epoch) : undefined,
  slot: await raydium.connection.getSlot(),
});
console.log("expected quote out:", res.amountB.toString());

const { execute } = await raydium.launchpad.sellToken({
  programId,
  mintA,
  mintAProgram: new PublicKey(mintInfo.programId),
  mintB:        poolInfo.mintB,
  mintBProgram: new PublicKey(mintBInfo.programId),
  poolInfo,
  configInfo:      poolInfo.configInfo,
  platformFeeRate: platformInfo.feeRate,
  sellAmount:      inAmount,
  slippage:        new BN(100),
  txVersion: TxVersion.V0,
});

await execute({ sendAndConfirm: true });
```

曲线的出售路径对称于购买路径：将 `real_a` 减少 `sellAmount` 返回 `quote_out` 等于曲线下在 `real_a − sellAmount` 和 `real_a` 之间的积分面积，减去出售费用。`sellAmount` 必须大于零——构建器在任何 RPC 之前在客户端拒绝零。

## 毕业

<Warning>
  **毕业不是集成商可以触发的事情。** 没有 `raydium.launchpad.graduate`、购买构建器上没有 `autoGraduate` 标志、它们的返回值上也没有 `willGraduate` 字段。在链上，毕业是 `MigrateToCpswap`（或旧版 `MigrateToAmm`），两个指令都将其签署者限制为绑定 `GlobalConfig` 上记录的钱包——`migrate_to_cpswap_wallet` 和 `migrate_to_amm_wallet` 分别。来自任何其他签署者的调用失败并显示 `InvalidOwner`。它**不是**无权限的，没有 MEV 机器人可以竞争它。
</Warning>

这些钱包由 Raydium 的毕业 crank 操作，所以实际上无论谁进行最后购买，迁移都会在阈值被超过后的几秒内进行。从集成商的角度来看，唯一要做的是将状态转换视为快速事件：轮询 `getRpcPoolInfo`（或通过下面的 gRPC 订阅）并在 `status` 更改时将你的 UI 从曲线切换到生成的 CPMM 池。

有关账户列表、迁移费用以及毕业时 LP 如何分割，请参阅 [`products/launchlab/instructions`](/zh/products/launchlab/instructions#migratetoamm-/-migratetocpswap)。

## 收取创建者费用

源代码：[`src/launchpad/claimCreatorFee.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/launchpad/claimCreatorFee.ts)（单个代币）和 [`collectAllCreatorFees.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/launchpad/collectAllCreatorFees.ts)（批处理）

方法是 **`claimCreatorFee`**（批处理形式为 `claimMultipleCreatorFee`）。它由**报价代币**键入，而不是由启动——一个调用会扫除签署者在该代币中跨其启动的累积创建者费用：

```ts theme={null}
import { NATIVE_MINT } from "@solana/spl-token";

const { execute } = await raydium.launchpad.claimCreatorFee({
  programId,
  mintB: NATIVE_MINT,          // 要在其中声称的报价代币
  // mintBProgram: TOKEN_PROGRAM_ID,   // 或 TOKEN_2022_PROGRAM_ID
  txVersion: TxVersion.V0,
});

await execute({ sendAndConfirm: true });
```

将累积的创建者费用计数器转移到创建者在报价代币上的 ATA。可在毕业前或毕业后调用；定期使用它而不是等待巨大的余额累积。平台运营商有并行的 `claimPlatformFee` / `claimAllPlatformFee` / `claimVaultPlatformFee` 构建器。

## 跟踪启动的整个生命周期

综合起来，监控脚本可能看起来像：

```ts theme={null}
async function watch(poolId: PublicKey) {
  const startStatus = (await raydium.launchpad.getRpcPoolInfo({ poolId })).status;

  while (true) {
    const p = await raydium.launchpad.getRpcPoolInfo({ poolId });
    const progress = Number(p.realB.toString()) / Number(p.totalFundRaisingB.toString());

    console.log(`status=${p.status}`, `progress=${(progress * 100).toFixed(2)}%`);

    if (p.status !== startStatus) {
      console.log("status changed - the launch has migrated; switch to the pool page");
      break;
    }
    await new Promise((r) => setTimeout(r, 10_000));
  }
}
```

在 10 秒计时器上轮询 `getRpcPoolInfo` 对于单个启动是可以的。对于索引器，改用下面的 gRPC 订阅——`getRpcPoolsInfo({ poolIdList })` 批处理，但轮询数百个池仍然会为无益处而消耗 RPC 信用。

## 通过 gRPC 监视配置和池

源代码：[`src/grpc/launchpadPoolInfo.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/grpc/launchpadPoolInfo.ts)

通过 Yellowstone gRPC 订阅 LaunchLab 程序，并记录每个 `GlobalConfig` 和 `PoolState` 更新及其各自代币的代币程序。一个 `accounts` 订阅上的两个 `datasize` 过滤器分离两种账户类型，因此单个流涵盖两者。

演示是对启动代币进行分类的最短正确参考，[Token-2022 报价代币发布](/zh/reference/changelog/2026-08-24-launchlab-token2022-quote-mint)使其成为必要：

* **池**直接从 `PoolState.token_program_flag` 分类，为基础代币解码 bit0，为报价代币解码 bit1，完全不获取代币。测试整个字节是否为 `0` ——在报价代币仅为旧版时足够——当报价代币为 Token-2022 时，会误读旧版基础代币为 Token-2022。
* **配置**不能以这种方式分类：`GlobalConfig` 存储其报价代币的地址且没有程序标志，所以演示读取代币账户的所有者一次并按代币缓存它。

它还相互交叉检查，这是在你自己的索引器中保持的有用断言。注意 `create` 与 `update` 是从演示自己的缓存推断的，在启动时从 `getProgramAccounts` 播种；禁用该播种后，将"create"读作"首次在此流上看到"。

gRPC 访问不是公共 RPC 表面的一部分——见演示仓库的 [`src/grpc/README.md`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/grpc/README.md)。

## Rust CPI

从你自己的 Anchor 程序调用 LaunchLab 很少见（大多数启动集成仅是 TS 端）。如果你这样做：

* crate 是 **`raydium-launchpad`**（lib 名称 `raydium_launchpad`），版本 `0.2.0`，它确实公开了 `cpi` 功能。**没有** `raydium_launchlab`。
* 它**未在 crates.io 上发布**——通过 git 修订依赖它，与其他 Raydium 程序 crate 相同。
* CPI 入口点以链上指令命名，而不是以 SDK 的 TypeScript 方法命名：`buy_exact_in`、`buy_exact_out`、`sell_exact_in`、`sell_exact_out`、`claim_creator_fee`、`create_vesting_account`、`claim_vested_token`。`migrate_to_amm` / `migrate_to_cpswap` 对绑定 `GlobalConfig` 迁移钱包的签署者进行门控，不可从集成商程序调用。

有关 crate 固定规则，请参阅 [`sdk-api/rust-cpi`](/zh/sdk-api/rust-cpi) ——特别是 Anchor 版本分割，它决定一个 crate 是否可以 CPI 到 LaunchLab 和 CLMM 两者。

## 陷阱

* **混淆链上指令名称与 SDK 方法名称。** 程序有 `buy_exact_in` / `sell_exact_in`；SDK 有 `buyToken` / `sellToken`。`buyExactIn` 和 `sellExactIn` 仅作为 **`Curve` 上的静态方法**存在，它们计算报价——它们不构建交易。
* **忘记 `extraSigners`。** `createLaunchpad` 从你生成的密钥对创建基础代币，所以该密钥对必须签署。省略 `extraSigners: [pair]` 交易会失败签名验证。
* **不选择报价代币。** 你不传递一个。报价代币是你通过 `configId` 绑定的 `GlobalConfig` 的属性——`getPdaLaunchpadConfigId(programId, quoteMint, curveType, index)`。要针对不同的报价代币启动，绑定不同的配置；`raydium.api.fetchLaunchConfigs()` 列出可用的配置及其默认参数。
* **元数据大小。** 长 `uri` 字符串会将 Metaplex CPI 推过预算。保持 `uri` 在约 200 个字符以下——大多数 CDN 托管的 JSON 元数据轻松适配。`symbol` 硬上限为 10 个字符。
* **将毕业视为你可以进入的竞争。** 它不是——见上面的警告。你的 UI 仍应将 `status` 转换视为快速事件，因为 Raydium 的 crank 在阈值被超过后的几秒内进行迁移。

## 接下来去哪里

* [`products/launchlab/bonding-curve`](/zh/products/launchlab/bonding-curve) ——曲线数学。
* [`products/cpmm/code-demos`](/zh/products/cpmm/code-demos) ——如何处理生成的池。
* [`user-flows/launch-token-launchlab`](/zh/user-flows/launch-token-launchlab) ——端到端启动代币指南，包括链下步骤。

源代码：

* [Raydium SDK v2](https://github.com/raydium-io/raydium-sdk-V2)
* Raydium LaunchLab 程序源代码


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