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.
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.
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.
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_inspends all ofamount_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.pyreproduces both to the unit.
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.
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_outis checked against what arrives. A floor computed without the fee reverts6004. - 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 permissionlessHarvestWithheldTokensToMintfirst. Standard coins' mints have no transfer-fee extension, and harvesting against one fails, so only harvest when the account holds withheld fees.
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.
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.
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.