Repository navigation
feat: launchpad connectors (pump.fun, Meteora DBC, Raydium LaunchLab) - #721
fengtality wants to merge 2 commits into
Conversation
0.2 is the first SDK that can build LaunchLab instructions. It also renames parts of the CLMM math and a few fields Gateway read untyped: - PoolInfoLayout's config is `configId` (was `ammConfig`); CLMM pool-info read the old name and would lose its fee. - PoolUtils.isOverflowDefaultTickarrayBitmap moved to PoolUtil; TickUtils, LiquidityMath and SqrtPriceMath's tick helpers became TickArrayUtil, TickUtil and LiquidityMathUtil (same numbers). - getAmountsFromLiquidity's slippage amounts are below the amount even for `add`, so quotePosition computes the deposit maximums itself. - CurveCalculator.swap/swapBaseOut became swapBaseInput/swapBaseOutput, which also charge the CPMM creator fee; the quote applies it as the program does. - CLMM quotes take the bitmap extension and the chain's block time. - DEV_CREATE_CPMM_POOL_* are gone; devnet uses DEVNET_PROGRAM_ID.
…adroom Raydium AMM v4 deposits failed with "Exceeded desired slippage limit" (0x1e) whenever the quote side was the limiting one: the v4 branch capped the other side at the user's raw amount, while the program computes that side from live vault reserves slightly off the API reserves the quote used. The fixed side stays what the user gave; the other side's max is the quoted amount times (1 + slippage), the AmountMax quote-liquidity already reports. CPMM is untouched. Ported from feat/stateless-build-endpoints (a8eeb27f1), which #721 did not take.
[High impact] The changes reach token launches and asset trades across several Solana workflows. Do not merge yet: existing configurations can fail startup, and a DBC first buy can spend the wrong currency and amount. Findings
SummaryAdds Pump, Meteora DBC, and Raydium LaunchLab connectors, launch routes, and connector-bound quote lookups.
|
| slippagePct: ConfigManagerV2.getInstance().get('pump.slippagePct'), | ||
| lookupTableAddress: ConfigManagerV2.getInstance().get('pump.lookupTableAddress'), |
There was a problem hiding this comment.
Older configurations stop startup
If an existing conf/root.yml lacks $namespace pump, Gateway cannot start after this upgrade. PumpConfig.config reads pump.slippagePct during import, but ConfigManagerV2 only copies the new root template when no root file exists. The lookup throws even when the operator never uses Pump.
Add the namespace to existing roots before importing the connector, or load its defaults without requiring a root-file change.
| if (firstBuy) { | ||
| const config = await dbc.getPoolConfig(configAddress); | ||
| const buyAmount = toRawAmount(params.firstBuySol, 9); |
There was a problem hiding this comment.
First buy spends wrong currency
firstBuySol accepts any DBC configAddress, but this path assumes the config’s quote token is SOL. If the config uses USDC, firstBuySol: 0.1 becomes 100,000,000 raw USDC units—100 USDC—not 0.1 SOL. Both the quote and first-buy builder receive that amount, so a funded wallet can spend the wrong asset and amount.
Reject a positive firstBuySol unless config.quoteMint equals NATIVE_MINT, or expose an amount explicitly measured in the config’s quote token.
| if (firstBuy) { | |
| const config = await dbc.getPoolConfig(configAddress); | |
| const buyAmount = toRawAmount(params.firstBuySol, 9); | |
| if (firstBuy) { | |
| const config = await dbc.getPoolConfig(configAddress); | |
| if (!config.quoteMint.equals(NATIVE_MINT)) { | |
| throw httpErrors.badRequest('firstBuySol requires a DBC config quoted in SOL'); | |
| } | |
| const buyAmount = toRawAmount(params.firstBuySol, 9); |
| if (page.length < PAGE || signatures.length >= maxTransactions) break; | ||
| before = page[page.length - 1].signature; | ||
| } | ||
| const complete = signatures.length <= maxTransactions; |
There was a problem hiding this comment.
Shortened history looks complete
complete becomes true when the request stops at exactly maxTransactions, even if older transactions remain. With the default limit of 1,000, a token with more than 1,000 transactions returns one full page and reports complete: true. Clients can then leave older trades out of their accounting.
Track whether the last page exhausted the history, or fetch one extra signature before claiming completeness.
…aunchLab) Launch connectors - pump: pump.fun bonding-curve swaps on the router surface (auto-routing to the token's PumpSwap pool after graduation), PumpSwap pools on the amm surface, and token creation (create_v2, optional atomic dev buy). New namespace `pump` (connectors/pump.yml, pump-schema.json). - meteora/launch: the Dynamic Bonding Curve as the `launch` trading type of the meteora connector: curve quotes and swaps, pool state, token creation from a named DBC config (request `configAddress`, else meteora.launch.configAddress). - raydium/launch: Raydium LaunchLab on Raydium's own platform: token creation in SOL or USDC, curve quotes and swaps, pool state (found by mint, whatever the quote), creator fees. /trading/launch routes: create-token, pool-info, quote-swap, execute-quote, execute-swap, creator-fees, claim-creator-fees, migrate, launch-history. The routes that sign are behind the API-token gate. create-token takes a metadataUri that the caller pinned. Gateway fetches no caller URLs and uploads nothing: metadataUri is required on pump, meteora and raydium, and imageUrl, description, twitter, telegram, website and pump.metadataUploadUrl do not exist. An existing conf/connectors/pump.yml that still has a metadataUploadUrl line is refused at startup until the line is removed. Fixes - A quote executes only at the connector that priced it: the quote cache stores each quote under its connector and hands it back to that connector only. The launch execute-quote routes use it, and so does every router connector. - The amm and clmm quote-swap and execute-swap routes refuse a pool connector that does not run on the chain the caller named. openapi.json is regenerated.
…and Privy wallets (#673, #682) Every Gateway route that sends a transaction gets a build-* twin that returns the unsigned transaction, and POST /chains/{chain}/submit broadcasts what the caller signed. Every execute route signs through one signer seam per chain, with local, hardware, external and Privy wallets behind it. This is the build half of the old #721 and the content of #727, #730, #728, #732 and #729, on top of the launch connectors. Solana build mode (from #721) - A build-* route runs the connector's own code and captures the transaction at Solana.sendAndConfirmTransactionForWallet instead of signing it: simulated, compute budget sized, fresh blockhash, ephemeral keypairs partial-signed, fee-payer slot empty. A broadcast attempted during a build throws; nothing is sent. - Routes: router build-swap and build-quote; clmm build-swap, build-open, build-add, build-remove, build-collect-fees, build-close and build-create-pool; amm build-swap, build-add, build-remove and build-create-pool; launch build-swap and build-create-token; POST /chains/{chain}/build-transaction for caller instructions; and POST /chains/{chain}/submit, which re-merges the ephemeral signatures and verifies every signature and the blockhash before it broadcasts. - The build shape on every build route: extraInstructions, feePayer, format (transaction or instructions), memo, references (Solana Pay) and priority (normal, high, urgent). - platformFee on builds, paid to a recipient the caller names (Jupiter through its feeAccount, other venues by a transfer after the operation). Router quotes take platformFeeBps. - pancakeswap-sol build routes are refused by name. poll reports currentBlockHeight beside the slot. - Build routes are behind the API-token gate with their execute routes; submit stays public like poll. Build coverage (#727) - One build response for an operation of several transactions; a wide Meteora DLMM open, add, remove, close or collect builds as one response. - collect-fees and build-collect-fees for Meteora DAMM v2. - build-claim-creator-fees and build-migrate; creator-fee claims on meteora and raydium. - pump: a dev-buy create names its lookup table, or has one made for it; each new lookup table gets its own slot, and a create after a new table compiles with the build's fee payer. - build-transaction takes feePayer, memo and priority. A legacy build's compute is sized against the RPC's own blockhash. - raydium AMM v4 add gives the non-limiting side its slippage headroom. Signer seam (#730) - The signer seam interface and the wallet record. Every Solana and EVM transaction is signed through it. - External wallets: Gateway holds no key, and a route that would sign for one refuses and names its build twin. - The key-handling helpers the seam replaced are deleted, with the old ledger helpers. - The route-coverage test: every route that sends a transaction is listed with its build twin, and only the seam signs. EVM build and submit (#728) - build-* twins and submit for every EVM transaction route. A build returns the unsigned transaction with the approvals it needs first. - Tests for build mode, approvals, submit and the EVM build routes. - The uniswap execute-quote Permit2 check moved to Ethereum.requirePermit2Allowance, so its own test (executeQuote-permit2-expiration.test.ts) is deleted; the EVM build-mode tests cover the Permit2 path with the max uint48 expiration. The last build twins (#732) - Solana build-wrap and build-unwrap, and launch build-quote. The route table has no gaps. Privy wallets (#729) - A Privy server-wallet REST client with request signing and error mapping. - The privy signer backend on the seam, and the add-privy and create-privy routes. - EVM quantities go to Privy as minimal hex. Wallet list stores change through updateListStore, one update at a time. - The Privy seam test calls the EVM chokepoint the way #728 left it. openapi.json is regenerated. Closes #682
1d90e39 to
00d543e
Compare
Pump (launchpad + PumpSwap AMM)
Meteora DBC (launchpad)
Raydium (LaunchLab + AMM/CLMM)
Test notes
|




Summary
This PR adds three launchpad connectors (pump.fun, Meteora Dynamic Bonding Curve and Raydium LaunchLab) and the
/trading/launchroutes. Hummingbot API and Condor can then create tokens and trade bonding curves.The stateless build/sign/submit work (#682) is no longer in this PR. It moved to the draft PR #734, which is stacked on this one.
Two commits:
chore(raydium): Raydium SDK 0.1.141 to 0.2.73. 0.2 is the first SDK that builds LaunchLab instructions. The commit message lists the renamed fields and math helpers that Gateway reads.feat(trading): connectors, launch routes, tests and the regeneratedopenapi.json. create-token takes a pinnedmetadataUri(required), and Gateway fetches no caller URLs (CodeQLjs/request-forgery).Connectors
pump (new connector, namespace
pump)create_v2: Token-2022, 6 decimals, 1B supply) from ametadataUrithe caller pinned, with an optional atomic dev buy. Creator fees, claim, migrate and launch history.conf/connectors/pump.yml(slippagePct,lookupTableAddress).lookupTableAddressis empty by default. A create with a dev buy is larger than a legacy transaction, so it needs a lookup table. When none is set, Gateway refuses it with a clear error. Create without a dev buy, then buy through/trading/router.@pump-fun/pump-sdk1.36.0,@pump-fun/pump-swap-sdk1.19.0, and@pump-fun/pump-sdk-v2(alias of pump-sdk 2.0.0) formigrateonly. 1.36's migrate does not have the accounts that the live program reads, and 2.0 everywhere would make a create with a dev buy too large.meteora
launch(Dynamic Bonding Curve)launchon the existingmeteoraconnector, with@meteora-ag/dynamic-bonding-curve-sdk1.5.12.configAddress, or elsemeteora.launch.configAddress(empty by default; when it is empty, a create withoutconfigAddressis refused).meteora'sammsurface.meteora.ymlgets alaunch:block (slippagePct,configAddress). Existing configs get it from the template when they load.raydium
launch(LaunchLab)launchon the existingraydiumconnector. It uses Raydium's own LaunchLab platform, curves in SOL or USDC, and graduation to CPMM.Routes
/trading/launch(new)POST create-tokenpump,meteoraorraydiumfrom ametadataUrithe caller pinned, and signs with the Gateway wallet. Gateway uploads no images or metadata and fetches no caller URL.GET pool-infoGET quote-swapmeteora,raydium)POST execute-quote/execute-swapGET creator-feesPOST claim-creator-feesPOST migrateGET launch-historyChanges to existing behavior a reviewer should know
quoteCache.get/getRequesttake the connector. AquoteIdfrom one connector cannot run through another connector's execute path; it gets 400 "Quote not found or expired". Jupiter, 0x, Uniswap and PancakeSwap did not check this before./trading/amm/close.test/app.integration.test.tssays the AMM surface must not have them;removeat 100% closes.create-token,execute-*,claim-creator-fees,migrate) are behind the API-token gate.test/spec/signing-routes-are-gated.test.tscovers this.pumpis added to the amm and router connector enums.meteoraandraydiumadvertise thelaunchtrading type.Not included
imageUrl): Gateway would fetch a URL the caller chose from inside the operator's network. The caller pins the metadata and passesmetadataUri.swigWalletdescriptor on build routes.refreshForkedAccounts, the Jupiter/DFlowdexesallowlist.Testing
pnpm build,pnpm lint(0 errors),pnpm test: 182 suites, 1622 tests pass. I ran them from a cleanconf/, as CI does.pnpm generate:openapileavesopenapi.jsonunchanged.root.ymlhas$namespace pump.GET /config?namespace=pumpreturns the pump config, andGET /config/connectorslistspump(router,amm,launch),meteora(clmm,amm,launch) andraydium(amm,clmm,launch). Thedevelopmentimage does not start with this config, because it has nopump-schema.json.Lockfile
pnpm-lock.yamlchanges a lot. Most of the change is not from this PR.development's lock is already out of sync with itspackage.json: runningpnpm install --lockfile-onlywith pnpm 10 ondevelopmentalone rewrites about 7,000 lines. This lock was made with pnpm 11, which the Dockerfile uses with--frozen-lockfile. The image above was built from it.🤖 Generated with Claude Code