From 7484b0ab8ad9d740db1e17b1f265f81fa413ab76 Mon Sep 17 00:00:00 2001 From: Tony Boyle Date: Fri, 18 Sep 2026 01:17:27 +0100 Subject: [PATCH 1/2] docs(genesis): document the Launch Pool soft cap The soft cap shipped in the Genesis program and JS client 0.42.0 (LaunchPoolV2Extensions.soft_cap / LaunchPoolV2ExtensionType::SoftCap) but was not documented anywhere on the developer hub. - Add a "Launch Pool Soft Cap" section covering configuration, the oversubscription split, the pro-rata excess refund, and how a soft cap interacts with minimumQuoteTokenThreshold - Document refundLaunchPoolV2, which serves both the failed-floor full refund and the oversubscribed excess refund, with a new umi example - Note that softCap is a required argument on addLaunchPoolBucketV2 in 0.42.0 and must be passed as null when no cap is wanted - Add a Launch Pool extensions table and a Common Errors table covering InvalidSoftCap (221) and SoftCapBelowThreshold (222) - Replace the "Out of Scope" section with an inline callout, per the GEO/LLM rubric --- .../genesis/refund_launch_pool_v2/index.js | 32 +++ .../genesis/refund_launch_pool_v2/umi.js | 35 +++ .../en/smart-contracts/genesis/launch-pool.md | 200 +++++++++++++++++- 3 files changed, 263 insertions(+), 4 deletions(-) create mode 100644 src/examples/genesis/refund_launch_pool_v2/index.js create mode 100644 src/examples/genesis/refund_launch_pool_v2/umi.js diff --git a/src/examples/genesis/refund_launch_pool_v2/index.js b/src/examples/genesis/refund_launch_pool_v2/index.js new file mode 100644 index 000000000..30e0f2b32 --- /dev/null +++ b/src/examples/genesis/refund_launch_pool_v2/index.js @@ -0,0 +1,32 @@ +/** + * Example: refund_launch_pool_v2 + * + * + * + * This file is auto-generated by scripts/build-examples.js + * Edit the native .js/.ts and .rs files, then run: node scripts/build-examples.js + */ + +const umiSections = { + "imports": "import {\n genesis,\n refundLaunchPoolV2,\n} from '@metaplex-foundation/genesis'\nimport { mplToolbox } from '@metaplex-foundation/mpl-toolbox'\nimport { createUmi } from '@metaplex-foundation/umi-bundle-defaults'", + "setup": "const umi = createUmi('https://api.mainnet-beta.solana.com')\n .use(mplToolbox())\n .use(genesis())\n\n// umi.use(keypairIdentity(yourKeypair));\n\n// Assumes genesisAccount, launchPoolBucket, and baseMint from previous steps.\n// Only valid after the deposit window closed AND either the\n// minimumQuoteTokenThreshold was missed or the softCap was exceeded.", + "main": "// The program computes the refundable amount, so there is no amount argument.\n// A missed threshold refunds the full deposit; an exceeded soft cap refunds\n// only the excess, leaving the depositor's token allocation intact.\nawait refundLaunchPoolV2(umi, {\n genesisAccount,\n bucket: launchPoolBucket,\n baseMint: baseMint.publicKey,\n recipient: umi.identity.publicKey,\n}).sendAndConfirm(umi)", + "output": "", + "full": "// [IMPORTS]\nimport {\n genesis,\n refundLaunchPoolV2,\n} from '@metaplex-foundation/genesis'\nimport { mplToolbox } from '@metaplex-foundation/mpl-toolbox'\nimport { createUmi } from '@metaplex-foundation/umi-bundle-defaults'\n// [/IMPORTS]\n\n// [SETUP]\nconst umi = createUmi('https://api.mainnet-beta.solana.com')\n .use(mplToolbox())\n .use(genesis())\n\n// umi.use(keypairIdentity(yourKeypair));\n\n// Assumes genesisAccount, launchPoolBucket, and baseMint from previous steps.\n// Only valid after the deposit window closed AND either the\n// minimumQuoteTokenThreshold was missed or the softCap was exceeded.\n// [/SETUP]\n\n// [MAIN]\n// The program computes the refundable amount, so there is no amount argument.\n// A missed threshold refunds the full deposit; an exceeded soft cap refunds\n// only the excess, leaving the depositor's token allocation intact.\nawait refundLaunchPoolV2(umi, {\n genesisAccount,\n bucket: launchPoolBucket,\n baseMint: baseMint.publicKey,\n recipient: umi.identity.publicKey,\n}).sendAndConfirm(umi)\n// [/MAIN]\n\n// [OUTPUT]\n// [/OUTPUT]\n" +} + +export const metadata = { + title: "refund_launch_pool_v2", + description: "", + tags: [], +} + +export const examples = { + umi: { + framework: 'Umi', + language: 'javascript', + code: umiSections.full, + sections: umiSections, + }, + +} diff --git a/src/examples/genesis/refund_launch_pool_v2/umi.js b/src/examples/genesis/refund_launch_pool_v2/umi.js new file mode 100644 index 000000000..09515ad1c --- /dev/null +++ b/src/examples/genesis/refund_launch_pool_v2/umi.js @@ -0,0 +1,35 @@ +// [IMPORTS] +import { + genesis, + refundLaunchPoolV2, +} from '@metaplex-foundation/genesis' +import { mplToolbox } from '@metaplex-foundation/mpl-toolbox' +import { createUmi } from '@metaplex-foundation/umi-bundle-defaults' +// [/IMPORTS] + +// [SETUP] +const umi = createUmi('https://api.mainnet-beta.solana.com') + .use(mplToolbox()) + .use(genesis()) + +// umi.use(keypairIdentity(yourKeypair)); + +// Assumes genesisAccount, launchPoolBucket, and baseMint from previous steps. +// Only valid after the deposit window closed AND either the +// minimumQuoteTokenThreshold was missed or the softCap was exceeded. +// [/SETUP] + +// [MAIN] +// The program computes the refundable amount, so there is no amount argument. +// A missed threshold refunds the full deposit; an exceeded soft cap refunds +// only the excess, leaving the depositor's token allocation intact. +await refundLaunchPoolV2(umi, { + genesisAccount, + bucket: launchPoolBucket, + baseMint: baseMint.publicKey, + recipient: umi.identity.publicKey, +}).sendAndConfirm(umi) +// [/MAIN] + +// [OUTPUT] +// [/OUTPUT] diff --git a/src/pages/en/smart-contracts/genesis/launch-pool.md b/src/pages/en/smart-contracts/genesis/launch-pool.md index 0e75bfba5..ef0145031 100644 --- a/src/pages/en/smart-contracts/genesis/launch-pool.md +++ b/src/pages/en/smart-contracts/genesis/launch-pool.md @@ -3,7 +3,7 @@ title: Launch Pool metaTitle: Genesis Launch Pool | Fair Launch & Token Distribution on Solana | Metaplex description: Fair launch token distribution on Solana. Users deposit SOL and receive SPL tokens proportionally — an on-chain crowdsale with organic price discovery on the Genesis token launchpad. created: '01-15-2025' -updated: '01-31-2026' +updated: '09-18-2026' keywords: - launch pool - token distribution @@ -17,10 +17,15 @@ keywords: - token launchpad alternative - SPL token launch - on-chain token launch + - soft cap + - oversubscription + - pro-rata refund + - minimum quote token threshold about: - Launch pools - Price discovery - Token distribution + - Soft caps proficiencyLevel: Intermediate programmingLanguage: - JavaScript @@ -45,6 +50,14 @@ faqs: a: After the deposit period ends and the claim window opens (defined by claimStartCondition). triggerBehaviorsV2 must be executed first to process end behaviors. - q: What's the difference between Launch Pool and Presale? a: 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. + - q: What is a Launch Pool soft cap? + a: 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. + - q: What is the difference between a soft cap and a minimum quote token threshold? + a: A soft cap is a ceiling on capital raised and never causes a launch to fail. A minimum quote token threshold 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. + - q: Do depositors receive fewer tokens when a Launch Pool is oversubscribed? + a: 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 divided by baseTokenAllocation. + - q: Does a soft cap change the Raydium graduation start price? + a: 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. --- **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. {% .lead %} @@ -64,11 +77,12 @@ Launch Pools are a crowdsale-style token launch mechanism that accepts deposits - Users deposit SOL during the deposit window ({% fee product="genesis" config="launchPool" fee="deposit" /%} fee applies) - Withdrawals allowed during deposit period ({% fee product="genesis" config="launchPool" fee="withdraw" /%} fee) - Token distribution is proportional to deposit share +- An optional [soft cap](#launch-pool-soft-cap) bounds how much the launch keeps, refunding the excess pro-rata - End behaviors route collected SOL to treasury buckets -## Out of Scope - -Fixed-price sales (see [Presale](/smart-contracts/genesis/presale)), bid-based auctions (see [Uniform Price Auction](/smart-contracts/genesis/uniform-price-auction)), and liquidity pool creation (use Raydium/Orca). +{% callout type="note" %} +Launch Pools discover price from deposits. For a fixed price set upfront, use [Presale](/smart-contracts/genesis/presale); for bid-based clearing, use [Uniform Price Auction](/smart-contracts/genesis/uniform-price-auction). Liquidity pool creation is handled by the Raydium graduation buckets, not by the Launch Pool bucket itself. +{% /callout %} ## Quick Start @@ -223,11 +237,130 @@ 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](#launch-pool-soft-cap) to put a ceiling on it. + ### Lifecycle 1. **Deposit Period** - Users deposit SOL during a defined window 2. **`triggerBehaviorsV2`** - End behaviors execute (e.g., send collected SOL to another bucket) 3. **Claim Period** - Users claim tokens proportional to their deposit weight +4. **Refund Period** (conditional) - If a [soft cap](#launch-pool-soft-cap) was exceeded or a [minimum quote token threshold](#soft-cap-and-minimum-quote-token-threshold-together) 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 | + +{% callout type="note" %} +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](#soft-cap-and-minimum-quote-token-threshold-together). +{% /callout %} + +### 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). + +{% totem %} + +```typescript {% title="Add a Launch Pool bucket with a 100 SOL soft cap" %} +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); +``` + +{% /totem %} + +{% callout type="warning" %} +`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. +{% /callout %} + +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: + +{% totem %} + +```text {% title="Per-depositor split in an oversubscribed Launch Pool" %} +filled_i = ceil(deposit_i * softCap / totalDeposits) +excess_i = deposit_i - filled_i +tokens_i = (weighted_i / weightedQuoteTokenTotal) * baseTokenAllocation +``` + +{% /totem %} + +`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. + +{% code-tabs-imported from="genesis/refund_launch_pool_v2" frameworks="umi" filename="refundLaunchPool" /%} + +Key properties of the refund path: + +- **Cranking is permissionless.** Only `payer` must 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 | + +{% callout type="note" %} +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. +{% /callout %} ## Fees @@ -305,6 +438,10 @@ After the deposit period ends and claims open: 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](#refunding-excess-deposits-with-refund-launch-pool-v2). + ## Admin Operations ### Executing `triggerBehaviorsV2` @@ -388,6 +525,34 @@ endBehaviors: [ {% /totem %} +### 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:** @@ -402,6 +567,10 @@ 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) +console.log('Soft cap:', bucket.extensions.softCap); +console.log('Floor:', bucket.extensions.minimumQuoteTokenThreshold); ``` {% /totem %} @@ -419,6 +588,7 @@ const maybeDeposit = await safeFetchLaunchPoolDepositV2(umi, depositPda); // ret if (deposit) { console.log('Amount deposited:', deposit.amountQuoteToken); console.log('Claimed:', deposit.claimed); + console.log('Refunded:', deposit.refunded); } ``` @@ -431,6 +601,11 @@ if (deposit) { - If a user withdraws their entire balance, the deposit PDA closes - `triggerBehaviorsV2` must be executed after deposits close for end behaviors to process - Users must have wSOL (wrapped SOL) to deposit +- `softCap` is a required argument on `addLaunchPoolBucketV2` in `@metaplex-foundation/genesis` 0.42.0 — pass `softCap: null` when no cap is wanted +- Launch Pool extensions, including `softCap`, can only be added or removed before `finalizeV2` +- Soft caps are supported by the Genesis program and JavaScript SDK; the [`mplx` CLI](/dev-tools/cli/genesis/launch-pool) does not expose a soft cap flag yet +- An oversubscribed Launch Pool leaves a few quantum units of rounding dust in the bucket, because each depositor's filled portion rounds up +- `quoteTokenDepositTotal` and `depositCount` are preserved as historical records after refunds; `refundCount` tracks refunds processed ## FAQ @@ -449,6 +624,18 @@ After the deposit period ends and the claim window opens (defined by `claimStart ### 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 | @@ -461,6 +648,11 @@ Launch Pool discovers price organically based on deposits with proportional dist | **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 From 32faea20059b04637423f9c67b504b594083468c Mon Sep 17 00:00:00 2001 From: Tony Boyle Date: Fri, 18 Sep 2026 02:18:49 +0100 Subject: [PATCH 2/2] docs(genesis): document the CLI soft cap flag and refund command Covers the CLI surface added in metaplex-foundation/cli#132. - Add --softCap to the add-launch-pool flags table and a worked example configuring a floor and a ceiling together - Add a "Soft Cap and Minimum Threshold" section contrasting the two, linking to the protocol page for the refund math - Document the new "mplx genesis refund" command, both refund cases, and its permissionless crank behaviour - Add the soft cap validation failures and refund errors to Common Errors - Add three FAQ entries, mirrored into faqs frontmatter, and four glossary terms - Replace the Out of Scope section with an inline callout, per the GEO/LLM rubric - Drop the interim note on the protocol page saying the CLI has no soft cap flag, now that it does --- .../en/dev-tools/cli/genesis/launch-pool.md | 105 ++++++++++++++++-- .../en/smart-contracts/genesis/launch-pool.md | 2 +- 2 files changed, 98 insertions(+), 9 deletions(-) diff --git a/src/pages/en/dev-tools/cli/genesis/launch-pool.md b/src/pages/en/dev-tools/cli/genesis/launch-pool.md index 3422cd6af..625cbeeba 100644 --- a/src/pages/en/dev-tools/cli/genesis/launch-pool.md +++ b/src/pages/en/dev-tools/cli/genesis/launch-pool.md @@ -8,11 +8,15 @@ keywords: - token distribution - proportional distribution - mplx genesis deposit + - mplx genesis refund + - soft cap + - oversubscription about: - launch pool bucket - proportional token distribution - deposit and claim lifecycle - end behaviors + - soft caps proficiencyLevel: Intermediate programmingLanguage: - Bash @@ -22,6 +26,7 @@ howToSteps: - Wrap SOL and deposit quote tokens during the deposit window - Transition collected funds to destination buckets after deposits close (if end behaviors are set) - Claim base tokens proportional to your deposit after the claim period opens + - Refund excess deposits with the refund command if the pool exceeded its soft cap howToTools: - Metaplex CLI (mplx) - Solana CLI @@ -34,6 +39,12 @@ faqs: a: End behaviors forward collected quote tokens from a launch pool to destination buckets (usually unlocked buckets) after the deposit period ends. Use the transition command to execute them. - q: What is a claim schedule? a: A claim schedule adds vesting to token claims — tokens are released gradually over time instead of all at once, with optional cliff periods. + - q: What does the softCap flag do? + a: The softCap flag caps the quote tokens a launch pool keeps. Deposits above the cap are still accepted and the launch still succeeds, but the excess is refunded pro-rata with the refund command. Token allocation is unaffected. + - q: What is the difference between softCap and minimumQuoteTokenThreshold? + a: softCap is a ceiling and never causes a launch to fail. minimumQuoteTokenThreshold is a floor — if deposits fall below it the launch fails and depositors can take a full refund. They can be used together, and softCap must be greater than or equal to minimumQuoteTokenThreshold. + - q: When can the refund command be used? + a: Only after the deposit window closes, and only when the launch missed its minimum quote token threshold or exceeded its soft cap. In any other case the command reports that the pool is not refundable. --- {% callout title="What You'll Do" %} @@ -48,15 +59,15 @@ Run the full launch pool lifecycle from the CLI: A launch pool collects deposits during a window and distributes tokens proportionally. This page covers the full launch pool lifecycle — from creating the bucket to claiming tokens. - **Distribution**: Proportional — your share of deposits determines your share of tokens -- **Commands**: `bucket add-launch-pool`, `deposit`, `withdraw`, `transition`, `claim` -- **Optional features**: End behaviors, deposit/withdraw penalties, bonus schedules, claim vesting, allowlists +- **Commands**: `bucket add-launch-pool`, `deposit`, `withdraw`, `transition`, `claim`, `refund` +- **Optional features**: Soft cap, minimum threshold, end behaviors, deposit/withdraw penalties, bonus schedules, claim vesting, allowlists - **Quote token**: Wrapped SOL by default — wrap SOL before depositing -## Out of Scope - -Presale buckets, unlocked buckets, Genesis account creation, finalization, frontend integration, token economics modeling. +{% callout type="note" %} +This page covers launch pool buckets only. Presale and unlocked buckets, Genesis account creation and finalization are covered on their own CLI pages. +{% /callout %} -**Jump to:** [Add Bucket](#add-launch-pool-bucket) · [Deposit](#deposit) · [Withdraw](#withdraw) · [Transition](#transition) · [Claim](#claim) · [Full Lifecycle](#full-lifecycle-example) · [Common Errors](#common-errors) · [FAQ](#faq) +**Jump to:** [Add Bucket](#add-launch-pool-bucket) · [Soft Cap](#soft-cap-and-minimum-threshold) · [Deposit](#deposit) · [Withdraw](#withdraw) · [Transition](#transition) · [Claim](#claim) · [Refund](#refund) · [Full Lifecycle](#full-lifecycle-example) · [Common Errors](#common-errors) · [FAQ](#faq) ## Add Launch Pool Bucket @@ -84,7 +95,8 @@ mplx genesis bucket add-launch-pool \ | `--endBehavior ` | | Format: `:` where `10000` = 100%. Can be specified multiple times | No | | `--minimumDeposit ` | | Minimum deposit per transaction in base units | No | | `--depositLimit ` | | Maximum deposit per user in base units | No | -| `--minimumQuoteTokenThreshold ` | | Minimum total quote tokens required for the bucket to succeed | No | +| `--minimumQuoteTokenThreshold ` | | Floor: minimum total quote tokens required for the bucket to succeed | No | +| `--softCap ` | | Ceiling: maximum total quote tokens the bucket keeps. Deposits above this are refunded pro-rata | No | | `--depositPenalty ` | | Penalty schedule JSON | No | | `--withdrawPenalty ` | | Withdraw penalty schedule JSON (same format as depositPenalty) | No | | `--bonusSchedule ` | | Bonus schedule JSON | No | @@ -148,6 +160,35 @@ mplx genesis bucket add-launch-pool \ --claimSchedule '{"startTime":1704153601,"endTime":1735689600,"period":86400,"cliffTime":1704240000,"cliffAmountBps":1000}' ``` +4. With a floor and a ceiling: +```bash {% title="With minimum threshold and soft cap" %} +mplx genesis bucket add-launch-pool \ + --allocation 500000000000000 \ + --depositStart 1704067200 \ + --depositEnd 1704153600 \ + --claimStart 1704153601 \ + --claimEnd 1735689600 \ + --minimumQuoteTokenThreshold 10000000000 \ + --softCap 100000000000 +``` + +## Soft Cap and Minimum Threshold + +The `--softCap` flag caps the quote tokens a launch pool keeps, and `--minimumQuoteTokenThreshold` sets the minimum it must raise to succeed. Both are optional, both are denominated in quote token quantum units (lamports for wrapped SOL), and they can be used together. + +| Flag | Role | Effect when breached | +|------|------|----------------------| +| `--minimumQuoteTokenThreshold` | Floor | Launch fails; every depositor can take a full refund | +| `--softCap` | Ceiling | Launch still succeeds; the excess above the cap is refunded pro-rata | + +A soft cap does not reject deposits at the cap. Deposits continue to be accepted for the whole window, the full base token allocation is still distributed proportionally, and only the excess quote tokens are returned. The effective price is therefore capped at `softCap / allocation` no matter how far deposits overshoot. + +{% callout type="note" %} +`--softCap` must be greater than zero and greater than or equal to `--minimumQuoteTokenThreshold`. The CLI checks both before sending the transaction, so a misconfiguration fails immediately rather than returning `InvalidSoftCap` or `SoftCapBelowThreshold` from the program. +{% /callout %} + +For the underlying protocol behaviour, including the pro-rata refund math and how a soft cap affects Raydium graduation pricing, see [Launch Pool Soft Cap](/smart-contracts/genesis/launch-pool#launch-pool-soft-cap). + ## Deposit The `mplx genesis deposit` command deposits quote tokens into a launch pool bucket during the deposit window. If using SOL as the quote token, wrap it first. @@ -232,6 +273,36 @@ mplx genesis claim --bucketIndex 0 mplx genesis claim --bucketIndex 0 --recipient ``` +## Refund + +The `mplx genesis refund` command returns quote tokens to a depositor after the deposit window closes. It takes no amount flag — the program derives the refundable amount. + +```bash {% title="Refund a launch pool deposit" %} +mplx genesis refund --bucketIndex 0 +``` + +Refunds are only available in two cases, and the command reports which one applied: + +| Case | Refund | Effect on tokens | +|------|--------|------------------| +| Launch missed `--minimumQuoteTokenThreshold` | Full deposit | No tokens were claimable | +| Launch exceeded `--softCap` | Excess above the cap only | Token allocation unaffected | + +### Options + +| Flag | Short | Description | Required | +|------|-------|-------------|----------| +| `--bucketIndex ` | `-b` | Index of the launch pool bucket (default: 0) | No | +| `--recipient ` | | Depositor being refunded (default: signer) | No | + +### Notes + +- Only the payer must sign, so a refund can be cranked on another wallet's behalf with `--recipient`. +- No fee or penalty is applied to a refund. +- An excess refund can be run before or after `mplx genesis claim` — the order does not matter. +- Each deposit can only be refunded once. +- If the pool met its floor and stayed under its cap, the command reports that the pool is not refundable rather than sending a transaction. + ## Full Lifecycle Example ```bash {% title="Complete launch pool lifecycle" %} @@ -295,6 +366,11 @@ mplx genesis revoke $GENESIS --revokeMint | Exceeds deposit limit | User's total deposits exceed `depositLimit` | Reduce the deposit amount — you've hit the per-user cap | | End behavior not configured | Running `transition` on a bucket without end behaviors | Transition is only needed for buckets with `--endBehavior` | | Deposit period not ended | Running `transition` before deposits close | Wait until after `depositEnd` timestamp | +| `"softCap" must be greater than zero` | Passed `--softCap 0` | Omit the flag entirely to leave the launch uncapped | +| `"softCap" must be greater than or equal to "minimumQuoteTokenThreshold"` | Ceiling set below the floor | Raise `--softCap` or lower `--minimumQuoteTokenThreshold` | +| Launch pool is not refundable | Running `refund` on a pool that met its floor and stayed under its cap | Refunds only apply to a failed threshold or an exceeded soft cap | +| Deposit already refunded | Running `refund` twice for the same depositor | Each deposit can only be refunded once | +| No deposit found | Running `refund` or `claim` for a wallet that never deposited | Check the address passed to `--recipient` | ## FAQ @@ -311,7 +387,16 @@ End behaviors forward collected quote tokens from a launch pool to destination b A claim schedule adds vesting to token claims. Instead of receiving all tokens at once, they are released gradually based on the configured `period`, `cliffTime`, and `cliffAmountBps`. **What happens if minimumQuoteTokenThreshold is not met?** -If the total deposits don't reach the threshold, the bucket does not succeed and depositors can reclaim their funds. +If the total deposits don't reach the threshold, the bucket does not succeed and depositors reclaim their full deposit with `mplx genesis refund`. + +**What does the softCap flag do?** +`--softCap` caps the quote tokens a launch pool keeps. Deposits above the cap are still accepted and the launch still succeeds, but the excess is refunded pro-rata with `mplx genesis refund`. Token allocation is unaffected. + +**What is the difference between softCap and minimumQuoteTokenThreshold?** +`--softCap` is a ceiling and never causes a launch to fail. `--minimumQuoteTokenThreshold` is a floor — if deposits fall below it the launch fails and depositors can take a full refund. They can be used together, and `--softCap` must be greater than or equal to `--minimumQuoteTokenThreshold`. + +**When can the refund command be used?** +Only after the deposit window closes, and only when the launch missed its minimum quote token threshold or exceeded its soft cap. In any other case the command reports that the pool is not refundable. **Can I split end behaviors across multiple destinations?** Yes. Specify `--endBehavior` multiple times with different destination addresses and percentages (in basis points, totaling 10000). @@ -328,4 +413,8 @@ Yes. Specify `--endBehavior` multiple times with different destination addresses | **Withdraw Penalty** | Fee applied to withdrawals during the deposit period | | **Bonus Schedule** | Extra token allocation for early or specific-timing deposits | | **Allowlist** | Merkle-tree-based access control limiting who can deposit | +| **Soft Cap** | Ceiling on the quote tokens a launch pool keeps; the 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 | +| **Refund** | Return of a deposit, in full when the floor was missed or excess-only when the cap was exceeded | | **Basis Points (bps)** | 1/100th of a percent — 10000 bps = 100%, 100 bps = 1% | diff --git a/src/pages/en/smart-contracts/genesis/launch-pool.md b/src/pages/en/smart-contracts/genesis/launch-pool.md index ef0145031..c79aa72d0 100644 --- a/src/pages/en/smart-contracts/genesis/launch-pool.md +++ b/src/pages/en/smart-contracts/genesis/launch-pool.md @@ -603,7 +603,7 @@ if (deposit) { - Users must have wSOL (wrapped SOL) to deposit - `softCap` is a required argument on `addLaunchPoolBucketV2` in `@metaplex-foundation/genesis` 0.42.0 — pass `softCap: null` when no cap is wanted - Launch Pool extensions, including `softCap`, can only be added or removed before `finalizeV2` -- Soft caps are supported by the Genesis program and JavaScript SDK; the [`mplx` CLI](/dev-tools/cli/genesis/launch-pool) does not expose a soft cap flag yet +- Soft caps are supported by the Genesis program, the JavaScript SDK, and the [`mplx` CLI](/dev-tools/cli/genesis/launch-pool) - An oversubscribed Launch Pool leaves a few quantum units of rounding dust in the bucket, because each depositor's filled portion rounds up - `quoteTokenDepositTotal` and `depositCount` are preserved as historical records after refunds; `refundCount` tracks refunds processed