On this page
Bull Launch is running on Arc Chain Testnet. Balances are test units with no value, and the stack may be redeployed.

Everything about Bull Launch, in one place.

Reference for the Bull Launch launch protocol on Arc Chain Testnet: launch mechanics, pricing, graduation, contracts, and integration endpoints.

Getting started

Bull Launch runs on Arc Chain Testnet. Nothing here has real value, which makes it the place to try the whole flow.

Add Arc Testnet to your wallet

Network nameArc Testnet
Chain ID5042002
RPC URLhttps://rpc.testnet.arc.io
Currency symbolUSDC
Block explorerhttps://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.

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 and run through the full loop: create a coin, buy and sell on its curve, and watch it climb toward graduation.

Overview

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 against one of the whitelisted base assets.

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.

How launches work

Creating a launch deploys the coin and opens its market in a single transaction: the launcher factory's 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. CreateThe 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. TradeBuys and sells move price through the main band. Every trade reports its own progress toward graduation as progressBps.
  3. GraduateOnce the main band has taken in its fill amount, it is withdrawn and redeployed as a permanently locked position in the same pool.
ParameterValue
Curve typeSingle Uniswap v4 pool with a concentrated main band, priced by the hook. Tick spacing 60, dynamic fee flag 0x800000.
Total supply per launch1,000,000,000 tokens, 18 decimals, minted once and never again
Supply split80% main band (tradeable), 20% tail band (locked at launch)
Launch feeNone. Gas only.
Anti-bot / wallet cap at launchNone. 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 taxOptional, 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.

Trading and pricing

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.

PriceThe current price in the coin's own pool.
Market capPrice multiplied by circulating supply.
FDVPrice multiplied by the full token supply. Supply is fixed at launch and never grows, so the two readings coincide.
Price impactThe price movement caused by the size of your trade.
SlippageThe maximum execution movement your transaction accepts, enforced as amountOutMinimum by the router.
LiquidityAssets 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.

Graduation

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.

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 launcher locker. The pool id never changes, so an integration that indexed the pool before graduation keeps working after it.

ParameterValue
Post-graduation poolThe same pool. Same pool id, same PoolManager, same hook.
Post-graduation pool fee tierDynamic fee flag 0x800000. The pool charges 0 and the hook takes the real fee, which is 0% (0 bps).
Liquidity lockPermanent. The tail band is locked at launch and the graduation position is locked on graduation, both held by the launcher locker, which exposes no withdrawal function.
Migration delayNone. 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.

Fees and royalties

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.

SplitShare
Swap fee0% of the trade (0 bps, from Hook.swapFeeBps())
Creator royalty90% of that swap fee
Protocol share10% of that swap fee (1000 bps, from Hook.protocolFeeShareBps()), paid to the Treasury
Creator taxSeparate 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 Contracts, which is where every protocol-side fee accrues. See also the fees page.

Community takeovers

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: 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.

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.

Risk disclosures

  • 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.

Network

Bull Launch runs on Arc Chain Testnet. Each coin lives in its own Uniswap v4 pool from launch through graduation.

NetworkArc Testnet
Chain ID5042002
Native assetUSDC, 18 decimals at the EVM level
Public RPChttps://rpc.testnet.arc.io
Explorerhttps://explorer.testnet.arc.io
Multicall30xcA11bde05977b3631167028862bE2a173976CA11
Curve fee0% (0 bps), taken by the hook. The pool's own fee is 0.
Launch feeNone. Gas only.
Default supply1,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.

Contract addresses

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.

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

ContractAddressTransactionBlock
Treasury0x4109...F3610x50cf...4f5b64,236,234
LauncherLocker0x6e32...018A0xba04...c9a064,236,238
Hook0xe332...b0440x16e2...1aba64,236,243
ConfigRegistry0xaB53...6bD10x0206...c06c64,236,252
LauncherFactory0x6874...60650x01be...c7ab64,236,256

Uniswap v4

ContractAddressTransactionBlock
PoolManager0xF208...B9340xba7c...afc562,422,857
PositionManager0x7545...54510x2171...302b62,422,873
UniversalRouter0x1be3...3eC80x337a...b38062,422,897
StateView0x82D1...62a60x745c...336e62,422,913
V4Quoter0xb072...09e80x8b69...467f62,422,917
Permit20x0000...8BA3Canonical 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

ContractAddressTransactionBlock
USDC (native)0x0000...0000Chain native coin-
TestUSDT0x85C5...8b8a0xeb96...f30962,423,041
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: the swap reverts, or on a pool that happens to exist there, trades somewhere else.

Base assets and curves

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.

BaseAddress and decimals
USDC0x0000000000000000000000000000000000000000 (native), 18 decimals
USDT0x85C59dc273d2F1807F2EF569285dff62FF548b8a, 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.

BaseOpens atGraduates atExact raise
USDC3 USDC8 USDC8.04 USDC
USDT3,000 USDT8,000 USDT8,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.

Onchain events

For a trust-minimized integration, index the launcher factory 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.

Coin created Launcher factory
TokenCreated(address)0x2e2b3f61b70d2d131b2a807371103cc98d51adcaa5e9a8f9c32658ad8426e74e
Coin launched Launcher factory, 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.

Reading token and curve state

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.

// Arc Chain Testnet client
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 poolIdOf(token, base = zeroAddress) {
  const [currency0, currency1] =
    BigInt(base) < BigInt(token) ? [base, token] : [token, base];
  return keccak256(
    encodeAbiParameters(
      [{ type: "address" }, { type: "address" }, { type: "uint24" }, { type: "int24" }, { type: "address" }],
      [currency0, currency1, DYNAMIC_FEE_FLAG, TICK_SPACING, HOOK]
    )
  );
}

With the id in hand, everything else is a view call. The functions below are the ones the app itself reads.

ReadCall
Launch recordHook.launches(poolId)
Pool orientationHook.baseIsCurrency0(poolId)
Swap fee, liveHook.swapFeeBps()
Protocol share, liveHook.protocolFeeShareBps()
Unclaimed feesHook.pendingFees(poolId, token)
Graduation target for a baseConfigRegistry.baseCurve(base), returns (startAmount, fillAmount)
Exact raise to graduateConfigRegistry.baseFillAmountActual(base)
Enabled basesConfigRegistry.getEnabledBases()
Creator tax ceilingConfigRegistry.maxTaxBps()
Total supply constantLauncherFactory.TOTAL_SUPPLY(), 1e27
Remaining raisePositionManager.getPositionLiquidity(mainBandTokenId). Answers 0 once graduated, because the band is withdrawn whole.
Spot priceStateView.getSlot0(poolId)
Quote a tradeV4Quoter. It runs the real hook, so a quote includes the fee and the tax.
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.

Indexer API

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.

GraphQLhttps://api.bulllaunch.fun/graphql
WebSocketwss://api.bulllaunch.fun/v1/tokens/{token}/market-stream
DeadlineThe 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.

// 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 } market { poolId } bondingProgressBps }
      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.

Support

For integration questions, technical support, and partnership requests, open the support panel. It is also at the bottom of every page.

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. Always pin the addresses under Contracts for the version you integrated against.

Terms and attribution

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 and Privacy.