crossr

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.

Same address, every chain

The manifest hash is the CREATE3 salt, so every leg deploys the same token address.

1B
tokens per launch, split across chains
1%
trade fee, 70% of it to the creator
LP lock after graduation
crossr.app/
crossr
TrendingNewVolumeGraduated
Robin Coin
$ROBIN
$21.4k
FDV
by 0x7a3f…c21e · 2 h ago
RobinhoodBNBrelaying…
$6.6k
Explore: one card per launch, all its legs as chain badges, curve progress across the bottom. A gold chip marks launches with at least one graduated leg.

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.
crossr.app/t/0x51ac…9b04?chain=4663
crossr
1 Trade panel: quote, fees and snipe tax are computed from the curve before you sign. 2 Curve card: quote raised against the graduation threshold.

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.
crossr.app/t/0x51ac…9b04?chain=4663
crossr
1
Robin Coin$ROBINRobinhoodgraduated
by 0x7a3f…c21e2 h ago0x51ac…9b04 ↗
Price
$0.0000214
FDV
$21.4k
24h volume
$8.2k
1 Once every chain has graduated the token shows the gold badge; on a graduated chain the price comes from the Uniswap v4 pool. 2 The curve card reports the liquidity locked forever.

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.
crossr.app/launch
crossr
1Token
1
2Chains

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.

+ Add chain
Home chain
Robinhood
Paired with
ETH
Supply %
60
Chain 2
BNB
Paired with
BNB
Supply %
40
Allocation total: 100%
3Creator settings
2
Robin Coin
$ROBIN
RobinhoodBNB
Total supply1,000,000,000
Curve trade fee1% + 2% creator tax
GraduationUniswap v4, LP locked
Launch fees0.002 ETH
Messaging0.0009 ETH
Dev buy0.1 ETH
Launch on 2 chains
1 Chains: the first row is the home chain where you sign; every row is a leg with its own quote asset and share of the 1B supply. 2 Summary: chain badges and the cost breakdown, including LayerZero messaging for remote legs.
crossr.app/t/0x51ac…9b04?chain=4663
crossr
1
Robinhood paired ETH 63%BNB paired BNB 41%Base relaying…
1 One row per chain the token trades on: quote asset, price there and curve progress, plus a dashed row while a remote chain is still being relayed. Click one to make it the trading venue. 2 Bridge panel: burn here, mint on the selected chain, fee quoted from LayerZero.

Solana

  • Solana is a leg like any other: the manifest carries a synthetic chain id (501 mainnet, 5010 devnet), and the crossr_launchpad program 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; swap trades 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_burn for 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.

crossr.app/t/0x51ac…9b04?chain=4663
crossr
Fees accrue per chain and per asset in FeeEscrow. The token page shows what the connected wallet can claim on that chain; the portfolio page lists every chain.

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.

Developer docs

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; views getCurve, quoteBuy, quoteSell, spotPrice, sellableTokens, predictToken, manifestHash.
  • LaunchToken: immutable ERC20 deployed with CREATE3. Exposes launchId(); the only mint path is bridgeMint from 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 / send burn here and mint there.
  • LiquidityLocker: owns every graduated Uniswap v4 position forever. LaunchHook gates pool init to the locker and charges the post-graduation fee.
  • FeeEscrow: all fees end up here. balanceOf(account, asset) then claim(asset); the native coin is asset 0x0.
  • Addresses per chain live in deployments/<chainId>.json and are re-exported by the shared package as deployment(chainId). ABIs are generated from the Foundry build with bun 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. Quote 0x0 is the chain's native coin.
  • Launch id = keccak256(abi.encode(manifest)). Field order matters, so build it with hashManifest from @crossr/shared and check it with validateManifest before sending. predictToken(launchId) returns the token address every chain will get.
  • nonce is 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/metadata with form fields name, symbol, description, website, twitter, telegram, creator, image (png/jpeg/gif/webp ≤ 1.5 MB). It returns {id, uri, image}; put uri in the manifest. Any HTTPS JSON with {name, symbol, description, image} works too.

Launching

  • Call createLaunch(manifest, legIndex, relays[], devBuyQuote, devBuyMinOut) on the home chain, from manifest.creator. legIndex is the leg that lives on this chain.
  • Each RelaySpec is {legIndex, options} for a remote leg; options are LayerZero executor options, use lzReceiveOptions(RELAY_LEG_GAS) from the shared package. The remote quote must be allow-listed on the home chain (remoteQuoteAllowed).
  • msg.value must equal exactly creationFee × (1 + relays.length) + Σ relay.quoteLeg(...), plus devBuyQuote when the local quote is native. Read creationFee from the launchpad and quote every relay first; the launchpad reverts with BadValue otherwise.
  • ERC20 quote: approve the launchpad for devBuyQuote before the call. Remote legs open a few seconds later once the LayerZero message lands; LegRelayed then LegCreated on 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.
crossr.app/launch
crossr
RobinhoodBNBBase
Launch fees3 × creationFee
MessagingΣ relay.quoteLeg(i)
Dev buydevBuyQuote
msg.value= sum of the above
Launch on 3 chains
What the summary adds up before calling createLaunch: creation fee per leg, one LayerZero fee per relayed leg, plus the dev buy when the home quote is native. The transaction reverts with BadValue if msg.value differs.

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 read getCurve once and quote locally with quoteBuy / quoteSell from @crossr/shared/curve, which mirrors the contract exactly.
  • buy(token, quoteAmount, minTokensOut, recipient, deadline). Native quote goes in msg.value and must equal quoteAmount; ERC20 quote needs an approval. A buy that crosses graduation is partially filled and the excess refunded, so treat minTokensOut as 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, otherwise InsufficientSold.
  • Curve state to track: Trade events carry realQuote and tokensSold after each fill. Progress is realQuote / target; the curve is sold out when tokensSold == supply − reserved.
  • After Graduated, trade the Uniswap v4 pool instead. The pool key is (token, quote, fee 0, tickSpacing 200, hook) sorted by address, which poolKey(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 via configPda, curvePda(programId, launchId), mintPda, ata. Build a Program with the deployment's programId from deployment(501).
  • create_leg(launchId, manifest, legIndex, creatorSig?) from the Solana wallet, with manifestArgs(manifest). The signature is personal_sign by manifest.creator over creatorBindingMessage(launchId, pubkey), or null when manifest.creator == derivedEvmAddress(pubkey). Pass treasury from the Config account; other accounts resolve from the IDL. Set a 400k compute-unit limit.
  • SPL quotes: create_leg_spl(launchId, quoteTag, manifest, legIndex, sig?) with quoteTag = solanaQuoteTag(mint), then buy_spl / sell_spl / swap_spl with the trader's quote token account; the treasury and creator quote ATAs are created on demand. NEXT_PUBLIC_QUOTES lists the mints for the UI.
  • buy(lamports, minTokensOut, deadline), sell(tokens, minLamportsOut, deadline), swap(amountIn, minOut, isBuy, deadline) after graduation. Pass curve, mint, treasury and the curve's creator explicitly (accountsPartial). Quote with the same quoteBuy / quoteSell over curveState(curve), plus solanaSnipeTaxBps and quotePoolSwap.
  • 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 reports token_decimals per 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 as msg.value burns the tokens and emits BridgeSent(guid, …).
  • dstEid is the LayerZero endpoint id, not the chain id: use chainMeta(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 same guid; the indexer stores both halves in bridge_transfers.

Claiming fees

  • Creator fees, protocol fees, hook fees and LP fees all accrue in FeeEscrow on the chain where they were paid. Read balanceOf(account, asset) per quote asset, then claim(asset) or claim(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.

@crossr/shared

  • Chains: CHAINS, chainMeta(id) (viem chain, LayerZero eid, Uniswap v4 addresses, explorer), SOLANA_CHAINS, isSolanaChain, chainLabel / chainColor / nativeCurrency / tokenDecimals for either kind, chainIdByEid, rpcUrl, solanaRpcUrl, explorerTx.
  • Manifest: hashManifest, encodeManifest, validateManifest, NATIVE, TOTAL_SUPPLY, MAX_LEGS.
  • Curve: quoteBuy, quoteSell, quoteForTokens, spotPrice, sellable, progress, reservedTokens over a CurveState that is exactly the on-chain Curve struct.
  • LayerZero: lzReceiveOptions(gas, value), RELAY_LEG_GAS, BRIDGE_RECEIVE_GAS. Deployments: deployment(chainId), deployedChainIds(). ABIs: launchpadAbi, launchTokenAbi, tokenBridgeAbi, launchRelayAbi, feeEscrowAbi, liquidityLockerAbi, launchHookAbi.

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/:id and /api/img/:id serve stored metadata.
  • Amounts are returned as decimal strings in raw units (wei / lamports / token base units, see quote_decimals and token_decimals); parse with BigInt. 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 in apps/indexer/db/schema.sql, migrations run at boot.
  • Configure with DATABASE_URL, CHAIN_IDS, ALCHEMY_API_KEY or RPC_URL_<chainId>, CONFIRMATIONS, POLL_MS, CHUNK. DEPLOYMENTS_JSON overrides addresses without a rebuild. GET /health on PORT.
  • With KEEPER_PRIVATE_KEY set, the keeper watches ReadyToGraduate / AutoGraduationFailed and calls graduate(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.