# Tartarus — complete integration documentation Network elysium-mainnet (chain 99901). Generated from the deployed contracts. 9 contracts · 188 functions · 60 events · 124 errors. --- # Adding Elysium to your wallet Tartarus runs on Elysium, so your wallet needs the Elysium network, set to the **Tartarus RPC**. Enter these details once and you are done. | | | |---|---| | Network name | `Elysium` | | RPC URL | `https://api.tartarus.world/v1/rpc` | | Chain ID | `99901` | | Currency symbol | `HYPE` | | Decimals | `18` | | Block explorer URL | `https://tartarus.world/explorer` | If your wallet asks for the chain ID in hex, it is `0x1863d`. ## Add it manually 1. Open your wallet's network settings. In most wallets this is **Add network** or **Add a custom network**. 2. Enter the details above, and paste the RPC URL exactly as shown. 3. Save, then switch to Elysium. Your address is the same as on every other EVM chain. You can also let the site do it. The first time you make a transaction on Tartarus, your wallet will ask to add or switch to Elysium. ## Already have Elysium in your wallet? Check which RPC it uses, and change it to the one above if it differs. Wallets keep their own network settings and ignore what a site suggests, so adding the network again usually does nothing. This matters because most community RPCs only pass along transactions for their own apps. With one of those, a Tartarus trade can fail with a generic error or stay pending forever, even though your balance looks fine. The Tartarus RPC gets trades and launches onto the chain, falling back through HyperEVM if the sequencer relay refuses them. Other transactions may be declined, including plain HYPE transfers. See [Which RPC to use](/documentation/network#which-rpc-to-use). ## Get HYPE on Elysium HYPE pays for gas on Elysium. Move some over from HyperEVM on the [Bridge](/bridge) page, which takes about a minute. Bringing it back takes about 6 days, so see [Bridging HYPE](/documentation/network#bridging-hype) first. --- # Tartarus Tartarus is a permissionless token launchpad on **Elysium**, Kinetiq's Arbitrum Orbit chain settling on HyperEVM. Anyone can launch a token in one transaction. There is no bonding curve and nothing to graduate: the token trades on a **Uniswap V4 pool from its first block**, and that pool holds the whole supply in a single position that is locked forever. New here? Start by [adding Elysium to your wallet](/documentation). ## What a launch is | | | |---|---| | Supply | Exactly **1,000,000,000** tokens, 18 decimals. No mint, no burn. | | Where the supply goes | **100%** into one single-sided V4 position, owned by a locker with no withdrawal function | | Team or presale allocation | None. A creator who wants tokens buys them, like everyone else | | Starting price | Set by the launch config. Config 0 opens at a market cap of about 36 HYPE | | Quote asset | Native **HYPE** today. The contracts also support owner-approved ERC-20s | | Swap fee | **1% to 10%**: a fixed 0.5% to the protocol plus 0.5% to 9.5% chosen by the creator, frozen at launch | | Launch fee | 0.001 HYPE, read it live from `launchFee()` | Fees are taken in the pool by a shared hook, converted to the quote asset, and credited to an escrow that recipients claim from. A creator can send their share to the token's holders instead of themselves. See [Fees](/documentation/fees) and [Holder rewards](/documentation/holder-rewards). ## What you actually call | To | Call | |---|---| | launch with no opening buy | `factory.launchToken` | | launch and buy in one transaction | `launchAndBuy.launchAndBuy` | | buy or sell | Uniswap's UniversalRouter, `V4_SWAP` | | quote a trade | Uniswap's V4Quoter, or `GET /v1/tokens/:id/quote` | | collect fees you are owed | `feeEscrow.claim()` or `claimToken(token)` | | claim holder rewards | `distributor.batchClaim` on the token's own distributor | There is one pool for every token, and no phase to branch on. If `getLaunchedToken(token).exists` is true, the token is tradeable. ## Where to start 1. **[Quickstart](/documentation/quickstart)** launches a token, trades it and collects the fees, in code you can run. 2. **[Approvals](/documentation/approvals)** says which contract pulls your funds. Most failed integrations start here: native HYPE needs no approval at all, and an ERC-20 swap needs two. 3. **[Elysium network](/documentation/network)** covers the RPC situation, which is unusual and decides how your transactions reach the chain. ## Building with an AI There is a **Use with AI** button at the top of every page. It copies the page, opens a Claude conversation pointed at the full reference, or installs a Claude Code skill. | | | |---|---| | [`/llms-full.txt`](/llms-full.txt) | All of this documentation as one document, with every address, signature, selector and event topic. | | [`/llms.txt`](/llms.txt) | A short index, following the [llms.txt](https://llmstxt.org) convention. | | [`/skill.md`](/skill.md) | A Claude Code skill. Save it as `.claude/skills/tartarus/SKILL.md`. | | `/documentation/.md` | Any single page as raw markdown. | --- # Quickstart Launch a token with an opening buy, trade it, and collect the fees, with [viem](https://viem.sh) on Elysium mainnet. Each step continues from the one before, so the blocks run as a single script. ## 1 · Set up Download the ABIs from the [ABI page](/documentation/abi), then point a client at Elysium. The chain is not yet in viem's built-in list, so define it yourself. The RPC below is the Tartarus proxy; the [network page](/documentation/network) explains why it is the one to use. ```ts import { createPublicClient, createWalletClient, defineChain, http, keccak256, maxUint256, parseAbi, parseEther, toBytes, encodeAbiParameters, parseAbiParameters, zeroAddress } from "viem"; import { privateKeyToAccount } from "viem/accounts"; import factoryAbi from "./TartarusLaunchFactory.json"; import launchAndBuyAbi from "./TartarusLaunchAndBuy.json"; import hookAbi from "./TartarusMemeHook.json"; import escrowAbi from "./TartarusFeeEscrow.json"; const elysium = defineChain({ id: 99901, name: "Elysium", nativeCurrency: { name: "HYPE", symbol: "HYPE", decimals: 18 }, rpcUrls: { default: { http: ["https://api.tartarus.world/v1/rpc"] } }, }); const FACTORY = "0x765f2ce211050ec8280257d76072cAa480D75A00"; const LAUNCH_AND_BUY = "0x044a835456057915028DDdFfa161DeA9d4d40615"; const HOOK = "0x00b2Ff65E61aD8c57F957624EDc6C81908Ade044"; const ESCROW = "0x44D1065fD59280cFa26B7BE3dBf13F364314616F"; const ROUTER = "0x1bd4d69bfb8927a045f20518a08390cee3913b0d"; const QUOTER = "0x379f556c5a748bd7cc9baf0f7a344094ac1814f5"; const PERMIT2 = "0x000000000022D473030F116dDEE9F6B43aC78BA3"; const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`); const client = createPublicClient({ chain: elysium, transport: http() }); const wallet = createWalletClient({ account, chain: elysium, transport: http() }); async function send(request: Parameters[0]) { const hash = await wallet.writeContract(request); const receipt = await client.waitForTransactionReceipt({ hash }); if (receipt.status !== "success") throw new Error(`reverted: ${hash}`); return receipt; } ``` You need a little HYPE on Elysium for gas and the launch fee. See [Bridging HYPE](/documentation/network#bridging-hype). ## 2 · Read the terms Check that launching is open, then read the fee and pin the terms you are agreeing to. ```ts const CONFIG_ID = 0n; // config 0: native HYPE const creatorFeeBps = 100; // 1% to you, on top of the protocol's 0.5% const canLaunch = await client.readContract({ address: FACTORY, abi: factoryAbi, functionName: "canLaunch", args: [account.address], }); if (!canLaunch) throw new Error("launching is not open for this address"); const launchFee = await client.readContract({ address: FACTORY, abi: factoryAbi, functionName: "launchFee", }) as bigint; const expectedEconomics = await client.readContract({ address: FACTORY, abi: factoryAbi, functionName: "previewLaunchEconomics", args: [CONFIG_ID, creatorFeeBps], }) as `0x${string}`; ``` `expectedEconomics` is a digest of the price, fee rates and launch fee. If the owner retunes any of them before your transaction lands, the launch reverts with `LaunchEconomicsMismatch` rather than landing on terms you never saw. Read it in the same flow as the send, and never cache it. ## 3 · Launch with an opening buy The launch fee and the buy both travel as `msg.value`, so nothing needs approving. ```ts const params = { name: "My Token", symbol: "MINE", // unique across Tartarus: [A-Z0-9], 1 to 6 characters logo: "", description: "", socials: { twitter: "", telegram: "", discord: "", website: "", farcaster: "" }, creatorFeeRecipient: zeroAddress, // zero means you creatorFeeBps, expectedEconomics, salt: keccak256(toBytes(crypto.randomUUID())), }; const openingBuy = parseEther("0.1"); const { result, request } = await client.simulateContract({ account, address: LAUNCH_AND_BUY, abi: launchAndBuyAbi, functionName: "launchAndBuy", args: [params, CONFIG_ID, openingBuy, 0n, account.address], value: launchFee + openingBuy, }); const [token, tokensOut] = result as [`0x${string}`, bigint]; await send(request); ``` `minTokensOut` can be `0n` here because nobody can trade ahead of you: the pool does not exist until this transaction creates it, and the buy happens inside the same transaction. To launch with no opening buy, call `factory.launchToken(params, CONFIG_ID)` with `value: launchFee`. The creator then starts with none of the supply. ## 4 · Buy Every token has the same shape of pool. Read its key from the factory, quote through the real hook, and swap through UniversalRouter. ```ts const quoterAbi = parseAbi([ "function quoteExactInputSingle(((address currency0, address currency1, uint24 fee, int24 tickSpacing, address hooks) poolKey, bool zeroForOne, uint128 exactAmount, bytes hookData) params) returns (uint256 amountOut, uint256 gasEstimate)", ]); const routerAbi = parseAbi(["function execute(bytes commands, bytes[] inputs, uint256 deadline) payable"]); const key = await client.readContract({ address: FACTORY, abi: factoryAbi, functionName: "poolKeyFor", args: [token], }) as { currency0: `0x${string}`; currency1: `0x${string}`; fee: number; tickSpacing: number; hooks: `0x${string}` }; const quote = async (zeroForOne: boolean, exactAmount: bigint) => { const { result } = await client.simulateContract({ address: QUOTER, abi: quoterAbi, functionName: "quoteExactInputSingle", args: [{ poolKey: key, zeroForOne, exactAmount, hookData: "0x" }], }); return result[0]; }; // V4_SWAP running SWAP_EXACT_IN_SINGLE, SETTLE_ALL, TAKE_ALL. const swap = async (zeroForOne: boolean, amountIn: bigint, minOut: bigint, value: bigint) => { const [input, output] = zeroForOne ? [key.currency0, key.currency1] : [key.currency1, key.currency0]; const params = encodeAbiParameters( parseAbiParameters("((address,address,uint24,int24,address),bool,uint128,uint128,uint256,bytes)"), [[[key.currency0, key.currency1, key.fee, key.tickSpacing, key.hooks], zeroForOne, amountIn, minOut, 0n, "0x"]], ); const settle = encodeAbiParameters(parseAbiParameters("address, uint256"), [input, amountIn]); const take = encodeAbiParameters(parseAbiParameters("address, uint256"), [output, minOut]); const v4Input = encodeAbiParameters(parseAbiParameters("bytes, bytes[]"), ["0x060c0f", [params, settle, take]]); const deadline = BigInt(Math.floor(Date.now() / 1000) + 600); return send({ address: ROUTER, abi: routerAbi, functionName: "execute", args: ["0x10", [v4Input], deadline], value, } as never); }; // A HYPE-quoted pool always has HYPE as currency0, so a buy is zeroForOne. const buyIn = parseEther("0.05"); const buyOut = await quote(true, buyIn); // net of the hook's fee await swap(true, buyIn, (buyOut * 99n) / 100n, buyIn); // 1% slippage; HYPE goes as value ``` ## 5 · Sell Selling spends an ERC-20, so it needs **two** grants: the token approves Permit2, then Permit2 approves the router. The second one is what most integrations forget. ```ts const erc20Abi = parseAbi(["function approve(address spender, uint256 amount) returns (bool)"]); const permit2Abi = parseAbi(["function approve(address token, address spender, uint160 amount, uint48 expiration)"]); const sellIn = tokensOut / 2n; const expiry = Math.floor(Date.now() / 1000) + 600; await send({ address: token, abi: erc20Abi, functionName: "approve", args: [PERMIT2, sellIn] } as never); await send({ address: PERMIT2, abi: permit2Abi, functionName: "approve", args: [token, ROUTER, sellIn, expiry] } as never); const sellOut = await quote(false, sellIn); await swap(false, sellIn, (sellOut * 99n) / 100n, 0n); // HYPE comes back to you as native ``` ## 6 · Collect your fees Trades leave fees in the hook until someone sweeps them. The keeper does that for every pool on a schedule, and a creator can pay out the quote side at any time. Then the balance is yours to claim from the escrow. ```ts const { poolId } = await client.readContract({ address: FACTORY, abi: factoryAbi, functionName: "getLaunchedToken", args: [token], }) as { poolId: `0x${string}` }; await send({ address: HOOK, abi: hookAbi, functionName: "sweepPoolQuoteFees", args: [poolId] } as never); const owed = await client.readContract({ address: ESCROW, abi: escrowAbi, functionName: "balanceOf", args: [account.address], }) as bigint; if (owed > 0n) { await send({ address: ESCROW, abi: escrowAbi, functionName: "claim", args: [] } as never); } ``` ## Next - [Launching a token](/documentation/launching) covers every `TokenParams` field and common reverts. - [Holder rewards](/documentation/holder-rewards) sends the creator share to holders instead. - [API](/documentation/api) gives you quotes, token data and live trades without running an indexer. --- # Addresses Everything below is **Elysium mainnet**, chain `99901`. These tables are generated from the deployment record when the site builds. `GET /v1/protocol` serves the same addresses live, so read them from there in anything long-running. ## Per-launch contracts have no fixed address Two contracts are deployed once per launch: - `TartarusLauncherToken`, the token itself. Its address is what the launch returns - `TartarusRewardsDistributor`, only for launches that chose **Fees go to: Holders** Read the distributor from the rewards factory. It is zero until the distributor is deployed, and for any launch that did not choose holder rewards: ```ts const distributor = await client.readContract({ address: REWARDS_FACTORY, abi: rewardsFactoryAbi, functionName: "distributorOf", args: [token], }); ``` ## The platform token $TTRS is `0x804f6f9c86ae4313cb277b3b8f71d3f2f04f6efa`. It is a standard Tartarus launch, so everything in these docs applies to it. The [$TTRS page](/platform) shows its buybacks and burns. ## Constants | | | |---|---| | `HOLDER_REWARDS` sentinel | `0xd76bd0238bbf53ebdff3126a0f34d2d8cd4bfebf` | | Launch config 0 | HYPE, `startingTick` 171400, `tickSpacing` 200 | | Native HYPE in a pool key | `0x0000000000000000000000000000000000000000` | ## Deployed contracts Network **elysium-mainnet**, chain `99901`. Explorer: https://tartarus.world/explorer Indexing starts at block `701345`. | Contract | Address | Kind | |---|---|---| | TartarusFeeEscrow | `0x44D1065fD59280cFa26B7BE3dBf13F364314616F` | singleton | | TartarusMemeHook | `0x00b2Ff65E61aD8c57F957624EDc6C81908Ade044` | singleton | | TartarusLaunchLocker | `0x8fABb780838c1662235002F70A94a7ef882a9598` | singleton | | TartarusLaunchFactory | `0x765f2ce211050ec8280257d76072cAa480D75A00` | singleton | | TartarusLaunchDeployer | `0x94b7ed8968bF4323c03DE97395801D487771C44E` | singleton | | TartarusLaunchAndBuy | `0x044a835456057915028DDdFfa161DeA9d4d40615` | singleton | | TartarusRewardsDistributorFactory | `0x3c8E2F5924629e629C5c4bFc56FddC4B2794E4F9` | singleton | | TartarusLauncherToken | — | per launch, read from the launch record | | TartarusRewardsDistributor | — | per launch, read from the launch record | ## External contracts | Key | Address | Purpose | |---|---|---| | poolManager | `0xE2e105d3F7209A9DA11f83cCf3E7398a753823F1` | Uniswap V4 PoolManager | | positionManager | `0x1d1C51b9d07fD27d36412A2b66CC23D19Eb3E518` | Uniswap V4 PositionManager (holds the locker's seed positions) | | universalRouter | `0x1bd4d69bfb8927a045f20518a08390cee3913b0d` | Uniswap UniversalRouter — pool swaps (V4_SWAP) | | v4Quoter | `0x379f556c5a748bd7cc9baf0f7a344094ac1814f5` | Uniswap V4 Quoter — swap estimates through the real hook | | stateView | `0x27ed73ae0652fdcf71d6793412a7525f32be9dfe` | Uniswap V4 StateView — pool state reads | | permit2 | `0x000000000022D473030F116dDEE9F6B43aC78BA3` | Permit2 — required for UniversalRouter | --- # Elysium network Elysium is Kinetiq's Arbitrum Orbit chain. It settles on HyperEVM and uses HYPE for gas. | | | |---|---| | Chain ID | `99901` | | Parent chain | HyperEVM (`999`) | | Native asset | HYPE, 18 decimals | | Block time | a few hundred milliseconds | | Base fee | floor of about 0.1 gwei | | Tartarus RPC | `https://api.tartarus.world/v1/rpc` | | Explorer | [tartarus.world/explorer](/explorer), until Kinetiq publishes one | The testnet is chain `99801`, with RPC `https://testnet-rpc.elysium.kinetiq.xyz`. It runs its own deployment, and none of the addresses in these docs apply there. ## Which RPC to use Elysium has no open public sequencer endpoint yet. Community RPCs serve reads, but most only relay transactions for their own apps, and will refuse or silently drop yours. A wallet pointed at the wrong one can fail with a generic error, or show a transaction as pending forever. **Use the Tartarus RPC.** It serves everything a wallet needs and gets Tartarus transactions onto the chain. If your wallet already has Elysium added with another RPC, change it there: wallets keep their own network settings and ignore what a site suggests. | Rule | Value | |---|---| | Requests | 60 per second per IP | | Batch size | up to 25 calls | | `eth_getLogs` | up to 2,000 blocks, or by `blockHash` | | Errors | always JSON-RPC over HTTP 200: `-32005` rate limited, `-32603` upstream failure | ### Sending transactions `eth_sendRawTransaction` tries the sequencer relay first. If that refuses, the transaction goes through Elysium's delayed inbox on HyperEVM instead. This takes about **15 to 30 seconds** rather than under a second. The same hash is returned either way, so your code does not change: just wait for the receipt. That fallback is paid for by Tartarus, so it only carries transactions to Tartarus contracts, UniversalRouter, Permit2, launched tokens, approved quote tokens and rewards distributors. Other transactions, including plain HYPE transfers, may be declined with `notAllowed`. ## Gas Gas costs very little: a swap costs a fraction of a cent. There are two things to get right. - **Set `maxFeePerGas` above the base fee.** A transaction priced below it is dropped with no error, and never appears on chain. `eth_gasPrice` on the Tartarus RPC returns double the base fee to leave headroom, and wallets on an unrecognized network tend to use that value directly. - **Tips are not paid.** Arbitrum Orbit chains have no priority auction, so `maxPriorityFeePerGas` of `0` is fine. `eth_estimateGas` is reliable on every Tartarus path, since no swap can trigger a migration or any other hidden work. A plain launch uses about 1.55M gas. ## Bridging HYPE The [Bridge](/bridge) page calls Elysium's canonical rollup contracts directly, and nothing is held by Tartarus. HYPE moves 1:1 to the same address, with no bridge fee beyond network gas. | Direction | How | Time | |---|---|---| | HyperEVM to Elysium | `Inbox.depositEth()` | About a minute | | Elysium to HyperEVM | `ArbSys.withdrawEth(address)`, then a claim on HyperEVM | About 6 days | **A withdrawal is two transactions.** The first leaves Elysium straight away. The second pays out on HyperEVM once the rollup confirms it, after a challenge period of 483,840 HyperEVM blocks plus the wait for the next assertion. Nothing can shorten it or cancel it, and the claim needs a little HYPE for gas on HyperEVM. The wait is read from the rollup, since Kinetiq has not published the mainnet figure. **Use a regular wallet.** `depositEth` credits a contract at an aliased address it cannot spend from, which includes Safes, smart accounts and EIP-7702 delegated accounts. The Bridge page refuses them. To claim from your own code, `GET /bridge/withdrawals/:address` returns each withdrawal with the `Outbox.executeTransaction` arguments, proof included, once it is ready. See [API](/documentation/api). Bridging only moves HYPE. Tartarus tokens do not bridge yet. See [HyperCore](/documentation/hypercore). ## Indexing Start from block `701345`, where the factory was deployed. `TokenLaunched` gives you the token, pool and terms; `LaunchSalt` fires just before it in the same transaction. `Swap` on the PoolManager, filtered by `poolId`, gives you trades. The [Events](/documentation/events) page has every `topic0`. The node behind the RPC is pruned, so state older than a few minutes is unavailable, and calls pinned to an old block return a "missing trie node" error. Logs remain available. If you need historical state, you will need your own archive node. Or use the [API](/documentation/api), which indexes everything for you. --- # How a launch works A Tartarus launch has one state after it lands: trading. There is no curve to fill, no threshold, and no migration. This page follows what happens inside the launch transaction and what runs afterwards. ``` launchToken / launchAndBuy | | one transaction v deploy token -> create pool -> register with hook -> lock whole supply | v trading on Uniswap V4, from this block, forever | v fees accrue in the hook -> sweep -> escrow -> claim ``` ## Inside the launch transaction The factory does all of this atomically, so a launch either completes or never happened. 1. **Checks.** The launch gate, the config, the quote asset, the creator tranche, the economics pin, the exact launch fee and the ticker. 2. **Deploys the token** with CREATE2 through `TartarusLaunchDeployer`. All 1,000,000,000 tokens are minted to the factory. Name, symbol and decimals are immutable, stored in the bytecode. 3. **Creates the bridge wallet** when a bridge factory is configured, so the token is ready for Elysium's mirror bridge. None is configured on mainnet yet. See [HyperCore](/documentation/hypercore). 4. **Initializes the V4 pool** at the config's starting price, with the Tartarus hook attached and an LP fee of zero. 5. **Registers the pool** with the hook, freezing its fee rates and recipients for life. 6. **Seeds one position** holding the entire supply, from the floor price to the top of the tick range, and hands it to `TartarusLaunchLocker`. Any rounding dust is locked too. 7. **Records the launch** and emits `LaunchSalt`, `PoolSeeded` and `TokenLaunched`. `launchAndBuy` runs the same steps, then buys inside the same transaction, so nobody can get in between the pool opening and the creator's first purchase. ## The pool The position is single-sided: it holds tokens and no quote. A buy pays quote into it and takes tokens out, moving the price up. A sell does the reverse. Sells can only return what buys paid in, so the price cannot fall below where the launch started. That starting price is the token's **floor**. [Price and market cap](/documentation/pricing) gives the math. The position cannot be removed. `TartarusLaunchLocker` has no withdrawal function and no arbitrary call, for anyone, including its owner. The pool's LP fee is zero, so the position earns nothing and there is nothing to collect from it. Every fee belongs to the hook. ## After launch | What | Who | How often | |---|---|---| | Take the fee on each swap | the hook | every swap | | Convert memecoin fees to the quote and pay them to the escrow | the sweep operator (keeper) | about every 6 hours per pool, once enough has accrued | | Pay out quote fees | the keeper, or the creator directly | any time | | Deploy a holder-rewards distributor | the jobs service, or anyone | soon after a holder-rewards launch | | Publish holder-reward epochs | the distributor owner | about weekly | Nothing here can stop a token from trading. If every Tartarus service went offline, the pools, the tokens and the escrow would keep working, and swaps could still go straight through Uniswap. ## Reading a launch ```ts const launch = await client.readContract({ address: FACTORY, abi: factoryAbi, functionName: "getLaunchedToken", args: [token], }); // { token, deployer, creatorFeeRecipient, quoteToken, poolId, positionId, // startingTick, tickSpacing, exists } ``` `getLaunchedToken` returns a **zeroed struct** for an unknown address rather than reverting, so check `exists` first. --- # Launching a token There are two ways in, and they differ only in whether the creator buys in the same transaction. | | `factory.launchToken` | `launchAndBuy.launchAndBuy` | |---|---|---| | Opening buy | none | required, `quoteIn` must be non-zero | | Native HYPE `msg.value` | exactly `launchFee()` | `launchFee() + quoteIn` | | ERC-20 quote | `msg.value` is the fee | `msg.value` is the fee; approve the router for `quoteIn` | | Returns | `token` | `(token, tokensOut)` | Use `launchAndBuy` if the creator wants tokens at all. A separate buy afterwards is a race against everyone watching the chain, on a pool that opens at its lowest price. `launchAndBuy` needs the router to be the factory's `launchForwarder`. It can be switched off in an emergency, so check `GET /v1/protocol` → `launchAndBuy.enabled` before offering it. Plain `launchToken` keeps working either way. ## TokenParams | Field | Rule | |---|---| | `name` | 1 to 31 bytes, no NUL byte. Immutable | | `symbol` | `[A-Z0-9]`, 1 to 6 characters, unique across Tartarus. Immutable | | `logo` | At most 512 bytes. A URL, not image data | | `description` | At most 2,048 bytes | | `socials` | `twitter`, `telegram`, `discord`, `website`, `farcaster`; each at most 256 bytes, may be empty | | `creatorFeeRecipient` | Zero means the caller. `HOLDER_REWARDS` means the token's holders. Anything else is used as given | | `creatorFeeBps` | Your tranche in basis points, inside the hook's bounds: `50` to `950` today | | `expectedEconomics` | From `previewLaunchEconomics`. Zero waives the check | | `salt` | A random 32 bytes. See below | All limits are in UTF-8 **bytes**, not characters, so an emoji in a name costs four. `creatorFeeBps` has no default worth relying on. Leaving it at zero is outside the bounds and reverts `CreatorFeeOutOfBounds`, so a launch can never land on a rate nobody chose. ### The ticker Symbols are unique because HyperCore tickers are. Whoever lands first owns the ticker for good. These are reserved and can never be used: `HYPE`, `USDC`, `USDT`, `USDE`, `USDH`, `PURR`, `BTC`, `ETH`, `SOL`, `UBTC`, `UETH`, `USOL`, `HFUN`. The owner can reserve more. Check before you show a form: ```ts const free = await client.readContract({ address: FACTORY, abi: factoryAbi, functionName: "isSymbolAvailable", args: ["MINE"], }); ``` `GET /v1/launch/symbol?symbol=MINE` gives the same answer with a reason: `ok`, `invalid`, `reserved` or `taken`. ### The salt and the token address The token address is CREATE2, salted with `keccak256(abi.encode(deployer, salt, creatorFeeRecipient, creatorFeeBps, launchConfigId))`, and the metadata is part of the creation code. So: - your salts are your own, and nobody can land a launch at an address derived from your account; - an address commits to exact terms, so a vanity salt only works for the recipient, tranche, config and metadata it was mined for. Do not compute the address by hand. Call `deployer.predictLaunchAddress`, or `POST /v1/launch/predict` with a session, which also returns the pool key and floor price so you can draw the token before it exists. For a holder-rewards launch, use a fresh salt every time. A distributor's address depends only on creator, salt and quote, so reusing one reverts `HolderRewardsDistributorTaken`. ### expectedEconomics The digest is: ```solidity keccak256(abi.encode(quoteToken, startingTick, tickSpacing, protocolFeeBps, creatorFeeBps, maxInternalPriceImpactBps, launchFee)) ``` `previewLaunchEconomics(configId, creatorFeeBps)` computes it. If any input moves between your read and your transaction, the launch reverts with `LaunchEconomicsMismatch(expected, actual)`. ## Metadata cannot be fixed later Everything on the token is permanent. The contract has no setter, and the name and symbol must never change because Elysium's bridge keys a token's mirror on them. Check spelling, the ticker and the logo before you sign. What can change is `creatorFeeRecipient`: the current recipient can hand it on with `factory.transferCreatorFeeRecipient(token, newRecipient)`. The creator tranche itself is frozen. ## Common reverts | Error | Meaning | |---|---| | `NotWhitelisted` / `NotApprovedLauncher` | Launching is closed and you are not whitelisted | | `IncorrectLaunchFee` / `WrongValue` | `msg.value` is not exactly right. Re-read `launchFee()` | | `LaunchConfigDisabled` / `InvalidLaunchConfigId` | That config is off or does not exist | | `QuoteTokenNotApproved` | The config's quote is no longer approved | | `InvalidName` / `InvalidSymbol` / `MetadataTooLong` | A `TokenParams` rule above | | `SymbolReserved` / `SymbolTaken(address)` | The ticker is unavailable | | `CreatorFeeOutOfBounds(bps, min, max)` | Your tranche is outside the bounds | | `LaunchEconomicsMismatch(expected, actual)` | Terms moved. Re-read and show the user the new ones | | `RewardsFactoryNotSet` | `HOLDER_REWARDS` used before the rewards factory was wired | | `HolderRewardsDistributorTaken(address)` | Salt reused for holder rewards. Use a new one | | `ZeroBuy` | `launchAndBuy` with `quoteIn` of zero. Use `launchToken` | `CreatorFeeOutOfBounds` comes from the hook, so a client holding only the factory ABI sees the raw selector `0x87ab5572`. Decode with the combined `tartarus.json` from the [ABI page](/documentation/abi), or look any selector up on the [Errors](/documentation/errors) page. --- # Trading Every Tartarus token trades in an ordinary Uniswap V4 pool, so any V4-aware router or aggregator can reach it. There is one path for a token's whole life. This page uses UniversalRouter, which is what the Tartarus site uses. ## The pool key Read it, do not rebuild it: ```ts const key = await client.readContract({ address: FACTORY, abi: factoryAbi, functionName: "poolKeyFor", args: [token], }); // { currency0, currency1, fee: 0, tickSpacing: 200, hooks: } ``` V4 sorts currencies by address. Native HYPE is `address(0)`, so on a HYPE pool it is always `currency0` and **a buy is `zeroForOne: true`**. With an ERC-20 quote the token can sort either way, so the rule is: a buy is `zeroForOne` exactly when the quote is `currency0`. ## Quoting There are two options, and both simulate through the real hook, so the figure is net of fees. **From the API**, which also applies slippage and returns everything you need to build the swap: ```bash curl "https://api.tartarus.world/v1/tokens/$TOKEN/quote?side=buy&amount=1&slippageBps=100" ``` `amount` is in **whole units** of what you are spending: quote on a buy, tokens on a sell. The response carries `amountRaw`, `outRaw`, `minOutRaw`, `zeroForOne`, `poolKey`, the fee split and `priceImpactBps`. Pass `minOutRaw`, not `outRaw`, as the swap's minimum. **From the chain**, with Uniswap's V4Quoter. It is not a `view` function, so call it with `simulateContract`: ```ts const { result } = await client.simulateContract({ address: QUOTER, abi: quoterAbi, functionName: "quoteExactInputSingle", args: [{ poolKey: key, zeroForOne: true, exactAmount: amountIn, hookData: "0x" }], }); const [amountOut] = result; ``` Blocks arrive every few hundred milliseconds, so quote again right before signing. ## Swapping The router program is `V4_SWAP` (`0x10`) running three actions: `SWAP_EXACT_IN_SINGLE` (`0x06`), `SETTLE_ALL` (`0x0c`) and `TAKE_ALL` (`0x0f`). ```ts const swapParams = encodeAbiParameters( parseAbiParameters("((address,address,uint24,int24,address),bool,uint128,uint128,uint256,bytes)"), [[[key.currency0, key.currency1, key.fee, key.tickSpacing, key.hooks], zeroForOne, amountIn, minOut, 0n, "0x"]], ); const settle = encodeAbiParameters(parseAbiParameters("address, uint256"), [inputCurrency, amountIn]); const take = encodeAbiParameters(parseAbiParameters("address, uint256"), [outputCurrency, minOut]); const v4Input = encodeAbiParameters(parseAbiParameters("bytes, bytes[]"), ["0x060c0f", [swapParams, settle, take]]); await wallet.writeContract({ address: ROUTER, abi: routerAbi, functionName: "execute", args: ["0x10", [v4Input], deadline], value: inputCurrency === zeroAddress ? amountIn : 0n, }); ``` > [!WARNING] > This router build's `ExactInputSingleParams` has an extra `uint256`, `minHopPriceX36`, before > `hookData`. Encoding the older five-field struct fails. Pass `0n`. `hookData` stays empty: the > Tartarus hook reads nothing from it. `TAKE_ALL`'s amount is your slippage bound: the swap reverts rather than deliver less. - **Buying with HYPE:** send `amountIn` as `value`. No approvals. - **Selling, or buying with an ERC-20 quote:** two grants first. See [Approvals](/documentation/approvals). - **Selling into HYPE:** `TAKE_ALL` of `address(0)` pays you native HYPE. There is no wrapping step. ## Where the fee comes from The hook takes its fee from the **unspecified** leg of each swap, the side you did not fix. | Swap | Fee taken in | Effect | |---|---|---| | Exact-in buy | tokens | you receive fewer tokens | | Exact-in sell | quote | you receive less quote | | Exact-out buy | quote | you pay more quote | | Exact-out sell | tokens | you pay more tokens | The rate is the same in every case: on exact-out the fee is grossed up, so it is exactly the pool's rate of what you hand over. V4Quoter and the API already account for it. The API's `feeCurrency` tells you which side it was taken in. ## Selling at the floor The protocol's liquidity ends at the launch price. If the price is already at the floor, there is no quote to sell into. The API returns `fillsNothing: true` with an output of zero. V4Quoter reverts with `UnexpectedRevertBytes` (`0x6190b2b0`), wrapping `NotEnoughLiquidity` (`0x7a5ed734`): the quoter works by reverting on purpose, so its real errors arrive inside that wrapper. Treat this as a normal state and show it to the user instead of an error. ## Failure cases | Symptom | Cause | |---|---| | `AllowanceExpired` (`0xd81b2f2e`) | The Permit2 grant is missing, or has expired | | `InsufficientAllowance` (`0xf96fb071`) | The Permit2 grant is smaller than the swap, or was used up by an earlier one | | A bare ERC-20 transfer revert | The token never approved Permit2 | | `V4TooLittleReceived` (`0x8b063d73`) | Price moved past your `minOut`. Quote again | | `TransactionDeadlinePassed` (`0x5bf6f916`) | The deadline passed, or your wallet sat on the prompt too long | | `UnexpectedRevertBytes` (`0x6190b2b0`) from the quoter | Selling into the floor, or buying more than the pool holds. The inner error says which | --- # Price and market cap The whole supply sits in one position spanning from the floor to the top of the tick range, so Tartarus pricing has a simple closed form. It behaves like a constant-product pool in which the quote side starts at zero. ## The math Let `M` be the starting market cap in quote, and `f` the fraction of supply bought so far. Before fees: ``` quote spent to reach f = M · f / (1 − f) price at f = floor / (1 − f)² market cap at f = M / (1 − f)² ``` Market cap here means **price × 1,000,000,000**, the fully diluted figure. There is only one supply number, and every token that is not in the pool has been bought from it. ## At launch config 0 Config 0 has `startingTick` 171400, tick spacing 200, and a floor of about 0.000000036 HYPE per token, which makes `M` about **36.02 HYPE**. | Supply bought | Cost (HYPE) | Market cap (HYPE) | Multiple of start | |---|---|---|---| | 0.1% | 0.04 | 36.1 | 1.00× | | 1% | 0.36 | 36.8 | 1.02× | | 5% | 1.90 | 39.9 | 1.11× | | 10% | 4.00 | 44.5 | 1.23× | | 25% | 12.0 | 64.0 | 1.78× | | 50% | 36.0 | 144 | 4× | | 75% | 108 | 576 | 16× | | 90% | 324 | 3,602 | 100× | | 99% | 3,566 | 360,217 | 10,000× | Costs are cumulative from launch and exclude the swap fee. A trade of the same size moves the price much less once a token has been bought into, because the same quote buys fewer tokens at a higher price. Live figures are in `GET /v1/protocol`: `launchConfigs[].floorPrice`, `floorMarketCap` and `floorMarketCapUsd`. ## The starting tick `startingTick` is the **tokens-per-quote** price at the floor, in raw units, for every quote asset. A higher tick means a cheaper token. ``` tokensPerQuote = 1e9 · quoteUsd / targetMcapUsd P = tokensPerQuote · 10^(18 − quoteDecimals) tick = floor(ln(P) / ln(1.0001)), rounded down to a multiple of tickSpacing ``` The owner retunes this with `updateLaunchConfig` as the quote's price moves. Launched pools keep the price they opened at. ## Reading the price `StateView.getSlot0(poolId)` returns `sqrtPriceX96`, which is V4's `currency1` per `currency0`, in raw units. On a HYPE pool that is tokens per HYPE, so invert it for a price in HYPE: ```ts const [sqrtPriceX96] = await client.readContract({ address: STATE_VIEW, abi: stateViewAbi, functionName: "getSlot0", args: [poolId], }); const tokensPerHype = (Number(sqrtPriceX96) / 2 ** 96) ** 2; const priceInHype = 1 / tokensPerHype; ``` That float is fine for display. Never use it to size a trade. The API's `price`, `marketCap`, `priceUsd` and `marketCapUsd` come already computed. ## What "floor" guarantees The floor is where **Tartarus's own liquidity** ends. It is not a promise about what a token is worth. Near the floor there may be very little quote in the pool to sell into. Third parties can also add their own V4 liquidity to a pool, because the hook does not block it, though that liquidity earns no LP fee and does not change the locked position. --- # Approvals Which contract pulls your funds depends on what you are calling and whether the asset is native. Native HYPE never needs an approval: it travels as `msg.value`. ## The matrix | Action | Asset | Approve | Send as `value` | |---|---|---|---| | `factory.launchToken` | launch fee | nothing | `launchFee()` | | `launchAndBuy` on a HYPE config | fee + buy | nothing | `launchFee() + quoteIn` | | `launchAndBuy` on an ERC-20 config | buy | the quote token, to **`TartarusLaunchAndBuy`**, for `quoteIn` | `launchFee()` | | Buy on a HYPE pool | HYPE | nothing | the amount in | | Buy on an ERC-20 pool | quote | **Permit2, then the router** | nothing | | Sell | the token | **Permit2, then the router** | nothing | | `feeEscrow.claim` / `claimToken` | none | nothing | nothing | | `sweepPoolFees` / `sweepPoolQuoteFees` | none | nothing | nothing | | `distributor.claim` / `batchClaim` | none | nothing | nothing | The launch fee is always native HYPE, even on an ERC-20 config, and `msg.value` must match exactly. Sending more does not refund: it reverts. ## Swaps need two grants UniversalRouter does not pull your tokens directly. It pulls them through Permit2, so there are two separate allowances and each has its own failure. ```ts // 1. The token lets Permit2 move it. A plain ERC-20 approval. await wallet.writeContract({ address: token, abi: erc20Abi, functionName: "approve", args: [PERMIT2, amount], }); // 2. Permit2 lets the router move it. This one expires. await wallet.writeContract({ address: PERMIT2, abi: permit2Abi, functionName: "approve", args: [token, ROUTER, amount, expiry], // amount is uint160, expiry is uint48 }); ``` | Missing | Revert | |---|---| | Step 1 | a transfer error, from the token | | Step 2 was never made, or has expired | `AllowanceExpired` (`0xd81b2f2e`), which reads as if a grant lapsed even when none was ever made | | Step 2 is smaller than the swap, or an earlier swap used it up | `InsufficientAllowance` (`0xf96fb071`) | Check both before asking the user to sign: ```ts const onToken = await client.readContract({ address: token, abi: erc20Abi, functionName: "allowance", args: [account, PERMIT2], }); const [onPermit2, expiration] = await client.readContract({ address: PERMIT2, abi: permit2Abi, functionName: "allowance", args: [account, token, ROUTER], }); ``` The Tartarus site grants the exact amount of each swap with a short expiry, so no allowance stays open afterwards. Granting more saves transactions and leaves a standing approval. That is your call to make. ## Why not approve the factory Older launchpads pull fees with an allowance. Tartarus does not: the factory never moves your ERC-20s. The only contract in the protocol you ever approve directly is `TartarusLaunchAndBuy`, and only for an ERC-20 opening buy. It takes exactly what the pool charges and holds nothing between transactions. --- # Decimals The arithmetic here is simple while everything is native HYPE, and that is exactly when code gets written that breaks on the first ERC-20 quote. | Asset | Decimals | Note | |---|---|---| | Every Tartarus token | 18 | Always. Supply is `1e27` raw | | HYPE | 18 | Native, `address(0)` in a pool key | | An approved ERC-20 quote | its own | Read it from `GET /v1/protocol`, never assume 18 | Today HYPE is the only approved quote. `approvedQuoteTokens` in `GET /v1/protocol` is the live list, and each `launchConfigs[].quoteToken` carries `decimals`. ## Whole units and raw units Contracts deal only in raw units. The API returns both, and the names tell you which: | API field | Unit | |---|---| | `amount`, `out`, `fee`, `quote`, `price` | whole units, as a JSON number. For display only | | `amountRaw`, `outRaw`, `minOutRaw`, `feeRaw` | raw units, as a string. Use these on chain | The quote endpoint **takes** whole units (`amount=1` means one HYPE) and returns raw units for the transaction. ## Never round-trip through a float ```ts // Wrong: 1e27 does not fit in a double const raw = BigInt(Math.floor(quote.out * 1e18)); // Right const raw = BigInt(quote.outRaw); const shown = formatUnits(raw, 18); ``` A JavaScript number has 53 bits of precision. A token amount in raw units regularly needs 90. Keep `bigint` from the moment a value leaves the API or the chain, and format only at the edge. ## Ticks and orientation `startingTick` is defined as tokens per quote in **raw units**, so it already includes the difference in decimals. A 6-decimal quote needs a much higher tick than HYPE for the same market cap. The same goes for `sqrtPriceX96`: convert with both assets' decimals in mind, and check which one is `currency0`. [Price and market cap](/documentation/pricing) has the formulas. --- # Fees Each pool charges one swap fee, made of two tranches frozen when the pool is created. | Tranche | Rate | Goes to | |---|---|---| | Protocol | **0.5%**, the same on every launch | the protocol fee recipient | | Creator | **0.5% to 9.5%**, chosen at launch | the launch's `creatorFeeRecipient` | | **Total** | **1% to 10%** | | The pool's Uniswap LP fee is zero, so this is the only swap fee. The total can never exceed 10% (`MAX_TOTAL_FEE_BPS`): the hook enforces that in its setters and again when registering a pool. There is also a launch fee of **0.001 HYPE**, credited to the protocol fee recipient. There are no referrals, no separate creator tax, and no fee on transfers: a Tartarus token is a plain ERC-20. The owner can change the rates for **future** launches only. A launched pool keeps the rates, protocol fee recipient and conversion limit it was registered with, for life. Read them with `factory.getLaunchFeePolicy(token)`. ## From a swap to your wallet ``` swap -> hook takes the fee -> sweep -> fee escrow -> claim (held as claims on (converted (per recipient, the PoolManager) and split) pull only) ``` ### 1 · Collection On every swap the hook takes its fee from the unspecified leg. That leg depends on the swap, so a pool collects in **two buckets**: exact-in buys leave fees in the memecoin, and sells leave them in the quote. Both sit in `hook.pendingFees(poolId, currency)` until a sweep. The hook moves no tokens during a swap: it holds its fees as ERC-6909 claims on the PoolManager. ### 2 · Sweep | Function | Who may call | What it does | |---|---|---| | `sweepPoolFees(poolId, minConversionQuoteOut)` | the sweep operator; the creator recipient only if no memecoin is pending | sells the memecoin bucket into the pool, then pays out all the quote | | `sweepPoolQuoteFees(poolId)` | the sweep operator or the creator recipient | pays out the quote bucket and leaves the memecoin pending | Converting is restricted to the operator because it is a swap, and a predictable swap against a spot price can be sandwiched. The operator supplies a real minimum. Paying out quote involves no swap and so is open to the creator as well. Each conversion is capped at **3% price impact** (`maxInternalPriceImpactBps`), so a large memecoin bucket converts over several sweeps. What remains stays pending, and `PoolConversionSkipped` reports it. The Tartarus keeper sweeps each pool about every 6 hours once at least $10 is waiting. ### 3 · Split Every sweep divides the quote between the tranches in the proportion they were charged: ``` creatorAmount = total × creatorFeeBps / (protocolFeeBps + creatorFeeBps) (rounded to the creator) protocolAmount = total − creatorAmount ``` On a 2% pool (0.5% + 1.5%) the creator receives 75% of every sweep. Both shares are credited to the escrow in the pool's quote asset. ### 4 · Claim `TartarusFeeEscrow` is pull-only, so a recipient that cannot receive funds can never block a sweep. Native HYPE and ERC-20s have separate functions: ```ts // HYPE const owed = await client.readContract({ address: ESCROW, abi: escrowAbi, functionName: "balanceOf", args: [recipient], }); await wallet.writeContract({ address: ESCROW, abi: escrowAbi, functionName: "claim", args: [] }); // An ERC-20 quote const owedToken = await client.readContract({ address: ESCROW, abi: escrowAbi, functionName: "balanceOfToken", args: [recipient, quoteToken], }); await wallet.writeContract({ address: ESCROW, abi: escrowAbi, functionName: "claimToken", args: [quoteToken] }); ``` `claim` and `claimToken` are overloaded: each also takes an amount, for a partial claim. In viem, pass `args` to choose the overload. A claim pays `msg.sender`, so the recipient must be able to send a transaction. A contract recipient also needs to be able to receive HYPE. `GET /v1/users/:address` returns `claimable`, with what is in the escrow and what is still in the hook. ## Changing the recipient The current recipient can hand the stream on at any time, and it takes effect immediately: ```ts await wallet.writeContract({ address: FACTORY, abi: factoryAbi, functionName: "transferCreatorFeeRecipient", args: [token, newRecipient], }); ``` The protocol owner can also override a recipient, with three days' public notice, mainly so that a creator who lost their key can be recovered. See [Trust model](/documentation/trust). ## Worked example A 1 HYPE exact-in sell on a 2% pool: | | HYPE | |---|---| | Pool pays out, gross | 1.0000 | | Hook fee at 2% | 0.0200 | | Seller receives | 0.9800 | | Later credited to the creator (1.5 / 2) | 0.0150 | | Later credited to the protocol (0.5 / 2) | 0.0050 | --- # Holder rewards A launch can send its creator tranche to the token's holders instead of the creator. On the create page this is **Fees go to: Holders**. The trading fee is unchanged, and the protocol keeps its 0.5%. > [!WARNING] > This is a one-way door. Only the current recipient can move a fee stream, and a distributor has no > function that could call `transferCreatorFeeRecipient`. Once a launch pays its holders, it pays them > for the life of the token, barring the owner's [timelocked override](/documentation/trust). ## Launching with holder rewards Set `creatorFeeRecipient` to the factory's `HOLDER_REWARDS()` constant: ```ts const HOLDER_REWARDS = await client.readContract({ address: FACTORY, abi: factoryAbi, functionName: "HOLDER_REWARDS", }); const params = { ...tokenParams, creatorFeeRecipient: HOLDER_REWARDS, salt: freshSalt }; ``` The factory resolves that to the token's distributor address itself, so the address cannot be mispredicted. Use a **fresh salt** for each launch. The distributor is a CREATE2 contract keyed by creator, salt and quote asset, and the launch names its address before it has code. Fees can accrue to it from the first trade. If you want the address ahead of time, call `rewardsFactory.predict(creator, salt, quoteToken)`. `POST /v1/launch/predict` with `feesTo: "holders"` returns it as `distributor`, and gives you the value to send as `paramsCreatorFeeRecipient`. ## How a distribution works 1. **Deploy.** `rewardsFactory.deploy(token, salt)` puts the contract in place. It is permissionless, and every argument is checked against the launch record, so it does not matter who sends it. The Tartarus jobs service does this soon after launch. `rewardsFactory.distributorOf(token)` is zero until then. The salt is in the `LaunchSalt` event. 2. **Sweep.** Fees reach the escrow under the distributor's address, like any recipient's. 3. **Harvest.** `distributor.harvest()` pulls that balance into its `unallocated` pot. Permissionless. The keeper runs it once about $1 is waiting. 4. **Snapshot.** About weekly, holder balances are read at one block. Each holder's share is `balance / eligible supply`, rounded down. 5. **Publish.** The shares become a Merkle tree, and the distributor owner calls `publishEpoch(root, total)`. An epoch can never commit more than `unallocated`. Epochs under about $1 are skipped and roll into the next one. 6. **Claim.** Holders have **90 days** to claim each epoch. 7. **Roll over.** After 90 days, anyone can call `rollOver(epochId)`. The unclaimed remainder returns to `unallocated` and funds a later epoch. ### Who is excluded Addresses that hold supply but could never claim get no share, and they do not count toward eligible supply: the zero address, the PoolManager, the locker, the hook, the escrow, the factory, the launch-and-buy router and the distributor itself. Paying the pool's locked position would destroy the money, so most of the balance held at launch is excluded. This is enforced twice: off chain when the tree is built, and on chain by `isExcluded` inside `claim`. A faulty root still cannot pay an excluded address. ## Checking an epoch Everything needed to rebuild an epoch is public: `Transfer` events give the balances, `isExcluded` gives the exclusions, and `leafFor(index, account, amount)` gives the exact leaf encoding: ```solidity keccak256(abi.encode(index, account, amount)) ``` Anyone can recompute the tree and compare it with `getEpoch(epochId).merkleRoot`. The distributor owner can publish any root, so this check is what keeps that role honest. See [Trust model](/documentation/trust). ## Claiming The portfolio page claims every open epoch in one transaction. In your own client, the API supplies the proofs: ```ts const { claimable } = await fetch(`${API}/users/${account}`).then((r) => r.json()); for (const t of claimable.holders.byToken) { const open = t.epochs.map((e) => e.calldata); await wallet.writeContract({ address: t.distributor, abi: distributorAbi, functionName: "batchClaim", args: [ open.map((c) => BigInt(c.epochId)), account, open.map((c) => BigInt(c.amountRaw)), open.map((c) => BigInt(c.index)), open.map((c) => c.proof), ], }); } ``` `batchClaim` is all-or-nothing, so one bad proof reverts the batch. `claim(epochId, account, amount, index, proof)` settles a single epoch. Anyone may submit a claim, but the proof fixes the recipient. A claim always pays `account`, never `msg.sender`. Each `(epochId, account)` pair claims once. Rewards arrive in the launch's quote asset, straight from the distributor, not through the escrow. | Error | Meaning | |---|---| | `InvalidProof` | The proof does not match this `(epoch, index, account, amount)` | | `AlreadyClaimed` | Already settled | | `ClaimWindowClosed` | More than 90 days old | | `ExcludedAccount` | On the exclusion list | | `NoEpoch` | That epoch does not exist | ## What the numbers mean - **Claimable** is the sum of your leaves in epochs still inside their 90-day window. It is exact. - **Pending** is an estimate: your share of what has accrued since the last epoch, from your current balance. It moves with every trade and transfer until the next snapshot. Selling before a snapshot forfeits that epoch's share, and buying just before one earns a full share of it. Only the balance at the snapshot block counts. --- # HyperCore Tartarus tokens are built so they can move from Elysium to a HyperCore spot listing without changing the token. This page separates what is already true of every launch from what is still to come. > [!NOTE] > **No Tartarus token can be listed on HyperCore yet.** Kinetiq has not published the mirror bridge > factory on Elysium mainnet, so launches create no bridge wallet today, and Elysium does not yet > expose HyperCore precompiles. ## What every launch already satisfies These are fixed by the contracts, for every token, from its first block: | Requirement | How Tartarus meets it | |---|---| | Immutable name, symbol and decimals | Stored in the bytecode, with no setter. The bridge keys a mirror on these exact values | | Valid ticker | `[A-Z0-9]`, 1 to 6 characters, the HyperCore ticker verbatim | | Unique ticker | One launch per symbol across Tartarus; major assets reserved | | Fixed supply | Exactly `1e27` raw, with no mint or burn | | Plain transfers | No tax, rebase, pause or blocklist on the token | | Representable on HyperCore | At 1,000,000,000 tokens, `weiDecimals` of 10 or fewer fits in `uint64` Core wei | | Bridge-ready | An escrow wallet created in the launch transaction, **once a bridge factory is set** | ## The path to a listing 1. **Bridge wallet.** The escrow wallet for the token on Elysium. New launches get one automatically once `elysiumBridgeFactory` is set. `createL2Wallet` is permissionless, so earlier launches can create theirs afterwards. 2. **Mirror.** A 1:1 mirror of the token is registered on HyperEVM, keyed on its exact name, symbol and decimals. 3. **HIP-1 deploy.** The issuer wins the HyperCore ticker auction and runs genesis. 4. **Link.** The HyperCore spot token is linked to the mirror. Steps 3 and 4 happen on HyperCore, outside Tartarus. A ticker auction costs real money, and winning one is not guaranteed. ## Reading a token's status `hypercoreStatus` on `GET /v1/tokens/:id`: | Value | Meaning | |---|---| | `none` | No bridge wallet yet. Every mainnet token today | | `bridgeWallet` | The escrow wallet exists | | `mirrorRegistered` | The HyperEVM mirror is registered | | `ascended` | Linked to a HyperCore spot token | `bridgeWallet` on the same record is the wallet's address, or `null`. On chain, `factory.bridgeWalletFor(token)` and `factory.expectedMirrorFor(token)` return the same facts once a bridge factory is set. **Both revert with `BridgeFactoryNotSet` until then**, so wrap them, or check `factory.elysiumBridgeFactory()` first. A launch records the outcome with `BridgeWalletCreated(token, wallet)` or `BridgeWalletSkipped(token)`. A bridge failure can never block a launch. With no factory set, neither event fires. --- # Trust model This page says what is fixed in code, what the protocol owner can change, and what you are trusting the operators to do. The contracts are not upgradeable: there are no proxies, and none of them can be replaced under a live launch. ## What nobody can do - **Withdraw the liquidity.** The locker has no withdrawal function and no arbitrary call. The position and any dust are locked for good, for the owner too. - **Mint or burn tokens**, or pause, tax or block transfers. The token is a plain fixed-supply ERC-20. - **Change a launched pool's fee rates.** They are frozen at registration, with a hard 10% ceiling. - **Change a token's name, symbol or metadata.** - **Take from the escrow.** Only a recipient can claim its own balance. - **Renounce ownership.** It is disabled on purpose, because fee sweeps and recovery depend on an owner existing. Ownership transfers in two steps. ## What the owner can do | Power | Effect | Safeguard | |---|---|---| | Open or close launching, manage the whitelist | Controls new launches only | Existing tokens keep trading | | Add or update launch configs, set the launch fee | Controls terms of new launches | `expectedEconomics` lets a creator refuse changed terms | | Change protocol rate, creator bounds, conversion limit, protocol recipient | **Future** launches only (the launch fee follows the current recipient) | Live pools keep their frozen terms | | Approve or revoke quote assets | New launches only | Existing pools keep trading | | Reserve tickers | Blocks new launches of a symbol | A launch that holds it is unaffected | | Change the fee sweep operator | Decides who may convert memecoin fees | Rotating it cannot redirect fees | | Override a creator fee recipient | Redirects a launch's **creator tranche**, for any launch | **3-day** public notice, then a 3-day execution window. Emits `CreatorFeeRecipientChangeProposed` | | Change the launch forwarder | A forwarder can attribute launches to any account | **48-hour** public notice | | Disable the launch forwarder | Turns off `launchAndBuy` | Immediate, because it only removes trust. Plain launches continue | | Rescue a pool's fees | Credits pending fees to the frozen recipients without converting | Goes to the normal recipients through the escrow, never to the owner | | Set the bridge factory | Where future launches create their bridge wallet | Failure can never block a launch | The recipient override exists so a creator who lost their key can be recovered, but it is not limited to that case. A creator cannot veto it by moving the recipient: a matured proposal still executes. A pending override appears in `GET /v1/tokens/:id` as `creatorFeeRecipientPending`, and a pending forwarder change appears in `GET /v1/protocol` as `launchAndBuy.pending`. ## What the operators do | Role | Can | Cannot | |---|---|---| | Fee sweep operator (keeper) | Decide when memecoin fees convert, and at what minimum | Send fees anywhere but the frozen recipients. Exceed the conversion's price-impact limit | | Distributor owner | Publish epoch roots, manage exclusions | Reach the distributor's balance directly, or commit more than `unallocated` | > [!WARNING] > **Holder rewards trust the distributor owner.** A published Merkle root decides who is paid, and the > contract cannot judge whether it is fair. The check is that anyone can rebuild the tree from public > data and compare the root. See [Checking an epoch](/documentation/holder-rewards#checking-an-epoch). ## What Tartarus depends on - **Uniswap V4.** Pools live on Uniswap's canonical deployment on Elysium. - **Elysium.** Its sequencer, and its bridge to HyperEVM. - **Tartarus services**, for fee sweeps, holder-reward publishing, the API and the RPC. If they stop, tokens keep trading and escrow balances stay claimable, but fees would stop reaching the escrow until someone sweeps them. Creators can always pay out quote fees themselves. ## Verifying this yourself Read the current owner, wiring and policy directly from the chain: ```ts await client.readContract({ address: FACTORY, abi: factoryAbi, functionName: "owner" }); await client.readContract({ address: HOOK, abi: hookAbi, functionName: "feeSweepOperator" }); await client.readContract({ address: FACTORY, abi: factoryAbi, functionName: "getLaunchFeePolicy", args: [token] }); await client.readContract({ address: FACTORY, abi: factoryAbi, functionName: "pendingCreatorFeeRecipient", args: [token] }); ``` --- # API The Tartarus API serves everything the site shows: indexed tokens, trades, candles, quotes and balances. Base URL: ``` https://api.tartarus.world/v1 ``` Responses are JSON. Amounts in **whole units** are JSON numbers, for display. Fields ending in `Raw` are strings in raw units, for transactions. See [Decimals](/documentation/decimals). ## Protocol | Endpoint | Returns | |---|---| | `GET /protocol` | Addresses, launch configs with floor prices, approved quotes, fee policy, launch-and-buy and holder-rewards wiring, `wsOrigin` | | `GET /bridge` | The HyperEVM Inbox and Outbox, whether deposits are paused, the estimated withdrawal wait | | `GET /bridge/withdrawals/:address` | HYPE withdrawn to an address, newest first, at most 50: `status` is `pending`, `ready` or `claimed`, with the `Outbox.executeTransaction` arguments in `claim` when ready | | `GET /status` | Indexer and keeper health | | `GET /health` | Liveness | | `GET /platform` | The platform token, buybacks, burns and protocol analytics | Read addresses and the fee policy from `GET /protocol` rather than hard-coding them. ## Tokens | Endpoint | Returns | |---|---| | `GET /tokens` | A page of tokens. Filter with `tab`, `category`, `q`, `mcMin`, `mcMax`, `volMin`, `volMax`; page with `page`, `perPage` | | `GET /tokens/bounds` | Filter ranges for the board | | `GET /tokens/search` | Search by name, symbol or address | | `GET /tokens/:id` | One token, including `fees`, `hypercoreStatus`, `bridgeWallet`, `creatorFeeRecipientPending` | | `GET /tokens/:id/quote` | A trade quote. `side`, `amount` in whole units, `slippageBps` (default 100, at most 5000) | | `GET /tokens/:id/trades` | Recent trades | | `GET /tokens/:id/candles` | OHLC candles | | `GET /tokens/:id/holders` | Holders | `:id` is the token address. Quotes are limited to 20 per second per IP. Details are in [Trading](/documentation/trading#quoting). ## Users | Endpoint | Returns | |---|---| | `GET /users/:address` | Positions and `claimable`: escrow balances, unswept fees, holder-reward proofs | | `GET /me`, `PUT /me` | The signed-in profile. Session required | ## Launching | Endpoint | Returns | |---|---| | `GET /launch/symbol?symbol=` | `available`, and `reason`: `ok`, `invalid`, `reserved` or `taken` | | `POST /launch/predict` | The token address, pool key, floor price, `expectedEconomics` and fee recipient for a launch. Session required | | `POST /launch-drafts` | Stores a draft launch. Session required | | `PUT /tokens/:id/meta` | Sets off-chain metadata such as the category. Session required | | `POST /uploads/sign`, `POST /uploads/finalize` | Upload a token image. Session required | `POST /launch/predict` answers `409` with a `reason` when a launch cannot go ahead: `taken`, `reserved`, `rewardsNotWired` or `saltUsed`. Pick a new salt on `saltUsed`. ## Sessions Sessions use Sign-In with Ethereum. `POST /auth/nonce` issues a nonce, and `POST /auth/verify` exchanges the signed message for a session token. Send that token as `authorization: Bearer `. ## JSON-RPC `POST /rpc` is the Elysium RPC proxy. See [Elysium network](/documentation/network#which-rpc-to-use). ## WebSocket Live updates come from `wss://ws.tartarus.world`. Use `wsOrigin` from `GET /protocol` rather than hard-coding it. Server messages share one envelope: ```json { "v": 1, "type": "trade", "room": "token:0x…", "data": {}, "ts": 1793921370041 } ``` `ts` is in milliseconds. HTTP responses report `now` in seconds. Subscribe to up to 32 rooms in one message: ```ts const ws = new WebSocket(protocol.wsOrigin); ws.onopen = () => ws.send(JSON.stringify({ type: "subscribe", rooms: ["board", `token:${address}`] })); ws.onmessage = (e) => handle(JSON.parse(e.data)); ``` | Room | Carries | |---|---| | `board` | New launches and token updates, for lists | | `token:
` | Trades, candles and updates for one token. Lowercase the address | | `head` | The latest indexed block | A join starts with a `snapshot` message, followed by deltas. Other message types are: | Type | What to do | |---|---| | `resync` | The feed restarted. Discard state and refetch over HTTP | | `closing` | The server is draining. Reconnect after `reconnectAfterMs` | | `error` | A bad room or request. `data` has `code` and `message` | | `pong` | The answer to `{ "type": "ping" }` | Frames carry the same shapes as the HTTP endpoints, so one parser handles both. --- # Contracts Every deployed contract: functions, events and errors. ## TartarusFeeEscrow `0x44D1065fD59280cFa26B7BE3dBf13F364314616F` ### Functions an application calls ### `balanceOf(address)` `0x70a08231` · `view` · view Returns the claimable native HYPE balance for `recipient`. Claimable native HYPE. **Parameters** | Name | Type | Description | |---|---|---| | `recipient` | `address` | | **Returns** | Name | Type | Description | |---|---|---| | `arg0` | `uint256` | | ### `balanceOfToken(address,address)` `0xf59e38b7` · `view` · view Returns the claimable balance of `token` for `recipient`. `(recipient, token)`. Read this rather than an indexer if you want on-chain truth for a claim button. **Parameters** | Name | Type | Description | |---|---|---| | `recipient` | `address` | | | `token` | `address` | | **Returns** | Name | Type | Description | |---|---|---| | `arg0` | `uint256` | | ### `claim(uint256)` `0x379607f5` · `nonpayable` · Pull-only, `msg.sender`. Pays out `amount` of the caller's claimable native HYPE balance. Partial native claim. **Parameters** | Name | Type | Description | |---|---|---| | `amount` | `uint256` | | **Returns** | Name | Type | Description | |---|---|---| | `arg0` | `uint256` | | ### `claim()` `0x4e71d92d` · `nonpayable` · Pull-only, `msg.sender`. Pays out the caller's entire claimable native HYPE balance. Native HYPE balance — the creator fees of every HYPE-quoted launch. **Returns** | Name | Type | Description | |---|---|---| | `amount` | `uint256` | | **Reverts** - `NoBalance` - `TransferFailed` — the caller rejected HYPE ### `claimToken(address,uint256)` `0x1698755f` · `nonpayable` · Pull-only, `msg.sender`. Pays out `amount` of the caller's claimable balance of `token`. A recipient's balance aggregates credits from every launch and `creditToken` is permissionless, so a third party can enlarge that figure at will. Against a token with a fixed maximum per transfer, a single full-balance claim would then revert and leave the recipient unable to draw any of it. Choosing the amount keeps the aggregate from being a single point of failure, and lets a recipient work around any per-transfer limit its quote asset imposes. Partial claim, for a quote asset with a per-transfer limit. **Parameters** | Name | Type | Description | |---|---|---| | `token` | `address` | | | `amount` | `uint256` | | **Returns** | Name | Type | Description | |---|---|---| | `arg0` | `uint256` | | **Reverts** - `InsufficientBalance` - `NoBalance` ### `claimToken(address)` `0x32f289cf` · `nonpayable` · Pull-only — claims the balance of `msg.sender`. You cannot claim for someone else. Pays out the caller's entire claimable balance of `token`. The escrow holds ONE balance per (recipient, asset), aggregated across every launch. Use this for an ERC-20 quote asset; native HYPE fees are claimed with `claim()`. **Parameters** | Name | Type | Description | |---|---|---| | `token` | `address` | | **Returns** | Name | Type | Description | |---|---|---| | `amount` | `uint256` | | **Reverts** - `NoBalance` — nothing accrued for you in that token ### Other functions | Signature | Selector | Mutability | |---|---|---| | `credit(address)` | `0xd5d44d80` | payable | | `creditToken(address,address,uint256)` | `0x09ad4dd9` | nonpayable | ### Events | Event | topic0 | |---|---| | `Claimed(address,uint256)` | `0xd8138f8a3f377c5259ca548e70e4c2de94f129f5a11036a15b69513cba2b426a` | | `ClaimedToken(address,address,uint256)` | `0xdbc1ea3a8459e4c7e11fb385b52bbb5cc8c8ab85eec5d883ac9aa78c171f5141` | | `Credited(address,address,uint256)` | `0x4e45da441832cf53bdaa69235704fc0575e68210f459ee1562911024b12967d5` | | `CreditedToken(address,address,address,uint256)` | `0x5d104c62f50449fadfe6f4013c8f36588d32737f94b5ac9b83ddad33b3e1ffdf` | ### Errors | Error | Selector | |---|---| | `InsufficientBalance(uint256,uint256)` | `0xcf479181` | | `NoBalance()` | `0xc2caa2a6` | | `ReentrancyGuardReentrantCall()` | `0x3ee5aeb5` | | `SafeERC20FailedOperation(address)` | `0x5274afe7` | | `TransferFailed()` | `0x90b8ec18` | | `ZeroAddress()` | `0xd92e233d` | --- ## TartarusMemeHook `0x00b2Ff65E61aD8c57F957624EDc6C81908Ade044` ### Functions an application calls ### `currentFeePolicy(uint16)` `0xf28f0e1b` · `view` · view Returns the terms a launch choosing `creatorFeeBps` would snapshot immutably right now. Reverts when the choice is outside the live bounds, so the factory cannot register a tranche the owner has not allowed. The terms a launch choosing `creatorFeeBps` would freeze now. A launch keeps its own copy, so later policy changes do not touch it. **Parameters** | Name | Type | Description | |---|---|---| | `creatorFeeBps` | `uint16` | | **Returns** | Name | Type | Description | |---|---|---| | `arg0` | `(address,uint16,uint16,uint16)` | | ### `pendingFees(bytes32,address)` `0x359b4f30` · `view` · view Fees collected per pool and currency, held as this hook's ERC-6909 claims on the pool manager until a sweep or rescue redeems them. `(poolId, currency)` — fees collected and not yet swept, held as ERC-6909 claims on the pool manager. **Parameters** | Name | Type | Description | |---|---|---| | `arg0` | `bytes32` | | | `currency` | `address` | | **Returns** | Name | Type | Description | |---|---|---| | `amount` | `uint256` | | ### `sweepPoolFees(bytes32,uint256)` `0xebe51768` · `nonpayable` · The fee-sweep operator, or the pool's creator when no internal conversion is needed. Converts any pending memecoin-denominated fee into the quote against the pool's own liquidity, then splits the combined quote total between protocol and creator in proportion to their frozen tranches. The trusted sweep operator is required whenever the sweep would execute an internal conversion. The creator may still distribute already-quoted fees when no internal swap is needed. Converts any pending memecoin-denominated fee into the quote against the pool's own liquidity, then credits protocol and creator shares into the fee escrow in proportion to their frozen tranches. A conversion needs the operator and a non-zero `minConversionQuoteOut`. **Parameters** | Name | Type | Description | |---|---|---| | `poolId` | `bytes32` | | | `minConversionQuoteOut` | `uint256` | | **Reverts** - `InternalSwapRequiresOperator` - `MinimumOutputRequired` - `SlippageExceeded` - `UnknownPool` ### `sweepPoolQuoteFees(bytes32)` `0x9f8e76a7` · `nonpayable` · The fee-sweep operator or the pool's creator. Pays out a pool's quote-denominated fees and leaves its memecoin bucket pending, without the internal conversion. For a memecoin bucket too small to convert: with no LP fee, a few million wei of memecoin fills for zero quote, so `sweepPoolFees` would revert on any minimum and refuse a zero one, and anyone could keep a pool's quote fees stuck that way with a dust-sized buy. No swap runs here, so there is nothing to manipulate and the creator may call it as well as the operator. The memecoin stays pending for the next `sweepPoolFees`, once it is worth converting. Pays out the quote-denominated fees only and leaves the memecoin bucket pending. No swap runs, so the creator can always use it to release their quote fees. **Parameters** | Name | Type | Description | |---|---|---| | `poolId` | `bytes32` | | **Reverts** - `UnknownPool` ### Other functions | Signature | Selector | Mutability | |---|---|---| | `acceptOwnership()` | `0x79ba5097` | nonpayable | | `afterAddLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),int256,int256,bytes)` | `0x9f063efc` | nonpayable | | `afterDonate(address,(address,address,uint24,int24,address),uint256,uint256,bytes)` | `0xe1b4af69` | nonpayable | | `afterInitialize(address,(address,address,uint24,int24,address),uint160,int24)` | `0x6fe7e6eb` | nonpayable | | `afterRemoveLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),int256,int256,bytes)` | `0x6c2bbe7e` | nonpayable | | `afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)` | `0xb47b2fb1` | nonpayable | | `beforeAddLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),bytes)` | `0x259982e5` | nonpayable | | `beforeDonate(address,(address,address,uint24,int24,address),uint256,uint256,bytes)` | `0xb6a8b0fa` | nonpayable | | `beforeInitialize(address,(address,address,uint24,int24,address),uint160)` | `0xdc98354e` | nonpayable | | `beforeRemoveLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),bytes)` | `0x21d0ee70` | nonpayable | | `beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)` | `0x575e24b4` | nonpayable | | `factory()` | `0xc45a0155` | view | | `feeEscrow()` | `0xc4b7de97` | view | | `feeSweepOperator()` | `0x8a36a6bb` | view | | `getHookPermissions()` | `0xc4e833ce` | pure | | `launches(bytes32)` | `0xad091230` | view | | `MAX_TOTAL_FEE_BPS()` | `0x431a9caa` | view | | `maxCreatorFeeBps()` | `0x0a3c0856` | view | | `maxInternalPriceImpactBps()` | `0x90addc1e` | view | | `minCreatorFeeBps()` | `0x88f4684d` | view | | `owner()` | `0x8da5cb5b` | view | | `pendingOwner()` | `0xe30c3978` | view | | `poolManager()` | `0xdc4c90d3` | view | | `protocolFeeBps()` | `0x35659fb8` | view | | `protocolFeeRecipient()` | `0x64df049e` | view | | `registerPool((address,address,uint24,int24,address),address,address,(address,uint16,uint16,uint16))` | `0x7337a8b7` | nonpayable | | `renounceOwnership()` | `0x715018a6` | pure | | `rescuePoolFees(bytes32)` | `0x5cbe8117` | nonpayable | | `setCreatorFeeBounds(uint256,uint256)` | `0x9f4042a2` | nonpayable | | `setCreatorFeeRecipient(bytes32,address)` | `0xed8ef7a3` | nonpayable | | `setFactory(address)` | `0x5bb47808` | nonpayable | | `setFeeSweepOperator(address)` | `0x54faf9c3` | nonpayable | | `setMaxInternalPriceImpactBps(uint256)` | `0xb89eddab` | nonpayable | | `setProtocolFeeBps(uint256)` | `0xc0417e58` | nonpayable | | `setProtocolFeeRecipient(address)` | `0xe521cb92` | nonpayable | | `transferOwnership(address)` | `0xf2fde38b` | nonpayable | | `unlockCallback(bytes)` | `0x91dd7346` | nonpayable | ### Events | Event | topic0 | |---|---| | `CreatorFeeBoundsUpdated(uint256,uint256)` | `0x7da93c15168a31f5bef5eb6ae509ab6c0af7799e7a17a7f1ace13cc30012b96e` | | `CreatorFeeRecipientUpdated(bytes32,address,address)` | `0xb45e6b72a7de9a2077babe9717744436f3880e114099956ca85f91a77469a532` | | `FactorySet(address)` | `0x1edf3afd4ac789736e00d216cd88be164ddcef26a6eedcc30cdb0cb62f3741b1` | | `FeeSweepOperatorUpdated(address)` | `0xae994ca926e252e299c3df7516cb609272a57bf80b0e0715297e55939f873420` | | `HookFeeCollected(bytes32,address,uint256)` | `0xcf54f8cbb5f9c3a7061090beb81e0337579190a0137ed295e96cfecd9bab9542` | | `MaxInternalPriceImpactUpdated(uint256)` | `0x6968b68c1fb468c8b257b012290bf803a6a6d7e79468e0326050724f7573cf01` | | `OwnershipTransferred(address,address)` | `0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0` | | `OwnershipTransferStarted(address,address)` | `0x38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e22700` | | `PoolConversionSkipped(bytes32,uint256)` | `0xeed2d18eb96f3c2cb8c7b6993512a506c170e17d29355f2d7a0d5961f338de09` | | `PoolFeesRescued(bytes32,address,uint256,uint256)` | `0x0fbb28f9c335f55dcc5cc19e595ab55f9e6a0fd1b58ad77be3a98f99901daaff` | | `PoolFeesSwept(bytes32,uint256,uint256)` | `0x2b33b68d948eb789fd57906bde3dc24d9f748c1df9b98d7359910f6aa1c06e2f` | | `PoolRegistered(bytes32,address,address,address)` | `0x01bf263a1db1652580721573296e1a1fa70b3d4c87f61d02a69c4e1109d2d573` | | `ProtocolFeeBpsUpdated(uint256)` | `0x0c69d666e7ba3ce81300fa14cb3e9c03596138bfc8ecec0eacb0a8ef31442cdc` | | `ProtocolFeeRecipientUpdated(address)` | `0xc1b5345cce283376356748dc57f2dfa7120431d016fc7ca9ba641bc65f91411d` | ### Errors | Error | Selector | |---|---| | `AlreadyRegistered()` | `0x3a81d6fc` | | `AlreadySet()` | `0xa741a045` | | `CreatorFeeOutOfBounds(uint256,uint256,uint256)` | `0x87ab5572` | | `HookNotImplemented()` | `0x0a85dc29` | | `InexactQuoteTransfer(address,uint256,uint256)` | `0x197001d6` | | `InternalSwapRequiresOperator()` | `0x31cdb504` | | `InvalidBps()` | `0xc6cc5d7f` | | `InvalidPoolKey()` | `0xc256622b` | | `MinimumOutputRequired()` | `0x3672d25f` | | `NotFactory()` | `0x32cc7236` | | `NotFeeSweepOperator()` | `0x8d42130c` | | `NothingToRescue()` | `0x00f6b210` | | `NotPoolManager()` | `0xae18210a` | | `OwnableInvalidOwner(address)` | `0x1e4fbdf7` | | `OwnableUnauthorizedAccount(address)` | `0x118cdaa7` | | `OwnershipCannotBeRenounced()` | `0x2fab92ca` | | `ReentrancyGuardReentrantCall()` | `0x3ee5aeb5` | | `SafeCastOverflowedIntToUint(int256)` | `0xa8ce4432` | | `SafeCastOverflowedUintToInt(uint256)` | `0x24775e06` | | `SafeERC20FailedOperation(address)` | `0x5274afe7` | | `SlippageExceeded(uint256,uint256)` | `0x71c4efed` | | `UnknownPool()` | `0xf7139e33` | | `ZeroAddress()` | `0xd92e233d` | --- ## TartarusLaunchLocker `0x8fABb780838c1662235002F70A94a7ef882a9598` ### Other functions | Signature | Selector | Mutability | |---|---|---| | `acceptOwnership()` | `0x79ba5097` | nonpayable | | `factory()` | `0xc45a0155` | view | | `isLocked(address)` | `0x4a4fbeec` | view | | `lockedPositions(address)` | `0xfa22143d` | view | | `lockedTokenSupply(address)` | `0x732e78e4` | view | | `lockPosition(address,uint256)` | `0x292d5732` | nonpayable | | `lockTokenSupply(address,uint256)` | `0xb8a0d7ab` | nonpayable | | `onERC721Received(address,address,uint256,bytes)` | `0x150b7a02` | view | | `owner()` | `0x8da5cb5b` | view | | `pendingOwner()` | `0xe30c3978` | view | | `positionManager()` | `0x791b98bc` | view | | `renounceOwnership()` | `0x715018a6` | pure | | `setFactory(address)` | `0x5bb47808` | nonpayable | | `transferOwnership(address)` | `0xf2fde38b` | nonpayable | ### Events | Event | topic0 | |---|---| | `FactorySet(address)` | `0x1edf3afd4ac789736e00d216cd88be164ddcef26a6eedcc30cdb0cb62f3741b1` | | `OwnershipTransferred(address,address)` | `0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0` | | `OwnershipTransferStarted(address,address)` | `0x38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e22700` | | `PositionLocked(address,uint256)` | `0x2cabb2a2973327d5863ceb4707e9441851243897e86d587ee35943599752eb54` | | `TokenSupplyLocked(address,uint256)` | `0xaf33c4aba92959b3e7ddc83ab728938262da159a6c05ca836f6c46f9bcb2c740` | ### Errors | Error | Selector | |---|---| | `AlreadyInitialized()` | `0x0dc149f0` | | `NotFactory()` | `0x32cc7236` | | `NotPositionManager()` | `0x20fdc658` | | `OwnableInvalidOwner(address)` | `0x1e4fbdf7` | | `OwnableUnauthorizedAccount(address)` | `0x118cdaa7` | | `OwnershipCannotBeRenounced()` | `0x2fab92ca` | | `PositionAlreadyLocked()` | `0xfe3099b6` | | `PositionNotHeld()` | `0x6b49c94a` | | `SafeERC20FailedOperation(address)` | `0x5274afe7` | | `ZeroAddress()` | `0xd92e233d` | --- ## TartarusLaunchFactory `0x765f2ce211050ec8280257d76072cAa480D75A00` ### Functions an application calls ### `bridgeWalletFor(address)` `0x31682149` · `view` · view The Elysium bridge escrow wallet created for `token`, or zero if none exists yet. Reverts while no bridge factory is configured. The Elysium bridge wallet created at launch, or zero. Reverts while no bridge factory is configured. **Parameters** | Name | Type | Description | |---|---|---| | `token` | `address` | | **Returns** | Name | Type | Description | |---|---|---| | `arg0` | `address` | | ### `canLaunch(address)` `0x58373f04` · `view` · view Whether `launcher` may launch right now: true while the public gate is open, and true for whitelisted addresses while it is closed. The same predicate `launchToken` enforces on its caller, exposed so routers like TartarusLaunchAndBuy can hold their own callers to this single list instead of maintaining a second one. Check this before showing a launch form; while the public gate is closed only whitelisted launchers pass. **Parameters** | Name | Type | Description | |---|---|---| | `launcher` | `address` | | **Returns** | Name | Type | Description | |---|---|---| | `arg0` | `bool` | | ### `executeCreatorFeeRecipientChange(address)` `0x3d3d2d58` · `nonpayable` · Permissionless once the timelock has elapsed. Applies a protocol-owner's proposed creator fee recipient change once its timelock has elapsed. Permissionless: anyone can execute an already-matured proposal. Applies a protocol-owner override proposed with `setCreatorFeeRecipient`. The API reports one as `creatorFeeRecipientPending` on the token. **Parameters** | Name | Type | Description | |---|---|---| | `token` | `address` | | **Reverts** - `NoPendingChange` - `TimelockNotElapsed` - `TimelockExpired` ### `getLaunchConfig(uint256)` `0x1cad862d` · `view` · view **Parameters** | Name | Type | Description | |---|---|---| | `id` | `uint256` | | **Returns** | Name | Type | Description | |---|---|---| | `arg0` | `(address,int24,int24,bool)` | | ### `getLaunchedToken(address)` `0x3cf28b5a` · `view` · view Returns the immutable record for a token created by this factory. The immutable launch record: deployer, quote asset, config, pool id and the frozen fee tranches. **Parameters** | Name | Type | Description | |---|---|---| | `token` | `address` | | **Returns** | Name | Type | Description | |---|---|---| | `arg0` | `(address,address,address,address,bytes32,uint256,int24,int24,bool)` | | ### `getLaunchFeePolicy(address)` `0x470ef5fc` · `view` · view Returns the fee policy frozen for a launch. **Parameters** | Name | Type | Description | |---|---|---| | `token` | `address` | | **Returns** | Name | Type | Description | |---|---|---| | `arg0` | `(address,uint16,uint16,uint16)` | | ### `HOLDER_REWARDS()` `0x1acda2e3` · `view` · view Pass as `TokenParams.creatorFeeRecipient` to pay the creator tranche to the launch's holders. The factory then resolves the recipient to the launch's rewards distributor itself, from the same inputs `TartarusRewardsDistributorFactory.deploy` checks later, so the address can never be mispredicted. A keyless address: it has no known preimage, so nobody can ever hold or spend from it. The sentinel to pass as `TokenParams.creatorFeeRecipient` for a holder-rewards launch. The factory resolves it to the launch's distributor. **Returns** | Name | Type | Description | |---|---|---| | `arg0` | `address` | | ### `holderRewardsDistributorUsed(address)` `0x7ca38ca2` · `view` · view Holder-rewards distributors already bound to a launch. A distributor's address depends only on (creator, salt, quote), while the token's also depends on its metadata, tranche and config, so a creator reusing a salt could otherwise send two launches' fees into one distributor that only the first can ever register, and the second's holders would get nothing. True once a distributor is bound to a launch; a second holder-rewards launch with the same `(creator, salt, quote)` would revert. **Parameters** | Name | Type | Description | |---|---|---| | `distributor` | `address` | | **Returns** | Name | Type | Description | |---|---|---| | `arg0` | `bool` | | ### `isSymbolAvailable(string)` `0xebe4f36a` · `view` · view True when a launch would accept `symbol`: valid ticker format, not reserved, not already taken. Valid HyperCore ticker format, not reserved, not taken. The API's `/v1/launch/symbol` answers the same question with a reason. **Parameters** | Name | Type | Description | |---|---|---| | `symbol` | `string` | | **Returns** | Name | Type | Description | |---|---|---| | `arg0` | `bool` | | ### `launchFee()` `0xcf3cf573` · `view` · view A flat charge on creating a launch, in native HYPE, credited to the protocol fee recipient through the escrow. Spam friction rather than revenue. Zero disables it. Native HYPE, sent as `msg.value`. **Returns** | Name | Type | Description | |---|---|---| | `arg0` | `uint256` | | ### `launchForwarder()` `0x9b924452` · `view` · view The router allowed to launch on a user's behalf. Launch-and-buy is available only while this is `TartarusLaunchAndBuy`. **Returns** | Name | Type | Description | |---|---|---| | `arg0` | `address` | | ### `launchToken((string,string,string,string,(string,string,string,string,string),address,uint16,bytes32,bytes32),uint256)` `0x36fbbab8` · `payable` · Permissionless, but gated by `canLaunch(msg.sender)` — check it before offering a launch UI. Deploys the launch token, creates and seeds its pool, and records the launch. `msg.value` must equal `launchFee` exactly. Deploys the token, creates its single-sided Uniswap V4 pool at the config's starting tick and records the launch, all in one call; the token trades from this block. The launch fee is native HYPE sent as `msg.value` (not an allowance), whatever the launch quotes in. Use this when there is NO opening buy; for one, go through `TartarusLaunchAndBuy.launchAndBuy`. Set `creatorFeeRecipient` to `HOLDER_REWARDS()` to pay the creator tranche to holders. **Parameters** | Name | Type | Description | |---|---|---| | `params` | `(string,string,string,string,(string,string,string,string,string),address,uint16,bytes32,bytes32)` | | | `launchConfigId` | `uint256` | | **Returns** | Name | Type | Description | |---|---|---| | `token` | `address` | | **Reverts** - `NotWhitelisted` — the public gate is closed and you are not a whitelisted launcher - `IncorrectLaunchFee` — `msg.value` is not exactly `launchFee()` - `InvalidLaunchConfigId` / `LaunchConfigDisabled` — no such config, or it is switched off - `QuoteTokenNotApproved` — the config's quote asset is no longer approved - `InvalidSymbol` / `SymbolReserved` / `SymbolTaken` — the ticker is not `[A-Z0-9]{1,6}`, is reserved, or is in use - `CreatorFeeOutOfBounds` (from the hook) — `creatorFeeBps` outside the live bounds - `LaunchEconomicsMismatch(expected,actual)` — terms moved since you read `previewLaunchEconomics` - `RewardsFactoryNotSet` / `HolderRewardsDistributorTaken` — `HOLDER_REWARDS` requested but not wired, or this salt's distributor is already bound ### `pendingCreatorFeeRecipient(address)` `0x9beacf4a` · `view` · view **Parameters** | Name | Type | Description | |---|---|---| | `token` | `address` | | **Returns** | Name | Type | Description | |---|---|---| | `newRecipient` | `address` | | | `effectiveAt` | `uint256` | | | `expiresAt` | `uint256` | | ### `pendingLaunchForwarder()` `0x88e25296` · `view` · view A timelocked forwarder change: the proposed address and its execution window. Anyone may execute it once matured. **Returns** | Name | Type | Description | |---|---|---| | `forwarder` | `address` | | | `effectiveAt` | `uint256` | | | `expiresAt` | `uint256` | | ### `poolKeyFor(address)` `0x21c099c8` · `view` · view The Uniswap V4 pool key of a launched token, for routers and quoters. Reverts for unknown tokens. The token's Uniswap V4 pool key, for the Universal Router's `V4_SWAP`. Reverts for an unknown token. **Parameters** | Name | Type | Description | |---|---|---| | `token` | `address` | | **Returns** | Name | Type | Description | |---|---|---| | `arg0` | `(address,address,uint24,int24,address)` | | ### `previewLaunchEconomics(uint256,uint16)` `0x63fa3362` · `view` · view Returns the economics digest a launch of `launchConfigId` choosing `creatorFeeBps` would produce right now, for a creator to pass back as TokenParams.expectedEconomics. Reading the digest and launching in separate transactions still leaves the terms free to move in between; the pin is what makes that movement revert instead of silently repricing the launch. Reverts with the hook's CreatorFeeOutOfBounds when the tranche is not allowed. Returns the digest to put in `TokenParams.expectedEconomics` for `(launchConfigId, creatorFeeBps)`. Fetch it in the same flow as the submit — an owner changing the fee policy or the launch fee between your read and your signature makes the launch revert rather than silently repricing. Reverts with the hook's `CreatorFeeOutOfBounds` for a disallowed tranche. **Parameters** | Name | Type | Description | |---|---|---| | `launchConfigId` | `uint256` | | | `creatorFeeBps` | `uint16` | | **Returns** | Name | Type | Description | |---|---|---| | `arg0` | `bytes32` | | ### `rewardsDistributorFactory()` `0xed743d1e` · `view` · view Resolves `HOLDER_REWARDS` launches to their distributor. Unset until wired; holder-rewards launches revert until then. Zero while holder rewards are not wired; a `HOLDER_REWARDS` launch reverts with `RewardsFactoryNotSet` then. **Returns** | Name | Type | Description | |---|---|---| | `arg0` | `address` | | ### `transferCreatorFeeRecipient(address,address)` `0x2931861b` · `nonpayable` · The launch's current creator fee recipient only. Lets the current creator fee recipient hand off future creator fees for `token` to a new address. Does not clear a pending protocol-owner override. If one is outstanding, it still executes on schedule and supersedes the address set here. See `setCreatorFeeRecipient`. Hands future creator fees to a new address. Does not cancel a pending protocol-owner override; a matured override still executes on schedule. **Parameters** | Name | Type | Description | |---|---|---| | `token` | `address` | | | `newRecipient` | `address` | | **Reverts** - `NotCreatorFeeRecipient` - `TokenNotFound` - `ZeroAddress` ### Other functions | Signature | Selector | Mutability | |---|---|---| | `acceptOwnership()` | `0x79ba5097` | nonpayable | | `addLaunchConfig((address,int24,int24,bool))` | `0x7c61e47f` | nonpayable | | `approvedQuoteTokens(address)` | `0x80729480` | view | | `BRIDGE_WALLET_GAS()` | `0xffe338f1` | view | | `cancelCreatorFeeRecipientChange(address)` | `0x6e47a188` | nonpayable | | `cancelLaunchForwarderChange()` | `0x813dbbfa` | nonpayable | | `CREATOR_FEE_RECIPIENT_EXECUTION_WINDOW()` | `0x02d4753d` | view | | `CREATOR_FEE_RECIPIENT_TIMELOCK()` | `0x5a83b00a` | view | | `disableLaunchForwarder()` | `0x74767ea9` | nonpayable | | `elysiumBridgeFactory()` | `0x2f828f0b` | view | | `executeLaunchForwarderChange()` | `0x31d1fdaf` | nonpayable | | `expectedMirrorFor(address)` | `0xa60651fb` | view | | `feeEscrow()` | `0xc4b7de97` | view | | `isSymbolReserved(string)` | `0xd5e17fe3` | view | | `LAUNCH_FORWARDER_EXECUTION_WINDOW()` | `0xf3feb0d3` | view | | `LAUNCH_FORWARDER_TIMELOCK()` | `0xafe9f556` | view | | `launchConfigCount()` | `0xae72d871` | view | | `launchDeployer()` | `0x858f5964` | view | | `launchEnabled()` | `0x236a4afb` | view | | `launchForwarderInitialized()` | `0x3ab6ec24` | view | | `launchTokenFor((string,string,string,string,(string,string,string,string,string),address,uint16,bytes32,bytes32),uint256,address)` | `0x99558b64` | payable | | `locker()` | `0xd7b96d4e` | view | | `memeHook()` | `0x6651812c` | view | | `owner()` | `0x8da5cb5b` | view | | `pendingOwner()` | `0xe30c3978` | view | | `permit2()` | `0x12261ee7` | view | | `poolManager()` | `0xdc4c90d3` | view | | `positionManager()` | `0x791b98bc` | view | | `proposeLaunchForwarder(address)` | `0xce12a5bc` | nonpayable | | `renounceOwnership()` | `0x715018a6` | pure | | `setCreatorFeeRecipient(address,address)` | `0xe102c9aa` | nonpayable | | `setElysiumBridgeFactory(address)` | `0x29fc5422` | nonpayable | | `setLaunchDeployer(address)` | `0x3a9391e8` | nonpayable | | `setLaunchEnabled(bool)` | `0xf56f05b2` | nonpayable | | `setLaunchFee(uint256)` | `0x5313be2c` | nonpayable | | `setLaunchForwarder(address)` | `0x767b7c16` | nonpayable | | `setQuoteTokenApproved(address,bool)` | `0x45619b6e` | nonpayable | | `setReservedSymbol(string,bool)` | `0x2ca1ff1b` | nonpayable | | `setRewardsDistributorFactory(address)` | `0x353fc828` | nonpayable | | `setWhitelistedLauncher(address,bool)` | `0x366f0f3e` | nonpayable | | `SUPPLY()` | `0xc50497ae` | view | | `tokenBySymbol(bytes32)` | `0x26a2a16d` | view | | `transferOwnership(address)` | `0xf2fde38b` | nonpayable | | `updateLaunchConfig(uint256,(address,int24,int24,bool))` | `0xa954dc4d` | nonpayable | | `whitelistedLaunchers(address)` | `0xda3eda65` | view | ### Events | Event | topic0 | |---|---| | `BridgeWalletCreated(address,address)` | `0x0784ee1925b81ceb1c4f909a494f11a35badad9b999bcceb2d1a5ad597a25abe` | | `BridgeWalletSkipped(address)` | `0xbcd7adc790364324699753894577b3c54f5c088135312e9ded902d5ae85d9a44` | | `CreatorFeeRecipientChangeCancelled(address,address)` | `0xbe2de91c1cbef653c760573fff8355c0c851d35ed2a898342b4db556301cccf4` | | `CreatorFeeRecipientChangeProposed(address,address,address,uint256,uint256)` | `0x7f119e44c84a715429bee60d30ad2e14afdef6c60bb1a7eaa01290ecf6d1b2e5` | | `CreatorFeeRecipientUpdated(address,address,address)` | `0x308c390ed1ab5873392818e036cabdf408bc8ad042fbaead3108954ff75ba980` | | `ElysiumBridgeFactorySet(address)` | `0xf93b11821bfd7ed7369be380bca7b1b96d8fe3219b8b59f682b1cf9aa348f1ae` | | `LaunchConfigAdded(uint256)` | `0xedd96c570c6e5ef9add0378e59df53579a283889dc5dab6440ef6eca2ee6c8ce` | | `LaunchConfigUpdated(uint256)` | `0x2f8ba78ae68cfd0c82c7756c540eaf4eead3341aef9ccebcb91d546bff10d62b` | | `LaunchDeployerSet(address)` | `0xd5ea7aa3e328a0594dcf6914cd9e5369779efaa194ee4dd4c5afcad4f4ebbb0c` | | `LaunchEnabledUpdated(bool)` | `0x4f1ea5016c51c2f82324e00e9b8a4a95ee5aeaa10c653dabaec5f1bc9047ba0b` | | `LaunchFeeUpdated(uint256)` | `0xc799be5eb19a1a6d6ba7368d21e2bc367c8a335e4a07cd3d954482e6f714d3c5` | | `LaunchForwarderChangeCancelled(address)` | `0x21ef2af51c6ff72bd6d142e45d2e01f0448c8fff50168bb73a1b862c427a76af` | | `LaunchForwarderChangeProposed(address,address,uint256,uint256)` | `0x40399dc1b46c2cbda42768dcf83944f62829bd45095ab22fb45ca8e387589135` | | `LaunchForwarderSet(address)` | `0x56b32d3633fed72f97c4df44a78b5fa04f1d662d4bddebcd8a9b216d26d093ad` | | `LaunchSalt(address,bytes32)` | `0x06ace689d4f9cc53fc9c544dbffde33266dedb0b9941235bc3086fb0824c03fd` | | `OwnershipTransferred(address,address)` | `0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0` | | `OwnershipTransferStarted(address,address)` | `0x38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e22700` | | `PoolSeeded(address,uint256,uint128,uint256,uint256)` | `0x1403071d82f5bf603e26432d669b9a1c9234f678a80ccd63ec2c4adf2428543b` | | `QuoteTokenApprovalUpdated(address,bool)` | `0x08a843625584ce1b83674addd05c56adbb63d59b99f3c11eabaab869734fd4c5` | | `ReservedSymbolUpdated(string,bool)` | `0xccaf9a1889196a07179623710ec905c42226f34ce9561f3e6a8ad2c545f64946` | | `RewardsDistributorFactorySet(address)` | `0x8584e5b12dbb992c74d868f3beef4084c46551850974ad70f5442beba0124fdf` | | `TokenLaunched(address,address,address,uint256,bytes32,uint256,address,int24,uint16,uint16)` | `0x94536bc2a8cb6d35f91cb7caf0da3a1c0af50c75501a7b69ab149d80c3ce1b60` | | `WhitelistedLauncherUpdated(address,bool)` | `0xef2b562a67f01ed4b7c4265ec09b539039c6d5dd7e752191d3940508c3dc0068` | ### Errors | Error | Selector | |---|---| | `AlreadySet()` | `0xa741a045` | | `BridgeFactoryNotSet()` | `0xfba5239c` | | `BridgeWalletOutOfGas()` | `0xd66282ab` | | `HolderRewardsDistributorTaken(address)` | `0x0c40ebbe` | | `IncorrectLaunchFee(uint256,uint256)` | `0xe14cee31` | | `InvalidLaunchConfigId()` | `0x68b42c59` | | `InvalidStartingTick()` | `0x21bfef7e` | | `InvalidSymbol()` | `0x04119bc4` | | `InvalidTickSpacing()` | `0x270815a0` | | `LaunchConfigDisabled()` | `0xa8b63076` | | `LaunchDependenciesNotWired()` | `0x1de25df3` | | `LaunchDeployerNotSet()` | `0x57332dcf` | | `LaunchEconomicsMismatch(bytes32,bytes32)` | `0xecb27319` | | `NoPendingChange()` | `0xa3fef2f8` | | `NotAContract()` | `0x09ee12d5` | | `NotCreatorFeeRecipient()` | `0xb9f93944` | | `NotLaunchForwarder()` | `0xea9eaa96` | | `NotWhitelisted()` | `0x584a7938` | | `OwnableInvalidOwner(address)` | `0x1e4fbdf7` | | `OwnableUnauthorizedAccount(address)` | `0x118cdaa7` | | `OwnershipCannotBeRenounced()` | `0x2fab92ca` | | `QuoteTokenNotApproved()` | `0x8b44d1ae` | | `ReentrancyGuardReentrantCall()` | `0x3ee5aeb5` | | `RewardsFactoryNotSet()` | `0x662b8375` | | `SafeERC20FailedOperation(address)` | `0x5274afe7` | | `SeedNotViable()` | `0x70a5d0b7` | | `SymbolReserved()` | `0x18049c93` | | `SymbolTaken(address)` | `0x30214533` | | `TimelockExpired(uint256)` | `0xb79d40e8` | | `TimelockNotElapsed(uint256)` | `0x810c4f2a` | | `TokenNotFound()` | `0xcbdb7b30` | | `ZeroAddress()` | `0xd92e233d` | --- ## TartarusLaunchDeployer `0x94b7ed8968bF4323c03DE97395801D487771C44E` ### Functions an application calls ### `isValidName(string)` `0x1e30397f` · `pure` · pure Whether `name` fits a single word (1..31 bytes, no NUL). 1..31 bytes, no NUL: the name packs into one `bytes32`. **Parameters** | Name | Type | Description | |---|---|---| | `name` | `string` | | **Returns** | Name | Type | Description | |---|---|---| | `arg0` | `bool` | | ### `isValidSymbol(string)` `0x8fd4f71a` · `pure` · pure Whether `symbol` satisfies the HyperCore ticker format (`[A-Z0-9]{1,6}`). `[A-Z0-9]{1,6}` — the HyperCore ticker verbatim. **Parameters** | Name | Type | Description | |---|---|---| | `symbol` | `string` | | **Returns** | Name | Type | Description | |---|---|---| | `arg0` | `bool` | | ### `predictLaunchAddress((address,address,bytes32,address,uint16,uint256,string,string,string,string,(string,string,string,string,string)))` `0xe1f82767` · `view` · view Returns the address `deployLaunch` would produce for `params`, without deploying anything. Reverts on an invalid name or symbol, so it doubles as a dry run of the metadata rules. Computes the CREATE2 token address before you send, and doubles as a dry run of the name and symbol rules (it reverts on either). The API's `/v1/launch/predict` wraps it and returns the pool id and `expectedEconomics` too. **Parameters** | Name | Type | Description | |---|---|---| | `params` | `(address,address,bytes32,address,uint16,uint256,string,string,string,string,(string,string,string,string,string))` | | **Returns** | Name | Type | Description | |---|---|---| | `token` | `address` | | ### Other functions | Signature | Selector | Mutability | |---|---|---| | `deployLaunch((address,address,bytes32,address,uint16,uint256,string,string,string,string,(string,string,string,string,string)))` | `0xe637ebaa` | nonpayable | | `factory()` | `0xc45a0155` | view | ### Errors | Error | Selector | |---|---| | `Create2EmptyBytecode()` | `0x4ca249dc` | | `FailedDeployment()` | `0xb06ebf3d` | | `InsufficientBalance(uint256,uint256)` | `0xcf479181` | | `InvalidName()` | `0x430f13b3` | | `InvalidSymbol()` | `0x04119bc4` | | `MetadataTooLong()` | `0x85b8e2f4` | | `NotFactory()` | `0x32cc7236` | --- ## TartarusLaunchAndBuy `0x044a835456057915028DDdFfa161DeA9d4d40615` ### Functions an application calls ### `launchAndBuy((string,string,string,string,(string,string,string,string,string),address,uint16,bytes32,bytes32),uint256,uint256,uint256,address)` `0xcb8a0252` · `payable` · Permissionless, but `factory.canLaunch(msg.sender)` must hold — routing through the router does not widen the gate. Only usable while the factory's `launchForwarder()` is this router. Launches a token and immediately buys into its pool for `recipient`, both in this transaction. Native quote: `msg.value == launchFee + quoteIn`. ERC-20 quote: `msg.value == launchFee` and the caller has approved this router for `quoteIn`; the pool pulls exactly what it charges at settlement. **Approve first:** `launchAndBuy` for native: `msg.value = launchFee + quoteIn`; ERC-20: `msg.value = launchFee` and an allowance of `quoteIn` to the router of native quote: nothing (send HYPE); ERC-20 quote: the launch's quote asset. Launches the token and buys into its pool for `recipient` in the same transaction. Arguments after the params are `(launchConfigId, quoteIn, minTokensOut, recipient)`. The opening buy is attributed to `recipient` (the indexer reports it as the trade's `recipient`; `trader` is the transaction sender). A scheduled forwarder change is visible as `pendingLaunchForwarder()`. **Parameters** | Name | Type | Description | |---|---|---| | `params` | `(string,string,string,string,(string,string,string,string,string),address,uint16,bytes32,bytes32)` | Launch parameters, forwarded to the factory untouched. | | `launchConfigId` | `uint256` | Factory launch config to launch against; it fixes the quote asset. | | `quoteIn` | `uint256` | Exact quote amount to spend on the opening buy, in the quote's own units. | | `minTokensOut` | `uint256` | Slippage bound on the opening buy. Reverts the whole launch when the buy would deliver less. | | `recipient` | `address` | Receives the purchased tokens. | **Returns** | Name | Type | Description | |---|---|---| | `token` | `address` | | | `tokensOut` | `uint256` | | **Reverts** - `NotApprovedLauncher` — the factory's launch gate is closed for you - `WrongValue` — `msg.value` does not match the rule above - `ZeroBuy` — `quoteIn` is zero; use `factory.launchToken` instead - `BuyTooLarge` — the opening buy exceeds what the launch allows - `SlippageExceeded` — fewer than `minTokensOut` tokens - `NotLaunchForwarder` (from the factory) — this router is not the configured forwarder - everything `launchToken` throws ### Other functions | Signature | Selector | Mutability | |---|---|---| | `acceptOwnership()` | `0x79ba5097` | nonpayable | | `factory()` | `0xc45a0155` | view | | `msgSender()` | `0xd737d0c7` | view | | `owner()` | `0x8da5cb5b` | view | | `pendingOwner()` | `0xe30c3978` | view | | `poolManager()` | `0xdc4c90d3` | view | | `renounceOwnership()` | `0x715018a6` | nonpayable | | `rescue(address,address)` | `0x4fdf5d1d` | nonpayable | | `transferOwnership(address)` | `0xf2fde38b` | nonpayable | | `unlockCallback(bytes)` | `0x91dd7346` | nonpayable | ### Events | Event | topic0 | |---|---| | `Launched(address,address,address,uint256,uint256)` | `0xec774f0683e9ac48e8d835f412f9f877a8a5dee9af3170d78cf3ef33149d15e7` | | `OwnershipTransferred(address,address)` | `0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0` | | `OwnershipTransferStarted(address,address)` | `0x38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e22700` | | `Rescued(address,address,uint256)` | `0x3af790fafda720819b2fc6e15090606e81154e0ac9a92d38ecad006d99d20ecc` | ### Errors | Error | Selector | |---|---| | `BuyTooLarge(uint256)` | `0xfaca41ea` | | `NotApprovedLauncher()` | `0x502ba015` | | `NotPoolManager()` | `0xae18210a` | | `OwnableInvalidOwner(address)` | `0x1e4fbdf7` | | `OwnableUnauthorizedAccount(address)` | `0x118cdaa7` | | `ReentrancyGuardReentrantCall()` | `0x3ee5aeb5` | | `SafeERC20FailedOperation(address)` | `0x5274afe7` | | `SlippageExceeded(uint256,uint256)` | `0x71c4efed` | | `TransferFailed()` | `0x90b8ec18` | | `WrongValue(uint256,uint256)` | `0x195a3ca1` | | `ZeroAddress()` | `0xd92e233d` | | `ZeroBuy()` | `0x6d01a63b` | --- ## TartarusLauncherToken Deployed **once per launch** — no fixed address. Read it from `factory.getLaunchedToken(token)`. ### Other functions | Signature | Selector | Mutability | |---|---|---| | `allowance(address,address)` | `0xdd62ed3e` | view | | `approve(address,uint256)` | `0x095ea7b3` | nonpayable | | `balanceOf(address)` | `0x70a08231` | view | | `decimals()` | `0x313ce567` | view | | `deployer()` | `0xd5f39488` | view | | `description()` | `0x7284e416` | view | | `getTokenInfo()` | `0xabb1dc44` | view | | `launchFactory()` | `0x536dac9b` | view | | `logo()` | `0xfb7f21eb` | view | | `name()` | `0x06fdde03` | view | | `socials()` | `0x53cd512a` | view | | `SUPPLY()` | `0xc50497ae` | view | | `symbol()` | `0x95d89b41` | view | | `totalSupply()` | `0x18160ddd` | view | | `transfer(address,uint256)` | `0xa9059cbb` | nonpayable | | `transferFrom(address,address,uint256)` | `0x23b872dd` | nonpayable | ### Events | Event | topic0 | |---|---| | `Approval(address,address,uint256)` | `0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925` | | `Transfer(address,address,uint256)` | `0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef` | ### Errors | Error | Selector | |---|---| | `ERC20InsufficientAllowance(address,uint256,uint256)` | `0xfb8f41b2` | | `ERC20InsufficientBalance(address,uint256,uint256)` | `0xe450d38c` | | `ERC20InvalidApprover(address)` | `0xe602df05` | | `ERC20InvalidReceiver(address)` | `0xec442f05` | | `ERC20InvalidSender(address)` | `0x96c6fd1e` | | `ERC20InvalidSpender(address)` | `0x94280d62` | | `ZeroAddress()` | `0xd92e233d` | --- ## TartarusRewardsDistributorFactory `0x3c8E2F5924629e629C5c4bFc56FddC4B2794E4F9` ### Functions an application calls ### `deploy(address,bytes32)` `0x32c02a14` · `nonpayable` · Permissionless — every argument is checked against the launchpad's own record, so the caller cannot change the outcome. The keeper runs it minutes after a holder-rewards launch. Deploys the distributor `token` already names as its creator fee recipient, and records the pair. Permissionless: every argument is checked against the launchpad's own record, so who sends it cannot change the outcome. That matters because a creator's setup is only finished once this has run, and nothing should depend on them coming back to do it. Deploys the distributor the launch already names as its `creatorFeeRecipient`, and records the pair in `distributorOf` / `tokenOf`. Re-attempting under the same salt is safe: the address is fixed, so the check either passes once or reverts. **Parameters** | Name | Type | Description | |---|---|---| | `token` | `address` | | | `salt` | `bytes32` | | **Returns** | Name | Type | Description | |---|---|---| | `distributor` | `address` | | **Reverts** - `TokenNotFound` — no such launch on the factory - `AlreadyRegistered` — this token already has a distributor, or the address is already bound to another token - `NotCreatorFeeRecipient(expected, actual)` — the launch's fee recipient is not `predict(deployer, salt, quoteToken)`; the launch was made with a different salt, creator, or recipient ### `distributorOf(address)` `0x3f20b9b4` · `view` · View, anyone. Token → its deployed distributor, or zero. Zero for a launch that pays its creator, and for a holder-rewards launch whose `deploy` has not run yet. **Parameters** | Name | Type | Description | |---|---|---| | `token` | `address` | | **Returns** | Name | Type | Description | |---|---|---| | `arg0` | `address` | | ### `initCodeHash(address)` `0x75fd9f28` · `view` · View, anyone. Exposed so a prediction can be verified without this contract. The creation-code hash `predict` uses, exposed so the prediction can be checked without this contract. **Parameters** | Name | Type | Description | |---|---|---| | `quoteToken` | `address` | | **Returns** | Name | Type | Description | |---|---|---| | `arg0` | `bytes32` | | ### `predict(address,bytes32,address)` `0x6339c853` · `view` · View, anyone. The address `deploy` will produce for `creator` and `salt` on a launch quoted in `quoteToken` (`address(0)` for native HYPE). Callers should read this rather than recomputing the salt rule, so the namespacing lives in exactly one place. Where the distributor for `(creator, salt, quoteToken)` will live (`address(0)` is native HYPE). `creator` is the launch's `deployer` (the account that signs `launchToken`, or the account the router launches for) and `salt` is the same `TokenParams.salt` the launch uses. Do NOT send the result as `TokenParams.creatorFeeRecipient`: pass the factory's `HOLDER_REWARDS()` sentinel and the factory resolves the distributor itself. Use this to display or check the address. Nothing exists there yet and nothing needs to: the escrow credits by address. **Parameters** | Name | Type | Description | |---|---|---| | `creator` | `address` | | | `salt` | `bytes32` | | | `quoteToken` | `address` | | **Returns** | Name | Type | Description | |---|---|---| | `arg0` | `address` | | ### `tokenOf(address)` `0x42ec38e2` · `view` · View, anyone. Distributor → the token it was registered for. Informational only: the authoritative direction is the launch record's `creatorFeeRecipient`. **Parameters** | Name | Type | Description | |---|---|---| | `distributor` | `address` | | **Returns** | Name | Type | Description | |---|---|---| | `arg0` | `address` | | ### Other functions | Signature | Selector | Mutability | |---|---|---| | `distributorOwner()` | `0x2127911d` | view | | `escrow()` | `0xe2fdcc17` | view | | `launchFactory()` | `0x536dac9b` | view | ### Events | Event | topic0 | |---|---| | `DistributorDeployed(address,address,address,address,bytes32)` | `0x3ef9707063aece3a59b74cf57d0c1ed7a69cceb8630a1b4d42bd1a3f6743f560` | ### Errors | Error | Selector | |---|---| | `AlreadyRegistered()` | `0x3a81d6fc` | | `Create2EmptyBytecode()` | `0x4ca249dc` | | `FailedDeployment()` | `0xb06ebf3d` | | `InsufficientBalance(uint256,uint256)` | `0xcf479181` | | `NotCreatorFeeRecipient(address,address)` | `0x02f5a580` | | `TokenNotFound()` | `0xcbdb7b30` | | `ZeroAddress()` | `0xd92e233d` | --- ## TartarusRewardsDistributor Deployed **once per launch** — no fixed address. Read it from `factory.getLaunchedToken(token)`. ### Functions an application calls ### `batchClaim(uint256[],address,uint256[],uint256[],bytes32[][])` `0xa12e01d4` · `nonpayable` · Permissionless to submit; the proof fixes the recipient. Settles several epochs for one account in a single transaction. A holder accumulates one open epoch per distribution, so by the time they get around to claiming there are usually several. Sending N transactions for what is one decision is a bad enough experience that dust goes unclaimed and rolls over. Deliberately all-or-nothing: a bad proof reverts the batch rather than being skipped. Silently dropping one entry would let a caller believe an epoch was settled when it was not, and `hasClaimed` is the only record. `claim` for several epochs in one transaction — a holder accumulates one open epoch per weekly distribution. Same checks per element; the whole batch reverts if any fails. **Parameters** | Name | Type | Description | |---|---|---| | `epochIds` | `uint256[]` | | | `account` | `address` | | | `amounts` | `uint256[]` | | | `indexes` | `uint256[]` | | | `proofs` | `bytes32[][]` | | **Reverts** - `LengthMismatch` — the four arrays differ in length - …then everything `claim` throws, per epoch ### `claim(uint256,address,uint256,uint256,bytes32[])` `0x3e4fcb21` · `nonpayable` · Permissionless to submit; the proof fixes the recipient. Pays `account`, never `msg.sender`. Claims `amount` from `epochId` for `account`. Permissionless in who submits it: the proof fixes the recipient, so a third party can settle on a holder's behalf without being able to redirect anything. Claims one epoch. `amount`, `index` and `proof` come from the published tree — the API returns them under `claimable.holders.byToken[].epochs[].calldata`, or rebuild the tree from chain data. Leaves are `keccak256(abi.encode(index, account, amount))` (`leafFor` returns the exact encoding); internal nodes hash sorted pairs. **Parameters** | Name | Type | Description | |---|---|---| | `epochId` | `uint256` | | | `account` | `address` | | | `amount` | `uint256` | | | `index` | `uint256` | | | `proof` | `bytes32[]` | | **Reverts** - `NoEpoch` — no such epoch id - `ClaimWindowClosed` — more than 90 days since the epoch was published; the funds have rolled over - `ExcludedAccount` — `account` is on the exclusion list (the pool manager, the locker, the distributor itself, …) - `AlreadyClaimed` — this `(epoch, account)` already claimed - `InvalidProof` — leaf or proof does not match the epoch's root - `EpochOverdrawn(total, claimed, amount)` — the tree over-allocated this epoch; cannot happen for a correctly built tree ### `epochCount()` `0x829965cc` · `view` · View, anyone. Epoch ids are `0 … epochCount - 1`. **Returns** | Name | Type | Description | |---|---|---| | `arg0` | `uint256` | | ### `getEpoch(uint256)` `0xbc0bc6ba` · `view` · View, anyone. Root, total, claimed so far, publication time and expiry for an epoch. **Parameters** | Name | Type | Description | |---|---|---| | `epochId` | `uint256` | | **Returns** | Name | Type | Description | |---|---|---| | `arg0` | `(bytes32,uint256,uint256,uint64,bool)` | | ### `harvest()` `0x4641257d` · `nonpayable` · Permissionless. The keeper calls it once at least about $1 of the launch's quote asset is waiting in the escrow. Pulls this contract's accrued fees out of the launchpad's escrow. Permissionless: the destination and amount are fixed by the escrow's ledger, so who triggers it does not matter. Credits the balance delta rather than the escrow's reported figure, so a quote asset that under- delivers can never make this contract believe it holds more than it does. Pulls the distributor's escrow balance into `unallocated`, from where epochs are published. Returns the amount moved. **Returns** | Name | Type | Description | |---|---|---| | `amount` | `uint256` | | **Reverts** - `NothingToHarvest` — the escrow holds nothing for this distributor ### `hasClaimed(uint256,address)` `0x873f6f9e` · `view` · View, anyone. **Parameters** | Name | Type | Description | |---|---|---| | `epochId` | `uint256` | | | `account` | `address` | | **Returns** | Name | Type | Description | |---|---|---| | `arg0` | `bool` | | ### `isExcluded(address)` `0xcba0e996` · `view` · View, anyone. Addresses that can never receive a distribution, whatever a published root says. The pool above all: its liquidity is locked forever, so anything sent there is destroyed rather than distributed. True for the distributor itself from construction (it self-excludes), and for anything the owner has excluded since. **Parameters** | Name | Type | Description | |---|---|---| | `account` | `address` | | **Returns** | Name | Type | Description | |---|---|---| | `arg0` | `bool` | | ### `leafFor(uint256,address,uint256)` `0x98cd4d42` · `pure` · Pure, anyone. The leaf a claim must prove, so callers and tree builders agree on the encoding rather than each guessing it. The exact leaf encoding, `keccak256(abi.encode(index, account, amount))`, so a tree builder and the contract cannot disagree. Not `encodePacked`, not double-hashed. **Parameters** | Name | Type | Description | |---|---|---| | `index` | `uint256` | | | `account` | `address` | | | `amount` | `uint256` | | **Returns** | Name | Type | Description | |---|---|---| | `arg0` | `bytes32` | | ### `outstanding(uint256)` `0x874a0e73` · `view` · View, anyone. What an epoch still owes, whether or not its window has closed. `total - claimed` for an epoch — what `rollOver` will return once the window closes. **Parameters** | Name | Type | Description | |---|---|---| | `epochId` | `uint256` | | **Returns** | Name | Type | Description | |---|---|---| | `arg0` | `uint256` | | ### `rollOver(uint256)` `0xd27411ce` · `nonpayable` · Permissionless. The keeper calls it once an epoch's window has closed. Returns an expired epoch's unclaimed remainder to the unallocated pool, so it funds a later distribution instead of being stranded. Permissionless once the window has closed. There is no path that sends it anywhere but back into the next epoch, so nobody needs to be trusted to run it. Returns an expired epoch's unclaimed remainder to `unallocated`, so a later epoch distributes it. Emits `RolledOver` even when the remainder is zero. **Parameters** | Name | Type | Description | |---|---|---| | `epochId` | `uint256` | | **Returns** | Name | Type | Description | |---|---|---| | `amount` | `uint256` | | **Reverts** - `NoEpoch` — no such epoch id - `ClaimWindowOpen` — the 90-day window has not closed - `AlreadyRolledOver` — already rolled ### `unallocated()` `0xdf1c455c` · `view` · View, anyone. Harvested but not yet committed to an epoch. Publishing draws from this, and a rolled-over epoch returns to it. Harvested funds not yet committed to an epoch — what the next epoch can distribute. **Returns** | Name | Type | Description | |---|---|---| | `arg0` | `uint256` | | ### Other functions | Signature | Selector | Mutability | |---|---|---| | `acceptOwnership()` | `0x79ba5097` | nonpayable | | `CLAIM_WINDOW()` | `0x9f34fc80` | view | | `escrow()` | `0xe2fdcc17` | view | | `owner()` | `0x8da5cb5b` | view | | `pendingOwner()` | `0xe30c3978` | view | | `publishEpoch(bytes32,uint256)` | `0xc16f10ee` | nonpayable | | `quoteToken()` | `0x217a4b70` | view | | `renounceOwnership()` | `0x715018a6` | pure | | `setExcluded(address,bool)` | `0x2836be24` | nonpayable | | `setExcludedBatch(address[],bool)` | `0x8018135d` | nonpayable | | `transferOwnership(address)` | `0xf2fde38b` | nonpayable | ### Events | Event | topic0 | |---|---| | `Claimed(uint256,address,uint256)` | `0x4ec90e965519d92681267467f775ada5bd214aa92c0dc93d90a5e880ce9ed026` | | `EpochPublished(uint256,bytes32,uint256,uint64)` | `0x92c85369eead3d8555a7298769ddafde22d0f2f5f1ec05b2aa4059520533f003` | | `ExclusionUpdated(address,bool)` | `0x83f2b279b6151af5a15cdbe8471d34fe8b34aed9bd9514ffeadb57c9ea366e61` | | `Harvested(uint256,uint256)` | `0xfa07446fad45314351eb89109a154880278451332bb87f1824d435fe58da5939` | | `OwnershipTransferred(address,address)` | `0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0` | | `OwnershipTransferStarted(address,address)` | `0x38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e22700` | | `RolledOver(uint256,uint256)` | `0xf52162a5e697efbc1377904d60df6a1b984cc85ca7964998e201cbea0b8e7549` | ### Errors | Error | Selector | |---|---| | `AlreadyClaimed()` | `0x646cf558` | | `AlreadyRolledOver()` | `0xd4b3db41` | | `ClaimWindowClosed()` | `0xf0f25a33` | | `ClaimWindowOpen()` | `0x29dfa3ce` | | `EpochOverdrawn(uint256,uint256,uint256)` | `0x1e8a6f77` | | `ExcludedAccount()` | `0xb7594bec` | | `InsufficientUnallocated(uint256,uint256)` | `0x30e44a1d` | | `InvalidProof()` | `0x09bde339` | | `LengthMismatch()` | `0xff633a38` | | `NoEpoch()` | `0x6a0b56a8` | | `NothingToHarvest()` | `0x3f29331a` | | `OwnableInvalidOwner(address)` | `0x1e4fbdf7` | | `OwnableUnauthorizedAccount(address)` | `0x118cdaa7` | | `OwnershipCannotBeRenounced()` | `0x2fab92ca` | | `ReentrancyGuardReentrantCall()` | `0x3ee5aeb5` | | `SafeERC20FailedOperation(address)` | `0x5274afe7` | | `TransferFailed(address,uint256)` | `0x1c43b976` | | `UnexpectedNativeTransfer()` | `0xecb8b30d` | | `ZeroAddress()` | `0xd92e233d` | | `ZeroAmount()` | `0x1f2a2005` | --- # ABI Every ABI below is emitted straight from the compiler by `tartarus/backend/ops/scripts/emit-ui-docs.ts`, so it matches the deployed bytecode on **elysium-mainnet** (chain `99901`). Each file is the bare ABI array — exactly what viem, ethers, wagmi and `cast` expect. ## Everything at once **[tartarus.json](/abi/tartarus.json)** — every contract with its address, the chain id, the factory's start block and the third-party addresses (Uniswap, Permit2) you need alongside them. One download is enough to build a client. ```bash curl -O https://tartarus.world/abi/tartarus.json ``` ```ts import tartarus from "./tartarus.json"; const factory = tartarus.contracts.find((c) => c.name === "TartarusLaunchFactory")!; await client.readContract({ address: factory.address as `0x${string}`, abi: factory.abi, functionName: "launchFee", }); ``` ## Individual contracts | File | Surface | Address | | --- | --- | --- | | [`TartarusFeeEscrow.json`](/abi/TartarusFeeEscrow.json) | 8 fn · 4 ev · 6 err | `0x44D1065fD59280cFa26B7BE3dBf13F364314616F` | | [`TartarusMemeHook.json`](/abi/TartarusMemeHook.json) | 41 fn · 14 ev · 23 err | `0x00b2Ff65E61aD8c57F957624EDc6C81908Ade044` | | [`TartarusLaunchLocker.json`](/abi/TartarusLaunchLocker.json) | 14 fn · 5 ev · 10 err | `0x8fABb780838c1662235002F70A94a7ef882a9598` | | [`TartarusLaunchFactory.json`](/abi/TartarusLaunchFactory.json) | 63 fn · 23 ev · 32 err | `0x765f2ce211050ec8280257d76072cAa480D75A00` | | [`TartarusLaunchDeployer.json`](/abi/TartarusLaunchDeployer.json) | 5 fn · 0 ev · 7 err | `0x94b7ed8968bF4323c03DE97395801D487771C44E` | | [`TartarusLaunchAndBuy.json`](/abi/TartarusLaunchAndBuy.json) | 11 fn · 4 ev · 12 err | `0x044a835456057915028DDdFfa161DeA9d4d40615` | | [`TartarusLauncherToken.json`](/abi/TartarusLauncherToken.json) | 16 fn · 2 ev · 7 err | per launch — no fixed address | | [`TartarusRewardsDistributorFactory.json`](/abi/TartarusRewardsDistributorFactory.json) | 8 fn · 1 ev · 7 err | `0x3c8E2F5924629e629C5c4bFc56FddC4B2794E4F9` | | [`TartarusRewardsDistributor.json`](/abi/TartarusRewardsDistributor.json) | 22 fn · 7 ev · 20 err | per launch — no fixed address | > [!NOTE] > `TartarusLauncherToken` and `TartarusRewardsDistributor` are deployed **once per launch**, so > they have no fixed address. Read the token's record from the factory with `getLaunchedToken(token)` and its distributor from `TartarusRewardsDistributorFactory.distributorOf(token)` — see > [Addresses](/documentation/addresses). ## Using one file ```bash curl -O https://tartarus.world/abi/TartarusLaunchFactory.json ``` ```ts import factoryAbi from "./TartarusLaunchFactory.json"; ``` The JSON is the array itself, not an object wrapping one, so it drops straight into `abi:` with no unwrapping. --- # Errors Every custom error across every contract. When a transaction reverts with a bare selector, look it up here. | Selector | Error | Declared by | |---|---|---| | `0x646cf558` | `AlreadyClaimed()` | TartarusRewardsDistributor | | `0x0dc149f0` | `AlreadyInitialized()` | TartarusLaunchLocker | | `0x3a81d6fc` | `AlreadyRegistered()` | TartarusMemeHook, TartarusRewardsDistributorFactory | | `0xd4b3db41` | `AlreadyRolledOver()` | TartarusRewardsDistributor | | `0xa741a045` | `AlreadySet()` | TartarusMemeHook, TartarusLaunchFactory | | `0xfba5239c` | `BridgeFactoryNotSet()` | TartarusLaunchFactory | | `0xd66282ab` | `BridgeWalletOutOfGas()` | TartarusLaunchFactory | | `0xfaca41ea` | `BuyTooLarge(uint256)` | TartarusLaunchAndBuy | | `0xf0f25a33` | `ClaimWindowClosed()` | TartarusRewardsDistributor | | `0x29dfa3ce` | `ClaimWindowOpen()` | TartarusRewardsDistributor | | `0x4ca249dc` | `Create2EmptyBytecode()` | TartarusLaunchDeployer, TartarusRewardsDistributorFactory | | `0x87ab5572` | `CreatorFeeOutOfBounds(uint256,uint256,uint256)` | TartarusMemeHook | | `0x1e8a6f77` | `EpochOverdrawn(uint256,uint256,uint256)` | TartarusRewardsDistributor | | `0xfb8f41b2` | `ERC20InsufficientAllowance(address,uint256,uint256)` | TartarusLauncherToken | | `0xe450d38c` | `ERC20InsufficientBalance(address,uint256,uint256)` | TartarusLauncherToken | | `0xe602df05` | `ERC20InvalidApprover(address)` | TartarusLauncherToken | | `0xec442f05` | `ERC20InvalidReceiver(address)` | TartarusLauncherToken | | `0x96c6fd1e` | `ERC20InvalidSender(address)` | TartarusLauncherToken | | `0x94280d62` | `ERC20InvalidSpender(address)` | TartarusLauncherToken | | `0xb7594bec` | `ExcludedAccount()` | TartarusRewardsDistributor | | `0xb06ebf3d` | `FailedDeployment()` | TartarusLaunchDeployer, TartarusRewardsDistributorFactory | | `0x0c40ebbe` | `HolderRewardsDistributorTaken(address)` | TartarusLaunchFactory | | `0x0a85dc29` | `HookNotImplemented()` | TartarusMemeHook | | `0xe14cee31` | `IncorrectLaunchFee(uint256,uint256)` | TartarusLaunchFactory | | `0x197001d6` | `InexactQuoteTransfer(address,uint256,uint256)` | TartarusMemeHook | | `0xcf479181` | `InsufficientBalance(uint256,uint256)` | TartarusFeeEscrow, TartarusLaunchDeployer, TartarusRewardsDistributorFactory | | `0x30e44a1d` | `InsufficientUnallocated(uint256,uint256)` | TartarusRewardsDistributor | | `0x31cdb504` | `InternalSwapRequiresOperator()` | TartarusMemeHook | | `0xc6cc5d7f` | `InvalidBps()` | TartarusMemeHook | | `0x68b42c59` | `InvalidLaunchConfigId()` | TartarusLaunchFactory | | `0x430f13b3` | `InvalidName()` | TartarusLaunchDeployer | | `0xc256622b` | `InvalidPoolKey()` | TartarusMemeHook | | `0x09bde339` | `InvalidProof()` | TartarusRewardsDistributor | | `0x21bfef7e` | `InvalidStartingTick()` | TartarusLaunchFactory | | `0x04119bc4` | `InvalidSymbol()` | TartarusLaunchFactory, TartarusLaunchDeployer | | `0x270815a0` | `InvalidTickSpacing()` | TartarusLaunchFactory | | `0xa8b63076` | `LaunchConfigDisabled()` | TartarusLaunchFactory | | `0x1de25df3` | `LaunchDependenciesNotWired()` | TartarusLaunchFactory | | `0x57332dcf` | `LaunchDeployerNotSet()` | TartarusLaunchFactory | | `0xecb27319` | `LaunchEconomicsMismatch(bytes32,bytes32)` | TartarusLaunchFactory | | `0xff633a38` | `LengthMismatch()` | TartarusRewardsDistributor | | `0x85b8e2f4` | `MetadataTooLong()` | TartarusLaunchDeployer | | `0x3672d25f` | `MinimumOutputRequired()` | TartarusMemeHook | | `0xc2caa2a6` | `NoBalance()` | TartarusFeeEscrow | | `0x6a0b56a8` | `NoEpoch()` | TartarusRewardsDistributor | | `0xa3fef2f8` | `NoPendingChange()` | TartarusLaunchFactory | | `0x09ee12d5` | `NotAContract()` | TartarusLaunchFactory | | `0x502ba015` | `NotApprovedLauncher()` | TartarusLaunchAndBuy | | `0xb9f93944` | `NotCreatorFeeRecipient()` | TartarusLaunchFactory | | `0x02f5a580` | `NotCreatorFeeRecipient(address,address)` | TartarusRewardsDistributorFactory | | `0x32cc7236` | `NotFactory()` | TartarusMemeHook, TartarusLaunchLocker, TartarusLaunchDeployer | | `0x8d42130c` | `NotFeeSweepOperator()` | TartarusMemeHook | | `0x3f29331a` | `NothingToHarvest()` | TartarusRewardsDistributor | | `0x00f6b210` | `NothingToRescue()` | TartarusMemeHook | | `0xea9eaa96` | `NotLaunchForwarder()` | TartarusLaunchFactory | | `0xae18210a` | `NotPoolManager()` | TartarusMemeHook, TartarusLaunchAndBuy | | `0x20fdc658` | `NotPositionManager()` | TartarusLaunchLocker | | `0x584a7938` | `NotWhitelisted()` | TartarusLaunchFactory | | `0x1e4fbdf7` | `OwnableInvalidOwner(address)` | TartarusMemeHook, TartarusLaunchLocker, TartarusLaunchFactory, TartarusLaunchAndBuy, TartarusRewardsDistributor | | `0x118cdaa7` | `OwnableUnauthorizedAccount(address)` | TartarusMemeHook, TartarusLaunchLocker, TartarusLaunchFactory, TartarusLaunchAndBuy, TartarusRewardsDistributor | | `0x2fab92ca` | `OwnershipCannotBeRenounced()` | TartarusMemeHook, TartarusLaunchLocker, TartarusLaunchFactory, TartarusRewardsDistributor | | `0xfe3099b6` | `PositionAlreadyLocked()` | TartarusLaunchLocker | | `0x6b49c94a` | `PositionNotHeld()` | TartarusLaunchLocker | | `0x8b44d1ae` | `QuoteTokenNotApproved()` | TartarusLaunchFactory | | `0x3ee5aeb5` | `ReentrancyGuardReentrantCall()` | TartarusFeeEscrow, TartarusMemeHook, TartarusLaunchFactory, TartarusLaunchAndBuy, TartarusRewardsDistributor | | `0x662b8375` | `RewardsFactoryNotSet()` | TartarusLaunchFactory | | `0xa8ce4432` | `SafeCastOverflowedIntToUint(int256)` | TartarusMemeHook | | `0x24775e06` | `SafeCastOverflowedUintToInt(uint256)` | TartarusMemeHook | | `0x5274afe7` | `SafeERC20FailedOperation(address)` | TartarusFeeEscrow, TartarusMemeHook, TartarusLaunchLocker, TartarusLaunchFactory, TartarusLaunchAndBuy, TartarusRewardsDistributor | | `0x70a5d0b7` | `SeedNotViable()` | TartarusLaunchFactory | | `0x71c4efed` | `SlippageExceeded(uint256,uint256)` | TartarusMemeHook, TartarusLaunchAndBuy | | `0x18049c93` | `SymbolReserved()` | TartarusLaunchFactory | | `0x30214533` | `SymbolTaken(address)` | TartarusLaunchFactory | | `0xb79d40e8` | `TimelockExpired(uint256)` | TartarusLaunchFactory | | `0x810c4f2a` | `TimelockNotElapsed(uint256)` | TartarusLaunchFactory | | `0xcbdb7b30` | `TokenNotFound()` | TartarusLaunchFactory, TartarusRewardsDistributorFactory | | `0x90b8ec18` | `TransferFailed()` | TartarusFeeEscrow, TartarusLaunchAndBuy | | `0x1c43b976` | `TransferFailed(address,uint256)` | TartarusRewardsDistributor | | `0xecb8b30d` | `UnexpectedNativeTransfer()` | TartarusRewardsDistributor | | `0xf7139e33` | `UnknownPool()` | TartarusMemeHook | | `0x195a3ca1` | `WrongValue(uint256,uint256)` | TartarusLaunchAndBuy | | `0xd92e233d` | `ZeroAddress()` | TartarusFeeEscrow, TartarusMemeHook, TartarusLaunchLocker, TartarusLaunchFactory, TartarusLaunchAndBuy, TartarusLauncherToken, TartarusRewardsDistributorFactory, TartarusRewardsDistributor | | `0x1f2a2005` | `ZeroAmount()` | TartarusRewardsDistributor | | `0x6d01a63b` | `ZeroBuy()` | TartarusLaunchAndBuy | --- # Events Every event with its `topic0`, for indexers. | topic0 | Event | Contract | |---|---|---| | `0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925` | `Approval(address,address,uint256)` | TartarusLauncherToken | | `0x0784ee1925b81ceb1c4f909a494f11a35badad9b999bcceb2d1a5ad597a25abe` | `BridgeWalletCreated(address,address)` | TartarusLaunchFactory | | `0xbcd7adc790364324699753894577b3c54f5c088135312e9ded902d5ae85d9a44` | `BridgeWalletSkipped(address)` | TartarusLaunchFactory | | `0xd8138f8a3f377c5259ca548e70e4c2de94f129f5a11036a15b69513cba2b426a` | `Claimed(address,uint256)` | TartarusFeeEscrow | | `0x4ec90e965519d92681267467f775ada5bd214aa92c0dc93d90a5e880ce9ed026` | `Claimed(uint256,address,uint256)` | TartarusRewardsDistributor | | `0xdbc1ea3a8459e4c7e11fb385b52bbb5cc8c8ab85eec5d883ac9aa78c171f5141` | `ClaimedToken(address,address,uint256)` | TartarusFeeEscrow | | `0x7da93c15168a31f5bef5eb6ae509ab6c0af7799e7a17a7f1ace13cc30012b96e` | `CreatorFeeBoundsUpdated(uint256,uint256)` | TartarusMemeHook | | `0xbe2de91c1cbef653c760573fff8355c0c851d35ed2a898342b4db556301cccf4` | `CreatorFeeRecipientChangeCancelled(address,address)` | TartarusLaunchFactory | | `0x7f119e44c84a715429bee60d30ad2e14afdef6c60bb1a7eaa01290ecf6d1b2e5` | `CreatorFeeRecipientChangeProposed(address,address,address,uint256,uint256)` | TartarusLaunchFactory | | `0xb45e6b72a7de9a2077babe9717744436f3880e114099956ca85f91a77469a532` | `CreatorFeeRecipientUpdated(bytes32,address,address)` | TartarusMemeHook | | `0x308c390ed1ab5873392818e036cabdf408bc8ad042fbaead3108954ff75ba980` | `CreatorFeeRecipientUpdated(address,address,address)` | TartarusLaunchFactory | | `0x4e45da441832cf53bdaa69235704fc0575e68210f459ee1562911024b12967d5` | `Credited(address,address,uint256)` | TartarusFeeEscrow | | `0x5d104c62f50449fadfe6f4013c8f36588d32737f94b5ac9b83ddad33b3e1ffdf` | `CreditedToken(address,address,address,uint256)` | TartarusFeeEscrow | | `0x3ef9707063aece3a59b74cf57d0c1ed7a69cceb8630a1b4d42bd1a3f6743f560` | `DistributorDeployed(address,address,address,address,bytes32)` | TartarusRewardsDistributorFactory | | `0xf93b11821bfd7ed7369be380bca7b1b96d8fe3219b8b59f682b1cf9aa348f1ae` | `ElysiumBridgeFactorySet(address)` | TartarusLaunchFactory | | `0x92c85369eead3d8555a7298769ddafde22d0f2f5f1ec05b2aa4059520533f003` | `EpochPublished(uint256,bytes32,uint256,uint64)` | TartarusRewardsDistributor | | `0x83f2b279b6151af5a15cdbe8471d34fe8b34aed9bd9514ffeadb57c9ea366e61` | `ExclusionUpdated(address,bool)` | TartarusRewardsDistributor | | `0x1edf3afd4ac789736e00d216cd88be164ddcef26a6eedcc30cdb0cb62f3741b1` | `FactorySet(address)` | TartarusMemeHook | | `0x1edf3afd4ac789736e00d216cd88be164ddcef26a6eedcc30cdb0cb62f3741b1` | `FactorySet(address)` | TartarusLaunchLocker | | `0xae994ca926e252e299c3df7516cb609272a57bf80b0e0715297e55939f873420` | `FeeSweepOperatorUpdated(address)` | TartarusMemeHook | | `0xfa07446fad45314351eb89109a154880278451332bb87f1824d435fe58da5939` | `Harvested(uint256,uint256)` | TartarusRewardsDistributor | | `0xcf54f8cbb5f9c3a7061090beb81e0337579190a0137ed295e96cfecd9bab9542` | `HookFeeCollected(bytes32,address,uint256)` | TartarusMemeHook | | `0xedd96c570c6e5ef9add0378e59df53579a283889dc5dab6440ef6eca2ee6c8ce` | `LaunchConfigAdded(uint256)` | TartarusLaunchFactory | | `0x2f8ba78ae68cfd0c82c7756c540eaf4eead3341aef9ccebcb91d546bff10d62b` | `LaunchConfigUpdated(uint256)` | TartarusLaunchFactory | | `0xd5ea7aa3e328a0594dcf6914cd9e5369779efaa194ee4dd4c5afcad4f4ebbb0c` | `LaunchDeployerSet(address)` | TartarusLaunchFactory | | `0xec774f0683e9ac48e8d835f412f9f877a8a5dee9af3170d78cf3ef33149d15e7` | `Launched(address,address,address,uint256,uint256)` | TartarusLaunchAndBuy | | `0x4f1ea5016c51c2f82324e00e9b8a4a95ee5aeaa10c653dabaec5f1bc9047ba0b` | `LaunchEnabledUpdated(bool)` | TartarusLaunchFactory | | `0xc799be5eb19a1a6d6ba7368d21e2bc367c8a335e4a07cd3d954482e6f714d3c5` | `LaunchFeeUpdated(uint256)` | TartarusLaunchFactory | | `0x21ef2af51c6ff72bd6d142e45d2e01f0448c8fff50168bb73a1b862c427a76af` | `LaunchForwarderChangeCancelled(address)` | TartarusLaunchFactory | | `0x40399dc1b46c2cbda42768dcf83944f62829bd45095ab22fb45ca8e387589135` | `LaunchForwarderChangeProposed(address,address,uint256,uint256)` | TartarusLaunchFactory | | `0x56b32d3633fed72f97c4df44a78b5fa04f1d662d4bddebcd8a9b216d26d093ad` | `LaunchForwarderSet(address)` | TartarusLaunchFactory | | `0x06ace689d4f9cc53fc9c544dbffde33266dedb0b9941235bc3086fb0824c03fd` | `LaunchSalt(address,bytes32)` | TartarusLaunchFactory | | `0x6968b68c1fb468c8b257b012290bf803a6a6d7e79468e0326050724f7573cf01` | `MaxInternalPriceImpactUpdated(uint256)` | TartarusMemeHook | | `0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0` | `OwnershipTransferred(address,address)` | TartarusMemeHook | | `0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0` | `OwnershipTransferred(address,address)` | TartarusLaunchLocker | | `0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0` | `OwnershipTransferred(address,address)` | TartarusLaunchFactory | | `0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0` | `OwnershipTransferred(address,address)` | TartarusLaunchAndBuy | | `0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0` | `OwnershipTransferred(address,address)` | TartarusRewardsDistributor | | `0x38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e22700` | `OwnershipTransferStarted(address,address)` | TartarusMemeHook | | `0x38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e22700` | `OwnershipTransferStarted(address,address)` | TartarusLaunchLocker | | `0x38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e22700` | `OwnershipTransferStarted(address,address)` | TartarusLaunchFactory | | `0x38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e22700` | `OwnershipTransferStarted(address,address)` | TartarusLaunchAndBuy | | `0x38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e22700` | `OwnershipTransferStarted(address,address)` | TartarusRewardsDistributor | | `0xeed2d18eb96f3c2cb8c7b6993512a506c170e17d29355f2d7a0d5961f338de09` | `PoolConversionSkipped(bytes32,uint256)` | TartarusMemeHook | | `0x0fbb28f9c335f55dcc5cc19e595ab55f9e6a0fd1b58ad77be3a98f99901daaff` | `PoolFeesRescued(bytes32,address,uint256,uint256)` | TartarusMemeHook | | `0x2b33b68d948eb789fd57906bde3dc24d9f748c1df9b98d7359910f6aa1c06e2f` | `PoolFeesSwept(bytes32,uint256,uint256)` | TartarusMemeHook | | `0x01bf263a1db1652580721573296e1a1fa70b3d4c87f61d02a69c4e1109d2d573` | `PoolRegistered(bytes32,address,address,address)` | TartarusMemeHook | | `0x1403071d82f5bf603e26432d669b9a1c9234f678a80ccd63ec2c4adf2428543b` | `PoolSeeded(address,uint256,uint128,uint256,uint256)` | TartarusLaunchFactory | | `0x2cabb2a2973327d5863ceb4707e9441851243897e86d587ee35943599752eb54` | `PositionLocked(address,uint256)` | TartarusLaunchLocker | | `0x0c69d666e7ba3ce81300fa14cb3e9c03596138bfc8ecec0eacb0a8ef31442cdc` | `ProtocolFeeBpsUpdated(uint256)` | TartarusMemeHook | | `0xc1b5345cce283376356748dc57f2dfa7120431d016fc7ca9ba641bc65f91411d` | `ProtocolFeeRecipientUpdated(address)` | TartarusMemeHook | | `0x08a843625584ce1b83674addd05c56adbb63d59b99f3c11eabaab869734fd4c5` | `QuoteTokenApprovalUpdated(address,bool)` | TartarusLaunchFactory | | `0x3af790fafda720819b2fc6e15090606e81154e0ac9a92d38ecad006d99d20ecc` | `Rescued(address,address,uint256)` | TartarusLaunchAndBuy | | `0xccaf9a1889196a07179623710ec905c42226f34ce9561f3e6a8ad2c545f64946` | `ReservedSymbolUpdated(string,bool)` | TartarusLaunchFactory | | `0x8584e5b12dbb992c74d868f3beef4084c46551850974ad70f5442beba0124fdf` | `RewardsDistributorFactorySet(address)` | TartarusLaunchFactory | | `0xf52162a5e697efbc1377904d60df6a1b984cc85ca7964998e201cbea0b8e7549` | `RolledOver(uint256,uint256)` | TartarusRewardsDistributor | | `0x94536bc2a8cb6d35f91cb7caf0da3a1c0af50c75501a7b69ab149d80c3ce1b60` | `TokenLaunched(address,address,address,uint256,bytes32,uint256,address,int24,uint16,uint16)` | TartarusLaunchFactory | | `0xaf33c4aba92959b3e7ddc83ab728938262da159a6c05ca836f6c46f9bcb2c740` | `TokenSupplyLocked(address,uint256)` | TartarusLaunchLocker | | `0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef` | `Transfer(address,address,uint256)` | TartarusLauncherToken | | `0xef2b562a67f01ed4b7c4265ec09b539039c6d5dd7e752191d3940508c3dc0068` | `WhitelistedLauncherUpdated(address,bool)` | TartarusLaunchFactory |