发行类型
Launch Pool
Last updated October 12, 2026
Launch Pool 为 Solana 上的公平代币发射提供有机价格发现机制。用户在窗口期内存入 SOL,并根据其在总存款中的份额按比例获得 SPL 代币。没有抢跑,没有抢先交易,每个人都能获得公平分配。
您将学到什么
本指南涵盖:
- Launch Pool 定价和分配的工作原理
- 设置存款和领取窗口
- 配置资金收集的结束行为
- 用户操作:存款、提款和领取
概要
Launch Pool 是一种众筹风格的代币发行机制,在定义的窗口期内接受存款,然后按比例分配代币。最终代币价格由总存款除以代币分配量确定——为您的代币生成事件 (TGE) 实现透明的链上价格发现。
- 用户在存款窗口期间存入 SOL(收取 0% 费用)
- 存款期间允许提款(收取 0% 费用)
- 代币分配与存款份额成比例
- 可选的软顶限制发行保留的资金额度,并按比例退还超出部分
- 结束行为将收集的 SOL 路由到资金库 bucket
Launch Pool 通过存款发现价格。如需预先设定固定价格,请使用 Presale;如需基于出价的清算,请使用 Uniform Price Auction。流动性池的创建由 Raydium 毕业 bucket 负责,而不是由 Launch Pool bucket 本身负责。
快速开始
工作原理
- 特定数量的代币被分配到 Launch Pool bucket
- 用户在存款窗口期间存入 SOL(允许带费用提款)
- 窗口关闭后,根据存款份额按比例分配代币
价格发现
代币价格从总存款中产生:
tokenPrice = totalDeposits / tokenAllocation
userTokens = (userDeposit / totalDeposits) * tokenAllocation
示例: 分配 1,000,000 个代币,总存款 100 SOL = 每个代币 0.0001 SOL
价格发现默认没有上限——认购越多,隐含价格越高。设置软顶可为其设定上限。
生命周期
- 存款期 - 用户在定义的窗口期间存入 SOL
triggerBehaviorsV2- 执行结束行为(例如,将收集的 SOL 发送到另一个 bucket)- 领取期 - 用户根据其存款权重按比例领取代币
- 退款期(视情况而定)- 如果超过了软顶或未达到最低 quote token 门槛,存款人需调用
refundLaunchPoolV2
Launch Pool 软顶
软顶是 Launch Pool 所保留的 quote token 的上限,通过 addLaunchPoolBucketV2 上的 softCap 扩展配置。超过上限的存款在存款窗口期间仍会被接受,发行仍然成功,超出的 quote token 会在窗口关闭后按比例退还。
没有软顶时,价格发现没有上限:所有存款都被保留,隐含代币价格随认购量上升。软顶固定了发行募集的最大金额,因此无论存款超出多少,有效价格都是 softCap / baseTokenAllocation。
| 属性 | 配置软顶后的行为 |
|---|---|
| 超过上限的存款 | 在存款窗口期间被接受——存款不会因达到上限而被拒绝 |
| 发行结果 | 成功——软顶是上限,绝不是失败条件 |
| Base token 分配 | 完整的 baseTokenAllocation 按比例分配给所有存款 |
| 超出的 quote token | 存款窗口结束后通过 refundLaunchPoolV2 按比例退还 |
| 结束行为所看到的募集金额 | 被限制为软顶,因此 SendQuoteTokenPercentage 最多转出 softCap |
| Raydium 毕业起始价格 | 基于被限制后的募集金额计算,而非原始存款总额 |
软顶是募集资金的上限,而不是下限。下限是一个单独的扩展 minimumQuoteTokenThreshold——参见同时使用软顶和最低 Quote Token 门槛。
在 Launch Pool Bucket 上配置软顶
添加 bucket 时,向 addLaunchPoolBucketV2 传入 softCap 值。金额以 quote token 的最小单位计价(wSOL 为 lamports)。
import { sol } from '@metaplex-foundation/umi';
await addLaunchPoolBucketV2(umi, {
genesisAccount,
baseMint: baseMint.publicKey,
baseTokenAllocation: TOTAL_SUPPLY,
// ...time conditions and end behaviors...
// Floor: the launch fails below this and everyone can take a full refund.
minimumQuoteTokenThreshold: { amount: sol(10).basisPoints },
// Ceiling: the launch keeps at most this much; the rest is refunded pro-rata.
softCap: { amount: sol(100).basisPoints },
}).sendAndConfirm(umi);
在 @metaplex-foundation/genesis 0.42.0 中,softCap 是 addLaunchPoolBucketV2 的必填参数。与其他 Launch Pool 扩展不同,它没有默认值,因此不需要上限时请显式传入 softCap: null。
软顶会在设置扩展时以及 finalizeV2 时进行验证:
| 规则 | 违反时的错误 |
|---|---|
softCap.amount 必须大于零 | InvalidSoftCap (221) |
softCap.amount 必须大于或等于 minimumQuoteTokenThreshold.amount | SoftCapBelowThreshold (222) |
扩展只能在 finalizeV2 之前添加或移除 | 账户因已最终化而被拒绝 |
也可以使用 LaunchPoolV2ExtensionType 的 SoftCap 成员,通过 addLaunchPoolBucketV2Extensions 和 removeLaunchPoolBucketV2Extensions 在现有 bucket 上设置或清除软顶——但仅限 Genesis Account 尚未最终化时。
超额认购与按比例退款的计算
当 quoteTokenDepositTotal 严格大于 softCap.amount 时,Launch Pool 处于超额认购状态。此时每笔存款会被拆分为计入上限的已成交部分和可退还的超出部分:
filled_i = ceil(deposit_i * softCap / totalDeposits)
excess_i = deposit_i - filled_i
tokens_i = (weighted_i / weightedQuoteTokenTotal) * baseTokenAllocation
filled 向上取整,使所有已成交部分之和始终不小于软顶。这样无论先执行退款还是先执行毕业,bucket 都能满足被限制的毕业转账;每位存款人的取整误差都小于一个最小单位,因此 bucket 中剩余的尾差总计最多为 depositCount - 1 个最小单位。
代币分配不受上限影响。退还超出部分不会移除存款人的加权贡献,因此每个人仍按其全部存款获得相应比例的代币。
示例——分配 1,000,000 个代币,软顶为 100 SOL,实际存入 150 SOL:
| 存款人 | 存入 | 已成交(保留) | 退还 | 获得的代币 |
|---|---|---|---|---|
| Alice | 50 SOL | ~33.33 SOL | ~16.67 SOL | 333,333 (1/3) |
| Bob | 100 SOL | ~66.67 SOL | ~33.33 SOL | 666,667 (2/3) |
| 合计 | 150 SOL | 100 SOL | 50 SOL | 1,000,000 |
有效价格为每个代币 0.0001 SOL(100 SOL / 1,000,000),而不是未设上限时由存款总额得出的每个代币 0.00015 SOL。链上数值以 lamports 计算,且 filled 向上取整,因此实际数字与上面取整后的 SOL 金额相差几个 lamports。
使用 refundLaunchPoolV2 退还超出的存款
refundLaunchPoolV2 在超额认购的存款窗口关闭后,退还存款人超出的 quote token。它没有金额参数——程序根据存款、软顶和 bucket 的存款总额计算可退还的金额。
1import {
2 genesis,
3 refundLaunchPoolV2,
4} from '@metaplex-foundation/genesis'
5import { mplToolbox } from '@metaplex-foundation/mpl-toolbox'
6import { createUmi } from '@metaplex-foundation/umi-bundle-defaults'
7
8const umi = createUmi('https://api.mainnet-beta.solana.com')
9 .use(mplToolbox())
10 .use(genesis())
11
12// umi.use(keypairIdentity(yourKeypair));
13
14// Assumes genesisAccount, launchPoolBucket, and baseMint from previous steps.
15// Only valid after the deposit window closed AND either the
16// minimumQuoteTokenThreshold was missed or the softCap was exceeded.
17
18// The program computes the refundable amount, so there is no amount argument.
19// A missed threshold refunds the full deposit; an exceeded soft cap refunds
20// only the excess, leaving the depositor's token allocation intact.
21await refundLaunchPoolV2(umi, {
22 genesisAccount,
23 bucket: launchPoolBucket,
24 baseMint: baseMint.publicKey,
25 recipient: umi.identity.publicKey,
26}).sendAndConfirm(umi)
退款路径的关键特性:
- 触发退款无需许可。 只有
payer必须签名。如果存款人也签名,其空的 base token 账户会被代为关闭。 - 退款不收取任何费用和罚金;存款和提款罚金计划不会影响退款。
- 领取顺序无关紧要。 超出部分的退款可以在
claimLaunchPoolV2之前或之后进行;两种顺序最终都会得到相同的状态。 - 每笔存款只能退款一次。 第二次调用会返回
DepositAlreadyRefunded。 - 退款需要存款窗口已结束。 更早调用会返回
LaunchPoolNotEnded。 - 退款要求未达到下限或超过上限。 如果两者都不适用,调用会返回
LaunchPoolThresholdMet。
同时使用软顶和最低 Quote Token 门槛
softCap 与 minimumQuoteTokenThreshold 是从相反方向约束 Launch Pool 的独立扩展,refundLaunchPoolV2 同时服务于两者。当下限未达到时,该情形优先,退款为全额退款。
| 配置 | 存款低于下限 | 存款介于下限和上限之间 | 存款高于上限 |
|---|---|---|---|
| 均未设置 | 发行成功,无退款 | 发行成功,无退款 | 发行成功,无退款 |
| 仅设下限 | 发行失败,全额退款 | 发行成功,无退款 | 发行成功,无退款 |
| 仅设上限 | 发行成功,无退款 | 发行成功,无退款 | 发行成功,超出部分按比例退还 |
| 同时设下限和上限 | 发行失败,全额退款 | 发行成功,无退款 | 发行成功,超出部分按比例退还 |
全额退款会移除存款人在 bucket 中的加权贡献,且不能在领取之后进行——下限未达到意味着无法领取。超出部分的退款会保留加权贡献,以确保按比例领取的公式保持正确。
费用
| 指令 | Solana |
|---|---|
| User deposit fee | 0% |
| User withdraw fee | 0% |
| Creator withdraw fee | 5%* |
* This fee only applies when creators withdraw liquidity
每笔存款:扣除 0% 用户存款费后的 SOL 净额才会记入存款账户余额。
设置指南
前置条件
npm install @metaplex-foundation/genesis @metaplex-foundation/umi @metaplex-foundation/umi-bundle-defaults @metaplex-foundation/mpl-toolbox
1. 初始化 Genesis Account
Genesis Account 创建您的代币并协调所有分发 bucket。
1import {
2 findGenesisAccountV2Pda,
3 genesis,
4 initializeV2,
5} from '@metaplex-foundation/genesis'
6import { mplToolbox } from '@metaplex-foundation/mpl-toolbox'
7import { generateSigner, keypairIdentity } from '@metaplex-foundation/umi'
8import { createUmi } from '@metaplex-foundation/umi-bundle-defaults'
9
10const umi = createUmi('https://api.mainnet-beta.solana.com')
11 .use(mplToolbox())
12 .use(genesis())
13
14// umi.use(keypairIdentity(yourKeypair));
15
16const baseMint = generateSigner(umi)
17const TOTAL_SUPPLY = 1_000_000_000_000_000n // 1 million tokens (9 decimals)
18
19// Store this account address for later or recreate it when needed.
20const [genesisAccount] = findGenesisAccountV2Pda(umi, {
21 baseMint: baseMint.publicKey,
22 genesisIndex: 0,
23})
24
25await initializeV2(umi, {
26 baseMint,
27 fundingMode: 0,
28 totalSupplyBaseToken: TOTAL_SUPPLY,
29 name: 'My Token',
30 symbol: 'MTK',
31 uri: 'https://example.com/metadata.json',
32}).sendAndConfirm(umi)
totalSupplyBaseToken 应等于所有 bucket 分配的总和。
2. 添加 Launch Pool Bucket
Launch Pool bucket 收集存款并按比例分配代币。在此处配置时间。
1import {
2 addLaunchPoolBucketV2,
3 findLaunchPoolBucketV2Pda,
4 findUnlockedBucketV2Pda,
5 genesis,
6} from '@metaplex-foundation/genesis'
7import { mplToolbox } from '@metaplex-foundation/mpl-toolbox'
8import { publicKey } from '@metaplex-foundation/umi'
9import { createUmi } from '@metaplex-foundation/umi-bundle-defaults'
10
11const umi = createUmi('https://api.mainnet-beta.solana.com')
12 .use(mplToolbox())
13 .use(genesis())
14
15// umi.use(keypairIdentity(yourKeypair));
16
17// Assumes genesisAccount, baseMint, and TOTAL_SUPPLY from the Initialize step.
18
19const [launchPoolBucket] = findLaunchPoolBucketV2Pda(umi, { genesisAccount, bucketIndex: 0 })
20const [unlockedBucket] = findUnlockedBucketV2Pda(umi, { genesisAccount, bucketIndex: 0 })
21
22const now = BigInt(Math.floor(Date.now() / 1000))
23const depositStart = now
24const depositEnd = now + 86400n // 24 hours
25const claimStart = depositEnd + 1n
26const claimEnd = claimStart + 604800n // 1 week
27
28await addLaunchPoolBucketV2(umi, {
29 genesisAccount,
30 baseMint: baseMint.publicKey,
31 baseTokenAllocation: TOTAL_SUPPLY,
32
33 // Timing
34 depositStartCondition: {
35 __kind: 'TimeAbsolute',
36 padding: Array(47).fill(0),
37 time: depositStart,
38 triggeredTimestamp: null,
39 },
40 depositEndCondition: {
41 __kind: 'TimeAbsolute',
42 padding: Array(47).fill(0),
43 time: depositEnd,
44 triggeredTimestamp: null,
45 },
46 claimStartCondition: {
47 __kind: 'TimeAbsolute',
48 padding: Array(47).fill(0),
49 time: claimStart,
50 triggeredTimestamp: null,
51 },
52 claimEndCondition: {
53 __kind: 'TimeAbsolute',
54 padding: Array(47).fill(0),
55 time: claimEnd,
56 triggeredTimestamp: null,
57 },
58
59 // Optional: Minimum deposit
60 minimumDepositAmount: null, // or { amount: sol(0.1).basisPoints }
61
62 // Required since @metaplex-foundation/genesis 0.42.0: pass null for no soft cap,
63 // or { amount: sol(100).basisPoints } to cap the quote tokens kept
64 softCap: null,
65
66 // Where collected SOL goes after transition
67 endBehaviors: [
68 {
69 __kind: 'SendQuoteTokenPercentage',
70 padding: Array(4).fill(0),
71 destinationBucket: publicKey(unlockedBucket),
72 percentageBps: 10000, // 100%
73 processed: false,
74 },
75 ],
76}).sendAndConfirm(umi)
3. 添加 Unlocked Bucket
Unlocked bucket 在 triggerBehaviorsV2 执行后接收来自 Launch Pool 的 SOL。
1import {
2 addUnlockedBucketV2,
3 genesis,
4} from '@metaplex-foundation/genesis'
5import { mplToolbox } from '@metaplex-foundation/mpl-toolbox'
6import { keypairIdentity } from '@metaplex-foundation/umi'
7import { createUmi } from '@metaplex-foundation/umi-bundle-defaults'
8
9const umi = createUmi('https://api.mainnet-beta.solana.com')
10 .use(mplToolbox())
11 .use(genesis())
12
13// umi.use(keypairIdentity(yourKeypair));
14
15// Assumes genesisAccount, baseMint, claimStart, and claimEnd from previous steps.
16
17await addUnlockedBucketV2(umi, {
18 genesisAccount,
19 baseMint: baseMint.publicKey,
20 baseTokenAllocation: 0n,
21 recipient: umi.identity.publicKey,
22 claimStartCondition: {
23 __kind: 'TimeAbsolute',
24 padding: Array(47).fill(0),
25 time: claimStart,
26 triggeredTimestamp: null,
27 },
28 claimEndCondition: {
29 __kind: 'TimeAbsolute',
30 padding: Array(47).fill(0),
31 time: claimEnd,
32 triggeredTimestamp: null,
33 },
34 backendSigner: null,
35}).sendAndConfirm(umi)
4. 最终化
配置完所有 bucket 后,进行最终化以激活发行。此操作不可逆。
1import {
2 genesis,
3 finalizeV2,
4} from '@metaplex-foundation/genesis'
5import { mplToolbox } from '@metaplex-foundation/mpl-toolbox'
6import { keypairIdentity } from '@metaplex-foundation/umi'
7import { createUmi } from '@metaplex-foundation/umi-bundle-defaults'
8
9const umi = createUmi('https://api.mainnet-beta.solana.com')
10 .use(mplToolbox())
11 .use(genesis())
12
13// umi.use(keypairIdentity(yourKeypair));
14
15// Assumes genesisAccount and baseMint from the Initialize step.
16
17await finalizeV2(umi, {
18 baseMint: baseMint.publicKey,
19 genesisAccount,
20}).sendAndConfirm(umi)
用户操作
包装 SOL
用户必须在存款前将 SOL 包装为 wSOL。
1import {
2 findAssociatedTokenPda,
3 createTokenIfMissing,
4 transferSol,
5 syncNative,
6 mplToolbox,
7} from '@metaplex-foundation/mpl-toolbox'
8import { WRAPPED_SOL_MINT, genesis } from '@metaplex-foundation/genesis'
9import { keypairIdentity, publicKey, sol } from '@metaplex-foundation/umi'
10import { createUmi } from '@metaplex-foundation/umi-bundle-defaults'
11
12const umi = createUmi('https://api.mainnet-beta.solana.com')
13 .use(mplToolbox())
14 .use(genesis())
15
16// umi.use(keypairIdentity(yourKeypair));
17
18const userWsolAccount = findAssociatedTokenPda(umi, {
19 owner: umi.identity.publicKey,
20 mint: WRAPPED_SOL_MINT,
21})
22
23await createTokenIfMissing(umi, {
24 mint: WRAPPED_SOL_MINT,
25 owner: umi.identity.publicKey,
26 token: userWsolAccount,
27})
28 .add(
29 transferSol(umi, {
30 destination: publicKey(userWsolAccount),
31 amount: sol(10),
32 })
33 )
34 .add(syncNative(umi, { account: userWsolAccount }))
35 .sendAndConfirm(umi)
存款
1import {
2 genesis,
3 depositLaunchPoolV2,
4 findLaunchPoolDepositV2Pda,
5 fetchLaunchPoolDepositV2,
6} from '@metaplex-foundation/genesis'
7import { mplToolbox } from '@metaplex-foundation/mpl-toolbox'
8import { keypairIdentity, sol } from '@metaplex-foundation/umi'
9import { createUmi } from '@metaplex-foundation/umi-bundle-defaults'
10
11const umi = createUmi('https://api.mainnet-beta.solana.com')
12 .use(mplToolbox())
13 .use(genesis())
14
15// umi.use(keypairIdentity(yourKeypair));
16
17// Assumes genesisAccount, launchPoolBucket, and baseMint from previous steps.
18
19await depositLaunchPoolV2(umi, {
20 genesisAccount,
21 bucket: launchPoolBucket,
22 baseMint: baseMint.publicKey,
23 amountQuoteToken: sol(10).basisPoints,
24}).sendAndConfirm(umi)
25
26// Verify
27const [depositPda] = findLaunchPoolDepositV2Pda(umi, {
28 bucket: launchPoolBucket,
29 recipient: umi.identity.publicKey,
30})
31const deposit = await fetchLaunchPoolDepositV2(umi, depositPda)
32
33console.log('Deposited (after fee):', deposit.amountQuoteToken)
同一用户的多次存款会累积到一个存款账户中。
提款
用户可以在存款期间提款。需要支付 0% 费用。
1import {
2 genesis,
3 withdrawLaunchPoolV2,
4} from '@metaplex-foundation/genesis'
5import { mplToolbox } from '@metaplex-foundation/mpl-toolbox'
6import { keypairIdentity, sol } from '@metaplex-foundation/umi'
7import { createUmi } from '@metaplex-foundation/umi-bundle-defaults'
8
9const umi = createUmi('https://api.mainnet-beta.solana.com')
10 .use(mplToolbox())
11 .use(genesis())
12
13// umi.use(keypairIdentity(yourKeypair));
14
15// Assumes genesisAccount, launchPoolBucket, and baseMint from previous steps.
16
17await withdrawLaunchPoolV2(umi, {
18 genesisAccount,
19 bucket: launchPoolBucket,
20 baseMint: baseMint.publicKey,
21 amountQuoteToken: sol(3).basisPoints,
22}).sendAndConfirm(umi)
如果用户提取全部余额,存款 PDA 将被关闭。
领取代币
在存款期结束且领取开放后:
1import {
2 claimLaunchPoolV2,
3 genesis,
4} from '@metaplex-foundation/genesis'
5import { mplToolbox } from '@metaplex-foundation/mpl-toolbox'
6import { createUmi } from '@metaplex-foundation/umi-bundle-defaults'
7
8const umi = createUmi('https://api.mainnet-beta.solana.com')
9 .use(mplToolbox())
10 .use(genesis())
11
12// umi.use(keypairIdentity(yourKeypair));
13
14// Assumes genesisAccount, launchPoolBucket, and baseMint from previous steps.
15
16await claimLaunchPoolV2(umi, {
17 genesisAccount,
18 bucket: launchPoolBucket,
19 baseMint: baseMint.publicKey,
20 recipient: umi.identity.publicKey,
21}).sendAndConfirm(umi)
代币分配:userTokens = (userDeposit / totalDeposits) * bucketTokenAllocation
退还存款
退款适用于两种情况:发行未达到其 minimumQuoteTokenThreshold(全额退款),或超过了其 softCap(仅退还超出部分)。两者使用同一条指令——参见使用 refundLaunchPoolV2 退还超出的存款。
管理员操作
执行 triggerBehaviorsV2
存款结束后,运行 triggerBehaviorsV2 将收集的 SOL 转移到 Unlocked bucket。
1import {
2 genesis,
3 triggerBehaviorsV2,
4 WRAPPED_SOL_MINT,
5} from '@metaplex-foundation/genesis'
6import {
7 findAssociatedTokenPda,
8 mplToolbox,
9} 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')
14 .use(mplToolbox())
15 .use(genesis())
16
17// umi.use(keypairIdentity(yourKeypair));
18
19// Assumes genesisAccount, launchPoolBucket, unlockedBucket, and baseMint from previous steps.
20
21const unlockedBucketQuoteTokenAccount = findAssociatedTokenPda(umi, {
22 owner: unlockedBucket,
23 mint: WRAPPED_SOL_MINT,
24})
25
26await triggerBehaviorsV2(umi, {
27 genesisAccount,
28 primaryBucket: launchPoolBucket,
29 baseMint,
30})
31 .addRemainingAccounts([
32 { pubkey: publicKey(unlockedBucket), isSigner: false, isWritable: true },
33 { pubkey: publicKey(unlockedBucketQuoteTokenAccount), isSigner: false, isWritable: true },
34 ])
35 .sendAndConfirm(umi)
为什么这很重要: 如果不执行 triggerBehaviorsV2,收集的 SOL 将保持锁定在 Launch Pool bucket 中。用户仍然可以领取代币,但团队无法获取募集的资金。
参考
时间条件
四个条件控制 Launch Pool 的时间:
| 条件 | 用途 |
|---|---|
depositStartCondition | 存款开放时间 |
depositEndCondition | 存款关闭时间 |
claimStartCondition | 领取开放时间 |
claimEndCondition | 领取关闭时间 |
使用带有 Unix 时间戳的 TimeAbsolute:
const condition = {
__kind: 'TimeAbsolute',
padding: Array(47).fill(0),
time: BigInt(Math.floor(Date.now() / 1000) + 3600), // 1 hour from now
triggeredTimestamp: null,
};
结束行为
定义存款期结束后收集的 SOL 会发生什么:
endBehaviors: [
{
__kind: 'SendQuoteTokenPercentage',
padding: Array(4).fill(0),
destinationBucket: publicKey(unlockedBucket),
percentageBps: 10000, // 100% = 10000 basis points
processed: false,
},
]
您可以将资金分配到多个 bucket:
endBehaviors: [
{
__kind: 'SendQuoteTokenPercentage',
padding: Array(4).fill(0),
destinationBucket: publicKey(treasuryBucket),
percentageBps: 2000, // 20%
processed: false,
},
{
__kind: 'SendQuoteTokenPercentage',
padding: Array(4).fill(0),
destinationBucket: publicKey(liquidityBucket),
percentageBps: 8000, // 80%
processed: false,
},
]
Launch Pool 扩展
扩展是在 Launch Pool bucket 上配置的可选保护机制。所有扩展都通过 addLaunchPoolBucketV2 设置,或在 finalizeV2 之前通过 addLaunchPoolBucketV2Extensions 和 removeLaunchPoolBucketV2Extensions 单独添加和移除。
| 扩展 | 类型 | 用途 |
|---|---|---|
softCap | { amount: bigint } | 所保留 quote token 的上限;超出部分按比例退还 |
minimumQuoteTokenThreshold | { amount: bigint } | 下限,低于此值发行失败并开放全额退款 |
minimumDepositAmount | { amount: bigint } | 每笔存款的最低 quote token 数量 |
depositLimit | { limit: bigint } | 每个账户的最大 quote token 数量 |
allowlist | Allowlist | 将存款限制为允许名单中的钱包 |
claimSchedule | ClaimSchedule | 对已领取的 base token 进行线性归属 |
bonusSchedule | LinearBpsScheduleV2 | 随时间加权的存款奖励 |
depositPenalty | LinearBpsScheduleV2 | 随时间加权的存款罚金 |
withdrawPenalty | LinearBpsScheduleV2 | 随时间加权的提款罚金 |
backendSigner | BackendSigner | 要求用户操作由后端共同签名 |
常见错误
下表涵盖无效的软顶配置,以及 refundLaunchPoolV2 拒绝退款请求的 Launch Pool 状态。
| 错误 | 代码 | 原因 |
|---|---|---|
InvalidSoftCap | 221 | softCap.amount 为零——请传入 softCap: null,而不是 { amount: 0n } |
SoftCapBelowThreshold | 222 | softCap.amount 低于 minimumQuoteTokenThreshold.amount |
LaunchPoolNotEnded | — | 在存款窗口关闭前调用了 refundLaunchPoolV2 |
LaunchPoolThresholdMet | 173 | 在下限已达到且未超过上限时请求了退款 |
DepositAlreadyRefunded | — | 对同一笔存款调用了两次 refundLaunchPoolV2 |
DepositAlreadyClaimed | — | 存款人已领取代币后请求了全额退款 |
获取状态
Bucket 状态:
import { fetchLaunchPoolBucketV2 } from '@metaplex-foundation/genesis';
const bucket = await fetchLaunchPoolBucketV2(umi, launchPoolBucket);
console.log('Total deposits:', bucket.quoteTokenDepositTotal);
console.log('Deposit count:', bucket.depositCount);
console.log('Claim count:', bucket.claimCount);
console.log('Token allocation:', bucket.bucket.baseTokenAllocation);
// Soft cap state (Option<SoftCap>)
console.log('Soft cap:', bucket.extensions.softCap);
console.log('Floor:', bucket.extensions.minimumQuoteTokenThreshold);
存款状态:
import { fetchLaunchPoolDepositV2, safeFetchLaunchPoolDepositV2 } from '@metaplex-foundation/genesis';
const deposit = await fetchLaunchPoolDepositV2(umi, depositPda); // throws if not found
const maybeDeposit = await safeFetchLaunchPoolDepositV2(umi, depositPda); // returns null
if (deposit) {
console.log('Amount deposited:', deposit.amountQuoteToken);
console.log('Claimed:', deposit.claimed);
console.log('Refunded:', deposit.refunded);
}
注意事项
- Launch Pool 的用户存款费与提款费见上文 费用。
- 同一用户的多次存款会累积在一个存款账户中
- 如果用户提取全部余额,存款 PDA 将被关闭
- 存款结束后必须执行
triggerBehaviorsV2以处理结束行为 - 用户必须拥有 wSOL(包装的 SOL)才能存款
- 在
@metaplex-foundation/genesis0.42.0 中,softCap是addLaunchPoolBucketV2的必填参数——不需要上限时请传入softCap: null - 包括
softCap在内的 Launch Pool 扩展只能在finalizeV2之前添加或移除 - Genesis 程序和 JavaScript SDK 支持软顶;
mplxCLI 暂未提供软顶参数 - 超额认购的 Launch Pool 会在 bucket 中留下最多
depositCount - 1个最小单位的取整尾差,因为每位存款人的已成交部分都向上取整 - 退款之后,
quoteTokenDepositTotal和depositCount会作为历史记录保留;refundCount记录已处理的退款数量
常见问题
Launch Pool 中的代币价格是如何确定的?
价格是根据总存款有机发现的。最终价格等于存入的总 SOL 除以分配的代币数量。存款越多意味着每个代币的隐含价格越高。
用户可以提取他们的存款吗?
可以,用户可以在存款期间提取。需要支付 0% 的提款费用,以防止系统被利用。
如果我多次存款会怎样?
同一钱包的多次存款会累积到一个存款账户中。您的总份额基于您的累计存款。
用户何时可以领取他们的代币?
在存款期结束且领取窗口开启后(由 claimStartCondition 定义)。必须先执行 triggerBehaviorsV2 来处理结束行为。
Launch Pool 和 Presale 有什么区别?
Launch Pool 根据存款有机发现价格,按比例分配。Presale 则是预先设定固定价格,按先到先得的方式分配,直到达到上限。
什么是 Launch Pool 软顶?
软顶是 Launch Pool 所保留的 quote token 的上限,通过 softCap 扩展设置。超过上限的存款仍会被接受,发行仍然成功,超出部分会在存款窗口关闭后按比例退还。
软顶与最低 quote token 门槛有什么区别?
软顶是募集资金的上限,永远不会导致发行失败。minimumQuoteTokenThreshold 是下限——如果总存款低于该下限,发行失败,每位存款人都可以领取全额退款。它们是相互独立的扩展,可以同时使用。
Launch Pool 超额认购时,存款人获得的代币会减少吗?
不会。完整的 base token 分配量仍然按比例分配给所有存款。超额认购时退还的是多余的 quote token,而不是削减代币分配,因此有效价格上限为 softCap / baseTokenAllocation。
软顶会改变 Raydium 毕业起始价格吗?
会。当 Launch Pool 超额认购时,毕业起始价格基于被限制后的募集金额而非原始存款总额计算,因此与 SendQuoteTokenPercentage 实际转出的金额一致。
术语表
| 术语 | 定义 |
|---|---|
| Launch Pool | 基于存款的分发方式,价格在结束时发现 |
| 存款窗口 | 用户可以存入和提取 SOL 的时间段 |
| 领取窗口 | 用户可以领取其按比例分配代币的时间段 |
| End Behavior | 存款期结束后执行的自动化操作 |
triggerBehaviorsV2 | 处理结束行为并路由资金的指令 |
| 按比例分配 | 基于用户在总存款中的份额进行代币分配 |
| Quote Token | 用户存入的代币(通常是 wSOL) |
| Base Token | 正在分发的代币 |
| 软顶(Soft Cap) | Launch Pool 所保留 quote token 的上限;超出部分按比例退还 |
| 最低 Quote Token 门槛(Minimum Quote Token Threshold) | 下限,低于此值 Launch Pool 失败并开放全额退款 |
| 超额认购(Oversubscription) | 总存款超过所配置软顶的状态 |
| 已成交部分(Filled Portion) | 存款中计入软顶并由发行保留的部分 |
| 超出部分退款(Excess Refund) | 退还存款中超出软顶的部分,同时保持代币分配不变 |
后续步骤
- Presale - 固定价格代币销售
- Uniform Price Auction - 基于出价的代币发售
- 发行代币 - 端到端代币发行指南
- Metaplex API - 通过 API 查询发射和代币销售数据
