On this page
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 name | Arc Testnet |
| Chain ID | 5042002 |
| RPC URL | https://rpc.testnet.arc.io |
| Currency symbol | USDC |
| Block explorer | 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.
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.
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.
- 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.
- TradeBuys and sells move price through the main band. Every trade reports its own progress toward graduation as
progressBps. - GraduateOnce 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.
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.
| 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. |
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.
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.
| 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 the launcher locker, 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.
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.
| 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
minOutbounds, which is why a claim can revert withSlippageExceeded.
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.
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.
| Network | Arc Testnet |
| Chain ID | 5042002 |
| Native asset | USDC, 18 decimals at the EVM level |
| Public RPC | https://rpc.testnet.arc.io |
| Explorer | 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 |
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.
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 | 0x50cf...4f5b | 64,236,234 |
| LauncherLocker | 0x6e32...018A | 0xba04...c9a0 | 64,236,238 |
| Hook | 0xe332...b044 | 0x16e2...1aba | 64,236,243 |
| ConfigRegistry | 0xaB53...6bD1 | 0x0206...c06c | 64,236,252 |
| LauncherFactory | 0x6874...6065 | 0x01be...c7ab | 64,236,256 |
Uniswap v4
| Contract | Address | Transaction | Block |
|---|---|---|---|
| PoolManager | 0xF208...B934 | 0xba7c...afc5 | 62,422,857 |
| PositionManager | 0x7545...5451 | 0x2171...302b | 62,422,873 |
| UniversalRouter | 0x1be3...3eC8 | 0x337a...b380 | 62,422,897 |
| StateView | 0x82D1...62a6 | 0x745c...336e | 62,422,913 |
| V4Quoter | 0xb072...09e8 | 0x8b69...467f | 62,422,917 |
| Permit2 | 0x0000...8BA3 | 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 | 0xeb96...f309 | 62,423,041 |
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.
| 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.
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.
TokenCreated(address)0x2e2b3f61b70d2d131b2a807371103cc98d51adcaa5e9a8f9c32658ad8426e74eTokenLaunched(address,address,address,address,uint256,uint256,bytes32,uint256,uint256,string,string,uint256,uint128,uint16,uint8)0x4bc3e1c7563116fd43fe920faebc0e711cddc122034360e8c8e1b4c81c5267a5PoolRegistered(bytes32,address,address,address)0x01bf263a1db1652580721573296e1a1fa70b3d4c87f61d02a69c4e1109d2d573CurveBuy(address,address,uint256,uint256,uint256,uint256,bytes32,address,uint16)0xc1742a443d639e06cae611e6969aa3e066a8edf9ca632e4d45b9167f487d1785CurveSell(address,address,uint256,uint256,uint256,uint256,bytes32,address,uint16)0xcf93b133041e0c9edcb77b3088a72ce25e769d56f3df661c1b0db04a3fe7fa0dPoolGraduated(address,uint256,uint256,uint256)0x0a44ef75df69c534f43cd6c1aa3ef8983065fe5fe79ef9e79f6494e6f258c259Swap(bytes32,address,int128,int128,uint160,uint128,int24,uint24)0x40e9cecb9f5f1f1c5b9c97dec2917b7ee92e57ba5563708daca94dd84ad7112fEvery 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.
| 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. |
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.
| 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.
// 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
} }`,
}),
});Support
For integration questions, technical support, and partnership requests, open the support panel. It is also at the bottom of every page.
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.
