How crossr works
crossr is a multichain token launchpad. A launch is one manifest: name, ticker, metadata, creator settings and a list of legs, one per chain. The manifest hash is the launch id and the CREATE3 salt, so the token has the same address on every chain.

The manifest hash is the CREATE3 salt, so every leg deploys the same token address.
Bonding curve (per leg)
- Total supply is 1,000,000,000 tokens, split across legs by the allocation you choose. Each leg mints only its share.
- Each leg is a constant-product curve with a phantom quote reserve, exactly like Pons V2:
k = phantom × supply. With the default ETH config (phantom 1.68 ETH, graduation 4.2 ETH) 5/7 of the leg supply is sold on the curve and 2/7 stays reserved. - Every trade pays a 1% fee on the quote side: 30% to the protocol, 70% to the creator. The creator may add a 0–10% creator tax that goes entirely to them.
- Buys in the first seconds after launch pay a snipe tax that starts at 99% and halves 14 times over the window, so bots cannot front-run the community. The creator and listed wallets are exempt.
- A buy that would overshoot the graduation threshold is partially filled and the rest refunded, so the curve never reverts on its last buy.
Graduation
- When the leg's sellable supply is gone (real quote reaches the threshold) the curve graduates. It happens inside the crossing buy; if that fails for any reason, anyone can call
graduate()and the indexer keeper does so automatically. - All raised quote plus 10/49 of the leg supply go into a full-range Uniswap v4 pool at the curve's terminal price, so there is no price gap. The remaining 4/49 is burned.
- The position is owned by the LiquidityLocker, which can never withdraw it. Post-graduation swaps pay a 1% hook fee (plus the creator tax) into the same fee escrow.
- Pools are keyed with the crossr hook, and only the locker can initialize such a pool, so nobody can pre-create the pool at a bad price.
Cross-chain
- Launching signs one transaction on the home chain. The LaunchRelay sends the manifest to every other chain through LayerZero V2, where the peer launchpad deploys the same token address and opens that chain's curve in its own coin (ETH on Robinhood, BNB or a BEP-20 on BNB Chain, …).
- If a chain is not wired to the relay yet, the creator can create that leg directly on that chain with the same manifest.
- The TokenBridge burns on the source chain and mints on the destination, so global supply is constant. Bridged tokens can be sold into the destination curve (up to what that curve has sold) or its pool, which keeps prices aligned across chains through arbitrage.
The first chain is where you sign. Every chain gets its own curve in the coin you pick and its own share of the supply.
Solana
- Solana is a leg like any other: the manifest carries a synthetic chain id (501 mainnet, 5010 devnet), and the
crossr_launchpadprogram runs the same curve in SOL with the same fees and snipe tax. It hashes the manifest exactly like the EVM contracts, so the launch id is shared; the mint is a PDA of that id, the CREATE3 equivalent. - Solana legs are not relayed through LayerZero. The creator opens them directly: an Ethereum creator signs a short message binding a Solana wallet as the fee recipient (verified on chain with secp256k1), a Solana-only creator launches with an address derived from their public key. The launch form does this right after the home-chain transaction; a missing leg can be created later from the token page.
- Tokens are Token-2022 mints with 9 decimals and on-chain metadata. Fees go straight to the creator and treasury wallets on every trade, so there is nothing to claim. Graduation happens inside the crossing buy into a constant-product pool held by the program forever;
swaptrades against it with a 1% fee plus the creator tax on the SOL side. - Pair with stocks on Solana: any SPL / Token-2022 mint such as the xStocks (TSLAx, SPYx, …) can be a quote asset once the owner enables it. The manifest carries the mint's 20-byte tag, trades move the stock between token accounts, and the graduated pool holds the stock. USD pricing comes from Jupiter.
- Bridging to and from Solana is not live yet: the program exposes
bridge_mint/bridge_burnfor a LayerZero OApp, and until then Solana legs trade independently.
Fees
All fees are claim-based from the FeeEscrow contract: launch fees, curve fees, creator tax and hook fees. Creators claim from their portfolio page.
Trust assumptions
Tokens are plain ERC20s with no owner and no mint function except through the bridge. The protocol owner can pause launches and bridging, change fee parameters within hard caps, enable quote assets, and set LayerZero peers. Use a multisig and timelock for the owner on mainnet. Contracts are new and unaudited.
Build on crossr
Everything below is what the web app itself does. Contracts are plain Solidity you can call from any client, the shared package gives you typed ABIs, manifest hashing and the curve math, and the indexer exposes a small REST API. Source: github.com/andrewkyrz/crossr-contracts.
Contracts
Launchpad: one per chain. Creates legs, runs the bonding curves, graduates them. Entry points:createLaunch,buy,sell,graduate; viewsgetCurve,quoteBuy,quoteSell,spotPrice,sellableTokens,predictToken,manifestHash.LaunchToken: immutable ERC20 deployed with CREATE3. ExposeslaunchId(); the only mint path isbridgeMintfrom the configured bridge.LaunchRelay: LayerZero V2 OApp that ships a manifest leg to another chain.quoteLeg/sendLeg, called by the launchpad.TokenBridge: LayerZero V2 OApp shared by every token on the chain.quoteSend/sendburn here and mint there.LiquidityLocker: owns every graduated Uniswap v4 position forever.LaunchHookgates pool init to the locker and charges the post-graduation fee.FeeEscrow: all fees end up here.balanceOf(account, asset)thenclaim(asset); the native coin is asset0x0.- Addresses per chain live in
deployments/<chainId>.jsonand are re-exported by the shared package asdeployment(chainId). ABIs are generated from the Foundry build withbun run abi.
Manifest
- A manifest is
{creator, nonce, creatorTaxBps, name, symbol, metadataURI, snipeExempt[], legs[]}with each leg{chainId, quote, allocationBps}. Allocations sum to 10,000. Quote0x0is the chain's native coin. - Launch id =
keccak256(abi.encode(manifest)). Field order matters, so build it withhashManifestfrom@crossr/sharedand check it withvalidateManifestbefore sending.predictToken(launchId)returns the token address every chain will get. nonceis creator-chosen; bump it to relaunch identical metadata. Limits: name ≤ 64, symbol ≤ 16, metadata URI ≤ 512 chars, ≤ 8 legs, ≤ 16 snipe-exempt wallets, creator tax ≤ 1,000 bps.- Metadata:
POST /api/metadatawith form fieldsname, symbol, description, website, twitter, telegram, creator, image(png/jpeg/gif/webp ≤ 1.5 MB). It returns{id, uri, image}; puturiin the manifest. Any HTTPS JSON with{name, symbol, description, image}works too.
Launching
- Call
createLaunch(manifest, legIndex, relays[], devBuyQuote, devBuyMinOut)on the home chain, frommanifest.creator.legIndexis the leg that lives on this chain. - Each
RelaySpecis{legIndex, options}for a remote leg;optionsare LayerZero executor options, uselzReceiveOptions(RELAY_LEG_GAS)from the shared package. The remote quote must be allow-listed on the home chain (remoteQuoteAllowed). msg.valuemust equal exactlycreationFee × (1 + relays.length) + Σ relay.quoteLeg(...), plusdevBuyQuotewhen the local quote is native. ReadcreationFeefrom the launchpad and quote every relay first; the launchpad reverts withBadValueotherwise.- ERC20 quote: approve the launchpad for
devBuyQuotebefore the call. Remote legs open a few seconds later once the LayerZero message lands;LegRelayedthenLegCreatedon the peer chain tell you when. - Unsupported chain on the relay? Send the same manifest with
createLaunch(manifest, legIndex, [], 0, 0)directly on that chain, from the same creator. Same hash, same token address.
Trading a curve
- Quote first:
quoteBuy(token, quoteAmount, buyer)returns(tokensOut, spent, totalFeeBps)including the snipe tax for that buyer;quoteSell(token, tokenAmount)returns(quoteOut, fees). Or readgetCurveonce and quote locally withquoteBuy/quoteSellfrom@crossr/shared/curve, which mirrors the contract exactly. buy(token, quoteAmount, minTokensOut, recipient, deadline). Native quote goes inmsg.valueand must equalquoteAmount; ERC20 quote needs an approval. A buy that crosses graduation is partially filled and the excess refunded, so treatminTokensOutas a price bound, not an exact output.sell(token, tokenAmount, minQuoteOut, recipient, deadline). Approve the launchpad for the tokens first. A leg only pays out what it took in:tokenAmount ≤ tokensSold, otherwiseInsufficientSold.- Curve state to track:
Tradeevents carryrealQuoteandtokensSoldafter each fill. Progress isrealQuote / target; the curve is sold out whentokensSold == supply − reserved. - After
Graduated, trade the Uniswap v4 pool instead. The pool key is(token, quote, fee 0, tickSpacing 200, hook)sorted by address, whichpoolKey(token, quote, hook)in the web app builds. Route through the chain's Universal Router with Permit2; quote with the v4 Quoter.
Solana program
- Anchor 0.31 IDL and types ship in
@crossr/shared(launchpadIdl,CrossrLaunchpad); PDAs viaconfigPda,curvePda(programId, launchId),mintPda,ata. Build aProgramwith the deployment'sprogramIdfromdeployment(501). create_leg(launchId, manifest, legIndex, creatorSig?)from the Solana wallet, withmanifestArgs(manifest). The signature ispersonal_signbymanifest.creatorovercreatorBindingMessage(launchId, pubkey), ornullwhenmanifest.creator == derivedEvmAddress(pubkey). Passtreasuryfrom theConfigaccount; other accounts resolve from the IDL. Set a 400k compute-unit limit.- SPL quotes:
create_leg_spl(launchId, quoteTag, manifest, legIndex, sig?)withquoteTag = solanaQuoteTag(mint), thenbuy_spl/sell_spl/swap_splwith the trader's quote token account; the treasury and creator quote ATAs are created on demand.NEXT_PUBLIC_QUOTESlists the mints for the UI. buy(lamports, minTokensOut, deadline),sell(tokens, minLamportsOut, deadline),swap(amountIn, minOut, isBuy, deadline)after graduation. Passcurve,mint,treasuryand the curve'screatorexplicitly (accountsPartial). Quote with the samequoteBuy/quoteSellovercurveState(curve), plussolanaSnipeTaxBpsandquotePoolSwap.- Events (
LaunchManifest,LegCreated,Trade,Graduated,PoolSwap, …) are Anchor events in the transaction logs; the indexer follows the program's signatures. Token amounts are 9-decimal base units; the API reportstoken_decimalsper leg and stores Solana prices as lamports per whole token with 18 fractional digits.
Bridging
TokenBridge.quoteSend(dstEid, token, to, amount, options)gives the LayerZero fee;send(...)with that fee asmsg.valueburns the tokens and emitsBridgeSent(guid, …).dstEidis the LayerZero endpoint id, not the chain id: usechainMeta(chainId).lzEid. Options:lzReceiveOptions(BRIDGE_RECEIVE_GAS). No approval needed, the bridge is the token's burn authority.- The destination emits
BridgeReceived(guid, token, to, srcEid, amount)with the sameguid; the indexer stores both halves inbridge_transfers.
Claiming fees
- Creator fees, protocol fees, hook fees and LP fees all accrue in
FeeEscrowon the chain where they were paid. ReadbalanceOf(account, asset)per quote asset, thenclaim(asset)orclaim(asset, amount, to). - Post-graduation hook fees are credited on every swap. LP fees on the locked position are collected with
LiquidityLocker.collect(token), permissionless, and then claimable the same way.
REST API
GET /api/launches?sort=trending|new|volume|mcap|progress|graduated&chain=<id>&q=<text>&status=→{launches, prices}. Each launch carries its legs with curve state, volume and holder counts.GET /api/launch/<token or launch id>→{launch, prices, holders}: the whole token, every chain, unique holders across chains./trades,/holders(merged by address, whole tokens) and/candles?interval=(one USD series blended by supply share) hang off the same path.GET /api/token/:chainId/:token→{launch, prices}. Sub-routes:/trades(last 100),/holders(top 50),/candles?interval=1m|5m|15m|1h|4h|1d(OHLCV in quote, up to 1,500 buckets).GET /api/me/:address→{created, held}.GET /api/prices→ USD price per quote asset.GET /api/meta/:idand/api/img/:idserve stored metadata.- Amounts are returned as decimal strings in raw units (wei / lamports / token base units, see
quote_decimalsandtoken_decimals); parse withBigInt. EVM addresses are lowercase, Solana addresses are base58 as-is.
Indexer
- One Bun process indexes every configured chain into Postgres:
launches,legs,trades,candles(1 minute),holders,bridge_transfers,hook_fees,relay_events,prices. Schema inapps/indexer/db/schema.sql, migrations run at boot. - Configure with
DATABASE_URL,CHAIN_IDS,ALCHEMY_API_KEYorRPC_URL_<chainId>,CONFIRMATIONS,POLL_MS,CHUNK.DEPLOYMENTS_JSONoverrides addresses without a rebuild.GET /healthonPORT. - With
KEEPER_PRIVATE_KEYset, the keeper watchesReadyToGraduate/AutoGraduationFailedand callsgraduate(token)itself. It needs a little gas on every chain.
Local stack
anvil, then forge script script/DeployLocal.s.sol --broadcast writes deployments/31337.json; bun run abi picks it up. Start Postgres, run the indexer with CHAIN_IDS=31337 CONFIRMATIONS=0, then bun run dev. script/Simulate.s.sol generates a launch, trades, a graduation and a post-graduation swap. Contract tests: forge test in packages/contracts, including cross-chain flows through a real LayerZero endpoint. Full commands are in the repository README.