Skip to main content
LaunchLab exposes a tight instruction set: six user-facing calls plus a handful of admin primitives. The SDK wraps all of them; this page documents the raw surface for aggregators, monitoring tools, and programs that need CPI.The 2026-09 program upgrade rebuilt LaunchLab on Anchor 1.0.2 / Solana 3.1.10 and cleared out three transition mechanisms: the deprecated Initialize now always fails, MigrateToAmm lost its three arguments and every OpenBook account, and the get_upgrade_timestamp gate that made several checks conditional on the clock is gone. It also adds one admin instruction, CollectExcessLamports. See the 2026-09-09 changelog entry.

Instruction inventory

The “ExactIn/ExactOut” split mirrors CPMM’s SwapBaseInput / SwapBaseOutput — on-chain they are separate instruction discriminators with slightly different rounding. Graduation path selection. Every new InitializeV2 and InitializeWithToken2022 call must set migrate_type = 1 (CPSWAP). Any attempt to initialize a new AMM v4-bound pool returns MigrateTypeNotMatch. amm_creator_fee_on only selects whether the resulting CPMM creator fee applies to the quote token or both tokens; it does not select the target program. The legacy Initialize cannot create a launch at all any more (see below). MigrateToAmm remains callable for an existing PoolState that was initialized with migrate_type = 0 before this restriction. The release does not rewrite existing pool state or remove the legacy instruction. Quote-side token program. The quote mint may be owned by either the SPL Token program or Token-2022. Every instruction that touches it — CreateConfig, InitializeV2, InitializeWithToken2022, all four trade instructions, CollectFee, CollectMigrateFee, ClaimCreatorFee, ClaimPlatformFee, and ClaimPlatformFeeFromVault — takes the owning program in its quote-program account slot. Account positions did not change; only the accepted value did. Pass the program that actually owns GlobalConfig.quote_mint, which you can read from PoolState.token_program_flag bit1 for an existing launch (see accounts) or from the mint account’s owner otherwise. The deprecated Initialize is moot here: its quote-program account is still typed to SPL Token, and since the 2026-09 upgrade the instruction fails before it reads any account. Launch through InitializeV2 or InitializeWithToken2022. MigrateToCpswap is the other exception, in the opposite direction — it takes both programs unconditionally rather than one per mint. See the migration accounts below.

Initialize

Removed in effect as of the 2026-09 upgrade — this instruction always fails. initialize’s handler is now nothing but a log of Not supported. Please use initialize_v2 instruction and NotApproved (6000). It is retained purely so its discriminator stays occupied and the IDL keeps a stable shape. (The Accounts struct is unchanged, so Anchor’s generated validation still runs first; the transaction reverts regardless.)Previously it was gated on the clock: it worked until three days after the get_upgrade_timestamp cut-over and failed after. That timestamp helper is gone, so the failure is now unconditional. Existing launches created through it are unaffected — they trade and graduate normally through MigrateToAmm or MigrateToCpswap depending on their stored migrate_type.The arguments and account list below are InitializeV2’s. The deprecated Initialize takes the same 18 accounts in the same order and the first three arguments only — it has no amm_fee_on — and its quote_token_program slot is typed to SPL Token rather than Interface<TokenInterface>.
Create a new launch. InitializeV2 adds only the amm_fee_on argument over the deprecated Initialize; the account count and order are identical, and the sole account-level difference is that quote_token_program is typed Interface<TokenInterface> so a Token-2022 quote mint is accepted. Arguments Four positional arguments, not one struct. (InitializeWithToken2022 appends a fifth, transfer_fee_extension_param: Option<TransferFeeExtensionParams>; the deprecated Initialize drops amm_fee_on.)
The variant must match global_config.curve_type or the instruction reverts with InputNotMatchCurveConfig (6003). There is no open_time, no quote_mint argument (it comes from global_config), no fees struct and no post_graduation_lp_policy — LP disposal is set on PlatformConfig, not per launch. Accounts — 18 in total (16 declared plus the two #[event_cpi] appends) Preconditions
  • quote_mint ∈ launch_config.allowed_quote_mints.
  • base_supply_graduation ≤ base_supply_max.
  • Fee parameters pass launch_config.max_*_fee_rate checks.
  • open_time ≥ now − slop (SDK enforces ≥ now; program tolerates slight backdating).
  • curve_type is recognized.
Postconditions
  • base_mint has supply = curve_param.supply, all in base_vault.
  • The mint authority is revoked in this same instruction (set_authority(MintTokens, None)) right after the supply is minted — not at graduation. The base mint is therefore permanently fixed-supply, and no later instruction can mint more.
  • PoolState initialized with status = Fund (0), real_a = 0, real_b = 0.
  • total_fund_raising_b comes straight from curve_param.total_quote_fund_raising.
  • For InitializeWithToken2022 with a TransferFeeConfig attached: transfer_fee_config_authority = launch_authority, and withdraw_withheld_authority = PlatformConfig.transfer_fee_extension_auth when that field is set, otherwise launch_authority. The withdraw side is written at mint creation precisely so the platform can sweep withheld fees before graduation. See platform-config.
Common errorsNotApproved (6000, unconditionally, for the deprecated Initialize), InvalidInput (6002, a supply / rate / fund-raising floor from GlobalConfig violated), InputNotMatchCurveConfig (6003, curve_param variant does not match global_config.curve_type), MigrateTypeNotMatch (6007, migrate_type != 1), MathOverflow (6008), VestingRatioTooHigh (6010), NoSupportExtension (6017), NotEnoughRemainingAccounts (6018), InvalidPlatformAllowConfig (6022), CurveParamNotMatchPlatformRule (6025). A failed Anchor address = / constraint = check surfaces as a 2xxx code, not one of these — none of InvalidQuoteMint, FeeRateTooHigh or InvalidCurveParams exists in the program’s error enum.

Buy (canonical variant: BuyExactIn)

User provides a fixed input amount; the curve computes the output. Arguments
All four trade instructions take three arguments, not two. An instruction built with only the two amounts is 8 bytes short and fails to deserialize.
Accounts Remaining accounts — the fee plumbing, read in this exact order:
  1. share_fee_receiveronly when share_fee_rate > 0.
  2. system_program — always; checked == System::id(), else InvalidInput.
  3. platform_fee_vault — PDA [platform_config, quote_token_mint]; created on first use.
  4. creator_fee_vault — PDA [creator, quote_token_mint]; created on first use.
Running short returns NotEnoughRemainingAccounts (6018). There is no associated_token_program slot, and system_program is a remaining account rather than a declared one. Preconditions
  • launch_state.status == Active.
  • now ≥ open_time.
  • user_quote_ata.balance ≥ quote_in.
  • quote_in > 0.
Effect
  1. Split quote_in into quote_in_after_fee and the fee parts.
  2. Newton-solve the curve for base_out given the post-fee quote.
  3. require(base_out ≥ minimum_base_out) else revert ExceededSlippage.
  4. Move quote_in user → vault. Move base_out vault → user.
  5. Update base_sold += base_out, quote_reserve_real += quote_in_after_fee × (lp_share / total_share).
  6. Update fee counters (protocol_fees_quote, creator_fees_quote).
  7. state_data.num_buys += 1.
  8. If quote_reserve_real ≥ quote_reserve_target after the update, the SDK typically chains a Graduate ix in the same transaction. The program does not auto-graduate inside Buy — a subsequent Graduate is required.

BuyExactOut

User specifies the exact base_out; program computes quote_in. Arguments
Same accounts and the same remaining-account contract as BuyExactIn. Uses the closed-form quadratic integral (or CPMM inverse, for curve_type 1) rather than Newton iteration.

Sell / SellExactIn / SellExactOut

Mirror of Buy. User returns base_in to the curve and receives quote_out. The fee is deducted from quote_out, so the user receives less than the raw integrated proceeds. Preconditions
  • user_base_ata.balance ≥ base_in.
  • Selling cannot push base_sold below 0 (redundant with the above given accounting is consistent).
  • Launch is Active.
Effect — symmetrical to Buy. base_sold decreases, quote_reserve_real decreases. Fees still accrue.

Quote-side transfer fees

When the quote mint carries a TransferFeeConfig, the amount the vault moves and the amount the payer is debited or credited differ, and the slippage bound is checked against the payer’s side. On a quote mint without the extension every case below is identical to a plain legacy mint. Two consequences for quoting:
  • A bound computed as if the mint were fee-free is rejected. Passing the fee-free cost as maximum_amount_in, or the fee-free proceeds as minimum_amount_out, reverts with ExceededSlippage.
  • real_quote advances only by what reached the vault. A BuyExactIn of amount_in on a 5% quote mint moves real_quote by amount_in × 0.95.
A 100%-fee quote mint (10000 basis points) cannot be inverted and reverts with CalculateOverflow on the exact-out paths. Both trade-side mints are also constrained to the program passed in their matching slot, so a mismatched base_token_program now fails rather than being ignored. See algorithms/token-2022-transfer-fees for the underlying fee math.

Trade remaining accounts

All four trade instructions take their fee plumbing through remaining_accounts, in this order:
Changed in 2026-09: the last three are now unconditional, and system_program is validated. Before this release the program only read them when unix_timestamp >= get_upgrade_timestamp(), and skipped the platform/creator fee split entirely before that moment. The timestamp is long past, so behaviour on mainnet is unchanged in practice — but the branch is gone from the code, and a builder that still omits the three accounts now always fails with NotEnoughRemainingAccounts (6018) instead of only after the cut-over. The system_program slot is additionally checked against System::id() and returns InvalidInput (6002) if it holds anything else, where previously any account was accepted in that position.

MigrateToAmm / MigrateToCpswap

Graduate a launch into a tradeable pool once the curve has hit total_quote_fund_raising. New launches are CPMM-only. MigrateToAmm is retained for existing pools whose stored migrate_type is 0. Who signs
  • MigrateToAmm — the migrate_to_amm_wallet recorded on the binding GlobalConfig.
  • MigrateToCpswap — the migrate_to_cpswap_wallet recorded on the binding GlobalConfig.
These wallets are typically held by the Raydium-operated graduation crank; in practice graduation lands seconds after the threshold is crossed, regardless of who triggered the final buy. Arguments Neither takes any.
Breaking change (migration wallet only, 2026-09). MigrateToAmm dropped all three arguments — base_lot_size, quote_lot_size, market_vault_signer_nonceand nine accounts. Its instruction data is now the bare discriminator, so an old builder both sends 17 unexpected argument bytes and supplies an account list that no longer aligns.This follows AMM v4’s own OpenBook removal: AMM v4’s Initialize2 no longer takes market_program or amm_open_orders, so LaunchLab had nothing left to forward. The program also stopped CPI-ing initialize_openbook_market, which is what the three arguments configured.Accounts removed: openbook_program, request_queue, event_queue, bids, asks, market_vault_signer, market_base_vault, market_quote_vault, and amm_open_orders. The market account stays in its position — AMM v4 still records it as a reference field — but it is now declared as a bare #[account(mut)]: no owner, address or seeds constraint. It is entirely unvalidated, it is forwarded straight into AMM v4’s Initialize2 CPI, and the program no longer initializes it. A caller that wants the market account to be a real initialized market has to create it itself beforehand.The remaining 23-account list is, in order: payer, base_mint, quote_mint, market, amm_program, amm_pool, amm_authority, amm_lp_mint, amm_base_vault, amm_quote_vault, amm_target_orders, amm_config, amm_create_fee_destination, authority, pool_state, global_config, base_vault, quote_vault, pool_lp_token, spl_token_program, associated_token_program, system_program, rent_program.MigrateToCpswap is unaffected — it never had arguments.
Effect (common to both)
  1. Verify pool_state.status == Migrate (i.e., quote_reserve_target has been reached). Otherwise revert with PoolMigrated (status was already Migrated) or PoolFunding (still in funding).
  2. Verify pool_state.migrate_type matches the instruction (0 for AMM, 1 for CPMM). Otherwise revert with MigrateTypeNotMatch.
  3. Compute the post-graduation reserves:
    • base_amount_out = base_vault.amount − vesting_schedule.total_locked_amount
    • quote_amount_out = quote_vault.amount − quote_protocol_fee − migrate_fee − platform_fee
  4. CPI into the target program (AMM v4 Initialize2 or CPMM InitializeWithPermission) with those reserves to create the post-graduation pool.
  5. For CPMM migrations executed after the 2026-08-17 upgrade, combine platform_scale + creator_scale into one platform-owned locked-LP share and mint at most one Fee Key NFT to platform_nft_wallet. Burn the burn_scale remainder. Before the upgrade, creator_scale was locked separately and its Fee Key went to the token creator. Completed historical migrations are not modified. For legacy AMM v4 graduation, the LP disposition follows that instruction’s existing flow.
  6. (No mint-authority step. base_mint.mint_authority was already set to None at launch creation — see the note below.)
  7. Flip pool_state.status = Migrated, set vesting_schedule.start_time = block_time + cliff_period.
Token-2022 transfer-fee authority handover — when the base mint is a Token-2022 mint carrying TransferFeeConfig and PlatformConfig.transfer_fee_extension_auth is non-default, migration also reassigns that extension’s authorities to the platform key:
  • transfer_fee_config_authority is always reassigned. The launch authority PDA holds it for the whole pre-graduation phase, so there is always something to move.
  • WithheldWithdraw is reassigned only when the authority PDA still holds it. Launches created from 2026-08-27 onward already carry transfer_fee_extension_auth on that authority from mint creation, so the step is skipped. The guard is what keeps migration from reverting on those mints — the PDA cannot sign away an authority it no longer holds.
If transfer_fee_extension_auth is Pubkey::default() at migration time, neither authority moves and both stay with the authority PDA permanently. See platform-config.
The base mint’s supply is fixed from creation, not from graduation. InitializeV2 and InitializeWithToken2022 mint the entire supply into the base vault and then immediately revoke MintTokens in the same instruction, so base_mint.mint_authority is None for the whole life of the launch. Migration does not touch it. (Earlier revisions of this page placed the revocation at graduation; that was wrong.) The only authorities migration can move are the Token-2022 transfer-fee ones described below.
PostconditionsBuyExactIn, BuyExactOut, SellExactIn, SellExactOut will reject from this point on with PoolMigrated. The resulting AMM pool is canonical and trades like any other AMM v4 / CPMM pool. Common errorsPoolFunding, PoolMigrated, MigrateTypeNotMatch, InvalidCpSwapConfig, MathOverflow.

CPMM migration remaining accounts

Clients building MigrateToCpswap must use these fixed remaining_accounts indices: The instruction requires at least ten remaining accounts. The support-mint accounts are read-only CPI inputs. Derive both addresses even when the mint has no initialized support record. Older builders that still append creator-lock accounts or omit indices 8–9 must be updated.
Changed in 2026-09. Two clean-ups, neither of which changes a correct builder:
  • The permissioned CPMM path is now the only path. MigrateToCpswap used to choose between InitializeCpSwap and InitializeCpSwapWithPermission based on unix_timestamp >= get_upgrade_timestamp(). The timestamp helper and the legacy CPI are both gone, so the permissioned path — and therefore the ten-account minimum — applies unconditionally.
  • Three address constraints moved from the account struct into the instruction body. platform_config, base_vault, and quote_vault are still required to match the values stored on PoolState, but the mismatch is now raised by require_keys_eq! rather than by Anchor’s address = constraint. The check is equivalent; only the error surface differs — you get Anchor’s generic RequireKeysEqViolated (2502) instead of ConstraintAddress (2012), and it is reported without an account name. Update any error handling that matched on 2012 for these three accounts.

CPMM migration token programs

MigrateToCpswap takes both token programs unconditionally and works out which one owns each mint itself. Its two token-program accounts were renamed accordingly: They replace the former base_token_program (whichever program owned the base mint) and quote_token_program (always legacy). Positions are unchanged, so this is a value change rather than a layout change — but the two values are close to inverted, and a builder that keeps passing its old pair will supply Token-2022 where the legacy program is required as soon as either mint is a Token-2022 mint. The legacy program is required even when neither mint uses it, because the CPMM LP mint and the locked-liquidity Fee Key NFT always live on it.

Platform GlobalConfig allowlist

PlatformConfig.restrict_global_config controls the check:
  • 0: the platform accepts any otherwise-valid GlobalConfig; no allow account is required.
  • 1: Initialize, InitializeV2, and InitializeWithToken2022 must include the matching PlatformAllowConfig anywhere in remaining_accounts.
The platform admin creates or closes the PDA with CreatePlatformAllowConfig and ClosePlatformAllowConfig. Its seeds are [b"platform_allow_config", platform_config, global_config]. The former admin-managed PlatformGlobalAccess instructions and PDA are retired.

Platform launch-parameter rules

Four instructions manage one PlatformCurveRule account. All four are signed by PlatformConfig.curve_rule_manager or by the platform admin — the program accepts the admin by re-deriving the PlatformConfig PDA from the signer, so no separate account proves it. A signer that is neither returns InvalidCurveRuleAuthority. platform_curve_rule is the PDA at [b"platform_curve_rule", platform_config, global_config].
  • Create allocates the account holding no group. That state does not restrict anything.
  • Update upserts the group with that group_id, replacing it wholesale if it exists. It resizes the account to fit, so the signer tops up the rent it grows by and receives back the rent it shrinks by. A new group beyond the tenth returns CurveRuleGroupsExceeded; more than 25 constraints, an unknown field or operator, or the same (field, op) pair twice in one group returns InvalidCurveRuleConstraint; the four TotalSellA-derived fields on a non-constant-product config return CurveRuleFieldNotSupportedByCurve.
  • Remove drops one group by id, shrinking the account and refunding the difference. An unknown id returns CurveRuleGroupNotExist.
  • Close returns the whole rent to the signer. The config is then unrestricted again even while restrict_curve_param stays 1.
None of the four changes whether rules are enforced. That is UpdatePlatformConfig::RestrictCurveParam(0 | 1), which only the platform admin can call. On the launch path. While restrict_curve_param is 1, InitializeV2 and InitializeWithToken2022 require the rule PDA in remaining_accounts — including when it does not exist, so that omitting it cannot skip the check. A missing account is NotEnoughRemainingAccounts; a launch that satisfies no group is CurveParamNotMatchPlatformRule. The check runs before GlobalConfig’s own limits and can only narrow them. Model and playbooks: products/launchlab/curve-rules. Both errors are avoidable client-side — the SDK mirrors this check as a pure function, see Check before you send.

CollectFee

Admin sweep of the protocol’s accrued trade fees on a single launch. Arguments — none. Accounts
quote_mint comes before recipient_token_account here — the reverse of ClaimCreatorFee, ClaimPlatformFee and ClaimPlatformFeeFromVault, which all put the recipient first. The two slots have different Anchor types (Mint vs TokenAccount), so swapping them fails deserialization at runtime and reads like a wrong-account bug. CollectMigrateFee has the same order as CollectFee.
Effect — transfer pool_state.quote_protocol_fee from quote_vault to recipient_token_account, then zero the counter. Callable any time after the first buy.

CollectMigrateFee

Admin sweep of the migration fee accumulated at graduation. Same account shape as CollectFee with migrate_fee_owner as the signer (instead of protocol_fee_owner) and pool_state.migrate_fee as the drained counter.

ClaimCreatorFee

Per-creator sweep of accrued creator fees across every launch the creator owns that uses the same quote mint. Drains the per-creator fee vault, not the per-pool one. Arguments — none. Accounts Effect — transfer the entire balance of creator_fee_vault to recipient_token_account. Reverts with a require-greater-than-zero check if the vault is empty.

ClaimPlatformFee

Per-platform sweep that drains a launch’s quote vault directly. Use this when a platform wants to claim its slice for one specific launch without going through the aggregated platform vault. Arguments — none. Accounts Effect — transfer pool_state.platform_fee from quote_vault to recipient_token_account, zero the counter.

ClaimPlatformFeeFromVault

Per-platform aggregated sweep. Drains the platform’s per-quote-mint fee vault that accumulates fees from every launch routed through the platform. Arguments — none. Accounts Effect — transfer the full balance of platform_fee_vault to recipient_token_account. Reverts if the vault is empty.

CollectExcessLamports

Admin sweep of lamports sitting above the rent-exempt minimum on accounts LaunchLab controls. Added in the 2026-09 upgrade so the protocol can reclaim the over-funding that the SIMD-0437 rent reduction leaves behind on accounts created before each step. Only the excess moves. Token balances, account data, owners, curve state and vesting are untouched, and the instruction is a no-op against an account already at its minimum — so it is safe to re-run after each rollout step. Arguments — none. Accounts Picking authority The account is passed unchecked and resolved by the program, which re-derives all three of LaunchLab’s authority PDAs and matches: A key matching none of the three fails the whole instruction with InvalidOwner (6001).
Group your source accounts by authority. One call carries one authority, and the token program requires the account’s actual owner to sign. A token account owned by a different one of the three PDAs than the authority you passed makes the CPI fail and takes the whole transaction with it. Sweep pool vaults, platform fee vaults and creator fee vaults in separate transactions.Program-owned PDAs are the exception — they are debited directly, so they can ride along with any authority.
How each source account is handled Base mints cannot be swept. InitializeV2 and InitializeWithToken2022 revoke MintTokens on the base mint in the same instruction that mints the supply, so no key can sign a WithdrawExcessLamports for it — the mint’s rent stays where it is permanently. Common errorsInvalidOwner (6001, wrong signer or an authority that is none of the three PDAs), LamportsCalculateError (6031, the wSOL round-trip did not net to zero), and InsufficientFunds from the program-owned path when an account holds less than its own rent minimum. No SDK builder. @raydium-io/raydium-sdk-v2 does not ship a builder for this instruction, and neither does the raydium-sdk-V2-demo repo — it is an admin path. Encode it by hand, the way the wallet-side sweep in solana-fundamentals/rent-and-reclaimable-rent does for the token-program instruction.

Vesting and platform-config instructions

These are documented on dedicated pages because each has its own state model:

State-change matrix

Where to go next

Sources: