发行类型
Presale
Last updated January 31, 2026
Presale提供 Solana 上的固定价格代币分配。根据分配量和 SOL 上限预先设定 SPL 代币价格——用户确切知道会得到什么,您也确切知道会筹集多少。在 Genesis 中,"Presale"指的是在初始交易之前直接出售代币——购买者直接收到代币,而非未来收到代币的权利。
学习内容
本指南涵盖:
- Presale定价如何运作(分配量 + 上限 = 价格)
- 设置存款窗口和领取期间
- 配置存款限额和冷却时间
- 用户操作:包装SOL、存款和领取
概述
Presale以预定价格出售代币,类似于传统的代币发售。价格根据您配置的代币分配量和SOL上限计算,非常适合已知估值的加密融资。
- 固定价格 = SOL上限 / 代币分配量
- 用户在存款窗口期间存入SOL(收取2%费用)
- 先到先得,直到SOL上限
- 可选:最小/最大存款限额、冷却时间、后端授权
超出范围
自然价格发现(参见Launch Pool)、基于出价的拍卖(参见Uniform Price Auction)和归属计划。
工作原理
- 您将代币分配到Presale,设置SOL上限来决定固定价格
- 用户在存款窗口期间以固定汇率存入SOL
- 存款期结束后,您执行过渡以转移资金
- 用户根据存款金额领取代币
价格计算
代币价格由分配代币与SOL上限的比率决定:
price = allocationQuoteTokenCap / baseTokenAllocation
tokens = deposit / price
例如,如果您分配1,000,000代币,SOL上限为100:
- 价格 = 100 SOL / 1,000,000代币 = 每代币0.0001 SOL
- 存入10 SOL可获得100,000代币
费用
| 指令 | Solana |
|---|---|
| Deposit | 2% |
| Graduation | 5% |
快速开始
设置指南
前提条件
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. 添加Presale Bucket
Presale Bucket收集存款并分配代币。在此配置时间和可选限制。
1import {
2 genesis,
3 addPresaleBucketV2,
4 findPresaleBucketV2Pda,
5 findUnlockedBucketV2Pda,
6} from '@metaplex-foundation/genesis'
7import { mplToolbox } from '@metaplex-foundation/mpl-toolbox'
8import { keypairIdentity, publicKey, 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, baseMint, and TOTAL_SUPPLY from the Initialize step.
18
19const [presaleBucket] = findPresaleBucketV2Pda(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 addPresaleBucketV2(umi, {
29 genesisAccount,
30 baseMint: baseMint.publicKey,
31 baseTokenAllocation: TOTAL_SUPPLY,
32 allocationQuoteTokenCap: 100_000_000_000n, // 100 SOL cap (sets price)
33
34 // Timing
35 depositStartCondition: {
36 __kind: 'TimeAbsolute',
37 padding: Array(47).fill(0),
38 time: depositStart,
39 triggeredTimestamp: null,
40 },
41 depositEndCondition: {
42 __kind: 'TimeAbsolute',
43 padding: Array(47).fill(0),
44 time: depositEnd,
45 triggeredTimestamp: null,
46 },
47 claimStartCondition: {
48 __kind: 'TimeAbsolute',
49 padding: Array(47).fill(0),
50 time: claimStart,
51 triggeredTimestamp: null,
52 },
53 claimEndCondition: {
54 __kind: 'TimeAbsolute',
55 padding: Array(47).fill(0),
56 time: claimEnd,
57 triggeredTimestamp: null,
58 },
59
60 // Optional: Deposit limits
61 minimumDepositAmount: null, // or { amount: sol(0.1).basisPoints }
62 depositLimit: null, // or { limit: sol(10).basisPoints }
63
64 // Where collected SOL goes after transition
65 endBehaviors: [
66 {
67 __kind: 'SendQuoteTokenPercentage',
68 padding: Array(4).fill(0),
69 destinationBucket: publicKey(unlockedBucket),
70 percentageBps: 10000, // 100%
71 processed: false,
72 },
73 ],
74}).sendAndConfirm(umi)
3. 添加Unlocked Bucket
Unlocked Bucket在过渡后从Presale接收SOL。
1import {
2 addUnlockedBucketV2,
3 genesis,
4} from '@metaplex-foundation/genesis'
5import { mplToolbox } from '@metaplex-foundation/mpl-toolbox'
6import { generateSigner, 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. Finalize
所有Bucket配置完成后,Finalize以激活预售。这是不可逆的。
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 depositPresaleV2,
4 findPresaleDepositV2Pda,
5 fetchPresaleDepositV2,
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, presaleBucket, and baseMint from previous steps.
18
19await depositPresaleV2(umi, {
20 genesisAccount,
21 bucket: presaleBucket,
22 baseMint: baseMint.publicKey,
23 amountQuoteToken: sol(1).basisPoints,
24}).sendAndConfirm(umi)
25
26// Verify
27const [depositPda] = findPresaleDepositV2Pda(umi, {
28 bucket: presaleBucket,
29 recipient: umi.identity.publicKey,
30})
31const deposit = await fetchPresaleDepositV2(umi, depositPda)
32
33console.log('Deposited (after fee):', deposit.amountQuoteToken)
同一用户的多次存款会累积到单个存款账户中。
领取代币
存款期结束并开始领取后:
1import {
2 genesis,
3 claimPresaleV2,
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, presaleBucket, and baseMint from previous steps.
16
17await claimPresaleV2(umi, {
18 genesisAccount,
19 bucket: presaleBucket,
20 baseMint: baseMint.publicKey,
21 recipient: umi.identity.publicKey,
22}).sendAndConfirm(umi)
代币分配:userTokens = (userDeposit / allocationQuoteTokenCap) * baseTokenAllocation
管理员操作
执行过渡
存款结束后,执行过渡将收集的SOL转移到Unlocked Bucket。
1import {
2 genesis,
3 transitionV2,
4 WRAPPED_SOL_MINT,
5} from '@metaplex-foundation/genesis'
6import {
7 findAssociatedTokenPda,
8 mplToolbox,
9} from '@metaplex-foundation/mpl-toolbox'
10import { keypairIdentity } 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, presaleBucket, unlockedBucket, and baseMint from previous steps.
20
21const [unlockedBucketQuoteTokenAccount] = findAssociatedTokenPda(umi, {
22 owner: unlockedBucket,
23 mint: WRAPPED_SOL_MINT,
24})
25
26await transitionV2(umi, {
27 genesisAccount,
28 primaryBucket: presaleBucket,
29 baseMint: baseMint.publicKey,
30})
31 .addRemainingAccounts([
32 { pubkey: unlockedBucket, isSigner: false, isWritable: true },
33 { pubkey: unlockedBucketQuoteTokenAccount, isSigner: false, isWritable: true },
34 ])
35 .sendAndConfirm(umi)
为什么这很重要: 没有过渡,收集的SOL将保持锁定在Presale Bucket中。用户仍然可以领取代币,但团队无法访问筹集的资金。
参考
配置选项
这些选项在创建Presale Bucket时设置:
| 选项 | 描述 | 示例 |
|---|---|---|
minimumDepositAmount | 每笔交易最小存款 | { amount: sol(0.1).basisPoints } |
depositLimit | 每用户最大总存款 | { limit: sol(10).basisPoints } |
depositCooldown | 存款之间的等待时间 | { seconds: 60n } |
perCooldownDepositLimit | 每个冷却期最大存款 | { amount: sol(1).basisPoints } |
时间条件
四个条件控制Presale时间:
| 条件 | 用途 |
|---|---|
depositStartCondition | 存款开始时间 |
depositEndCondition | 存款结束时间 |
claimStartCondition | 领取开始时间 |
claimEndCondition | 领取结束时间 |
使用Unix时间戳的TimeAbsolute:
const condition = {
__kind: 'TimeAbsolute',
padding: Array(47).fill(0),
time: BigInt(Math.floor(Date.now() / 1000) + 3600), // 1小时后
triggeredTimestamp: null,
};
结束行为
定义存款期后收集的SOL如何处理:
endBehaviors: [
{
__kind: 'SendQuoteTokenPercentage',
padding: Array(4).fill(0),
destinationBucket: publicKey(unlockedBucket),
percentageBps: 10000, // 100% = 10000基点
processed: false,
},
]
获取状态
Bucket状态:
import { fetchPresaleBucketV2 } from '@metaplex-foundation/genesis';
const bucket = await fetchPresaleBucketV2(umi, presaleBucket);
console.log('Total deposits:', bucket.quoteTokenDepositTotal);
console.log('Deposit count:', bucket.depositCount);
console.log('Token allocation:', bucket.bucket.baseTokenAllocation);
console.log('SOL cap:', bucket.allocationQuoteTokenCap);
存款状态:
import { fetchPresaleDepositV2, safeFetchPresaleDepositV2 } from '@metaplex-foundation/genesis';
const deposit = await fetchPresaleDepositV2(umi, depositPda); // 未找到则抛出错误
const maybeDeposit = await safeFetchPresaleDepositV2(umi, depositPda); // 返回null
if (deposit) {
console.log('Amount deposited:', deposit.amountQuoteToken);
console.log('Amount claimed:', deposit.amountClaimed);
console.log('Fully claimed:', deposit.claimed);
}
注意事项
- 存款需支付2%协议费
- 用户必须在存款前将SOL包装为wSOL
- 同一用户的多次存款累积在一个存款账户中
- 存款结束后必须执行过渡,团队才能访问资金
- Finalize是永久性的——在调用
finalizeV2之前请仔细检查所有配置
常见问题
Presale中代币价格如何计算?
价格等于SOL上限除以代币分配量。对于100 SOL上限和1,000,000代币,价格为每代币0.0001 SOL。
如果没有达到SOL上限会怎样?
用户仍按存款比例获得代币。如果对100 SOL上限只存入了50 SOL,存款人将获得分配代币的50%。
可以设置每用户存款限额吗?
可以。使用minimumDepositAmount设置每笔交易的最低限额,使用depositLimit设置每用户的最大存款总额。
Presale和Launch Pool有什么区别?
Presale的价格由代币分配量和SOL上限固定。Launch Pool根据总存款量自然发现价格。
什么时候应该使用Presale vs Launch Pool?
当您需要可预测的定价并明确知道要筹集多少时使用Presale。使用Launch Pool进行自然价格发现。
术语表
| 术语 | 定义 |
|---|---|
| Presale | 预定汇率的固定价格代币销售 |
| SOL Cap | Presale接受的最大SOL(决定价格) |
| Token Allocation | Presale中可用的代币数量 |
| Deposit Limit | 每用户允许的最大总存款 |
| Minimum Deposit | 每笔存款交易所需的最小金额 |
| Cooldown | 用户在存款之间必须等待的时间 |
| End Behavior | 存款期结束后的自动化操作 |
| Transition | 处理结束行为的指令 |
下一步
- Launch Pool - 公平发射与有机价格发现
- Uniform Price Auction - 拍卖式基于出价的分配
- 发行代币 - 端到端代币发行指南
- 开始使用 - Genesis 发射台基础
