Launch Types
Launch Pool
Last updated September 18, 2026
Launch Pools provide organic price discovery for fair token launches on Solana. Users deposit SOL during a window and receive SPL tokens proportional to their share of total deposits. No sniping, no front-running, fair distribution for everyone.
What You'll Learn
This guide covers:
- How Launch Pool pricing and distribution works
- Setting up deposit and claim windows
- Configuring end behaviors for fund collection
- User operations: Deposit, withdraw, and claim
Summary
Launch Pools are a crowdsale-style token launch mechanism that accepts deposits during a defined window, then distributes tokens proportionally. The final token price is determined by total deposits divided by token allocation — enabling transparent, on-chain price discovery for your token generation event (TGE).
- Users deposit SOL during the deposit window (0% fee applies)
- Withdrawals allowed during deposit period (0% fee)
- Token distribution is proportional to deposit share
- An optional soft cap bounds how much the launch keeps, refunding the excess pro-rata
- End behaviors route collected SOL to treasury buckets
Launch Pools discover price from deposits. For a fixed price set upfront, use Presale; for bid-based clearing, use Uniform Price Auction. Liquidity pool creation is handled by the Raydium graduation buckets, not by the Launch Pool bucket itself.
Quick Start
How It Works
- A specific quantity of tokens is allocated to the Launch Pool bucket
- Users deposit SOL during the deposit window (withdrawals allowed with fee)
- When the window closes, tokens distribute proportionally based on deposit share
Price Discovery
The token price emerges from total deposits:
tokenPrice = totalDeposits / tokenAllocation
userTokens = (userDeposit / totalDeposits) * tokenAllocation
Example: 1,000,000 tokens allocated, 100 SOL total deposits = 0.0001 SOL per token
Price discovery is unbounded by default — the more the pool is subscribed, the higher the implied price. Set a soft cap to put a ceiling on it.
Lifecycle
- Deposit Period - Users deposit SOL during a defined window
triggerBehaviorsV2- End behaviors execute (e.g., send collected SOL to another bucket)- Claim Period - Users claim tokens proportional to their deposit weight
- Refund Period (conditional) - If a soft cap was exceeded or a minimum quote token threshold was missed, depositors call
refundLaunchPoolV2
Launch Pool Soft Cap
A soft cap is a ceiling on the quote tokens a Launch Pool keeps, configured with the softCap extension on addLaunchPoolBucketV2. Deposits above the cap are still accepted during the deposit window, the launch still succeeds, and the excess quote tokens are refunded pro-rata once the window closes.
Without a soft cap, price discovery is unbounded: every deposit is kept and the implied token price rises with subscription. A soft cap fixes the maximum the launch raises, so the effective price is softCap / baseTokenAllocation no matter how far deposits overshoot.
| Property | Behaviour when a soft cap is configured |
|---|---|
| Deposits above the cap | Accepted during the deposit window — deposits are never rejected at the cap |
| Launch outcome | Succeeds — a soft cap is a ceiling, never a failure condition |
| Base token distribution | Full baseTokenAllocation distributed pro-rata across all deposits |
| Excess quote tokens | Refunded pro-rata via refundLaunchPoolV2 after the deposit window ends |
| Proceeds seen by end behaviors | Clamped to the soft cap, so SendQuoteTokenPercentage forwards at most softCap |
| Raydium graduation start price | Derived from the capped proceeds, not the raw deposit total |
A soft cap is a ceiling on capital raised, not a floor. The floor is a separate extension, minimumQuoteTokenThreshold — see Soft Cap and Minimum Quote Token Threshold Together.
Configuring a Soft Cap on a Launch Pool Bucket
Pass a softCap value to addLaunchPoolBucketV2 when you add the bucket. The amount is denominated in quote token quantum units (lamports for wSOL).
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);
softCap is a required argument on addLaunchPoolBucketV2 in @metaplex-foundation/genesis 0.42.0. Unlike the other Launch Pool extensions it has no default, so pass softCap: null explicitly when you do not want a cap.
Soft caps are validated when the extension is set and again at finalizeV2:
| Rule | Error if violated |
|---|---|
softCap.amount must be greater than zero | InvalidSoftCap (221) |
softCap.amount must be greater than or equal to minimumQuoteTokenThreshold.amount | SoftCapBelowThreshold (222) |
Extensions can only be added or removed before finalizeV2 | Account is rejected as already finalized |
A soft cap can also be set or cleared on an existing bucket with addLaunchPoolBucketV2Extensions and removeLaunchPoolBucketV2Extensions using the SoftCap member of LaunchPoolV2ExtensionType — but only while the Genesis Account is unfinalized.
Oversubscription and Pro-Rata Refund Math
A Launch Pool is oversubscribed when quoteTokenDepositTotal is strictly greater than softCap.amount. Each deposit is then split into a filled portion that counts toward the cap and an excess portion that is refundable:
filled_i = ceil(deposit_i * softCap / totalDeposits)
excess_i = deposit_i - filled_i
tokens_i = (weighted_i / weightedQuoteTokenTotal) * baseTokenAllocation
filled rounds up so that the sum of all filled portions is always at least the soft cap. This keeps the bucket solvent against the capped graduation transfer regardless of whether refunds or graduation are cranked first; the rounding leaves at most a few quantum units of dust in the bucket.
Token allocation is unaffected by the cap. Refunding excess does not remove a depositor's weighted contribution, so everyone still receives tokens proportional to their full deposit.
Worked example — 1,000,000 tokens allocated, a 100 SOL soft cap, and 150 SOL deposited:
| Depositor | Deposited | Filled (kept) | Refunded | Tokens received |
|---|---|---|---|---|
| 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) |
| Total | 150 SOL | 100 SOL | 50 SOL | 1,000,000 |
The effective price is 0.0001 SOL per token (100 SOL / 1,000,000), not the 0.00015 SOL per token the uncapped deposit total would have implied. On-chain values are computed in lamports with filled rounded up, so real figures differ from the rounded SOL amounts above by a few lamports.
Refunding Excess Deposits with refundLaunchPoolV2
refundLaunchPoolV2 returns a depositor's excess quote tokens after an oversubscribed deposit window closes. It takes no amount argument — the program computes the refundable amount from the deposit, the soft cap, and the bucket's deposit total.
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)
Key properties of the refund path:
- Cranking is permissionless. Only
payermust sign. If the depositor also signs, their empty base token account is closed for them. - No fee and no penalty are applied to a refund; deposit and withdraw penalty schedules do not affect it.
- Claim order does not matter. An excess refund is allowed before or after
claimLaunchPoolV2; both orderings converge on the same final state. - One refund per deposit. A second call returns
DepositAlreadyRefunded. - Refunds are gated on the deposit window ending. Calling earlier returns
LaunchPoolNotEnded. - Refunds require a failed floor or an exceeded cap. If neither applies, the call returns
LaunchPoolThresholdMet.
Soft Cap and Minimum Quote Token Threshold Together
softCap and minimumQuoteTokenThreshold are independent extensions that bound a Launch Pool from opposite directions, and refundLaunchPoolV2 serves both. When the floor fails, that takes precedence and the refund is a full one.
| Configuration | Deposits below the floor | Deposits between floor and cap | Deposits above the cap |
|---|---|---|---|
| Neither set | Launch succeeds, no refunds | Launch succeeds, no refunds | Launch succeeds, no refunds |
| Floor only | Launch fails, full refunds | Launch succeeds, no refunds | Launch succeeds, no refunds |
| Cap only | Launch succeeds, no refunds | Launch succeeds, no refunds | Launch succeeds, excess refunded pro-rata |
| Floor and cap | Launch fails, full refunds | Launch succeeds, no refunds | Launch succeeds, excess refunded pro-rata |
A full refund removes the depositor's weighted contribution from the bucket and cannot follow a claim — a failed floor means no claim was possible. An excess refund leaves the weighted contribution intact so the pro-rata claim formula stays correct.
Fees
| Instruction | Solana |
|---|---|
| User deposit fee | 0% |
| User withdraw fee | 0% |
| Creator withdraw fee | 5%* |
* This fee only applies when creators withdraw liquidity
Each deposit increases your credited balance by the SOL left after the user deposit fee (0%) is withheld from the deposit.
Setup Guide
Prerequisites
npm install @metaplex-foundation/genesis @metaplex-foundation/umi @metaplex-foundation/umi-bundle-defaults @metaplex-foundation/mpl-toolbox
1. Initialize the Genesis Account
The Genesis Account creates your token and coordinates all distribution buckets.
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)
The totalSupplyBaseToken should equal the sum of all bucket allocations.
2. Add the Launch Pool Bucket
The Launch Pool bucket collects deposits and distributes tokens proportionally. Configure timing here.
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 // Where collected SOL goes after transition
63 endBehaviors: [
64 {
65 __kind: 'SendQuoteTokenPercentage',
66 padding: Array(4).fill(0),
67 destinationBucket: publicKey(unlockedBucket),
68 percentageBps: 10000, // 100%
69 processed: false,
70 },
71 ],
72}).sendAndConfirm(umi)
3. Add the Unlocked Bucket
The Unlocked bucket receives SOL from the Launch Pool after triggerBehaviorsV2 executes.
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. Finalize
Once all buckets are configured, finalize to activate the launch. This is irreversible.
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)
User Operations
Wrapping SOL
Users must wrap SOL to wSOL before depositing.
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)
Depositing
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)
Multiple deposits from the same user accumulate into a single deposit account.
Withdrawing
Users can withdraw during the deposit period. A 0% fee applies.
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)
If a user withdraws their entire balance, the deposit PDA is closed.
Claiming Tokens
After the deposit period ends and claims open:
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)
Token allocation: userTokens = (userDeposit / totalDeposits) * bucketTokenAllocation
Refunding a Deposit
Refunds are available in two cases: the launch missed its minimumQuoteTokenThreshold (full refund), or it exceeded its softCap (excess-only refund). Both use the same instruction — see Refunding Excess Deposits with refundLaunchPoolV2.
Admin Operations
Executing triggerBehaviorsV2
After deposits close, run triggerBehaviorsV2 to move collected SOL to the 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)
Why this matters: Without running triggerBehaviorsV2, collected SOL stays locked in the Launch Pool bucket. Users can still claim tokens, but the team cannot access the raised funds.
Reference
Time Conditions
Four conditions control Launch Pool timing:
| Condition | Purpose |
|---|---|
depositStartCondition | When deposits open |
depositEndCondition | When deposits close |
claimStartCondition | When claims open |
claimEndCondition | When claims close |
Use TimeAbsolute with a Unix timestamp:
const condition = {
__kind: 'TimeAbsolute',
padding: Array(47).fill(0),
time: BigInt(Math.floor(Date.now() / 1000) + 3600), // 1 hour from now
triggeredTimestamp: null,
};
End Behaviors
Define what happens to collected SOL after the deposit period:
endBehaviors: [
{
__kind: 'SendQuoteTokenPercentage',
padding: Array(4).fill(0),
destinationBucket: publicKey(unlockedBucket),
percentageBps: 10000, // 100% = 10000 basis points
processed: false,
},
]
You can split funds across multiple buckets:
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 Extensions
Extensions are optional guards configured on the Launch Pool bucket. All are set through addLaunchPoolBucketV2, or added and removed individually with addLaunchPoolBucketV2Extensions and removeLaunchPoolBucketV2Extensions before finalizeV2.
| Extension | Type | Purpose |
|---|---|---|
softCap | { amount: bigint } | Ceiling on quote tokens kept; excess refunded pro-rata |
minimumQuoteTokenThreshold | { amount: bigint } | Floor below which the launch fails and full refunds open |
minimumDepositAmount | { amount: bigint } | Minimum quote tokens per deposit |
depositLimit | { limit: bigint } | Maximum quote tokens per account |
allowlist | Allowlist | Gates deposits to an allowlisted set of wallets |
claimSchedule | ClaimSchedule | Vests claimed base tokens over time |
bonusSchedule | LinearBpsScheduleV2 | Time-weighted deposit bonus |
depositPenalty | LinearBpsScheduleV2 | Time-weighted deposit penalty |
withdrawPenalty | LinearBpsScheduleV2 | Time-weighted withdrawal penalty |
backendSigner | BackendSigner | Requires a backend co-signer on user actions |
Common Errors
| Error | Code | Cause |
|---|---|---|
InvalidSoftCap | 221 | softCap.amount is zero — omit the extension instead of setting it to 0 |
SoftCapBelowThreshold | 222 | softCap.amount is below minimumQuoteTokenThreshold.amount |
LaunchPoolNotEnded | — | refundLaunchPoolV2 called before the deposit window closed |
LaunchPoolThresholdMet | 173 | Refund requested when the floor was met and the cap was not exceeded |
DepositAlreadyRefunded | — | refundLaunchPoolV2 called twice for the same deposit |
DepositAlreadyClaimed | — | Full refund requested after the depositor already claimed tokens |
Fetching State
Bucket state:
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);
Deposit state:
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);
}
Notes
- User deposit and withdraw fees for Launch Pool are shown in Fees above.
- Multiple deposits from the same user accumulate in one deposit account
- If a user withdraws their entire balance, the deposit PDA closes
triggerBehaviorsV2must be executed after deposits close for end behaviors to process- Users must have wSOL (wrapped SOL) to deposit
softCapis a required argument onaddLaunchPoolBucketV2in@metaplex-foundation/genesis0.42.0 — passsoftCap: nullwhen no cap is wanted- Launch Pool extensions, including
softCap, can only be added or removed beforefinalizeV2 - Soft caps are supported by the Genesis program, the JavaScript SDK, and the
mplxCLI - An oversubscribed Launch Pool leaves a few quantum units of rounding dust in the bucket, because each depositor's filled portion rounds up
quoteTokenDepositTotalanddepositCountare preserved as historical records after refunds;refundCounttracks refunds processed
FAQ
How is the token price determined in a Launch Pool?
The price is discovered organically based on total deposits. Final price equals total SOL deposited divided by tokens allocated. More deposits means higher implied price per token.
Can users withdraw their deposits?
Yes, users can withdraw during the deposit period. A 0% withdrawal fee applies to discourage gaming the system.
What happens if I deposit multiple times?
Multiple deposits from the same wallet accumulate into a single deposit account. Your total share is based on your combined deposits.
When can users claim their tokens?
After the deposit period ends and the claim window opens (defined by claimStartCondition). triggerBehaviorsV2 must be executed first to process end behaviors.
What's the difference between Launch Pool and Presale?
Launch Pool discovers price organically based on deposits with proportional distribution. Presale has a fixed price set upfront with first-come-first-served allocation up to the cap.
What is a Launch Pool soft cap?
A soft cap is a ceiling on the quote tokens a Launch Pool keeps, set with the softCap extension. Deposits above the cap are still accepted, the launch still succeeds, and the excess is refunded pro-rata after the deposit window closes.
What is the difference between a soft cap and a minimum quote token threshold?
A soft cap is a ceiling on capital raised and never causes a launch to fail. A minimumQuoteTokenThreshold is a floor — if total deposits fall below it, the launch fails and every depositor can take a full refund. They are separate extensions and can be used together.
Do depositors receive fewer tokens when a Launch Pool is oversubscribed?
No. The full base token allocation is still distributed pro-rata across all deposits. Oversubscription refunds excess quote tokens instead of cutting token allocations, so the effective price is capped at softCap / baseTokenAllocation.
Does a soft cap change the Raydium graduation start price?
Yes. When a Launch Pool is oversubscribed, the graduation start price is derived from the capped proceeds rather than the raw deposit total, so it matches the amount actually forwarded by SendQuoteTokenPercentage.
Glossary
| Term | Definition |
|---|---|
| Launch Pool | Deposit-based distribution where price is discovered at close |
| Deposit Window | Time period when users can deposit and withdraw SOL |
| Claim Window | Time period when users can claim their proportional tokens |
| End Behavior | Automated action executed after deposit period ends |
triggerBehaviorsV2 | Instruction that processes end behaviors and routes funds |
| Proportional Distribution | Token allocation based on user's share of total deposits |
| Quote Token | The token users deposit (usually wSOL) |
| Base Token | The token being distributed |
| Soft Cap | Ceiling on the quote tokens a Launch Pool keeps; excess is refunded pro-rata |
| Minimum Quote Token Threshold | Floor below which a Launch Pool fails and full refunds open |
| Oversubscription | State where total deposits exceed the configured soft cap |
| Filled Portion | The part of a deposit that counts toward the soft cap and is kept by the launch |
| Excess Refund | Return of the portion of a deposit above the soft cap, leaving token allocation intact |
Next Steps
- Presale - Fixed-price token sale
- Uniform Price Auction - Bid-based token offering
- Launch a Token - End-to-end token launch guide
- Integration APIs - Query launch and token sale data via API
