ローンチタイプ

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 を参照)、およびべスティングスケジュールは本ガイドの対象外です。

仕組み

  1. SOL 上限で固定価格を決定し、Presale にトークンを割り当てる
  2. ユーザーが入金ウィンドウ中に固定レートで SOL を入金する
  3. 入金期間終了後、Transition を実行して資金を移動する
  4. ユーザーが入金額に基づいてトークンを請求する

価格計算

トークン価格は割り当てトークン数と 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
Deposit2%
Graduation5%

クイックスタート

セットアップガイド

前提条件

npm install @metaplex-foundation/genesis @metaplex-foundation/umi @metaplex-foundation/umi-bundle-defaults @metaplex-foundation/mpl-toolbox

1. Genesis Account の初期化

Genesis Account はトークンを作成し、すべての配布 bucket を調整します。

initializeV2.ts
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 は入金を収集し、トークンを配布します。タイミングとオプションの上限をここで設定します。

addPresaleBucket.ts
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 を受け取ります。

addUnlockedBucket.ts
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 を有効化します。この操作は元に戻せません。

finalize.ts
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 にラップする必要があります。

wrapSol.ts
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)

入金

depositPresale.ts
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 つの入金アカウントに累積されます。

トークンの請求

入金期間が終了し、請求が開始された後:

claimPresale.ts
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 に移動します。

transitionPresale.ts
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 CapPresale が受け入れる最大 SOL 額(価格を決定)
Token AllocationPresale で利用可能なトークン数
Deposit Limitユーザーごとに許可される最大入金総額
Minimum Deposit入金トランザクションごとに必要な最低額
Cooldownユーザーが入金間に待機する必要がある時間
End Behavior入金期間終了後の自動アクション
TransitionEnd Behavior を処理するインストラクション

次のステップ