ローンチタイプ
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 を参照)、およびべスティングスケジュールは本ガイドの対象外です。
仕組み
- SOL 上限で固定価格を決定し、Presale にトークンを割り当てる
- ユーザーが入金ウィンドウ中に固定レートで SOL を入金する
- 入金期間終了後、Transition を実行して資金を移動する
- ユーザーが入金額に基づいてトークンを請求する
価格計算
トークン価格は割り当てトークン数と SOL 上限の比率で決まります:
price = allocationQuoteTokenCap / baseTokenAllocation
tokens = deposit / price
例えば、1,000,000 トークンを 100 SOL の上限で割り当てた場合:
- 価格 = 100 SOL / 1,000,000 トークン = 1 トークンあたり 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 は Transition 後に 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. ファイナライズ
すべての bucket が設定されたら、ファイナライズして Presale を有効化します。この操作は元に戻せません。
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)
同じユーザーからの複数回の入金は、1 つの入金アカウントに累積されます。
トークンの請求
入金期間が終了し、請求が開始された後:
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
管理者操作
Transition の実行
入金が締め切られた後、Transition を実行して収集した 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)
これが重要な理由: Transition を実行しないと、収集した SOL は Presale bucket にロックされたままになります。ユーザーはトークンを請求できますが、チームは調達した資金にアクセスできません。
リファレンス
設定オプション
これらのオプションは Presale bucket の作成時に設定します:
| オプション | 説明 | 例 |
|---|---|---|
minimumDepositAmount | トランザクションごとの最低入金額 | { amount: sol(0.1).basisPoints } |
depositLimit | ユーザーごとの最大入金総額 | { limit: sol(10).basisPoints } |
depositCooldown | 入金間の待機時間 | { seconds: 60n } |
perCooldownDepositLimit | クールダウン期間ごとの最大入金額 | { amount: sol(1).basisPoints } |
Time Condition
4 つの条件で 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 hour from now
triggeredTimestamp: null,
};
End Behavior
入金期間後に収集した SOL の処理方法を定義します:
endBehaviors: [
{
__kind: 'SendQuoteTokenPercentage',
padding: Array(4).fill(0),
destinationBucket: publicKey(unlockedBucket),
percentageBps: 10000, // 100% = 10000 basis points
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); // throws if not found
const maybeDeposit = await safeFetchPresaleDepositV2(umi, depositPda); // returns null
if (deposit) {
console.log('Amount deposited:', deposit.amountQuoteToken);
console.log('Amount claimed:', deposit.amountClaimed);
console.log('Fully claimed:', deposit.claimed);
}
注意事項
- 入金には 2% のプロトコル手数料が適用されます
- ユーザーは入金前に SOL を wSOL にラップする必要があります
- 同じユーザーからの複数回の入金は 1 つの入金アカウントに累積されます
- チームが資金にアクセスするには、入金締め切り後に Transition を実行する必要があります
- ファイナライズは永続的です。
finalizeV2を呼び出す前にすべての設定を十分に確認してください
FAQ
Presale でのトークン価格はどのように計算されますか?
価格は SOL 上限をトークン割り当てで割った値です。1,000,000 トークンで 100 SOL の上限の場合、価格は 1 トークンあたり 0.0001 SOL になります。
SOL の上限に達しなかった場合はどうなりますか?
ユーザーは入金額に比例してトークンを受け取ります。100 SOL の上限に対して 50 SOL しか入金されなかった場合、入金者は割り当てトークンの 50% を受け取ります。
ユーザーごとの入金上限を設定できますか?
はい。minimumDepositAmount でトランザクションごとの最低額を、depositLimit でユーザーごとの最大入金総額を設定できます。
Presale と Launch Pool の違いは何ですか?
Presale はトークン割り当てと SOL 上限で決定される固定価格です。Launch Pool は入金総額に基づいて価格が自然に決定されます。
Presale と Launch Pool のどちらを使うべきですか?
予測可能な価格設定が必要で、調達額を正確に把握したい場合は Presale を使用してください。自然な価格発見には Launch Pool を使用してください。
用語集
| 用語 | 定義 |
|---|---|
| Presale | 事前に決められたレートでの固定価格トークン販売 |
| SOL Cap | Presale が受け入れる最大 SOL 額(価格を決定) |
| Token Allocation | Presale で利用可能なトークン数 |
| Deposit Limit | ユーザーごとに許可される最大入金総額 |
| Minimum Deposit | 入金トランザクションごとに必要な最低額 |
| Cooldown | ユーザーが入金間に待機する必要がある時間 |
| End Behavior | 入金期間終了後の自動アクション |
| Transition | End Behavior を処理するインストラクション |
次のステップ
- Launch Pool - フェアローンチによる自然な価格発見
- Uniform Price Auction - オークション式入札ベース割り当て
- トークンをローンチする - エンドツーエンドのトークンローンチガイド
- Getting Started - Genesis ローンチパッドの基礎
