> Bull Launch docs. Every page is available as Markdown by adding `.md` to its URL; the index is https://bulllaunch.fun/llms.txt. This page on the web: https://bulllaunch.fun/docs # Introduction What Bull Launch is, and how to connect to Arc Chain Testnet and run a launch end to end. Bull Launch is a fair-launch memecoin platform on Arc Chain Testnet. Anyone can create a coin, and every coin trades on the same curve shape, in its own Uniswap v4 pool, against one of the whitelisted base assets. These docs are the reference for the protocol: launch mechanics, pricing, graduation, contracts, and the endpoints to integrate with. > Bull Launch never holds your funds. Every launch and trade is a transaction your wallet asks you to approve. ## Key facts - Running on testnet. Balances carry no value, and the contracts may be redeployed. - Names and symbols can be copied. Always check the contract address. - Prices come from each token's own Uniswap v4 pool, not a fixed rate. - Launches can be volatile, illiquid, or lose all value. - Deployed contracts are immutable. Nothing here can be paused or rolled back for you. ## Add Arc Testnet to your wallet | | | | --- | --- | | Network name | Arc Testnet | | Chain ID | 5042002 | | RPC URL | `https://rpc.testnet.arc.io` | | Currency symbol | USDC | | Block explorer | [explorer.testnet.arc.io](https://explorer.testnet.arc.io) | The app offers to add the network for you on first connect, so these values are here for wallets that add a chain by hand and for backend services. ## Fund a wallet Bring USDC on Arc Chain Testnet. It is the chain's native coin, so it pays gas and it is also what most coins are priced in. Test USDC comes from Circle's faucet at [faucet.circle.com](https://faucet.circle.com). Test USDT has a public `faucet()` on its contract, `0x85C59dc273d2F1807F2EF569285dff62FF548b8a`, which sends 100,000 to the caller. A launch itself costs no protocol fee, only gas, so the practical minimum is gas plus whatever you want the launch to open with. ## Try it end to end Connect a wallet on [bulllaunch.fun](https://bulllaunch.fun/) and run through the full loop: [create a coin](https://bulllaunch.fun/create), buy and sell on its curve, and watch it climb toward graduation. Then read [How launches work](https://bulllaunch.fun/docs/how-launches-work.md) for what happened on chain. --- > Bull Launch docs. Every page is available as Markdown by adding `.md` to its URL; the index is https://bulllaunch.fun/llms.txt. This page on the web: https://bulllaunch.fun/docs/how-launches-work # How launches work One transaction deploys the coin, opens its Uniswap v4 pool and locks the tail band; the pool never migrates. Creating a launch deploys the coin and opens its market in a single transaction: `LauncherFactory.launch` deploys the token, creates its config, opens the Uniswap v4 pool, locks the tail band, and optionally buys for the creator. The creator sets the name, symbol, image, description, and links at creation. > There is no separate curve contract and no later migration. The pool a coin graduates into is the pool it was born in. 1. **Create.** The whole supply is minted once and split between two ranges of one Uniswap v4 pool: the main band, which is what trades, and the tail band, which is locked immediately and forever. 2. **Trade.** Buys and sells move price through the main band. Every trade reports its own progress toward graduation as `progressBps`. 3. **Graduate.** Once the main band has taken in its fill amount, it is withdrawn and redeployed as a permanently locked position in the same pool. | Parameter | Value | | --- | --- | | Curve type | Single Uniswap v4 pool with a concentrated main band, priced by the hook. Tick spacing 60, dynamic fee flag `0x800000`. | | Total supply per launch | 1,000,000,000 tokens, 18 decimals, minted once and never again | | Supply split | 80% main band (tradeable), 20% tail band (locked at launch) | | Launch fee | None. Gas only. | | Anti-bot / wallet cap at launch | None. There is no per-wallet cap, cooldown, or allowlist in the contracts. The creator's own opening buy happens inside the launch transaction as `preBuyBaseAmount`, and is uncapped: a large enough pre-buy graduates the pool in the launch transaction itself. | | Creator tax | Optional, chosen at launch, ceiling 10% (1000 bps, from `maxTaxBps()`) | The pre-buy is worth understanding before you trade a brand new coin: a creator can take a large share of the main band in the same transaction that creates it. Read the launch transaction, not just the chart. --- > Bull Launch docs. Every page is available as Markdown by adding `.md` to its URL; the index is https://bulllaunch.fun/llms.txt. This page on the web: https://bulllaunch.fun/docs/trading # Trading and pricing Price, market cap, slippage and fees in a coin's pool, and the two permissions an ERC-20 spend needs. Every coin trades in its own Uniswap v4 pool against one base asset chosen at launch. The price you see is that pool's live price, and it moves with each trade. Slippage sets how much movement you accept. | | | | --- | --- | | Price | The current price in the coin's own pool. | | Market cap | Price multiplied by circulating supply. | | FDV | Price multiplied by the full token supply. Supply is fixed at launch and never grows, so the two readings coincide. | | Price impact | The price movement caused by the size of your trade. | | Slippage | The maximum execution movement your transaction accepts, enforced as `amountOutMinimum` by the router. | | Liquidity | Assets available in the pool around the current price. | > Fees are charged on the unspecified leg of a swap, not always in the base asset. A buy that specifies exactly what it spends pays its fee in the launched token. `V4Quoter` runs the real hook, so a quote already accounts for this. **Spending an ERC-20 takes two permissions, not one.** The Universal Router pulls an ERC-20 through Permit2, so the token must allow Permit2 (a transaction) and Permit2 must allow the router (a signature, replayed inside the swap). Selling a coin therefore asks for both the first time. A buy paid in native USDC needs neither. To build these calls yourself, see [Quotes and swaps](https://bulllaunch.fun/docs/quotes-and-swaps.md). --- > Bull Launch docs. Every page is available as Markdown by adding `.md` to its URL; the index is https://bulllaunch.fun/llms.txt. This page on the web: https://bulllaunch.fun/docs/graduation # Graduation When a curve fills, the hook locks its liquidity in the same pool; nothing migrates and the pool id never changes. A launch graduates once its main band has taken in its configured fill amount of the base asset. Progress toward that amount is what each coin's page tracks, and what `progressBps` reports on every `CurveBuy` and `CurveSell`. > Graduation threshold is per base asset, read from `ConfigRegistry.baseCurve(base)`. On Arc Chain Testnet today: 8 USDC for a USDC-priced launch, 8,000 USDT for a USDT-priced launch. See [Base assets and curves](https://bulllaunch.fun/docs/base-assets.md). There is no migration step, and no second pool. A launch is a Uniswap v4 pool from the moment it is created, because the hook is part of its pool key. On the swap that crosses the fill target, the hook withdraws the main band whole inside `afterSwap` and redeploys the proceeds as a locked position held by the LP lock, `LauncherLocker`. The pool id never changes, so an integration that indexed the pool before graduation keeps working after it. | Parameter | Value | | --- | --- | | Post-graduation pool | The same pool. Same pool id, same PoolManager, same hook. | | Post-graduation pool fee tier | Dynamic fee flag `0x800000`. The pool charges 0 and the hook takes the real fee, which is 0% (0 bps). | | Liquidity lock | Permanent. The tail band is locked at launch and the graduation position is locked on graduation, both held by `LauncherLocker`, which exposes no withdrawal function. | | Migration delay | None. It happens inside the swap that crosses the target. | Graduation only confirms the threshold was reached. It is not a quality signal and does not guarantee future liquidity, price, or an exit. --- > Bull Launch docs. Every page is available as Markdown by adding `.md` to its URL; the index is https://bulllaunch.fun/llms.txt. This page on the web: https://bulllaunch.fun/docs/fees # Fees and royalties The swap fee, how it splits between creator and protocol, the creator tax, and how creators claim. Trading generates a swap fee. The creator keeps most of it, the protocol keeps a share, and a creator may also have set a separate tax at launch. | Split | Share | | --- | --- | | Swap fee | 0% of the trade (0 bps, from `Hook.swapFeeBps()`) | | Creator royalty | 90% of that swap fee | | Protocol share | 10% of that swap fee (1000 bps, from `Hook.protocolFeeShareBps()`), paid to the Treasury | | Creator tax | Separate from the split above and goes to the creator in full. Set per launch, ceiling 10%. Most launches set 0. | The split is settled at claim time rather than at swap time, so the hook holds the live value and the numbers above are what is deployed today. ## How creators claim their royalty From the coin's page on the app, which calls `Hook.claimFees(poolId, minOut)`. Two things are worth knowing: - The claim is not permissionless. Only the pool's creator or one of the hook's operators may call it. - A pool accrues fees in both currencies, and token-denominated fees are sold into the base asset inside the claim. That sale is what `minOut` bounds, which is why a claim can revert with `SlippageExceeded`. A creator can hand the payout over without moving the coin: `transferCreator` for an immediate transfer, or `proposeCreatorTransfer` and `executeCreatorTransfer` for a two-step one. ## What the protocol share funds It is paid to the Treasury contract listed under [Contract addresses](https://bulllaunch.fun/docs/contracts.md), which is where every protocol-side fee accrues. See also the [fees page](https://bulllaunch.fun/fees). --- > Bull Launch docs. Every page is available as Markdown by adding `.md` to its URL; the index is https://bulllaunch.fun/llms.txt. This page on the web: https://bulllaunch.fun/docs/community-takeovers # Community takeovers Handing a coin's creator payout to an active community when the original creator steps away. When a coin's original creator steps away, the community can request a takeover. A takeover transfers the social presence and, where applicable, the creator royalty payout to an active community. Onchain, this is the creator handover described under [Fees and royalties](https://bulllaunch.fun/docs/fees.md): `transferCreator`, or the two-step `proposeCreatorTransfer` and `executeCreatorTransfer` on the hook. Only the current creator or a hook operator can move it. To request one, reach the team through [Support](https://bulllaunch.fun/docs/support.md). > The coin, its pool, and its locked liquidity are unaffected. Only the creator payout wallet and creator-facing surfaces change. Never share a private key or seed phrase; Bull Launch will never ask you to send funds to process a request. --- > Bull Launch docs. Every page is available as Markdown by adding `.md` to its URL; the index is https://bulllaunch.fun/llms.txt. This page on the web: https://bulllaunch.fun/docs/risks # Risk disclosures What can go wrong: volatility, uncapped pre-buys, copied names, failing infrastructure, immutable contracts. - This is testnet. Balances have no value, and the deployment may be replaced without notice. - Prices can move quickly and liquidity can be thin. - A creator's opening buy is uncapped and happens in the launch transaction. Read the launch before trading a new coin. - Similar names and images can represent unrelated tokens. The contract address is the only identity that matters. - Smart contracts, wallets, RPCs, and indexers can fail. - Displayed values are estimates, not execution guarantees. - Deployed contracts are immutable. There is no admin who can reverse a trade or recover a mistaken transfer. - Bull Launch is an interface, not investment advice or a representation of token quality. --- > Bull Launch docs. Every page is available as Markdown by adding `.md` to its URL; the index is https://bulllaunch.fun/llms.txt. This page on the web: https://bulllaunch.fun/docs/network # Network Chain ID, RPC, explorer and native-asset decimals for Arc Chain Testnet. Bull Launch runs on Arc Chain Testnet. Each coin lives in its own Uniswap v4 pool from launch through graduation. | | | | --- | --- | | Network | Arc Testnet | | Chain ID | 5042002 | | Native asset | USDC, 18 decimals at the EVM level | | Public RPC | `https://rpc.testnet.arc.io` | | Explorer | [explorer.testnet.arc.io](https://explorer.testnet.arc.io) | | Multicall3 | `0xcA11bde05977b3631167028862bE2a173976CA11` | | Curve fee | 0% (0 bps), taken by the hook. The pool's own fee is 0. | | Launch fee | None. Gas only. | | Default supply | 1,000,000,000 tokens, 18 decimals | > Arc's native coin is USDC, and it has 18 decimals where the EVM can see it: `eth_getBalance`, `msg.value` and every native amount in these contracts. The ERC-20 predeploy for the same balance presents 6 decimals. The two are one balance behind two interfaces, and mixing them misprices a figure by 1e12. --- > Bull Launch docs. Every page is available as Markdown by adding `.md` to its URL; the index is https://bulllaunch.fun/llms.txt. This page on the web: https://bulllaunch.fun/docs/contracts # Contract addresses Every Bull Launch deployment: addresses, creation transactions and blocks, per network. Every Bull Launch deployment, with the transaction that created each contract. The Bull Launch contracts reference each other in both directions, so any one of them can be checked against the rest on chain. > **Warning:** If there are contracts not listed here but claiming to be Bull Launch, they are not canonical. The hook address is part of every pool key, so a launch made against a different deployment is invisible to this one. Pin these addresses; do not resolve them at runtime from another source. ## Mainnet deployments None yet. Bull Launch runs on testnet only; this list will name the mainnet contracts once they are live. ## Testnet deployments ### Arc Testnet (5042002) **Bull Launch** | Contract | Address | Transaction | Block | | --- | --- | --- | --- | | Treasury | [`0x4109...F361`](https://explorer.testnet.arc.io/address/0x4109C3bDd31cbF84AAAA6E40d088e7dAa0AbF361) | [`0x50cf...4f5b`](https://explorer.testnet.arc.io/tx/0x50cfef2855934597e399ca28274552e5c24350f74cc3314cd635d23fbd6d4f5b) | [64,236,234](https://explorer.testnet.arc.io/block/64236234) | | LauncherLocker (LP lock) | [`0x6e32...018A`](https://explorer.testnet.arc.io/address/0x6e3209d04096de6503097279C3516f017503018A) | [`0xba04...c9a0`](https://explorer.testnet.arc.io/tx/0xba0466152b9e0e094ff0cfee76bdb51e0ee5377cae4e60564356d296e7b8c9a0) | [64,236,238](https://explorer.testnet.arc.io/block/64236238) | | Hook | [`0xe332...b044`](https://explorer.testnet.arc.io/address/0xe332196A3bf409899E990846373cf47255e7b044) | [`0x16e2...1aba`](https://explorer.testnet.arc.io/tx/0x16e28122c7306b117773fb524a3691acf8cc3e71432e325663ac2f87cf2f1aba) | [64,236,243](https://explorer.testnet.arc.io/block/64236243) | | ConfigRegistry | [`0xaB53...6bD1`](https://explorer.testnet.arc.io/address/0xaB53045522C110DEB14f46f39322f5bD43956bD1) | [`0x0206...c06c`](https://explorer.testnet.arc.io/tx/0x020679867067d44d332e0cd0469684201a9870192732d4700e35b24a1930c06c) | [64,236,252](https://explorer.testnet.arc.io/block/64236252) | | LauncherFactory | [`0x6874...6065`](https://explorer.testnet.arc.io/address/0x687434E8Efb28be2EEF123b23A1dBBC5d2956065) | [`0x01be...c7ab`](https://explorer.testnet.arc.io/tx/0x01be6cdf0931dee58315b6f95ac903c6591e602f2d4ce0cba8a09b4a0f7fc7ab) | [64,236,256](https://explorer.testnet.arc.io/block/64236256) | - **Treasury**: Receives the protocol share of every creator fee claim. - **LauncherLocker (LP lock)**: Holds every launch’s liquidity forever: the tail band from launch, and the graduated position. It has no withdrawal function. - **Hook**: Prices the curve, takes the swap fee and the creator tax, graduates the pool, and pays creators their fees. - **ConfigRegistry**: Whitelists base assets and holds each one’s curve, plus the creator tax ceiling. - **LauncherFactory**: The one entry point for a launch: deploys the token, opens its pool, locks the tail band and runs the pre-buy. **Uniswap v4** | Contract | Address | Transaction | Block | | --- | --- | --- | --- | | PoolManager | [`0xF208...B934`](https://explorer.testnet.arc.io/address/0xF2082547d33f0A47e64b4C6Be9A5f8396D5EB934) | [`0xba7c...afc5`](https://explorer.testnet.arc.io/tx/0xba7c35c3d4483feb70079b4ae9299ae70a2b087c3aebcea16be46c104243afc5) | [62,422,857](https://explorer.testnet.arc.io/block/62422857) | | PositionManager | [`0x7545...5451`](https://explorer.testnet.arc.io/address/0x7545bF82b2647e2d2EFA5514b4af266E555c5451) | [`0x2171...302b`](https://explorer.testnet.arc.io/tx/0x2171b38ac8371e471d197f355452c8db3bbc2710f735728c6eb4d03f9ac0302b) | [62,422,873](https://explorer.testnet.arc.io/block/62422873) | | UniversalRouter | [`0x1be3...3eC8`](https://explorer.testnet.arc.io/address/0x1be3dB20c64C02CD97DdD15c3A65C4F461A43eC8) | [`0x337a...b380`](https://explorer.testnet.arc.io/tx/0x337a7e5b6642d6ac36f53047e1481ad161578a7be3b15c95a487bc0fa6e6b380) | [62,422,897](https://explorer.testnet.arc.io/block/62422897) | | StateView | [`0x82D1...62a6`](https://explorer.testnet.arc.io/address/0x82D18315638A6143f05EBeCFc1D1d912666762a6) | [`0x745c...336e`](https://explorer.testnet.arc.io/tx/0x745c54b49c3bbdcc5a97e954de933a8363096d9be5555052a6466ad9951a336e) | [62,422,913](https://explorer.testnet.arc.io/block/62422913) | | V4Quoter | [`0xb072...09e8`](https://explorer.testnet.arc.io/address/0xb07209ef64E49ef41E6DEC9B01D0dBa3248d09e8) | [`0x8b69...467f`](https://explorer.testnet.arc.io/tx/0x8b69209dca99d4bcff970289330b4838ed9069babab17403cabbb537eda4467f) | [62,422,917](https://explorer.testnet.arc.io/block/62422917) | | Permit2 | [`0x0000...8BA3`](https://explorer.testnet.arc.io/address/0x000000000022D473030F116dDEE9F6B43aC78BA3) | Canonical predeploy | - | Arc Testnet has no official Uniswap v4, so these are a separate deployment of the same bytecode. Wallets and aggregators pointed at the official addresses will not see Bull pools. **Base assets** | Contract | Address | Transaction | Block | | --- | --- | --- | --- | | USDC (native) | `0x0000...0000` | Chain native coin | - | | TestUSDT | [`0x85C5...8b8a`](https://explorer.testnet.arc.io/address/0x85C59dc273d2F1807F2EF569285dff62FF548b8a) | [`0xeb96...f309`](https://explorer.testnet.arc.io/tx/0xeb966e5863c43bb439ced46b6013b3eee98a22ba3c4bd0e2ec31e901aeeff309) | [62,423,041](https://explorer.testnet.arc.io/block/62423041) | > Use the UniversalRouter listed for the chain, never a same-chain router from another address registry. Each router is wired to one PoolManager, and a router on another PoolManager cannot reach a Bull Launch pool. Versioning means new addresses, not upgrades. Deployed contracts are immutable, so a new version ships as a new factory and a new hook, and because the hook is part of every pool key, every pool id changes with it. Coins from an older deployment stay tradeable at their own addresses but are invisible to the new one. Pin the addresses for the version you integrated against. --- > Bull Launch docs. Every page is available as Markdown by adding `.md` to its URL; the index is https://bulllaunch.fun/llms.txt. This page on the web: https://bulllaunch.fun/docs/base-assets # Base assets and curves Which assets a launch can be priced in, and each one's opening valuation and graduation target. A launch is priced in exactly one base asset, chosen at creation from what the registry whitelists. Read the live list from `ConfigRegistry.getEnabledBases()` rather than hardcoding it: the owner can add or drop an entry at any time, and a launch against a base that is not enabled reverts with `BaseNotAllowed`. | Base | Address and decimals | | --- | --- | | USDC | `0x0000000000000000000000000000000000000000` (native), 18 decimals | | USDT | `0x85C59dc273d2F1807F2EF569285dff62FF548b8a`, 6 decimals | Each base carries its own curve on the registry, read as `baseCurve(base)` returning `(startAmount, fillAmount)`. `startAmount` is the valuation a launch opens at and `fillAmount` is what the main band must take in to graduate, both denominated in that base. | Base | Opens at | Graduates at | Exact raise | | --- | --- | --- | --- | | USDC | 3 USDC | 8 USDC | 8.04 USDC | | USDT | 3,000 USDT | 8,000 USDT | 8,039.32 USDT | `baseFillAmountActual(base)` is the figure the hook actually settles against, and it is slightly above `fillAmount` because the band is priced in ticks and the target lands inside one. Use it when you need the exact raise, and `fillAmount` when you need the number the protocol advertises. --- > Bull Launch docs. Every page is available as Markdown by adding `.md` to its URL; the index is https://bulllaunch.fun/llms.txt. This page on the web: https://bulllaunch.fun/docs/events # Onchain events The events to index, with their signatures and topic0 hashes. For a trust-minimized integration, index `LauncherFactory` for new coins and the hook for curve activity and graduation. The pool id is stable from launch, so you can register a coin's pool once and index PoolManager swaps for it from then on. | Event | Emitted by | Signature | topic0 | | --- | --- | --- | --- | | Coin created | LauncherFactory | `TokenCreated(address)` | `0x2e2b3f61b70d2d131b2a807371103cc98d51adcaa5e9a8f9c32658ad8426e74e` | | Coin launched | LauncherFactory, carries the full launch config | `TokenLaunched(address,address,address,address,uint256,uint256,bytes32,uint256,uint256,string,string,uint256,uint128,uint16,uint8)` | `0x4bc3e1c7563116fd43fe920faebc0e711cddc122034360e8c8e1b4c81c5267a5` | | Pool registered | Hook, maps poolId to coin, base and creator | `PoolRegistered(bytes32,address,address,address)` | `0x01bf263a1db1652580721573296e1a1fa70b3d4c87f61d02a69c4e1109d2d573` | | Buy on the curve | Hook, last field is progressBps | `CurveBuy(address,address,uint256,uint256,uint256,uint256,bytes32,address,uint16)` | `0xc1742a443d639e06cae611e6969aa3e066a8edf9ca632e4d45b9167f487d1785` | | Sell on the curve | Hook | `CurveSell(address,address,uint256,uint256,uint256,uint256,bytes32,address,uint16)` | `0xcf93b133041e0c9edcb77b3088a72ce25e769d56f3df661c1b0db04a3fe7fa0d` | | Graduated | Hook, emitted inside the crossing swap | `PoolGraduated(address,uint256,uint256,uint256)` | `0x0a44ef75df69c534f43cd6c1aa3ef8983065fe5fe79ef9e79f6494e6f258c259` | | Swap | Uniswap v4 PoolManager | `Swap(bytes32,address,int128,int128,uint160,uint128,int24,uint24)` | `0x40e9cecb9f5f1f1c5b9c97dec2917b7ee92e57ba5563708daca94dd84ad7112f` | Every topic0 above is computed from the ABIs this app trades through. --- > Bull Launch docs. Every page is available as Markdown by adding `.md` to its URL; the index is https://bulllaunch.fun/llms.txt. This page on the web: https://bulllaunch.fun/docs/reading-state # Reading token state Deriving a pool id, and the view calls that read a launch, its fees and its curve. A pool id is derived, not looked up. It is `keccak256(abi.encode(PoolKey))`, and the two currencies have to be sorted by address rather than written base first: native USDC sorts below every launched token, but a base that sorts above one gives a mirrored pool, and an unsorted key produces an id that matches no pool at all. `launches()` then returns zeroes and the coin reads as "not a launch" instead of failing loudly. ```js import { createPublicClient, http, keccak256, encodeAbiParameters, zeroAddress } from "viem"; const arc = { id: 5042002, name: "Arc Testnet", nativeCurrency: { name: "USD Coin", symbol: "USDC", decimals: 18 }, rpcUrls: { default: { http: ["https://rpc.testnet.arc.io"] } }, contracts: { multicall3: { address: "0xcA11bde05977b3631167028862bE2a173976CA11" } }, }; const client = createPublicClient({ chain: arc, transport: http() }); const HOOK = "0xe332196A3bf409899E990846373cf47255e7b044"; const REGISTRY = "0xaB53045522C110DEB14f46f39322f5bD43956bD1"; // Pool key is fixed by the factory: dynamic fee flag, tick spacing 60, the hook. const DYNAMIC_FEE_FLAG = 0x800000; const TICK_SPACING = 60; // Sorted by address, not base first. This is the one step that silently // returns a wrong-but-valid id if you skip it. function poolKeyOf(token, base = zeroAddress) { const [currency0, currency1] = BigInt(base) < BigInt(token) ? [base, token] : [token, base]; return { currency0, currency1, fee: DYNAMIC_FEE_FLAG, tickSpacing: TICK_SPACING, hooks: HOOK }; } function poolIdOf(token, base = zeroAddress) { const k = poolKeyOf(token, base); return keccak256( encodeAbiParameters( [{ type: "address" }, { type: "address" }, { type: "uint24" }, { type: "int24" }, { type: "address" }], [k.currency0, k.currency1, k.fee, k.tickSpacing, k.hooks] ) ); } ``` With the id in hand, everything else is a view call. The functions below are the ones the app itself reads. | Read | Call | | --- | --- | | Launch record | `Hook.launches(poolId)` | | Pool orientation | `Hook.baseIsCurrency0(poolId)` | | Swap fee, live | `Hook.swapFeeBps()` | | Protocol share, live | `Hook.protocolFeeShareBps()` | | Unclaimed fees | `Hook.pendingFees(poolId, token)` | | Graduation target for a base | `ConfigRegistry.baseCurve(base)`, returns (startAmount, fillAmount) | | Exact raise to graduate | `ConfigRegistry.baseFillAmountActual(base)` | | Enabled bases | `ConfigRegistry.getEnabledBases()` | | Creator tax ceiling | `ConfigRegistry.maxTaxBps()` | | Total supply constant | `LauncherFactory.TOTAL_SUPPLY()`, 1e27 | | Remaining raise | `PositionManager.getPositionLiquidity(mainBandTokenId)`. Answers 0 once graduated, because the band is withdrawn whole. | | Spot price | `StateView.getSlot0(poolId)` | | Quote a trade | `V4Quoter`. It runs the real hook, so a quote includes the fee and the tax. See [Quotes and swaps](https://bulllaunch.fun/docs/quotes-and-swaps.md). | > Read the pool through StateView and PoolManager with a pool id. Uniswap v4 has no per-pool contract, so there is no `slot0()` to call on a pool address the way there was in V3. --- > Bull Launch docs. Every page is available as Markdown by adding `.md` to its URL; the index is https://bulllaunch.fun/llms.txt. This page on the web: https://bulllaunch.fun/docs/quotes-and-swaps # Quotes and swaps Quote a trade with V4Quoter and execute it through the Universal Router, with a tested example. Trading a Bull Launch coin is an ordinary Uniswap v4 swap on the coin's pool. Quote it with `V4Quoter`, then send it through the `UniversalRouter` as one `V4_SWAP` command. Both addresses are under [Contract addresses](https://bulllaunch.fun/docs/contracts.md). > **Note:** Use the UniversalRouter listed for the chain. Each router is connected to one PoolManager, and a router on another PoolManager, even on the same chain, cannot reach a Bull Launch pool. ## 1. Build the PoolKey Every launch has the same key shape: the two currencies sorted by address, fee `0x800000` (the dynamic-fee flag), tick spacing 60, and the hook. `poolKeyOf` in [Reading token state](https://bulllaunch.fun/docs/reading-state.md) builds it. For a native-USDC coin, `currency0` is always `address(0)`, so a buy is `zeroForOne = true`. For an ERC-20 base, check which slot the base landed in, or read `Hook.baseIsCurrency0(poolId)`. ## 2. Quote `V4Quoter.quoteExactInputSingle` returns `(amountOut, gasEstimate)`. It is not a view: call it with `eth_call` (viem's `simulateContract`), never in a transaction. It runs the hook, so the swap fee and the creator tax are already in the answer. ## 3. Swap Encode three v4 actions, `SWAP_EXACT_IN_SINGLE` (`0x06`), `SETTLE_ALL` (`0x0c`) and `TAKE_ALL` (`0x0f`), wrap them in one `V4_SWAP` command (`0x10`), and call `UniversalRouter.execute(commands, inputs, deadline)`. `TAKE_ALL` enforces `amountOutMinimum`, so the router, not your code, is what holds the slippage bound. > The swap parameters include `minHopPriceX36`, a per-hop price floor that is newer than most Uniswap quickstarts. Encoding the older tuple without it produces calldata the router decodes into garbage. Pass `0` to leave it unset. ```js // Buy a native-USDC coin: quote, then swap. Tested against Arc Testnet. import { createPublicClient, createWalletClient, http, encodeAbiParameters, encodePacked, parseAbi, parseAbiParameters, parseEther, zeroAddress } from "viem"; const arc = { id: 5042002, name: "Arc Testnet", nativeCurrency: { name: "USD Coin", symbol: "USDC", decimals: 18 }, rpcUrls: { default: { http: ["https://rpc.testnet.arc.io"] } }, contracts: { multicall3: { address: "0xcA11bde05977b3631167028862bE2a173976CA11" } }, }; const client = createPublicClient({ chain: arc, transport: http() }); // const wallet = createWalletClient({ account, chain: arc, transport: http() }); const HOOK = "0xe332196A3bf409899E990846373cf47255e7b044"; const QUOTER = "0xb07209ef64E49ef41E6DEC9B01D0dBa3248d09e8"; const ROUTER = "0x1be3dB20c64C02CD97DdD15c3A65C4F461A43eC8"; const TOKEN = "0x…"; // the coin const POOL_KEY = "struct PoolKey { address currency0; address currency1; uint24 fee; int24 tickSpacing; address hooks; }"; const quoterAbi = parseAbi([ POOL_KEY, "struct QuoteExactSingleParams { PoolKey poolKey; bool zeroForOne; uint128 exactAmount; bytes hookData; }", "function quoteExactInputSingle(QuoteExactSingleParams params) returns (uint256 amountOut, uint256 gasEstimate)", ]); const routerAbi = parseAbi(["function execute(bytes commands, bytes[] inputs, uint256 deadline) payable"]); const SWAP_PARAMS = parseAbiParameters([ "ExactInputSingleParams params", "struct ExactInputSingleParams { PoolKey poolKey; bool zeroForOne; uint128 amountIn; uint128 amountOutMinimum; uint256 minHopPriceX36; bytes hookData; }", POOL_KEY, ]); const poolKey = { currency0: zeroAddress, currency1: TOKEN, fee: 0x800000, tickSpacing: 60, hooks: HOOK }; const amountIn = parseEther("0.01"); // 1. Quote. const { result: [amountOut] } = await client.simulateContract({ address: QUOTER, abi: quoterAbi, functionName: "quoteExactInputSingle", args: [{ poolKey, zeroForOne: true, exactAmount: amountIn, hookData: "0x" }], }); const amountOutMinimum = (amountOut * 99n) / 100n; // 1% slippage // 2. Swap: SWAP_EXACT_IN_SINGLE, SETTLE_ALL, TAKE_ALL inside one V4_SWAP. const actions = encodePacked(["uint8", "uint8", "uint8"], [0x06, 0x0c, 0x0f]); const params = [ encodeAbiParameters(SWAP_PARAMS, [{ poolKey, zeroForOne: true, amountIn, amountOutMinimum, minHopPriceX36: 0n, hookData: account.address }]), encodeAbiParameters(parseAbiParameters("address, uint256"), [zeroAddress, amountIn]), encodeAbiParameters(parseAbiParameters("address, uint256"), [TOKEN, amountOutMinimum]), ]; const input = encodeAbiParameters(parseAbiParameters("bytes, bytes[]"), [actions, params]); const deadline = BigInt(Math.floor(Date.now() / 1000) + 300); await wallet.writeContract({ address: ROUTER, abi: routerAbi, functionName: "execute", args: ["0x10", [input], deadline], value: amountIn, // native USDC is paid as msg.value }); ``` ## hookData: say who the trader is The hook reads the first 20 bytes of `hookData` as the trader, and falls back to `tx.origin` when there are none. Behind a smart wallet, a relayer or an aggregator, `tx.origin` is not the trader, so pass the trader's address. It is what the `CurveBuy` and `CurveSell` events, and every feed built on them, attribute the trade to. ## Selling, and ERC-20 bases - A sell is the same call with `zeroForOne = false` for a native-base coin: `SETTLE_ALL` names the coin, `TAKE_ALL` the base, and no `value` is sent. - An ERC-20 input is pulled through Permit2. The token must `approve(Permit2)` once, and Permit2 must allow the router: sign a `PermitSingle` and put a `PERMIT2_PERMIT` command (`0x0a`) in front of `V4_SWAP` in the same `execute`, or call `Permit2.approve` on chain. - A coin priced in one base can be bought with another by routing through the pool between them: `SWAP_EXACT_IN` (`0x07`) with a path. Its parameters are `(currencyIn, path, minHopPriceX36[], amountIn, amountOutMinimum)`, in that order; put the hop prices anywhere else and the router reverts with no data. --- > Bull Launch docs. Every page is available as Markdown by adding `.md` to its URL; the index is https://bulllaunch.fun/llms.txt. This page on the web: https://bulllaunch.fun/docs/token-metadata # Token metadata How a coin's description, image and links are encoded at launch, and how to read them back. A coin's name and symbol are ERC-20 fields. Everything else a creator enters, the description, the image and the links, travels as the `tokenData` argument of `LauncherFactory.launch`, is stored on chain once, and cannot be changed afterwards. ## Encoding `tokenData` is the ABI encoding of one tuple. Send `0x` when there is no metadata at all: the factory accepts it, whereas bytes it cannot decode revert the launch. ```js import { encodeAbiParameters, toHex } from "viem"; const tokenData = encodeAbiParameters( [{ type: "tuple", components: [ { name: "description", type: "string" }, { name: "website", type: "string" }, { name: "image", type: "string" }, { name: "extraData", type: "bytes" }, ] }], [{ description: "The bull that never sleeps.", website: "https://bullcoin.example", image: "https://…/bull.webp", extraData: toHex(JSON.stringify({ x: "https://x.com/bullcoin", telegram: "https://t.me/bullcoin", website: "https://bullcoin.example", })), }] ); ``` | Field | What goes in it | | --- | --- | | description | Plain text, shown on the coin's page. | | website | One URL. The coin's own site when it has one, otherwise its X link, then Telegram. Older launches put an X or Telegram URL here, so classify it by host before drawing it as a website. | | image | An absolute `https` URL, stored on chain as written. PNG, JPEG or WebP; square, around 600px, is what every surface draws. | | extraData | UTF-8 JSON, hex-encoded, with any of `x`, `telegram`, `website` as full URLs. The contract never reads it; it exists so a launch can carry more than one link. Empty is `0x`. | ## Reading it back The indexer decodes all of it: `token { name symbol image description }` plus the links, on the same GraphQL API the app uses (see [Indexer API](https://bulllaunch.fun/docs/indexer-api.md)). From the chain alone, decode `tokenData` from the `TokenLaunched` event or the launch transaction's input. > Treat every field as untrusted input: it is whatever the creator typed. Allow only `http` and `https` links, and never render the description as HTML. --- > Bull Launch docs. Every page is available as Markdown by adding `.md` to its URL; the index is https://bulllaunch.fun/llms.txt. This page on the web: https://bulllaunch.fun/docs/indexer-api # Indexer API The GraphQL and WebSocket endpoints the app itself reads, with an example query. If you do not want to index the chain yourself, the indexer behind Bull Launch answers GraphQL over HTTP, and a WebSocket per token for live market and discussion updates. It is the same API the app uses. | | | | --- | --- | | GraphQL | `https://api.bulllaunch.fun/graphql` | | WebSocket | `wss://api.bulllaunch.fun/v1/tokens/{token}/market-stream` | | Deadline | The backend enforces its own 10s query deadline and answers `DEADLINE_EXCEEDED`. | Every paged response carries a `checkpoint { blockNumber timestamp }`. Check it before trusting a figure: it tells you how far behind the chain the answer is, and it is the fastest way to tell an indexer that is merely lagging from one following the wrong chain. ```js // The newest launches, the same query the home page runs const r = await fetch("https://api.bulllaunch.fun/graphql", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ query: `query { discoverLaunches(order: NEW, first: 5) { nodes { token { address symbol image description } market { poolId baseAsset { symbol } } bondingProgressBps } checkpoint { blockNumber timestamp } endCursor hasNextPage } }`, }), }); ``` > CORS is allowlisted per origin. If you are calling from a browser on your own domain, ask the team to add it, otherwise a preflight is refused and the browser discards every reply. --- > Bull Launch docs. Every page is available as Markdown by adding `.md` to its URL; the index is https://bulllaunch.fun/llms.txt. This page on the web: https://bulllaunch.fun/docs/security # Security How to report a vulnerability, and what the contracts can and cannot do. ## Reporting a vulnerability If you find a bug that could put user funds at risk, report it privately through the support panel below, with enough detail to reproduce it. Please do not disclose it publicly, open a public issue, or test it against other people's funds before it is fixed. Open the support panel from the footer of any page on https://bulllaunch.fun. ## Audits No audit report is published for this deployment yet. When one is, it will be linked here, next to the addresses it covers. ## What the contracts can and cannot do - Deployed contracts are immutable. No one can upgrade them, pause them, or reverse a trade. - Launch liquidity is locked in `LauncherLocker`, which has no withdrawal function, for the tail band from launch and for the graduated position. - The registry's owner can whitelist or drop base assets and set curves for new launches. That changes what can be launched next, not a pool that already exists. - Fee claims are restricted to a pool's creator or a hook operator, and a claim only ever pays the creator and the Treasury. > Bull Launch will never ask for a private key or seed phrase, and never asks you to send funds to fix, verify or unlock anything. Anyone who does is not us. --- > Bull Launch docs. Every page is available as Markdown by adding `.md` to its URL; the index is https://bulllaunch.fun/llms.txt. This page on the web: https://bulllaunch.fun/docs/support # Support Where to ask integration questions and request a community takeover. For integration questions, technical support, community takeovers and partnership requests, open the support panel. It is also at the bottom of every page. Open the support panel from the footer of any page on https://bulllaunch.fun. Machine-readable versions of these docs: every page is available as Markdown by adding `.md` to its URL, and [/llms.txt](https://bulllaunch.fun/llms.txt) lists them all, with [/llms-full.txt](https://bulllaunch.fun/llms-full.txt) holding the whole set in one file. --- > Bull Launch docs. Every page is available as Markdown by adding `.md` to its URL; the index is https://bulllaunch.fun/llms.txt. This page on the web: https://bulllaunch.fun/docs/terms # Terms and attribution How to reference Bull Launch, and where the full terms live. Onchain data is public and free to read. You are responsible for how you use it. Bull Launch is provided as is, without warranties, and the team is not liable for losses arising from integrations, interfaces, RPCs, or indexers. - When you reference Bull Launch, write the name as "Bull Launch" or "bulllaunch.fun" and link back to the app. - Do not present third-party services as operated by Bull Launch. - Do not imply a partnership, endorsement, or official status without a written agreement. - Availability of interfaces and public infrastructure is not guaranteed. The full terms and the privacy policy live on the app: [Terms](https://bulllaunch.fun/terms) and [Privacy](https://bulllaunch.fun/privacy).