Skip to content

Latest commit

 

History

History
174 lines (143 loc) · 9.57 KB

launchlab-protocol.md

File metadata and controls

174 lines (143 loc) · 9.57 KB

Raydium LaunchLab: every launchpad, letsbonk.fun and StonkFun

Every launchpad here runs its pools on one program, Raydium LaunchLab (LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj). src/platforms/launchlab/ is the launchlab platform and covers all of them; platforms/letsbonk/ and platforms/stonkfun/ are presets that only name their platform configs, and platforms/launchlab_presets.py maps each platform to what it covers. The IDL is idl/raydium_launchlab_idl.json, pulled from the program's on-chain IDL account.

Platforms are platform configs

A launchpad on LaunchLab is the set of PlatformConfig accounts its pools are created under. Every launch and every trade names one, at account 3.

Platform Platform configs
letsbonk.fun 5thqcDwKp5QQ8US4XRMoseGeGbmLKMmoKZmS6zHrQAsA
StonkFun, standard 4E876qZTE9FJMrBzgVtBrSrzz2TLivB5Y5QXPjB4gZL7
StonkFun, reward 6BwHHDg3u1854jC8PDLXvR4spTcLNaoBxLJNGC4nTESt

Any other launchpad is reached through platform: launchlab, which takes a launch under any platform config; launchlab.platform_configs narrows it, and narrows a preset within its own configs. TokenInfo.platform_config and the trade log record which config a coin came from.

The geyser, blocks and shreds listeners subscribe on the platform configs when there is a list (EventParser.get_stream_filter_accounts), so other launchpads stay out of the stream; launchlab without one subscribes on the program, and sees every trade on it. No account appears in every launch and in no trade, so the shreds listener cannot narrow to launches the way it does on pump.fun. Several platforms on one program also mean nothing may key a lookup by program id alone.

Launches

StonkFun launches are initialize_with_token_2022, as legacy, v0 and v1 transactions, sometimes issued by a router as an inner instruction. Read accounts by IDL name: payer is 0 and creator is 1, and the pool's creator — which keys the creator fee vault — is the creator account, not the payer. The quote mint is account 7 and its token program account 11. The transfer fee is the transfer_fee_extension_param argument.

Other launchpads launch with initialize_v2 (SPL Token coin, Metaplex metadata) or initialize_with_token_2022, quoted in SOL or another token, with the same accounts. A platform config can restrict which global configs and curve parameters its launches use (restrict_global_config, restrict_curve_param); its launches then pass two PDAs after the IDL's accounts, ["platform_allow_config", platform_config, global_config] and ["platform_curve_rule", platform_config, global_config]. StonkFun's configs do. tools/launchlab_create_coin.py always passes both, which an unrestricted config ignores.

A global config names a curve: curve_type 0 is constant product, 1 fixed price, 2 linear, and a launch states its own in the curve_param variant. The price and trade math here are for the constant-product curve, so the parser ignores a launch whose curve_param is anything else. The global config is PDA(["global_config", quote_mint, curve_type u8, index u16]).

Everything a buy needs is in the instruction, so the parsers set state_from_event. The program's PoolCreateEvent carries no base mint, quote mint or platform config, which is why there is no logs listener for LaunchLab. The same instruction is all the pre-execution shreds listener has, and gives it the TokenInfo geyser builds; a launch made through another program is an inner instruction there, and invisible to it.

Trades

buy_exact_in and sell_exact_in take the IDL's 15 accounts, then three the program reads positionally: the system program, the platform fee vault PDA([platform_config, quote_mint]) and the creator fee vault PDA([creator, quote_mint]). The pool is PDA(["pool", base_mint, quote_mint]). Every one of these is keyed by the pool's quote mint.

  • buy_exact_in spends all of amount_in. The trader hands an exact-in builder the configured amount, unpadded (spends_exact_amount_in).
  • Price is (virtual_quote + real_quote) / (virtual_base - real_base). The virtual reserves are fixed at launch. Pricing off them alone returns the launch price forever, and take-profit and stop-loss never fire.
  • Fees: trade_fee_rate (GlobalConfig) + fee_rate + creator_fee_rate (PlatformConfig), in parts per million, read live by the program. A buy pays them from its input and a sell from its output, rounded up. Curve outputs round down. platforms/launchlab/curve_math.py reproduces both to the unit.

Quote assets

The launcher picks the quote: SOL, an xStock, another coin. A SOL-quoted trade settles through a throwaway wrapped-SOL account opened and closed in the same transaction. Any other quote is paid from, and paid into, the wallet's own account for that mint. The bot never buys a quote asset, so the balance must already be there, and a sell leaves the proceeds in that asset. The quote's token program comes from the launch instruction. xStocks are Token-2022 mints.

xStocks carry a pause switch, a permanent delegate, a scaled UI amount and a transfer-hook extension with no program set. If the issuer sets a hook program, every transfer needs that program's extra accounts, and the builder does not pass them.

Transfer fees

StonkFun reward coins are Token-2022 mints with a 1% or 3% transfer fee.

  • A buy delivers its output less the fee, and minimum_amount_out is checked against what arrives. A floor computed without the fee reverts 6004.
  • A sell sends its amount and the pool trades on what arrives, less the fee.
  • The mint schedules two fees, each a rate and a cap: a change takes effect two epochs after it is set. The bot charges whichever withholds more on the amount, so it needs no epoch read and never underestimates the fee.
  • The fee is withheld inside the receiving account: the bot's own account, on a buy. Token-2022 refuses to close an account holding withheld fees (0x23), so cleanup runs the permissionless HarvestWithheldTokensToMint first. Standard coins' mints have no transfer-fee extension, and harvesting against one fails, so only harvest when the account holds withheld fees.

Graduation

When a curve raises its target, Raydium's migrate wallet moves it to a pool, and the curve takes no more trades. PoolState.status is 1 while it waits and 2 once migrated; a migrated pool keeps its final reserves, so the curve manager reports graduated and calculate_price raises rather than return a frozen price.

migrate_type 1 means Raydium CPMM, which is where every StonkFun coin goes. The CPMM pool is PDA(["pool", amm_config, token_0, token_1]) under the CPMM program, with amm_config the platform's PlatformConfig.cpswap_config and the two mints ordered by their bytes, so it is derived, never searched for. Price is the vault balances less the protocol, fund and creator fees still sitting in them.

Both sides trade with swap_base_input, 13 accounts; the creator fee accrues inside the pool and adds none. The input token's account, vault, token program and mint come before the output token's, so a buy is a sell with the two sides swapped. A buy spends exactly its amount_in, quote fees included, and its minimum_amount_out is checked against what reaches the wallet, so on a transfer-fee coin the floor is net of that fee, as on the curve. The pool state carries no transfer fee; the buyer takes it from the curve. A SOL buy pays from a throwaway wrapped-SOL account funded with the spend, any other quote from the wallet's account, and the coin's account is created first, under the coin's token program.

The buyer reaches the pool when the curve reports graduated. While the curve waits for its pool (status 1) the buy is skipped with nothing sent. In extreme_fast_mode a graduated coin is skipped: the pool buy is sized from the pool's reserves, which that mode does not read.

migrate_type 0, Raydium AMM v4, has no market in the bot; a coin there is reported, not bought or sold.

Detecting a migration

Raydium's migrate wallet (GlobalConfig.migrate_to_cpswap_wallet, the only payer the instruction takes) sends migrate_to_cpswap as a top-level instruction: account 1 is the coin, 2 the quote mint, 3 the platform config, 5 the CPMM pool and 10 its cpswap_config. The program's logs name none of them, so the listeners decode the instruction, after a whole-line check for Instruction: MigrateToCpswap in the logs. A report needs the platform config to be one the parser covers and the pool to be CPMM's PDA(["pool", cpswap_config, token_0, token_1]). The wallet often retries a migration that already landed; the retry fails with PoolMigrated (6006), so the executed listeners drop it with the other failed transactions, and shreds reports it as another pre-execution attempt.

Past the IDL's 28 accounts a migration passes ten more, which the program reads positionally: the platform's platform_nft_wallet, a new fee-NFT mint (a second signer), its ATA for that wallet, the lock program's PDA(["locked_liquidity", nft_mint]), the NFT's Metaplex metadata, the pool's creator, CPMM's PDA(["permission", launchlab_authority]), the platform's platform_cp_creator, and CPMM's PDA(["support_mint", mint]) for token 0 and token 1. Every real migration loads the rest through lookup table AcL1Vo8oy1ULiavEcjSUcwfBSForXMudcZvDZy5nzJkU.

Verifiers

verify_launchlab_trades, verify_launchlab_launch_parsing, verify_generic_launchlab, verify_cleanup_harvests_withheld_fees, verify_sell_after_graduation, verify_cpmm_buy and verify_launchlab_migration_detection cover all of the above against captured mainnet transactions.