# Welcome

**Kumbaya is a crypto social launchpad and DEX built on** [**MegaETH**](https://megaeth.com/) - a real-time blockchain with \~10ms blocks. That means your trades, swaps, and token launches happen at the speed of a website, not a blockchain.

This is your guide to using the Kumbaya app. Whether you're here to swap tokens, provide liquidity, launch your own coin, or just hang out and yap about memes, you'll find what you need below.

## What you can do on Kumbaya

* 🔁 [**Swap tokens**](/trading/swap) - trade any listed token in seconds.
* 💧 [**Provide liquidity**](/liquidity/add-liquidity) - earn fees by adding liquidity to pools.
* 🔥 [**Launch a token**](/launchpad/launch-a-token) - create your own token with a built-in bonding curve.
* 🗣 [**Yap and tip**](/social-features/comments-and-yaps) - comment on tokens, tip creators, build your profile.

## New to crypto?

Start with [**What is Kumbaya?**](/getting-started/what-is-kumbaya) and then [**Connect your wallet**](/getting-started/connect-your-wallet). You'll be set up in under a minute - no seed phrase to write down, no browser extension to install.

> 💡 **Pro tip:** the **`/` key** opens [the search overlay](/getting-started/search) from any page. It's the fastest way to jump to any token or pool on Kumbaya.

## Need help?

* [**FAQ**](/help/faq) - common questions
* [**Troubleshooting**](/help/troubleshooting) - when something doesn't work
* Find us on Twitter [@kumbaya\_xyz](https://twitter.com/kumbaya_xyz)


# What is Kumbaya?

Kumbaya is two things in one app:

1. **A decentralized exchange (DEX)** where you can swap tokens and provide liquidity. It runs on a fork of Uniswap V3, so the trading experience will feel familiar if you've used Uniswap before.
2. **A social launchpad** where anyone can launch a new token using a bonding curve, comment on launches, tip creators, and build a profile around their on-chain activity.

It's all built on **MegaETH**, a layer 2 designed for real-time applications. In practice that means swaps, comments, and tips feel instant - no waiting 10–20 seconds for a transaction to confirm.

## Who Kumbaya is for

* **Traders** who want fast, cheap swaps on a Uniswap-style DEX.
* **Liquidity providers** who want to earn fees on concentrated liquidity positions.
* **Token creators** who want to launch a coin without writing any code.
* **Communities** who want a hangout that's wired into the on-chain action.

> Kumbaya is **not** custodial. You own your wallet, your tokens, and your positions. We never hold your funds.

## What's coming next

Head to [**Connect your wallet →**](/getting-started/connect-your-wallet)


# Connect your wallet

When you click **Connect** in the top-right of the Kumbaya app, the **"How do you want to use Kumbaya?"** modal asks you to pick one of two paths. Both are non-custodial - you stay in full control of your funds - but they unlock different parts of the platform.

![The Kumbaya connect-wallet modal showing the two paths: Kumbaya Wallet (Full Access) and DEX only (Stay Private)](/files/3YjwiYyogWpzTsoCl9pX)

| Path                               | What you get                                   | What you don't get                    |
| ---------------------------------- | ---------------------------------------------- | ------------------------------------- |
| **Kumbaya Wallet** *(recommended)* | DEX + Launchpad + comments + tipping + profile | -                                     |
| **DEX only**                       | Swap + liquidity pools                         | Launchpad, profile, comments, tipping |

You can switch later if you change your mind.

## Kumbaya Wallet *(Full Access)*

This is the path you want if you plan to launch tokens, comment, tip, or have a profile. It uses [Privy](https://www.privy.io/) under the hood, and supports two ways to sign in:

**1. With your existing wallet** - connect MetaMask, Coinbase Wallet, Rainbow, or any WalletConnect-compatible wallet. Privy attaches your Kumbaya profile to that wallet address. Your assets stay in your wallet; Privy doesn't move them. You sign every transaction in your existing wallet as normal.

**2. With email or a social account** - Privy creates an embedded wallet for you, tied to your login. You don't install a browser extension, you don't write down a seed phrase. Supported login methods:

* **Email** (passcode sent to your inbox)
* **Google**, **X (Twitter)**, **Discord**, **GitHub**, **Telegram**

After signing in, your Kumbaya profile appears under that wallet - whether it was your existing one or a freshly-created embedded one.

> Your private key is never shared with Kumbaya. Embedded-wallet keys are held in Privy's secure infrastructure; only you can authorize transactions. You can export your key at any time if you want to move to a different wallet.

> ⚠️ **Each social-login method gives you a&#x20;*****different*****&#x20;embedded wallet.** Signing in with Google one day and email the next gives you two separate wallets with two separate balances. Pick one method and stick with it (or write down which method you used so you can find your funds again). Connecting an existing external wallet doesn't have this problem - that wallet is yours regardless.

## DEX only *(Stay Private)*

This is the path for traders who only want to swap and provide liquidity, with no social profile or attribution. You connect an existing wallet directly - no Kumbaya account.

* **Supported wallets:** any standard EVM wallet (MetaMask, Coinbase, Rainbow, WalletConnect-compatible).
* **What works:** the swap UI, the pool UI, adding/removing liquidity, collecting LP fees.
* **What doesn't:** launching tokens, claiming a token listing, posting yaps, tipping, building a profile, the launchpad feed actions. If you try to access launchpad-only features, the app will prompt you to switch to the Kumbaya Wallet path.

You can always switch from DEX-only to Kumbaya Wallet later - the modal lets you upgrade without losing your wallet connection.

## Switch to MegaETH

Kumbaya runs on **MegaETH**. The first time you do anything that requires a transaction, your wallet will prompt you to add and switch to the network. Approve the prompt - that's it.

If you want to add it manually:

| Field               | Mainnet value                     |
| ------------------- | --------------------------------- |
| **Network name**    | MegaETH                           |
| **Chain ID**        | `4326`                            |
| **RPC URL**         | `https://mainnet.megaeth.com/rpc` |
| **Currency symbol** | `ETH`                             |
| **Block explorer**  | `https://mega.etherscan.io/`      |

For testnet (chain ID `6343`), use `https://carrot.megaeth.com/rpc` and the testnet explorer.

## Get some ETH

You'll need a small amount of ETH on MegaETH to pay for gas. Most actions cost a tiny fraction of a cent thanks to MegaETH's design.

* **Bridge from Ethereum mainnet** using [**Rabbithole**](https://rabbithole.megaeth.com/bridge), the official MegaETH bridge.
* **On testnet**, grab free ETH from the [MegaETH testnet faucet](https://testnet.megaeth.com).

Once you've got a balance, you're ready to [**make your first swap →**](/trading/swap).

## Disconnecting

Click your address in the top-right corner of the app, then **Disconnect**. Your wallet stays - you're just signing out of this session.


# The basics: tokens, gas, and slippage

Three things every Kumbaya user should be comfortable with. None of them are complicated.

## Tokens

A **token** on Kumbaya is just an ERC-20 - a contract on MegaETH that keeps track of who owns how much. Every token has a contract address (a long hex string starting with `0x...`), a symbol like `$KUM`, and a name. The address is what's authoritative - anyone can deploy a token called "USDC", but only one address is the real USDC.

When you "send a token" or "swap a token", you're really telling that token's contract to update its internal balances. There's no central database and no platform-side ledger.

## Gas

Every transaction on MegaETH costs **gas** - a tiny amount of ETH paid to the network for processing the transaction. On MegaETH, gas costs are typically a small fraction of a cent per swap, so this rarely matters in practice. But you do need a small amount of ETH in your wallet to cover gas, even if you're swapping other tokens.

If you ever see a transaction fail with **"Insufficient gas"**, top up some ETH and try again.

## Slippage

When you swap, the price you saw in the UI and the price you actually got can differ. That difference is **slippage**, and it happens because:

* Other people's trades may move the price between when you clicked and when your transaction confirms.
* Your own trade moves the price (especially in pools with less liquidity).

Kumbaya lets you set a **slippage tolerance** - a maximum acceptable difference. If the actual price would exceed your tolerance, the transaction reverts and you're not charged.

* **Default tolerance**: 0.5%. Fine for most major tokens.
* **For new launches or thin pools**: try 1–3%.
* **If your swap fails with "Slippage exceeded"**: bump tolerance or reduce trade size.

For more detail see [**Slippage and gas explained**](/trading/slippage-and-gas).

## Where to next

* [**Connect your wallet →**](/getting-started/connect-your-wallet) if you haven't yet
* [**Swapping tokens →**](/trading/swap) for your first trade


# The search overlay

There's a search bar at the top of every page on Kumbaya. Click it (or press **`/`** on your keyboard) and a search overlay opens - it's the fastest way to jump to any token or pool on the platform.

![The Kumbaya search overlay in default state, showing curated bluechip tokens and top pools](/files/r3LEqhTzFrEiLxOb0hAp)

## What it covers

The overlay searches across **everything Kumbaya tracks on-chain**:

* **Tokens** - both curated (bluechip, verified) and any token launched on the Kumbaya launchpad.
* **Pools** - every Uniswap V3 pool indexed on Kumbaya, with fee tier and TVL.

It's powered by Kumbaya's [search service](https://github.com/Kumbaya-xyz/documentation/tree/main/developer/apis/search-service.md), so results are fast and update as new tokens and pools land on-chain.

## How to use it

### Open it

* Click the **search bar** in the top header, or
* Press **`/`** on your keyboard.

### Type to search

Start typing a token symbol, name, or even a partial contract address. Matches surface as you type - no need to press Enter.

![Typing a query - results filter live with fuzzy substring matching across symbol, name, and address](/files/p6XBjnLi56mLjbgWU4Jk)

The search is fuzzy and substring-aware: typing `eth` will match "Ether", "Wrapped Ether", "ETH BURN", "ETHERBUTTS", and so on. Each row shows the token's icon, symbol, name, partial address, and current price + 24h change.

### Filter by what you're looking for

Two layers of filtering:

**Tab - Kumbaya Tokens / All Tokens**

* **Kumbaya Tokens** - curated and verified tokens (the default). Best for general use.
* **All Tokens** - every indexed token, including unverified ones. Use when you're looking for something specific that isn't on the curated list.

**Chips - All / Bluechip / Memecoins / Pools / ⚙**

* **All** - tokens and pools mixed, default view.
* **Bluechip** - only blue-chip tokens (ETH, USDC, etc.).
* **Memecoins** - community tokens launched on the Kumbaya launchpad.
* **Pools** - only pools, useful when you're looking for liquidity opportunities.
* **⚙ (cog)** - opens **Manage**, where you can control which token lists are active and add or remove manually-imported tokens. See [**Managing token lists**](#managing-token-lists) below.

### Select a result

Click any token to go to its detail page (`kumbaya.xyz/#/launch/<address>` for launchpad tokens, or the swap page pre-filled with that token). Click any pool to go to the pool detail page.

### Close it

* Press **Esc**.
* Click outside the overlay.

## What appears by default

When you open the overlay without typing, you'll see two curated lists:

* **Bluechip** - the most recognizable tokens on Kumbaya (ETH, MegaUSD, WETH, USDT0, etc.).
* **Pools** - top Uniswap V3 pools by TVL.

This is meant as a quick-jump for the most common destinations.

## Managing token lists

Click the **⚙ cog** in the search overlay to open **Manage**. It has two tabs:

**Lists**

* Every token list connected to your Kumbaya session - Kumbaya's default list plus any you've imported.
* Toggle each list on or off. Tokens from disabled lists won't appear in your search results.
* Add a new list by pasting its URL (any standard [Uniswap-format token list](https://tokenlists.org/) works).
* Remove lists you've imported.

**Tokens**

* Tokens you've manually imported by contract address (separate from any list).
* Search/paste a contract address to import a single token without adding a whole list.
* Remove a manually-imported token to clear it from your search results.

This is the right place to go if a token isn't showing up in search but you have its contract address, or if you want a curated list provider's tokens to appear alongside Kumbaya's defaults.

## Tips

* **Pasting an address works.** If you paste a `0x…` token or pool address into the search box, the matching token or pool surfaces directly. Useful for verifying an address you got from elsewhere.
* **Search launchpad tokens by ticker.** Launchpad tokens are tagged in the search results so you can tell them apart from pre-existing tokens.
* **Use `/` first thing.** It's the fastest navigation shortcut on the platform - works from any page.

## Where to next

* [**Discovering tokens →**](/launchpad/discover) - the launchpad feed view
* [**Swapping tokens →**](/trading/swap) - once you've found one


# Swapping tokens

Swapping is the simplest thing you can do on Kumbaya. Pick the token you have, pick the token you want, click swap. The whole flow is designed to feel instant - most swaps confirm in well under a second.

![The Swap page on kumbaya.xyz](/files/7BvvmOTMlaFTfRkVMlCC)

## Make your first swap

1. Go to [**kumbaya.xyz/#/swap**](https://kumbaya.xyz/#/swap) (or click **Swap** in the top nav).
2. In the top field, pick the token you're paying with and enter an amount.
3. In the bottom field, pick the token you want to receive. The amount fills in automatically based on the current best route.
4. Review the **rate**, **price impact**, and **minimum received** lines.
5. If this is your first time swapping with that token, you'll see an **Approve** button - click it and confirm the approval in your wallet. (You only do this once per token.)
6. Click **Swap** and confirm in your wallet.

That's the whole flow.

## Reading the swap details

Before you confirm, expand the swap card to see:

* **Rate** - how much of the destination token you get per unit of source token.
* **Price impact** - how much your trade is moving the market price. Under 1% is normal; over 5% means the pool is thin and you might want a smaller trade.
* **Minimum received** - the absolute least you'll get back, accounting for slippage.
* **Network fee (gas)** - what you'll pay in ETH to MegaETH for processing the transaction.
* **Route** - which pools the trade is going through. Sometimes the best price comes from hopping through an intermediate token like ETH or USDC.

## Slippage

Slippage is the difference between the price you saw and the price you actually got. It exists because the pool moves with every trade, including yours.

* The default tolerance is **0.5%** for most tokens - fine for blue chips.
* For new or low-liquidity tokens, you may need to bump it to **1–3%**.
* Click the gear icon ⚙️ in the swap card to change it.

If your swap fails with a **"Slippage exceeded"** error, it usually means the price moved more than your tolerance allowed between clicking and confirming. Try again with a slightly higher tolerance or a smaller size.

For more, see [**Slippage and gas explained**](/trading/slippage-and-gas).

## Finding the right token

Most tokens you'd want to trade show up in the picker without any setup:

* **Verified tokens** (curated by Kumbaya) appear by default and carry a **Verified** badge.
* **Launchpad tokens** show up automatically as soon as they're launched - you don't need to import them. You can also go to the launch page (`kumbaya.xyz/#/launch/<address>`) and use the swap panel right there.

The only time you'd manually import is for an arbitrary on-chain token that isn't on Kumbaya's lists and didn't launch through the launchpad. To do that:

1. Click the token picker.
2. Paste the token's contract address into the search box.
3. Kumbaya looks it up on-chain and lets you select it. Once imported it carries an **Imported** badge.

### ⚠️ Unknown tokens carry real risk

Tokens that aren't on Kumbaya's verified list show up with an **Unknown** badge (golden, tooltip: *"This token is not on our token list or launchpad"*). Treat these with caution. Anyone can deploy an ERC-20 with any name and symbol, and an Unknown token can:

* **Be a scam impersonator** - same name/symbol as a popular token but a different address that nobody has audited.
* **Be a honeypot** - designed to let you buy but not sell, trapping your funds.
* **Have admin transfer/freeze powers** - the contract owner may be able to move tokens out of your wallet, blacklist your address, or pause transfers entirely.
* **Have hidden taxes** - fees on every transfer that drain value out of holders.

Before swapping into an Unknown token:

* Verify the contract address is the one you expect - check the block explorer ([mega.etherscan.io](https://mega.etherscan.io/)) and the project's official channels.
* Start with a tiny amount and try selling it back before scaling in.
* Read the contract source on the explorer if you can - look for `onlyOwner` modifiers on transfer functions, blocklists, or taxable transfers.
* When in doubt, skip it. Kumbaya can't recover funds lost to a malicious token contract.

## Wrap and unwrap ETH

ETH and WETH are interchangeable on Kumbaya. The app wraps and unwraps automatically when needed for a swap, so you don't have to think about it. If you ever want to manually wrap/unwrap, do an ETH ↔ WETH swap - the rate is always 1:1 with no fees.

## Common swap errors

| Error                  | What it means                         | Fix                                |
| ---------------------- | ------------------------------------- | ---------------------------------- |
| Slippage exceeded      | Price moved more than your tolerance  | Increase slippage or reduce size   |
| Insufficient gas       | Not enough ETH to pay the network fee | Top up your ETH balance            |
| Insufficient allowance | Token approval was too small          | Approve again with a higher amount |
| Pool not found         | No pool exists for that pair          | Try routing through ETH or USDC    |

### "Transaction failed on chain"

If your swap submitted, then the UI showed **"Transaction failed on chain. Please try again."**, the transaction reached MegaETH but reverted. The Kumbaya UI doesn't always surface the underlying revert reason - to see it, **open the transaction on the block explorer** ([mega.etherscan.io](https://mega.etherscan.io/)) by clicking the tx hash in the failed-swap notification (or by pasting the hash into the explorer's search). The explorer shows the contract-level error message, which usually points straight at the cause:

* `Too little received` → slippage exceeded; bump tolerance or reduce size.
* `STF` → token allowance too low; re-approve.
* A revert from the token contract itself (e.g. `transfer paused`, `blocklisted`) → the token has admin restrictions; see the [**Unknown tokens warning**](#-unknown-tokens-carry-real-risk) above.
* No reason given → usually a bad path or a token with non-standard transfer behaviour.

If something else goes wrong, check [**Troubleshooting**](/help/troubleshooting).

## Where to next?

* [**Adding liquidity →**](/liquidity/add-liquidity) to earn fees on your tokens
* [**Discovering tokens →**](/launchpad/discover) on the launchpad


# Slippage and gas explained

When you trade on Kumbaya, two things shape what you actually receive: **slippage** (how much the price can move during your trade) and **gas** (what you pay the network to process it). Both are normal AMM concepts; both are tunable.

## Slippage

The **slippage tolerance** is the maximum difference you'll accept between the price quoted in the UI and the price your trade fills at. If the difference would exceed your tolerance, the transaction reverts and your funds stay where they are.

### Why slippage happens

* **Other trades.** Between you clicking and your transaction landing, other people's trades may have moved the pool's price.
* **Your own trade.** Even your trade, by itself, moves price along the pool's curve. Bigger trades = bigger move.

The UI shows you the "minimum received" line - that's what you're guaranteed assuming your tolerance holds.

### Setting slippage

Click the gear icon ⚙️ on the swap card to set tolerance.

| Token type                          | Reasonable starting point |
| ----------------------------------- | ------------------------- |
| Major / blue-chip (ETH, USDC, USDT) | **0.5%**                  |
| Mid-cap, established                | **1%**                    |
| Pre-graduation launchpad tokens     | **1–3%**                  |
| Thin liquidity / very new           | **3–5%**                  |

Higher tolerance = more likely to fill, but with more downside risk on price.

### Failed with "Slippage exceeded"?

Either bump tolerance or reduce trade size. If you're trading on a thin pool, splitting one big trade into several smaller ones often helps because each trade moves price less than one combined.

## Price impact

**Price impact** is the *minimum* price movement caused by your trade alone, regardless of slippage. Slippage tolerance protects you from total movement (yours + others); price impact is just yours.

The UI shows price impact as a percentage. Under 1% is normal. Over 5% means the pool is quite thin for your trade size - consider trading a smaller amount.

## Gas

Every transaction on MegaETH costs ETH paid to the network. MegaETH is fast and cheap (10ms blocks, designed for real-time apps), so gas is typically a small fraction of a cent per swap. You shouldn't really need to think about it - but you do need a small ETH balance for it to come out of.

If you bridge in just enough ETH to swap with, you might end up with no gas. Always keep a small buffer. The UI warns you when you're cutting it close.

### Common gas-related errors

* **"Insufficient gas for transaction"** - you don't have enough ETH. Bridge or buy more.
* **Transaction stuck pending** - rare on MegaETH given block speed. If it happens, it usually clears quickly; otherwise check your wallet for a pending tx.

## Where to next

* [**Swapping tokens →**](/trading/swap)
* [**Adding liquidity →**](/liquidity/add-liquidity) - same slippage logic applies


# Reading a price chart

Every token's detail page on Kumbaya has a chart at the top. It's not just a price chart - it's three different views you can switch between, plus chart-type toggles and creator-trade markers. This page covers what each one shows.

## The three views

A row of buttons above the chart switches between:

* **Price** - the token's USD price over time, with a volume histogram below.
* **Buyers** - cumulative number of unique buyers over time (a step-area chart).
* **Flow** - net USD buy/sell flow per period (a histogram, green for net buying, red for net selling).

## Timeframes

The same row offers three timeframes: **1m**, **1h**, **1d**. There's no 5m, 15m, or 4h - Kumbaya keeps it to three.

* **1m** rolls a window of the last \~1,440 minutes (24 hours).
* **1h** rolls \~720 hours (30 days).
* **1d** rolls \~120 days (4 months).

## Price view

The default. You'll spend most of your time here.

### Candlestick or Area

A toggle on the chart switches between:

* **Candlestick** - open/high/low/close per period, with green candles for periods that closed up and red for periods that closed down. The close of the most recent (still-in-progress) period snaps to the live price.
* **Area** - a smoothed area chart of close prices, easier to read at a glance.

Your choice is remembered between visits.

### Volume below the candles

Underneath the price, a volume histogram shows how much USD changed hands in each period.

### Creator trade markers

When the token's creator buys or sells, the chart marks it on the price line:

* **Green up-arrow with "B"** below the bar - creator buy.
* **Red down-arrow with "S"** above the bar - creator sell.

Markers snap to the nearest real candle (within \~2 hours). They're a useful signal: if a creator is buying or selling their own token, that's worth knowing.

### Hover for OHLC

Hovering anywhere on the chart shows:

* **O / H / L / C** - Open, High, Low, Close for that period.
* **Volume** - total USD volume during that period.
* **Change** - % change from open to close (green if up, red if down).
* **Time** - period start, in your local time on the 1m and 1h views, in UTC on the daily view.

## Buyers view

A cumulative count of unique buyers over the chosen timeframe. The line only goes up - it doesn't subtract sellers. Useful for spotting:

* **Steady accumulation** - a smooth curve climbing means new buyers are arriving consistently.
* **Sharp jump** - a spike usually pairs with a price candle pushing higher; check the Price view to confirm.
* **Flat** - no new wallets have bought; existing holders are trading among themselves (or volume has dried up).

## Flow view

A histogram of **net buy minus sell volume** per period.

* **Green bars** - more USD bought than sold; net buying pressure.
* **Red bars** - more USD sold than bought; net selling pressure.

Flow is the cleanest way to spot turning points: a string of green bars suddenly going red often precedes a price move down.

## Gap-fill behaviour

If no trades happen in a given period, the chart carries the previous close price forward as a flat candle (open = high = low = close = previous close). That's why you'll often see flat horizontal segments on low-activity tokens - those reflect on-chain reality, not chart bugs.

Creator-trade markers only snap to *real* candles (periods where actual trades occurred), not gap-fill candles.

## Pre-graduation vs. post-graduation

For a launchpad token still in the bonding phase, the chart reflects whatever swaps happened on the V3 pool - there's nothing visually different about it from a graduated token's chart. Price moves in both directions as buyers and sellers act on the curve, not "only up". After graduation, the chart continues uninterrupted on the same pool, so you won't see a visible break.

## What to look for

Take all of this with a grain of salt - DYOR - but useful signals:

* **Volume + price both rising** - interest is real.
* **Price up, volume thin** - be skeptical; one buyer can move a low-liquidity pool.
* **A green Flow histogram while price is flat** - buyers absorbing supply at this level; could break up.
* **A red Flow histogram while price is rising** - distribution into the rally; rallies on net selling rarely hold.
* **Creator-sell markers on the way up** - exactly what it looks like.

The **Buyers tab** on the token page (separate from the Buyers chart view) shows the actual holder leaderboard - useful when you want to know *who* is holding rather than *how many*.

## Where to next

* [**Discovering tokens →**](/launchpad/discover) for the wider feed view
* [**Swapping tokens →**](/trading/swap) when you're ready to trade


# What is concentrated liquidity?

Kumbaya's DEX runs on **Uniswap V3 concentrated liquidity** - a different model from the classic 50/50 pool you might know from Uniswap V2. This page explains the idea in plain English so the rest of the liquidity docs make sense.

## The classic AMM, briefly

In a classic 50/50 AMM (Uniswap V2-style), liquidity providers deposit equal value of two tokens. The pool's price is whatever ratio those tokens currently sit at. As people trade, the ratio shifts and price moves. Anyone's deposit is "active" across **all possible prices, from zero to infinity**.

This is simple but inefficient - most of the deposited capital sits at prices that almost never get touched.

## What concentrated liquidity changes

In Uniswap V3 (which Kumbaya forks), **you choose a price range** when you deposit:

* You only deposit liquidity active between, say, $1.00 and $1.20 for a stablecoin pair, or $2,500 and $3,500 for ETH/USDC.
* Your liquidity is "concentrated" inside that range - much denser per dollar than a V2-style position.
* You earn fees from any swap that crosses through your range.
* If price leaves your range, your position stops earning fees and converts entirely to one of the two tokens.

The trade-off: **you earn more fees per dollar of liquidity** while in range, but you're exposed if price moves out.

## Mental model: setting your range

Think of your range as a bet: "I think the price will spend a lot of time between X and Y." Tighter ranges earn more per dollar but are more likely to go out of range.

| You think the pair is…           | Pick a range           |
| -------------------------------- | ---------------------- |
| Pegged stablecoins (1:1 forever) | Very tight, e.g. ±0.5% |
| Two correlated tokens            | Tight, e.g. ±10%       |
| Volatile but you have a thesis   | Medium, e.g. ±25%      |
| You have no view on direction    | Wide, or full-range    |

## What's a "tick"?

The pool tracks price in discrete steps called **ticks**. Each tick represents a 0.01% (1 basis point) change in price - `price = 1.0001^tick`. You set your range by picking a lower tick and an upper tick.

You don't usually deal with raw ticks in the UI - Kumbaya's UI lets you drag bounds on a chart or type prices, and converts under the hood.

## Fee tiers

V3 pools come in four fee tiers:

| Fee tier  | Tick spacing | Best for                                 |
| --------- | ------------ | ---------------------------------------- |
| **0.01%** | 1            | Stable ↔ stable (e.g. USDC/USDT)         |
| **0.05%** | 10           | Major correlated pairs (e.g. ETH/USDC)   |
| **0.30%** | 60           | Most pairs (the V2 default)              |
| **1.00%** | 200          | Exotic, volatile, or low-liquidity pairs |

Tighter fee tiers have tighter tick spacing - meaning your range can be more precise - but tend to host less volatile pairs.

## Impermanent loss, in plain English

If price moves significantly while you're an LP, the value of your position can be lower than if you'd just held the two tokens. That's **impermanent loss**, and it applies to all AMM liquidity - V2 and V3.

In V3, IL is **amplified within your range** because your capital is concentrated, but you're also earning more fees. Whether the trade-off is positive depends on volume, range tightness, and how often price moves out of your range.

## Where to next

* [**Adding liquidity →**](/liquidity/add-liquidity)
* [**Managing your positions →**](/liquidity/manage-positions)
* [**Removing liquidity and collecting fees →**](/liquidity/remove-liquidity)


# Fees

Every Kumbaya pool charges a small fee on every swap. That fee gets split between the people providing liquidity and the protocol itself. There's nothing else baked on top - no interface fee, no hidden tax. The pool's swap fee is the only fee a trader pays.

For an overview of the fee tiers themselves (0.01%, 0.05%, 0.30%, 1.00%) and how fees accumulate inside your position, see [**What is concentrated liquidity?**](/liquidity/concentrated-liquidity) and [**Adding liquidity**](/liquidity/add-liquidity).

## The protocol fee

The **protocol fee** is the cut Kumbaya governance takes from the swap fee before the rest goes to LPs. Crucially, it doesn't change what traders pay - it just changes how the existing swap fee is split.

The protocol fee depends on the pool's fee tier:

| Pool type                                     | Fee tier  | Protocol fee                          | What LPs earn         |
| --------------------------------------------- | --------- | ------------------------------------- | --------------------- |
| **Standard pools** (Pool page)                | **0.01%** | 25%                                   | 75% of swap fees      |
| **Standard pools** (Pool page)                | **0.05%** | 25%                                   | 75% of swap fees      |
| **Standard pools** (Pool page)                | **0.30%** | \~16.67% (1/6)                        | \~83.33% of swap fees |
| **Standard pools** (Pool page)                | **1.00%** | \~16.67% (1/6)                        | \~83.33% of swap fees |
| **Launchpad-originated pools** (`FireLaunch`) | 1.00%     | 0% (protocol takes its cut elsewhere) | 100% of swap fees     |

These splits keep Kumbaya **in line with the industry standard** - Uniswap caps protocol fees at 25% on its own deployments, and Kumbaya's lower tiers match that ceiling while the higher tiers come in below it. So an LP on a 0.30% pool earns about 0.25% per swap (the other \~0.05% is the protocol fee); on a 1% pool, they earn about 0.83%.

## How the cap works

At the contract level the protocol fee can technically go up to 50% (Kumbaya's V3 fork allows a wider range than upstream). The current values above sit well below that cap and within Uniswap's standard range.

For the technical mechanics of how the protocol fee is computed inside the V3 contract (`feeAmount / feeProtocol`), see the developer docs on [**Differences from Uniswap V3**](https://github.com/Kumbaya-xyz/documentation/tree/main/developer/dex/differences-from-uniswap.md).

## Where the fees go

Protocol fees fund Kumbaya's infrastructure, ongoing development, and ecosystem alignment with MegaETH.

For launchpad pools specifically, the equivalent of "protocol fees" is taken via a separate fee-streaming system rather than through the V3 protocol-fee mechanism - see [**Creator fees**](/launchpad/creator-fees) for how that works on the launchpad side.

## Where to next

* [**Adding liquidity →**](/liquidity/add-liquidity) - pick a tier and provide liquidity
* [**Removing liquidity and collecting fees →**](/liquidity/remove-liquidity) - claim what you've earned
* [**Creator fees →**](/launchpad/creator-fees) - how launchpad creators get paid


# Adding liquidity

Providing liquidity (LPing) means depositing two tokens into a pool so other people can swap between them. In return, you earn a slice of every swap fee that pool generates.

![The Pools page on kumbaya.xyz, showing TVL, volume, and pool list](/files/GUNKyQXwEQoAlc66n7Nz)

Kumbaya uses **concentrated liquidity** (Uniswap V3-style), so you get to choose the price range your liquidity is active in. That's a powerful tool - but it also means LPing here takes a little more thought than depositing into a vault and walking away.

> 💡 New to concentrated liquidity? Start with [**What is concentrated liquidity?**](/liquidity/concentrated-liquidity) for the mental model, then come back here.

## The short version

1. Go to [**kumbaya.xyz/#/pool**](https://kumbaya.xyz/#/pool) and click **+ New Pool**.
2. Pick your two tokens.
3. Pick a **fee tier** (most pairs have one obvious choice - see below).
4. Pick a **price range**.
5. Enter how much of each token you want to deposit.
6. Approve and confirm.

You now own an NFT representing your position. As people swap through your pool while the price is in your range, fees accumulate inside that NFT.

## Step-by-step

### 1. Pick the tokens

Choose the two tokens you want to provide liquidity for. The order doesn't matter - the app sorts them automatically.

### 2. Pick a fee tier

Each pair can have pools at four fee tiers:

| Fee tier  | Best for                                                                          |
| --------- | --------------------------------------------------------------------------------- |
| **0.01%** | Stablecoin ↔ stablecoin (USDC ↔ USDT)                                             |
| **0.05%** | Major pairs that move closely (WETH ↔ stablecoin, sometimes also stable ↔ stable) |
| **0.30%** | Most pairs - the default for "regular" volatility                                 |
| **1.00%** | Exotic, low-liquidity, or volatile pairs                                          |

If a pool already exists with serious liquidity at one tier, that's almost always the right one to join - that's where the volume is. The app shows you which tiers have existing liquidity.

### 3. Pick a price range

This is the most important step. Your liquidity only earns fees when the **current price is inside your chosen range**.

* **Tight range** (close to current price) → higher fees per dollar of liquidity, but your position goes "out of range" sooner if the price moves.
* **Wide range** → lower fees per dollar of liquidity, but you stay in range longer.
* **Full range** → behaves like Uniswap V2 LP. Always earning, always low capital efficiency.

A common starting point is **±10% to ±25% around the current price** for major pairs, and wider for volatile or new tokens.

> ⚠️ **Out-of-range warning.** When the price leaves your range, you stop earning fees and your position converts entirely to one of the two tokens. That's not a loss in the bug sense - it's how concentrated liquidity works - but it does mean you're now holding 100% of one asset. See [**Managing your positions**](/liquidity/manage-positions) for what to do about it.

### 4. Enter your deposit amounts

Once you've set tokens, fee tier, and range, the app calculates the ratio you need. If the current price is right in the middle of your range, you'll deposit roughly equal value of each token. If the current price is near one edge, you'll deposit mostly one of them.

You can type the amount in either field - the other auto-fills.

### 5. Approve and confirm

If this is your first time providing liquidity with these tokens, you'll be prompted to approve each token. Then click **Add Liquidity** and confirm in your wallet.

A few seconds later, your **position NFT** appears under **My Positions** on the Pool page.

## Understanding what you own

Your position is an ERC-721 NFT that encodes:

* The token pair
* The fee tier
* Your price range (lower tick and upper tick)
* The amount of liquidity you contributed

You can transfer the NFT, sell it, or use it as collateral elsewhere - but most of the time you'll just hold it and let it accrue fees.

## What you'll see after

* **In Range** - the current price is between your bounds, and you're earning fees.
* **Out of Range** - the price has moved outside your bounds. You stop earning fees but you don't lose the position. If the price comes back, you start earning again.
* **Uncollected fees** - the fees that have accumulated but haven't been claimed. You can claim them anytime without closing the position. See [**Removing liquidity and collecting fees**](/liquidity/remove-liquidity).

## A note on impermanent loss

If the price moves significantly during your time as an LP, the value of your position can be lower than if you'd just held the two tokens - that's **impermanent loss**, and it applies to all AMM liquidity. Concentrated liquidity *amplifies* it within your range. The fees you earn are what compensates you for that risk. Pick ranges thoughtfully and don't LP what you can't afford to be down on.

## Where to next?

* [**Managing your positions →**](/liquidity/manage-positions)
* [**Removing liquidity and collecting fees →**](/liquidity/remove-liquidity)


# Managing your positions

Once you've added liquidity, your position is an NFT in your wallet. Every position appears under **My Positions** on the [Pool page](https://kumbaya.xyz/#/pool).

## What you'll see for each position

* **Token pair + fee tier** - `USDC / WETH 0.05%`, etc.
* **Status** - In Range or Out of Range.
* **Liquidity value** - current USD value of the underlying tokens.
* **Uncollected fees** - fees that have accumulated since you last claimed.
* **Range** - the lower and upper bounds you originally chose.

## In Range vs. Out of Range

* **In Range** - the current price is between your bounds. Your liquidity is active and earning fees on every swap that crosses through your range.
* **Out of Range** - price has moved outside your bounds. You stop earning fees, and your position has converted entirely to whichever of the two tokens is on the side price moved away from.

Out-of-range isn't a loss in the bug sense - it's how concentrated liquidity is supposed to work. If price comes back into your range, you start earning again. If you don't think it will, you can rebalance (close + reopen with new bounds).

## Adding to an existing position

You can add more liquidity to an existing position without changing its range. Click the position, then **Add**.

This is useful when:

* You want to increase your exposure with the same range thesis.
* You've collected fees and want to compound them back in.

## Collecting fees

Click the position, then **Collect Fees**. This claims uncollected fees to your wallet without touching the underlying liquidity.

You can collect as often or as rarely as you like - there's no penalty for letting fees accumulate.

## When to rebalance

Rebalancing means closing the position (or part of it) and opening a new one with different bounds. Reasons you might:

* Price has stabilized in a new range and you want to recapture fees.
* The pair has gotten more (or less) volatile and your old range no longer makes sense.
* You want to shrink or widen your range for a different fee/risk profile.

Rebalancing involves gas + a swap (to rebalance ratios), so don't do it constantly - for active LPing, larger occasional moves usually beat lots of small adjustments.

## Where to next

* [**Adding liquidity →**](/liquidity/add-liquidity)
* [**Removing liquidity and collecting fees →**](/liquidity/remove-liquidity)


# Removing liquidity and collecting fees

You can pull liquidity out of a position at any time, in any amount - there are no lockups, no withdrawal queues, no platform-side limits.

## Two separate actions

These are intentionally split so you can claim fees without rebalancing or closing:

* **Collect Fees** - claims accumulated trading fees to your wallet. Doesn't change your liquidity or range.
* **Remove Liquidity** - pulls some or all of the underlying tokens out of the position.

## How to remove

1. Go to the [Pool page](https://kumbaya.xyz/#/pool) and click your position.
2. Click **Remove Liquidity**.
3. Pick the percentage to remove (25% / 50% / 75% / 100%, or a custom value).
4. Optionally toggle whether to receive WETH as ETH (most people prefer ETH).
5. Confirm in your wallet.

## What you get back

If you remove 100%:

* Your share of token A and token B in their current pool ratio.
* All uncollected fees, automatically.
* The position NFT is burned.

If you remove less than 100%:

* A proportional share of the underlying tokens.
* Fees are typically collected at the same time (depending on the UI flow).
* The position stays open with reduced liquidity.

## Token ratios depend on price

If your range is currently in-range, you'll receive a mix of both tokens.

If your range is out-of-range:

* Price below your range → you'll receive **only token A**.
* Price above your range → you'll receive **only token B**.

This is normal - the position has fully converted to one side because that's how concentrated liquidity works.

## Where to next

* [**Managing your positions →**](/liquidity/manage-positions)
* [**Adding liquidity →**](/liquidity/add-liquidity) - open a new position with new bounds


# Discovering tokens

Most of the action on Kumbaya happens on the **launchpad feed** - a stream of new and trending tokens with their threads, tip jars, and trade activity all visible at a glance.

![The launchpad feed on kumbaya.xyz](/files/nky1KDLD4pN1auYbhBxJ)

## The feeds

Kumbaya groups tokens into a few different feeds. You can switch between them in the launchpad nav:

* **All / Trending** - the main feed of recent and active launches across categories.
* [**Memes**](/launchpad/dares-and-memes) - meme-style launches.
* [**Dares**](/launchpad/dares-and-memes) - community-challenge launches with a different visual treatment.

You can also reach a specific creator's launches from their profile page.

## Reading a token card

Each entry in the feed shows:

* **Image + name + ticker** - the basics.
* **A graduation progress bar** for tokens still in the bonding phase, or a gold **Grad** badge for graduated ones.
* **Stats** - comment count, recent activity, and more.
* **A snippet of the prompt** - the creator's brief.

Click into a card to land on the token's full detail page.

## On the token detail page

The token page (`kumbaya.xyz/#/launch/<token-address>`) is where everything happens:

* **Header** - name, ticker, prompt, socials, graduation progress, and the gold "Grad" pill if the token has graduated.
* **Stats row** - Market Cap, ATH MC, price changes across windows.
* **Trade panel** - buy/sell directly without leaving the page.
* **Tabs** - Floor, Thread, Buyers, Tips, Trades.
* **Creator earnings** - only visible if you're the creator, after you've claimed the listing.

![A token detail page on kumbaya.xyz](/files/6iZCCPfJq7E54Xyb9IIz)

## Tips for finding worth-trading tokens

Take all of this with a grain of salt - DYOR - but useful signals:

* **Active thread + buyers diversity.** A token where one wallet holds 90% of the supply is a different shape from a token with hundreds of small buyers.
* **Graduation proximity.** Tokens close to graduation often see speculative interest before and right after the graduation transaction.
* **Creator track record.** Click the creator's profile - have they launched before? Did those launches graduate?
* **Image quality and prompt clarity.** Effort signals.

## When you're ready to trade

* **Buy with the in-page trade panel** - fastest path. It auto-fills the right pool.
* **Or use the main** [**Swap**](/trading/swap) **page** - the launched token will be discoverable by ticker or address.

## Where to next

* [**Comments and yaps**](/social-features/comments-and-yaps) - how to participate in token threads
* [**Tipping**](/social-features/tipping) - how to tip creators / good comments
* [**Launching your own token**](/launchpad/launch-a-token) - your turn


# Launching your own token

Launching a coin on Kumbaya is meant to feel like posting - pick a name, write a prompt, hit launch. Under the hood, every launch is a fully on-chain ERC-20 deployed by the Kumbaya launchpad, with a Uniswap V3 pool, a bonding curve, and a graduation path. There's no platform-side custody or admin lever on your token.

> 🛡 **Audited.** The entire Kumbaya launchpad - including the token contract - has a signed BlockSec audit, and the underlying DEX inherits Uniswap V3's audit lineage. See [**Audit & security**](/help/audit-and-security) for the details.
>
> See [**Creator fees**](/launchpad/creator-fees) for the economics and [**How bonding curves and graduation work**](/launchpad/bonding-curves) for the mechanics.

> The Create flow is behind sign-in, so the screenshots below are placeholders. To start a launch, click the orange **+ Launch Token** button at the top of the [launchpad feed](https://github.com/Kumbaya-xyz/documentation/tree/main/client/.gitbook/assets/screenshots/launchpad-feed.png).

## Before you start

* **You'll need a wallet connected** ([Connect your wallet](/getting-started/connect-your-wallet)).
* **You'll need a small amount of ETH** for gas. If you want to buy your own tokens at launch (the "Initial buy" field), you'll need extra ETH for that too.
* Pick the **kind** of token you're launching - Meme or Dare. This sets the visual treatment and which feed it shows up in. See [**Dares and Memes**](/launchpad/dares-and-memes).

## The four steps

### Step 1 - Choose Type

* **Meme** - a launch focused on community, vibes, and traders.
* **Dare** - a launch tied to a community challenge or stunt.

You can change this up to launch time.

### Step 2 - Details

The fields, in the order they appear:

* **Token name** (1–50 characters) - what shows up at the top of your token's page.
* **Ticker symbol** (1–10 characters, **uppercase letters and numbers only**, auto-uppercased) - the `$SYMBOL` you'll see everywhere. Be unique.
* **Prompt** (8–500 characters) - a short description. The placeholder hints "*Choose your text carefully to incentivise content creation*" - this is what people who post in your token's thread are responding to. Treat it like a community brief, not a tagline.
* **Website** *(optional)* - `yourcoin.com`
* **X handle** *(optional)* - `@yourcoin` (1–15 characters)
* **Telegram group** *(optional)* - `t.me/yourcoin` (5–32 characters)

### Step 3 - Media & Initial Buy

**Token image** - drag and drop or pick a file. PNG/JPG/GIF; the app validates size on upload. This shows everywhere your token appears (feed, swap UI, profile).

**Initial buy** *(optional but recommended)* - buy your own tokens in the same launch flow:

* Enter either an amount of `$YOURTICKER` you want to receive **or** an amount of ETH you want to spend. The other field auto-calculates from the curve.
* There's a per-launch cap based on the bonding-curve allocation. With the production defaults (1B supply, 72% to curve) the theoretical maximum is about **720 million tokens** - but the UI shows the live limit.
* The app warns you if you don't have enough ETH for the buy and gas combined.

> ℹ️ **Why initial buy is two transactions.** Your launch is one transaction (`ignite` to deploy the token + create the pool); your buy is a second transaction (a normal swap on the pool you just created). The app signs both up front and broadcasts them back-to-back. **They are not atomic** - in the unlikely event the buy fails (slippage, gas spike), the token is still deployed, you just won't have bought any. You can buy later by going to your token's page and using the swap.

### Step 4 - Confirm & Launch

A preview card on the right (or bottom on mobile) shows the final look. Hit **Launch $YOURTICKER**. The button label updates if anything's missing - for example "Need X ETH for gas" or "Connect to Launch".

You'll be asked to sign in your wallet. Then you wait a few seconds for confirmation and you're redirected to your token's page.

## What gets created on-chain

In the time it takes for the page to redirect, the protocol has:

1. **Deployed your `$YOURTICKER` ERC-20 token** at a fresh address with a fixed supply of **1,000,000,000 tokens** (1B, 18 decimals).
2. **Created a Uniswap V3 pool** for `$YOURTICKER / WETH` at the 1% fee tier.
3. **Seeded the pool with 50 stacked concentrated positions** that act as a bonding curve.
4. **Reserved tail liquidity** (28% of supply) as a full-range position above the graduation tick, so the pool never empties.
5. *(If you opted in)* Bought your initial allocation by swapping ETH for your new token in a second transaction.

The token is now alive and tradeable.

## What you control vs. what's fixed

| You choose                                | Set automatically by the launchpad                              |
| ----------------------------------------- | --------------------------------------------------------------- |
| Name, symbol, prompt, image, socials      | Total supply: **1B tokens**                                     |
| Type (Meme or Dare)                       | Fee tier: **1%**, position count: **50**                        |
| Whether to do an initial buy and how much | Allocation: **72%** to bonding curve, **28%** to tail liquidity |
|                                           | No on-token creator allocation in the default flow              |

The "Set automatically" column comes from chain-level defaults baked into the launchpad - it keeps every launch consistent and predictable for buyers. Power users who want a custom curve or a different split can call the launchpad contracts directly (see the [**developer launching guide**](https://github.com/Kumbaya-xyz/documentation/tree/main/developer/launchpad/launching.md)).

## After you launch

* Your token has a page at `kumbaya.xyz/#/launch/<address>` (the app uses hash-based routing).
* The page shows trading activity, the comments thread, the buyer leaderboard, and a graduation progress bar.
* You can collect creator earnings as they accrue - see [**Creator fees**](/launchpad/creator-fees).
* If your wallet wasn't your original deployer (e.g. you launched from a hot wallet you've since changed), see [**If your token shows as Unclaimed**](/launchpad/unclaimed-tokens).

## Common questions

**Can I change the name, image, or prompt later?** Editing the off-chain metadata (description, image, social links, category) is currently admin-gated rather than self-serve. If you need a change made, reach out and the team can update it. The on-chain symbol and supply cannot change at all - those are immutable on the token contract.

**Can I rug?** Not by design. The bonding-curve and tail liquidity aren't yours to pull - they live in the V3 pool as concentrated positions. The standard launch flow doesn't even give creators a direct token allocation. Your reward as a creator is the post-graduation streaming fees, which only flow once the token actually trades enough to graduate.

**What happens if no one buys?** Nothing bad. The token sits dormant. There's no cost to leaving it alone, and someone discovering it later can still trade it.

**What if I want to launch a coin without an initial buy?** Leave the Initial Buy fields empty and proceed. The launch becomes a single transaction.

## Where to next

* [**How bonding curves and graduation work →**](/launchpad/bonding-curves)
* [**Creator fees: how you earn →**](/launchpad/creator-fees)
* [**If your token shows as Unclaimed →**](/launchpad/unclaimed-tokens)


# How bonding curves and graduation work

Every token launched on Kumbaya goes through two phases: **bonding** and **graduated**. The mechanics aren't complicated, but they affect how your token trades and how creators earn - so it's worth understanding.

## The bonding phase

When a token launches, the protocol seeds its pool with a stack of overlapping liquidity positions. As people buy, they consume liquidity in the lower price ranges first, which makes the price go up. As people sell, the same in reverse - price goes down.

This is what people mean by **bonding curve**: early buyers get a better price, and the price moves predictably as more tokens are bought. There's no "hidden" liquidity sitting somewhere else - what you see in the pool is what you get.

Two things to know:

* **You're trading on a normal Uniswap pool.** Buying a bonding-phase token is just a swap. You'll see it in the same swap UI as any other token. The "curve" is a property of how liquidity is laid out, not a separate AMM.
* **There's a "tail" position sitting above the graduation tick.** Once the price hits the graduation tick, the token is eligible to graduate - but the actual graduation transaction has to be triggered, and there's a short grace period before just anyone can call it. If buyers push the price *past* the graduation tick during that window, the tail is what keeps the token tradeable: it provides liquidity above the curve so trades don't fail. The tail is intentionally thin (it's there as a safety net, not a deep market), so trades in that range move price sharply until graduation finalizes and the pool consolidates into one full-range position.

![A graduated token's detail page, showing the gold border and "Grad" pill](/files/6iZCCPfJq7E54Xyb9IIz)

## Graduation

Each token has a **graduation tick** - a specific price level. When the price reaches that level (i.e. enough buying pressure has lifted the token there), the token is eligible to graduate.

Graduation is a one-time event that:

1. **Burns the bonding-curve positions.** The stacked liquidity is dismantled.
2. **Mints one big full-range Uniswap V3 position** with the recovered liquidity.
3. **Hands that position to a contract called `FireStream`**, which collects fees from then on and distributes them between protocol recipients and the creator.
4. **Starts the Tip Jar burn countdown** (1–90 days, set by Kumbaya governance) for any *user-side* tip credits that were never sent to a creator. Tips already received by creators are protected.

After graduation, the token trades like any other Uniswap V3 token. The pool address doesn't change; the liquidity layout does.

## How long does graduation take?

It depends entirely on how much demand the token gets. Some tokens graduate in hours, some never do. **Force graduation** is also a thing: if a token's been around long enough (currently 180 days / 6 months, governance-set), anyone can trigger graduation manually so the token can transition out of the bonding state regardless.

## Why graduation matters to traders

* **Before graduation**, a swap moves price along the curve. Trades feel "thicker" near the launch tick (lots of liquidity, small impact) and "thinner" near the graduation tick (less liquidity, more impact per dollar).
* **After graduation**, trades behave like any other V3 pool - price impact depends on whatever organic LPs have added at relevant ticks, plus the full-range graduated position. There's usually plenty of liquidity around the price the token graduated at.

If you're trading a token that's *close to* graduation, expect a flurry of activity around the actual graduation event (the tx that calls `graduate`) - people watching the curve often pile in just before.

## Why graduation matters to creators

This is where the economics shift hardest:

* **During the bonding phase, your creator income comes from tips** that buyers send you; the streaming trading-fee income switches on at graduation. (Pre-grad trading fees flow to the protocol.)
* **Post-graduation, creators earn a share of every trade** through `FireStream`. That income keeps flowing as long as people trade your token, in both ETH and your own token.

Full breakdown: [**Creator fees**](/launchpad/creator-fees).

## What about the curve parameters - can a creator pick them?

On Kumbaya's UI, **no** - the curve range, fee tier, position count, and vesting duration are set to chain-level defaults so every launch behaves consistently. Creators get to pick name, symbol, image, prompt, social links, and (optionally) an initial buy. Power users who want non-default parameters can launch by calling the launchpad contracts directly (see the [developer docs](https://github.com/Kumbaya-xyz/documentation/tree/main/developer/launchpad/launching.md)).

## Where to next

* [**Launching your own token →**](/launchpad/launch-a-token)
* [**Creator fees →**](/launchpad/creator-fees)
* [**If your token shows as Unclaimed →**](/launchpad/unclaimed-tokens)


# Creator fees: how you earn

If you launch a token on Kumbaya, your on-chain reward comes from one main source: **a share of trading fees after the token graduates**, paid out forever for as long as people trade your token. This page covers how that works, what triggers it, and where to find your earnings in the app.

![A graduated token's detail page - the Claim Fees panel for streaming trading fees sits on the right side once you're signed in as the creator.](/files/6iZCCPfJq7E54Xyb9IIz)

> **Quick clarification on terminology.** "Tips" on Kumbaya are a separate income stream - they're how *content creators* (people posting yaps, replies, and media in token threads) earn. As a token creator, your launch reward is the streaming-fee income this page covers. And if you also post content in your own token's thread, you can earn tips on top of that, like any other content creator - see [**Tipping**](/social-features/tipping).

## How it works

When your token graduates, the bonding-curve liquidity consolidates into a single **full-range Uniswap V3 position**. That position keeps collecting fees on every swap going forward, forever. The fees are then split as follows on every claim:

| Recipient            | Share |
| -------------------- | ----- |
| **Protocol**         | 80%   |
| **You, the creator** | 20%   |

On-chain today the protocol collects a single **80%** and you get the **20%** remainder. Of that 80%, **30%** (of total fees) is reserved against an upcoming Kumbaya feature that rewards cults - it's collected by the protocol for now and will be split out when the feature ships (the docs will be updated then, and the reserved funds distributed on release). Your 20% is unaffected either way.

You earn **both sides** of every trade:

* The **numeraire** (typically ETH) when someone buys your token.
* Your **own token** when someone sells it.

So a successful, actively traded token compounds for you in both directions. The longer it's traded, the more you accrue.

## Where to claim

On your token's page on `kumbaya.xyz`, the creator earnings panel shows **Claim Fees**. Clicking it triggers a single transaction that sweeps both sides of the accumulated fees to your wallet.

> 💡 **Anyone can call Claim Fees.** The smart contract lets anyone trigger a fee distribution; you just receive your share. So even if you're hands-off, your earnings will flow through if someone else calls it. Calling it yourself is the most reliable.

## When earnings start

| Phase                              | What's earning                                                                                         |
| ---------------------------------- | ------------------------------------------------------------------------------------------------------ |
| **Pre-graduation (bonding phase)** | The curve is building toward graduation - the milestone that switches on your fee stream.              |
| **At graduation**                  | The bonding-curve liquidity consolidates into the full-range NFT, and your streaming-fee meter starts. |
| **Post-graduation (forever)**      | Every swap on the pool flows fees to you (alongside the protocol's share).                             |

Graduation is what starts your fee stream, and **force graduation** guarantees it gets there: anyone can trigger graduation after a long enough wait (currently 180 days / 6 months, governance-set), so your stream begins regardless.

## Visual: timeline of token-creator earnings

```
LAUNCH                              GRADUATION                         FOREVER
   │                                    │                                 │
   ▼                                    ▼                                 ▼
   ────────[ no creator income ]────────┤
                                        │
                                        ┌─ Streaming trading fees ─────────▶
                                        │  (Claim any time, any trade adds)
```

## Common questions

**Are creator fees real ETH and real tokens?** Yes - real on-chain ETH and your real on-chain token, sent directly to your wallet via on-chain transactions. There's no platform-side "points" sitting between the contract and you.

**What if my token never graduates?** Your streaming fees switch on at graduation, and it's guaranteed to get there eventually: if a token sits a long time without crossing the threshold, anyone can force-graduate it (currently 180 days / 6 months, governance-set), starting your fee stream.

**Do tips affect my creator income at all?** No. Tips are a content-creator economy: buyers earn small tip-bonus credits when they buy a token, and they can tip *any* user - typically yappers in the token thread - by signing a gasless message. Tips land in the recipient's **Tip Jar** (jar icon in the header, **Received** tab). If you also post content in your token's thread, you can earn tips that way like anyone else, but it's separate from the streaming-fee income that's tied to launching the token.

**My wallet isn't recognized as the creator - what do I do?** You're probably looking at an [unclaimed token](/launchpad/unclaimed-tokens). Most of the time this means your launch was associated with a different signer (e.g. an embedded wallet from a different login). The fix is to **claim** the listing - see that page for the full flow.

**Do I get fees on swaps that happen&#x20;*****during*****&#x20;the graduation transaction?** The graduation transaction itself sweeps any pre-graduation fees to the protocol; from the graduation transaction onward, every swap flows to you under the post-grad split.

**Why don't creators get a share of pre-graduation trading fees?** By design - pre-grad trading happens on a bonding curve where every trade contributes to graduating the token. Pre-grad fees fund protocol operations, such as the cost to run our guardian which performs graduations and tips at the protocols expense. The creator's reward is the *post*-graduation stream that compounds for the lifetime of the token.

## Where to next

* [**How bonding curves and graduation work →**](/launchpad/bonding-curves)
* [**Tipping →**](/social-features/tipping) - the content-creator side of Kumbaya's economy
* [**If your token shows as Unclaimed →**](/launchpad/unclaimed-tokens)
* [**Launching your own token →**](/launchpad/launch-a-token)


# If your token shows as Unclaimed

When you visit a token's page on Kumbaya, the name might appear in italics with an **Unclaimed** badge near the creator's row, and the description reads *"No description - token launched directly from contract"*. This page explains what that means, why it happens, and how to fix it.

![An unclaimed token's detail page - the italicized name and "Unclaimed" badge are visible to everyone; a green Claim button additionally appears in the header for the on-chain creator wallet only.](/files/FTRsPe7z98GnfzvSCLqI)

## What "Unclaimed" actually means

There are two layers of identity for a launched token:

1. **The on-chain creator.** This is whoever called `ignite()` to deploy the token. It's set permanently in the smart contract - Kumbaya can't change it.
2. **The Kumbaya account that "owns" the listing.** This is what controls the token's metadata on the website (name display, image, prompt, socials), and it's what links the token to a creator profile.

When those two are linked, your token is **claimed** - it shows your real name, your image, your description, and the Claim Fees / Withdraw / Release Vested buttons all work normally.

When they're *not* linked yet, your token is **unclaimed**: everyone sees the italicized name and an **Unclaimed** badge, with no real description, image, or social links. **The green Claim button only appears in the header for the wallet that's the on-chain creator** - other visitors don't see it.

## Why this happens

A Kumbaya launch is two steps: an on-chain `ignite()` transaction that deploys the token, and a follow-up call to Kumbaya's backend that finalizes the listing with your description, image, and socials. A token shows up as Unclaimed when step one happened but step two didn't. The two ways that occurs:

* **Your launch via the website was interrupted before it finalized.** The `ignite()` transaction confirmed on-chain, but the launchpad UI didn't get a chance to register the metadata afterwards - most often because the tab was closed, the connection dropped, or the page refreshed between the on-chain confirmation and the final submit step. The token exists, but Kumbaya's backend never received the "yes, this is mine, here's the description" call that closes the loop.
* **You launched directly via the contracts**, not through Kumbaya's launchpad UI. If you called `ignite()` from a script or an external integrator, you skipped the launchpad UI entirely, so there's no associated metadata or social profile on the Kumbaya side.

## How to claim

If the wallet you're signed in with **is** the on-chain creator (i.e. it's the address that originally called `ignite`), you can claim the listing in a couple of clicks:

1. Go to your token's page (`kumbaya.xyz/#/launch/<token-address>`).
2. Click the green **Claim** button in the header.
3. The Claim modal opens.
4. Fill in / confirm the metadata:
   * **Description** (1–500 characters) - the prompt people will reply to in the token's thread.
   * **Category** - Meme or Dare.
   * **Website** *(optional)*.
   * **X handle** *(optional)*.
   * **Telegram group** *(optional)*.
5. **Sign the message** when prompted in your wallet. This is a free signature (no gas) that proves you control the on-chain creator address. Your wallet will show a structured "Kumbaya Token Claim" message with the token address, chain, timestamp, and a one-time nonce - signing this is what proves your wallet matches the on-chain creator.
6. Submit. The page refreshes and your token now shows your name, your prompt, and your creator controls.

> ⏱ The signature expires in **1 hour**. If you sign and don't submit within an hour, you'll be asked to sign again.

You can optionally upload an image as a separate step on the same flow.

## What if you don't recognize the wallet that launched it?

The on-chain creator is the wallet that signed the `ignite` transaction. If you can't sign messages from that wallet, you can't claim the token - Kumbaya cannot help reassign creator status, because the assignment is on-chain and not under platform control.

If you used **a Privy social login**, try signing in to Kumbaya again with the same login method (email / Google / X / Discord / GitHub / Telegram) you used originally - Privy will give you back the same embedded wallet. The "different wallet" is often just a different login choice in disguise.

## Common questions

**Can I claim someone else's token?** No. The signature has to come from the on-chain creator address. Kumbaya verifies the signature server-side against the creator that's recorded on the contract.

**Does claiming change anything on-chain?** No. Claiming is purely about metadata association - your name, image, social links. It doesn't move tokens, doesn't update the contract, and doesn't cost gas. Just a wallet signature.

**I claimed but my Claim Fees / Withdraw / Vesting buttons still aren't visible.** Refresh the page. If the buttons still don't appear, double-check that the wallet you signed with is the same one you're now logged in with. Mismatch is the usual culprit.

**Is there a deadline to claim?** There's no on-chain deadline for claiming the *listing*. The on-chain timer worth knowing about is the **Tip Jar burn countdown** that starts at graduation: it only affects *user-side* credits that were never tipped to creators (those get burned after the countdown). Tips already received by creators are protected by separate accounting and are not at risk.

## Where to next

* [**Creator fees: how you earn →**](/launchpad/creator-fees)
* [**Launching your own token →**](/launchpad/launch-a-token)
* [**Connecting your wallet →**](/getting-started/connect-your-wallet) - if you want to switch login methods


# Dares and Memes

When you launch a token on Kumbaya you pick one of two **types**: **Meme** or **Dare**. They use the same underlying launchpad contracts, but they're meant to host different kinds of launches and they get different visual treatment in the UI.

## Memes

A Meme launch is the open-ended one. The prompt is usually a vibe, an inside joke, or a piece of culture you want to rally around. Threads on meme tokens tend to be image-heavy.

Use Meme if:

* You want a launch that's fun, playful, or community-driven.
* The prompt is more "what is this thing?" than "do this specific thing".

## Dares

A Dare is a community challenge with a token attached. The prompt is action-oriented - *"do X and post evidence in the thread"* - and the token's value is tied to the community deciding the dare was completed.

Use Dare if:

* Your prompt asks people to *do* something specific.
* The token is going to act as a stake or a reward for someone completing the challenge.
* You want the visual treatment that signals "this is a contest, not just a vibe".

## What's actually different between them?

* **The visual treatment** - color theming, badges, and how the token shows up in feeds.
* **Which feed it appears in** - Memes and Dares each have their own feed plus the unified All/Trending feed.
* **Tone of comment threads** - Memes tend to skew creative; Dares skew toward proof-of-completion posts.

Everything *protocol-side* is identical: same bonding curve, same graduation behavior, same fee mechanics, same creator earnings. The choice is editorial, not technical.

## Where to next

* [**Discovering tokens →**](/launchpad/discover)
* [**Launching your own token →**](/launchpad/launch-a-token)
* [**Comments and yaps →**](/social-features/comments-and-yaps) - how the thread works


# Kumbaya Dares: Season 1

The internet has never been short of bad takes, ridiculous stories, questionable investments, or people willing to do stupid things for the timeline. Kumbaya Dares are challenges built around content people actually want to watch, share, and take part in.

Pick a Dare, create your entry on Kumbaya, and share it to X. The more attention your entry earns, the higher it climbs. You can take part in as many Dares as you want, as many times as you want.

Not keen on the five launched Dares? Launch your own. Or back the creators you want to see win.

**Two weeks. $10,000+ in prizes.**

## When Season 1 runs

|                       |                                  |
| --------------------- | -------------------------------- |
| **Opens**             | Monday 17 August 2026, 14:00 UTC |
| **Closes**            | Monday 31 August 2026, 14:00 UTC |
| **Winners announced** | One week after close             |

## The Season 1 Dares

### $SPICY: Confess your worst trade

Take a shot of hot sauce and confess the worst investment you've ever made. The memecoin you were convinced was going higher. The leverage you definitely shouldn't have used. The out-of-the-money call you were sure would pay off.

Take the shot. Tell the story. Show the receipts if you've got them.

### $SHAME: Put crypto's worst in the hall of shame

Nominate the worst scam, rug, or collapse you've witnessed or been part of. Tell us what happened, why it deserves the nomination, and how badly it ended.

Keep it real. Shame something that actually happened rather than inventing accusations for the sake of an entry.

### $DEN: Show us where you trade

Six-monitor command centre? MacBook from bed? Mattress still on the floor? Give us the tour of your setup and reveal the place where your best (and worst) decisions get made.

### $BIGBALLS: 100x or bust

Open a 100x leveraged trade and see what you can make from one position. Show the trade, document what happens, and post the final P\&L.

One trade. Maximum leverage. Biggest balls wins.

### $AWKWARD: Make the timeline uncomfortable

Do something awkward in public. Film it. Post it. Live with the second-hand embarrassment.

Make it uncomfortable, not dangerous: keep it legal, keep it safe, and don't harass people who didn't sign up to be part of your content.

## Prizes

Season 1 rewards original content that travels. Five prizes are up for grabs.

| Prize          | Amount | Who wins it                                                        |
| -------------- | ------ | ------------------------------------------------------------------ |
| 🥇 Grand prize | $5,000 | The best-performing individual entry on X                          |
| 🥈 Silver      | $2,500 | The second-best individual entry on X                              |
| 🥉 Bronze      | $1,000 | The third-best individual entry on X                               |
| 🚩 Team pick   | $1,000 | The boldest and most popular Dare, hand-picked by the Kumbaya team |
| 💜 Top tipper  | $500   | The biggest supporter of the season                                |

The first three are decided by public engagement on X. The Team Pick rewards the best original Dare somebody launched, judged on originality, audacity, and execution. Top Tipper is tracked on-chain, so you can win it without filming a thing.

## How to take part

There are three ways in, and you can do all of them.

1. **Enter a Dare.** Choose one of the live challenges, create your content on Kumbaya, and share it to X. There's no one-shot rule: enter multiple Dares, or submit another entry to the same Dare if you come up with something better.
2. **Launch your own Dare.** Got an idea that could spread further than ours? Create your own Dare and get people taking part. This is what the Team Pick prize is for.
3. **Back a creator.** Tip the entries you want to support. Tip the most across the season and you take the Top Tipper prize.

Your audience can do all of the same: engage with your entry on X, come through to Kumbaya, enter the Dare themselves, launch their own, or back the creators they want to see win.

## How entries are scored

Individual entries are scored on their engagement on X, including views, likes, reposts, replies, and quotes. The exact weighting is kept private so the leaderboard can't be gamed.

Your entry has to be created on Kumbaya and shared to your connected X account for its performance to count. Shortlisted entries may be asked to provide X analytics before prizes are confirmed.

## Frequently asked questions

**Can I enter more than one Dare?**\
Yes, and it's encouraged. Enter as many Season 1 Dares as you want.

**Can I submit more than one entry to the same Dare?**\
Yes. If you get a better idea later, make another one.

**Where do I post my entry?**\
Create your entry through Kumbaya and share it to your connected X account. Your X performance feeds back into the competition.

**Can I promote my entry?**\
Yes! Paid promotion is encouraged but remember scoring is not just from impressions alone. This means if you pay to advertise your content but the content itself is not original and does not get any engagement, its likely to be ranked lower than a non-promoted post with more engagement but less impressions!

**When are winners announced?**\
One week after the competition closes, three weeks after Season 1 launches.

**What can't I post?**\
Keep it legal, safe, and genuine. Entries involving fabricated claims, artificial engagement, dangerous behaviour, or content that puts other people at risk may be removed.

## Where to next

* [**Launching your own token →**](/launchpad/launch-a-token) - how to create a Dare
* [**Dares and Memes →**](/launchpad/dares-and-memes) - what the two launch types mean
* [**Tipping →**](/social-features/tipping) - how backing a creator works


# Comments and yaps

Every token on Kumbaya has its own comment thread. We call posts in those threads **yaps**. This is where most of the conversation, memeing, and community-building happens.

## Posting a yap

On any token's page, the **Thread** (or **Comments**) tab shows the active conversation. To post:

1. Click the input box at the top of the thread.
2. Type your message. The input is a plain text field, so any **emoji** you can pull from your system's keyboard (⌘⌃Space on macOS, Win+. on Windows) works inline.
3. Optionally **attach media** with the paperclip / attach icon. Supported formats:
   * Images: **JPG**, **PNG**, **WebP**, **GIF**
   * Video: **MP4**, **MOV** (QuickTime)
4. Optionally reply to a specific post by clicking the reply icon on it (or typing `>>postNumber` in your message - see below).
5. Optionally toggle **Anon mode** on the composer (the small Anon-mascot icon) - see [**Anonymous posting**](#anonymous-posting) below.
6. Hit submit. Sign the action in your wallet if prompted.

## Anonymous posting

Each yap can be posted **as Anon** instead of under your profile. Toggle the Anon icon in the composer before submitting; the post will appear with the Kumbaya Anon mascot instead of your avatar and name. The on-chain action still comes from your wallet, but the displayed identity is anonymised at the UI level. The first time you switch on Anon mode, a one-time modal explains how it works.

If you'd rather not see Anon posts at all, the thread has a **hide Anon** toggle that filters them out of your view. Other users still see them; this is a personal display preference, not a moderation action.

## Replying - the `>>postNumber` syntax

Yaps are numbered sequentially per token (post #1, #2, #3 …). You can reference any earlier post by typing `>>` followed by its number - for example `>>14`. The reference renders as a clickable link, and the post you referenced gets a "replied to" indicator.

This 4chan-style threading lets long conversations stay coherent without nesting indefinitely.

## Likes, dislikes, tips

Every yap can be:

* **Liked** - a thumbs-up signal.
* **Disliked** - a thumbs-down signal.
* **Tipped** - sent tip credits. See [**Tipping**](/social-features/tipping).

Likes/dislikes affect what gets surfaced as trending; tips go directly to the poster's wallet.

## What gets surfaced

The thread sort defaults to chronological with active replies bumped, but trending mechanisms can pull a particularly liked or tipped yap toward the top of the token's page.

## Etiquette

* Stay on theme with the token's prompt - that's what people are here for.
* Posts with original media tend to attract tips.
* Spam, low-effort replies, and obvious shill posts get downvoted fast.

## Where to next

* [**Tipping →**](/social-features/tipping)
* [**Your profile and stats →**](/social-features/profile)
* [**Discovering tokens →**](/launchpad/discover)


# Tipping

Kumbaya runs on tips. When you trade, comment, or interact, you build up a balance in your **Tip Jar** that you can send to creators and posters whose work you appreciate. Tips go directly on-chain - Kumbaya doesn't custody them.

## Where tips come from

Two ways your Tip Jar fills up:

1. **Tip bonus on every buy.** When you buy a token on the launchpad, a tiny portion of the trade (typically 3%) is credited to your Tip Jar instead of you. It's a "tip bonus" - credit you can later send to whichever creator or post you want.
2. **Direct credit deposits.** Some flows (rewards, platform-side seeding) credit your Tip Jar directly.

Your Tip Jar is on-chain (held in the launchpad's tip ledger contract). You can see your balance any time - the **Tip Jar** button is in the top header and the left nav.

## How to tip

Every yap in a token thread has a small **$ button** in its action bar (next to the like / dislike controls). Hovering it shows a tooltip with the post's tip totals, your share so far, and your available balance for that token.

To tip a post:

1. **Tap the $ button once** to queue a single tip unit. Or **hold the button** (long-press) to keep adding units rapidly - useful when you want to tip more than the minimum.
2. As you queue units, a small popover opens under the button showing:
   * The amount you've queued (in token / USD).
   * Whether it's coming **From Jar** (your tip credits for that token) or **From Wallet** (a fallback if your jar balance can't cover what you queued).
   * A countdown bar - the tip fires automatically a moment after your last tap, so quick repeated taps stack into a single transaction instead of one tx per tap.
3. Wait out the short countdown. The queued tip is relayed on-chain by Kumbaya's guardian, so **you don't pay gas**. Your wallet may prompt you to sign a one-line gasless message; that's the only signature needed.
4. The tip lands in the recipient's bucket on that token. They claim it from their **Tip Jar → Received** tab.

The token doing the tipping is automatic - it's whichever token's thread the post is in. There's no per-tip token picker; if you're tipping a yap in the $FOO thread, you're tipping in $FOO credits.

> ⚠️ **You can't tip yourself.** The contract rejects self-tips, so tipping your own content is not possible.

> 💡 **Cancel a queued tip.** If you tap once and change your mind, click outside the popover before the countdown ends - the queued tip is dropped without firing.

## How tips reach creators

When you tip a creator their token's tip credits:

* **Pre-graduation**, the tip splits into a **liquid** portion (the creator can withdraw immediately) and a **vested** portion (locked until the token graduates, then unlocked).
* **Post-graduation**, the full amount is liquid - no waiting.

## Your tip balance - what you can (and can't) do with it

The tip credits in your Tip Jar are denominated in **specific launchpad tokens** - i.e. if you bought $FOO and got tip-bonus credits, those credits are $FOO credits. They live inside the launchpad's tip ledger; they're not freely transferable like a normal ERC-20 balance.

The only thing you can do with them is **tip them to someone else** (a yapper, a creator, anyone except yourself). You can't withdraw your own tip-jar credits to your wallet, and you can't sell or trade them - they're a use-it-or-lose-it credit.

### ⏱ Use them before they're burned

Tip credits are not held indefinitely. Once a token **graduates**, a burn countdown starts on those credits. If you haven't tipped them by the time the countdown elapses, they're **permanently destroyed**.

The Tip Jar surfaces this directly: as soon as a token graduates, each credit balance for that token shows the **time remaining** next to it (formatted as `Xd Yh`, `Xh Ym`, or `Xm`). Hover the countdown for a reminder: *"Credits burn in {time}. Tip some content before it's too late."*

The countdown duration is set by Kumbaya governance (1–90 days). Pre-graduation there's no timer - credits stick around for as long as the token sits on the bonding curve. The clock only starts at graduation.

> Note: this only affects *your* tip-jar credits - i.e. credits you earned from buying but never tipped. Tips you've already sent to creators are safe in their buckets and aren't subject to the burn.

## Receiving tips on your posts

If you post yaps in token threads, other users can tip them. The tip lands in **your bucket on that thread's token** - i.e. a tip on a yap in the $FOO thread arrives as $FOO credits. Each token whose threads you've been tipped in shows up as a separate row.

To collect, open the **Tip Jar** from the jar icon in the top header and switch to the **Received** tab. Each row shows the token, the total tipped, and a **Withdraw** button that pays your liquid balance out to your wallet. (Pre-graduation tips are split into liquid + vested portions - the vested portion unlocks on your first post-graduation withdraw on that token.)

There's no platform-side custody - the credits sit in the tip ledger contract until you withdraw, and the contract pays out to your wallet directly.

## Where to next

* [**Comments and yaps →**](/social-features/comments-and-yaps) - posting in threads
* [**Your profile and stats →**](/social-features/profile)
* [**Creator fees →**](/launchpad/creator-fees) if you've launched a token


# Your profile and stats

Every wallet that uses Kumbaya gets a profile. It's where your activity surfaces - your launches, yaps, tips, and trades - and how other users find you.

## What's on a profile

* **Identity** - display name, avatar, bio, and social links if you've added them.
* **Wallet address** - short form (`0x12...abcd`).
* **Stats**:
  * **Launches** - tokens you've launched on the launchpad.
  * **Yaps** - posts and comments you've made.
  * **Tips** - tips you've sent and received.
  * **Trades** - swap history.
* **Activity feed** - recent on-chain and on-platform actions.

## Editing your profile

Click your address in the top-right, then **Profile**. From there you can:

* Set or change your display name (with availability check).
* Upload an avatar.
* Add a short bio.
* Connect X (Twitter).

These are stored against your account in the Kumbaya backend - they don't change anything on-chain.

## Sharing

Your profile has a stable URL: `kumbaya.xyz/#/profile/<your-wallet-address>`. Share it freely.

## Privacy

* Your wallet address is public - that's how blockchains work.
* Your trades and on-chain activity are public, again because that's how blockchains work - though the way the UI surfaces them is shapeable via the profile settings.
* Display name, avatar, and bio are optional. You can use Kumbaya entirely "anonymously" with just a wallet, no profile setup at all.

## Where to next

* [**Comments and yaps →**](/social-features/comments-and-yaps)
* [**Tipping →**](/social-features/tipping)
* [**Launching your own token →**](/launchpad/launch-a-token)


# FAQ

Quick answers to the most common questions.

## General

**Is Kumbaya custodial?** No. You always control your wallet and your tokens. Kumbaya never holds your funds.

**What chain is Kumbaya on?** [MegaETH](https://megaeth.com/) - chain ID `4326` for mainnet, `6343` for testnet.

**Do I need ETH to use Kumbaya?** Yes, for gas - but transactions cost a tiny fraction of a cent on MegaETH.

**Can I use Kumbaya from outside the website?** Yes. The DEX is permissionless - anyone can swap, LP, or interact directly via the Uniswap V3 fork. See the [developer docs](https://github.com/Kumbaya-xyz/documentation/tree/main/developer/README.md).

**Is Kumbaya audited?** Yes - the Kumbaya launchpad has a signed BlockSec audit, and the DEX inherits Uniswap V3's Trail of Bits and ABDK audits. See [**Audit & security**](/help/audit-and-security) for the full breakdown, the bundled BlockSec report, and links to the upstream V3 audits.

## Wallets

**Do I need a browser extension?** No. Kumbaya offers two paths in the connect modal: **Kumbaya Wallet** (full access; uses Privy and supports either an existing wallet OR email / Google / X / Discord / GitHub / Telegram social login) and **DEX only** (existing wallet, swap and LP only). See [**Connect your wallet**](/getting-started/connect-your-wallet).

**What's the difference between Kumbaya Wallet and DEX only?** Kumbaya Wallet gets you the full platform - DEX, launchpad, comments, tipping, profile. DEX only gets you swap and pool features without any social attribution. You can switch later.

**Can I export my embedded wallet's private key?** Yes. If you signed in via Privy with email or social, Privy provides an export flow. Once you have your private key you can import it into any wallet you want.

**I changed my login method and now my balances are gone - what happened?** Different *social* login methods give you different embedded wallets. Sign back in with your original method (email / Google / X / Discord / GitHub / Telegram) and your funds will be there. (This doesn't apply if you connected an existing external wallet - that wallet stays the same regardless of how you sign in.)

## Trading

**Why did my swap fail?** Most likely slippage exceeded your tolerance, or the price moved more than expected between clicking and confirming. Bump tolerance or reduce trade size - see [**Slippage and gas explained**](/trading/slippage-and-gas).

**How do I trade a token that isn't in the list?** Click the token picker, paste the contract address. Always verify the address - anyone can deploy a token with any name.

## Liquidity

**What's "concentrated liquidity"?** A way of providing liquidity where you pick a price range. You earn more fees per dollar while in range, but stop earning when price leaves your range. See [**What is concentrated liquidity?**](/liquidity/concentrated-liquidity).

**Are my LP positions tokenized?** Yes - each position is an NFT in your wallet. You can transfer it like any other NFT.

## Launchpad

**How does a launchpad token graduate?** When the price reaches a configured graduation tick, the bonding curve consolidates into a regular Uniswap V3 pool. See [**How bonding curves and graduation work**](/launchpad/bonding-curves).

**How do creators earn?** Two streams: **tips** from buyers (the protocol gives buyers a tiny tip-bonus credit on each buy, and buyers can tip creators with it) and **post-graduation streaming fees** from the consolidated pool. See [**Creator fees**](/launchpad/creator-fees).

**Can I rug?** By design, no. The bonding curve and tail liquidity aren't yours to pull, and the standard launch flow gives creators no direct token allocation.

**My token shows as "Unclaimed" - what do I do?** The on-chain creator wallet hasn't been linked to a Kumbaya profile yet. Sign in with that wallet and click **Claim**. See [**If your token shows as Unclaimed**](/launchpad/unclaimed-tokens).

## Where to next

* Still stuck? Check [**Troubleshooting**](/help/troubleshooting).
* DM us on [@kumbaya\_xyz](https://twitter.com/kumbaya_xyz).


# Troubleshooting

Common problems and how to fix them.

## "Slippage exceeded"

**What it means**: the price moved more than your tolerance allowed between clicking and confirming.

**Fix**: bump your slippage tolerance (gear icon ⚙️ on the swap card), or reduce the trade size. For new launches or thin pools, 1–3% is reasonable. See [**Slippage and gas explained**](/trading/slippage-and-gas).

## "Insufficient gas"

**What it means**: you don't have enough ETH to pay the network fee.

**Fix**: top up your ETH balance. On MegaETH gas is cheap, but you do need a small amount.

## "Token approval required"

**What it means**: this is your first time swapping this token from this wallet. The pool needs your permission to move it.

**Fix**: click **Approve \[TOKEN]** and sign in your wallet. You only do this once per token per wallet.

## "Pool not found"

**What it means**: there's no Kumbaya pool for the exact pair you're trying to swap.

**Fix**: try routing through ETH or USDC. Most tokens have an ETH or USDC pool - the swap UI will route through them automatically when possible.

## I can't see my new token

If you just got tokens and they're not showing up:

1. **Check the chain.** Make sure your wallet is on MegaETH mainnet (chain ID `4326`).
2. **Refresh the token list.** Kumbaya updates frequently - a hard refresh of the page often fixes it.
3. **Add the token by address.** Click the token picker, paste the contract address.

## My liquidity position shows "Out of Range"

That's expected behavior - the current price has moved outside the range you set.

You stop earning fees while out of range, but you don't lose the position. Either:

* **Wait** for price to move back into your range.
* **Rebalance** by removing the position and opening a new one with bounds around the current price.

See [**Managing your positions**](/liquidity/manage-positions).

## My embedded wallet (social login) is empty after signing in on a new device

Embedded wallets are tied to your **login method**. If you signed in with Google originally and now you're using email, you'll have a different wallet. Sign in with the original method and your funds will appear.

(This only affects social-login embedded wallets. If you connected an existing external wallet via "Kumbaya Wallet" or "DEX only", that wallet stays the same regardless of which path or login method you use later.)

## I picked "DEX only" and now I can't access the launchpad

By design - the DEX only path skips the Kumbaya profile layer, so it can't see launchpad, comments, or tipping. Click on the wallet button top right and you should see a prompt to switch to a **Kumbaya Wallet**. Your existing wallet connection carries over.

## My token shows as "Unclaimed"

The on-chain creator address hasn't been linked to your Kumbaya profile yet. If you launched the token, sign in with the wallet that called `ignite()` and click **Claim**. See [**If your token shows as Unclaimed**](/launchpad/unclaimed-tokens).

## My transaction is stuck pending

Rare on MegaETH given block speed (\~10ms), but if it happens:

1. Wait 30 seconds - most "stuck" transactions on MegaETH clear quickly.
2. Check your wallet for a pending transaction; some wallets let you cancel or speed up.
3. If it's truly stuck, try increasing gas in your wallet's advanced settings.

## I clicked "Claim" but nothing happened

Most common cause: the wallet you signed in with isn't the on-chain creator address. Kumbaya verifies the claim signature against the contract's `creator()` value - if your wallet doesn't match, the claim fails silently in some cases.

Check the on-chain creator address on the block explorer ([mega.etherscan.io](https://mega.etherscan.io/)) and sign in with that wallet.

## Still stuck?

* Check the [**FAQ**](/help/faq).
* Reach out via [@kumbaya\_xyz](https://twitter.com/kumbaya_xyz) on X.


# Audit & security

Kumbaya runs on contracts that have been audited by independent third parties. This page is the short version of who audited what, and what you do - and don't - control as a user.

## Two protocol layers, both audited

Kumbaya has two protocol layers on top of each other: a DEX that's a fork of Uniswap V3, and the Kumbaya launchpad (whose smart contracts are prefixed `Fire*` - a campfire metaphor that fits the "Kumbaya" name). Each layer has its own audit history.

### 🔥 Kumbaya launchpad - BlockSec

Every launchpad contract - `FireLaunch`, `FireToken`, `FireGraduator`, `FireRegistry`, `FireStream`, `FuelVault` - was reviewed by [BlockSec](https://blocksec.com/), a well-known smart-contract security firm. The audit covered the full launch → graduation → fee-streaming flow, the on-token vesting, and the tip-credit ledger.

**No critical or high findings remain open.**

The full signed report is published in the integrator-kit: [`blocksec_Kumbaya-xyz_Fire_v1.0-signed.pdf`](https://github.com/Kumbaya-xyz/integrator-kit/blob/main/audits/blocksec_Kumbaya-xyz_Fire_v1.0-signed.pdf).

### 💱 DEX - inherits Uniswap V3's audits

Kumbaya's DEX is a fork of Uniswap V3, which has been live since May 2021 and is one of the most battle-tested AMMs in DeFi. The Kumbaya fork is bytecode-equivalent to the upstream Uniswap V3 code (verified by an automated check in our [`integrator-kit`](https://github.com/Kumbaya-xyz/integrator-kit)) - so it inherits every audit Uniswap V3 has had.

The two formal V3 audits are public and live in Uniswap's v3-core repo:

* **Trail of Bits** - [v3-core/audits/tob/audit.pdf](https://github.com/Uniswap/v3-core/blob/main/audits/tob/audit.pdf)
* **ABDK Consulting** - [v3-core/audits/abdk/audit.pdf](https://github.com/Uniswap/v3-core/blob/main/audits/abdk/audit.pdf)
* Index: [Uniswap/v3-core/audits](https://github.com/Uniswap/v3-core/tree/main/audits)

Kumbaya's only protocol-level change to V3 is a wider protocol-fee range *at the contract level* (max 50% vs. upstream's 25%) - though in production Kumbaya runs within Uniswap's standard range (see [**Fees**](/liquidity/fees)). Pools, positions, the swap engine, and tick math are otherwise unchanged.

## What this means for you

* **Your funds aren't held by Kumbaya.** When you swap, your tokens move directly between you and the pool contract. When you provide liquidity, your position is an NFT in your wallet. When you launch a token, the launchpad doesn't custody it.
* **Kumbaya can't freeze, seize, or redirect your assets.** No admin function exists for that on the protocol contracts.
* **Kumbaya can't reverse trades or refund mistakes.** Transactions are final once confirmed on-chain.
* **Launchpad tokens are safe at the contract level.** Every token launched through the launchpad is a `FireToken` deployed by the audited `FireLaunch` contract - the same bytecode every time. There's no admin who can mint more, blacklist holders, freeze transfers, or take fees. Audit coverage extends to every launch.

## What's not audited

* **Third-party tokens** deployed outside the launchpad (i.e. tokens you import manually by contract address - not launched via `kumbaya.xyz/launchpad/create`). Anyone can deploy an arbitrary ERC-20 with admin powers, transfer hooks, or hidden taxes. The audit doesn't extend to those. The Kumbaya UI flags these with an **Unknown** badge - see [**Swapping tokens → Unknown tokens carry real risk**](/trading/swap#-unknown-tokens-carry-real-risk).
* **Off-chain services** (the website, the search service, the indexer). These can have bugs that affect what you see, but they can't move funds - only your wallet can, and only with your signature.

## If you find a security issue

Email **<support@kumbaya.xyz>** with details. Please don't disclose publicly until we've had a chance to fix it.

## Where to next

* [**FAQ**](/help/faq)
* [**Connect your wallet**](/getting-started/connect-your-wallet) - the non-custodial onboarding flow
* [**Unknown tokens**](/trading/swap#-unknown-tokens-carry-real-risk) - the one place where you take on more risk than the protocol-level audits cover


# Welcome

Kumbaya is a DEX and social launchpad on **MegaETH**. This space is for developers, integrators, and partners who want to build on top of Kumbaya - whether that's routing swaps through our pools, embedding the Kumbaya launchpad in your app, or pulling pool data into a dashboard.

## What's here

| Section                                                                           | What it covers                                                                                                                                                    |
| --------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [**Overview**](/developers/overview)                                              | Architecture at a glance, what's open vs. gated, how the pieces fit together                                                                                      |
| [**Networks & Contracts**](/developers/networks-and-contracts/contract-addresses) | Deployed contract addresses on MegaETH mainnet and testnet                                                                                                        |
| [**DEX Integration**](/developers/dex-integration/overview)                       | Quoting, swapping, and reading pool state on the V3 fork                                                                                                          |
| [**Launchpad**](/developers/launchpad/overview)                                   | Token launches, bonding curves, graduation, fee streaming                                                                                                         |
| [**SDKs**](/developers/sdks/quickstart)                                           | The `@kumbaya_xyz/*` packages on npm                                                                                                                              |
| [**APIs**](/developers/apis/exchange-api)                                         | Hosted REST APIs: Exchange (`exchange.kumbaya.xyz`), Client (`clients.kumbaya.xyz`), Search (`search.kumbaya.xyz`)                                                |
| [**Resources**](/developers/resources/integrator-kit)                             | Integrator kit, ABIs, token list                                                                                                                                  |
| [**Building Agents**](/developers/building-agents/overview)                       | The [Agent Kit](/developers/building-agents/agent-kit) (MCP servers + skills), SIWE auth, programmatic launches, registry reads, recipes - for AI agents and bots |

## Building on Kumbaya in 5 minutes

If you just want to see something working, head straight to [**SDK Quickstart**](/developers/sdks/quickstart) - you'll get a price quote on MegaETH in about a dozen lines of TypeScript.

**Building an agent or bot?** The [**Kumbaya Agent Kit**](/developers/building-agents/agent-kit) gives an LLM the whole platform as MCP tools with no glue code. For the from-scratch path, see [**Building Agents → Overview**](/developers/building-agents/overview) - the SIWE-based onboarding flow, on-chain entry points, and copy-pasteable recipes.

## Three things worth knowing up front

1. **Kumbaya's DEX is a Uniswap V3 fork** with one meaningful protocol-level change: the contract supports a wider protocol-fee range (up to 50% vs. Uniswap's 25% cap), though the values currently set in production sit within Uniswap's standard range. Pools and positions are otherwise V3-compatible. The pool init code hash is **different** from upstream Uniswap - see [**Contract Addresses**](/developers/networks-and-contracts/contract-addresses).
2. **Tokens launched via the Kumbaya launchpad go through a bonding curve** before becoming ordinary V3 pools. Until graduation, swaps still go through V3 - they're just routed against tightly-packed seed positions. After graduation, the LP becomes a full-range NFT and trades like any other V3 pool. (The launchpad's smart contracts are prefixed `Fire*` - `FireLaunch`, `FireGraduator`, etc. - see the launchpad [**Overview**](/developers/launchpad/overview) for the naming background.)
3. **Refer to Uniswap's V3 docs** at <https://developers.uniswap.org/docs/protocols/v3/overview> for everything we don't re-document here. **Do not reference V2 or V4 guides** - they don't apply.

## Repos and links

* [**fire**](https://github.com/Kumbaya-xyz/fire) (private) - Kumbaya launchpad contracts and tests (the `Fire*` contract family)
* [**integrator-kit**](https://github.com/Kumbaya-xyz/integrator-kit) - addresses, ABIs, Foundry integration tests
* [**sdks**](https://github.com/Kumbaya-xyz/sdks) (private) - `@kumbaya_xyz/*` packages
* [**v3-core**](https://github.com/Kumbaya-xyz/v3-core) / [**v3-periphery**](https://github.com/Kumbaya-xyz/v3-periphery) (private) - Kumbaya's V3 fork

## Help

* GitHub issues on the relevant repo
* Twitter [@kumbaya\_xyz](https://twitter.com/kumbaya_xyz)
* Email: <support@kumbaya.xyz>


# Overview

Kumbaya is a real-time DEX and social launchpad on **MegaETH**. This page is the architecture map for integrators - what's deployed on-chain, what's hosted by Kumbaya, what's open-source, and how the pieces fit together.

## The shape of the stack

```
┌──────────────────────────────────────────────────────────────────────┐
│                          YOUR INTEGRATION                            │
│              (frontend, bot, agent, aggregator, dashboard)           │
└──────────────┬───────────────────────────────────────┬───────────────┘
               │                                       │
               ▼                                       ▼
┌──────────────────────────────┐    ┌─────────────────────────────────┐
│    @kumbaya_xyz SDKs         │    │  Hosted services (kumbaya.xyz)  │
│  • sdk-core                  │    │ ─────────────────────────────── │
│  • v3-sdk                    │    │  • Exchange API                 │
│  • router-sdk                │    │      exchange.kumbaya.xyz       │
│  • universal-router-sdk      │    │      quotes, pools, stats       │
│  • smart-order-router        │    │  • Client API                   │
└──────────────┬───────────────┘    │      clients.kumbaya.xyz        │
               │                    │      auth (SIWE/Privy), social, │
               │                    │      launch metadata, claims    │
               │                    │  • Search Service               │
               │                    │      search.kumbaya.xyz         │
               │                    │      tokens, pools, typeahead   │
               │                    │  • Hasura GraphQL (read-only)   │
               │                    │      ql.kumbaya.xyz             │
               │                    │      indexed on-chain data      │
               │                    └────────────────┬────────────────┘
               │                                     │
               ▼                                     ▼
┌──────────────────────────────────────────────────────────────────────┐
│                      On-chain (MegaETH 4326)                         │
│  • Uniswap V3 fork (factory, pools, router, quoter, position mgr)    │
│  • Kumbaya launchpad (FireLaunch, Graduator, Token, Stream,          │
│                       Vault, Registry)                               │
│  • Permit2, WETH9, TickLens                                          │
└──────────────────────────────────────────────────────────────────────┘
```

## What's open and what's gated

| Layer                                                  | What it is                                                     | Access                                                                                                                                           |
| ------------------------------------------------------ | -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| **On-chain contracts**                                 | V3 fork + Kumbaya launchpad on MegaETH                         | Permissionless. Call directly via RPC.                                                                                                           |
| **SDKs** (`@kumbaya_xyz/*`)                            | npm packages for typed contract interaction                    | Permissionless. `npm install`.                                                                                                                   |
| [**Agent Kit**](/developers/building-agents/agent-kit) | MCP servers + skill pack that expose the platform as LLM tools | Permissionless. `npx @kumbaya_xyz/onchain-mcp` / `kumbaya-mcp`.                                                                                  |
| **Exchange API**                                       | Hosted REST at `exchange.kumbaya.xyz`                          | Mix: most read endpoints public; partner quote endpoints require an API key. See [Authentication](/developers/apis/exchange-api/authentication). |
| **Client API**                                         | Hosted REST at `clients.kumbaya.xyz`                           | User accounts, social, launches, claims. JWT-based.                                                                                              |
| **Search Service**                                     | Hosted REST at `search.kumbaya.xyz`                            | Public token + pool search. No auth.                                                                                                             |
| **Hasura GraphQL**                                     | Public read-only indexer at `ql.kumbaya.xyz`                   | On-chain data: pools, swaps, tokens, positions, time-series. No auth.                                                                            |
| **Frontend** ([kumbaya.xyz](https://kumbaya.xyz))      | The Kumbaya web app                                            | Anyone. End users only - not a public API.                                                                                                       |

## Two protocols, one DEX

Kumbaya runs two distinct protocols on a shared V3 fork:

### 1. The DEX - a Uniswap V3 fork

Pools, positions, quoting, swapping, fee tiers - all standard V3. The single material change is a wider protocol-fee range *at the contract level* (up to 50%, vs. upstream's 25%), Kumbaya's own deployments, and the pool init code hash. Currently-set protocol-fee values sit within Uniswap's standard range (see [Differences from Uniswap V3](/developers/dex-integration/differences-from-uniswap)).

**Reference:** [**DEX Integration**](/developers/dex-integration/overview), plus Uniswap's [V3 protocol docs](https://developers.uniswap.org/docs/protocols/v3/overview). Don't use V2 or V4 docs.

### 2. The Kumbaya launchpad - bonding-curve launches

A six-contract system that lets anyone launch a new token. The token starts trading on a V3 pool seeded with overlapping concentrated positions that simulate a bonding curve. When the price reaches a configured graduation tick, the seed positions consolidate into a single full-range NFT and the pool becomes an ordinary V3 pool. The contracts are prefixed `Fire*` (`FireLaunch`, `FireGraduator`, `FireStream`, …) - a campfire metaphor that fits the "Kumbaya" theme. They're part of Kumbaya, not a separate protocol.

**Reference:** [**Launchpad**](/developers/launchpad/overview).

## Three things to read first

1. [**Contract Addresses**](/developers/networks-and-contracts/contract-addresses) - every deployed address, mainnet and testnet
2. [**SDK Quickstart**](/developers/sdks/quickstart) - get a real quote in \~12 lines of TypeScript
3. [**Pool Init Code Hash**](/developers/dex-integration/pool-init-code-hash) - the one easy gotcha when computing pool addresses

## Networks

| Network         | Chain ID | RPC                               |
| --------------- | -------- | --------------------------------- |
| MegaETH Mainnet | `4326`   | `https://mainnet.megaeth.com/rpc` |
| MegaETH Testnet | `6343`   | `https://carrot.megaeth.com/rpc`  |

See [**Networks & Contracts**](/developers/networks-and-contracts/contract-addresses) for the full set of details.

## Versions

The Kumbaya SDKs track Uniswap upstream closely. Today's published versions:

| Package                             | Version |
| ----------------------------------- | ------- |
| `@kumbaya_xyz/sdk-core`             | 7.12.2  |
| `@kumbaya_xyz/v3-sdk`               | 3.15.5  |
| `@kumbaya_xyz/router-sdk`           | 1.17.3  |
| `@kumbaya_xyz/universal-router-sdk` | 4.26.3  |

Always check the [npm registry](https://www.npmjs.com/org/kumbaya_xyz) for current versions.

## Help

* GitHub issues on the relevant repo
* Twitter [@kumbaya\_xyz](https://twitter.com/kumbaya_xyz)
* Email **<support@kumbaya.xyz>** for partner API keys, indexer access, or anything that needs a human


# Audit & security

The Kumbaya stack is built from two layers, each with its own audit lineage:

| Layer                                     | Auditor(s)                                                          | Status                                                 |
| ----------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------ |
| **Kumbaya launchpad** (`Fire*` contracts) | [BlockSec](https://blocksec.com/)                                   | Signed report; no critical or high findings open       |
| **DEX (Uniswap V3 fork)**                 | Trail of Bits, ABDK Consulting (inherited from Uniswap V3 upstream) | Bytecode-verified against upstream; both audits public |

## Kumbaya launchpad - BlockSec

Every contract in the Kumbaya launchpad - `FireLaunch`, `FireToken`, `FireGraduator`, `FireRegistry`, `FireStream`, `FuelVault` (the `Fire*` family) - is covered by a BlockSec audit. The scope includes the full lifecycle: token deployment via `ignite`, the bonding-curve seed positions, graduation and the position-consolidation step, the tip-credit ledger (`FuelVault`), and post-graduation fee streaming through `FireStream`.

**The signed report is published in the integrator-kit:** [`blocksec_Kumbaya-xyz_Fire_v1.0-signed.pdf`](https://github.com/Kumbaya-xyz/integrator-kit/blob/main/audits/blocksec_Kumbaya-xyz_Fire_v1.0-signed.pdf).

No critical or high findings remain open. Lower-severity findings have been addressed and verified.

## DEX - Uniswap V3 audit lineage

The Kumbaya DEX is a fork of Uniswap V3. The fork is **bytecode-equivalent to upstream** for everything except the protocol-fee range - Kumbaya's contract allows `feeProtocol ∈ {0, 2..10}` (up to 50%) vs. upstream's `{0, 4..10}` (up to 25%). The values currently set in production sit within Uniswap's standard range (25% on 0.01% / 0.05% tiers, \~16.67% on 0.30% / 1.00% tiers, 0% on launchpad pools). Pool math, swap math, tick spacing, position management, and periphery routers are all unchanged.

### Bytecode equivalence verification

The [`integrator-kit`](https://github.com/Kumbaya-xyz/integrator-kit) ships a script that compiles upstream Uniswap V3 contracts at the same Solidity version and flags and compares the resulting bytecode against the Kumbaya deployments:

```bash
cd integrator-kit
pnpm install
node scripts/compare-bytecode.ts
```

The comparison is byte-for-byte modulo the documented protocol-fee range change. Any other divergence would surface as a test failure.

### Inherited Uniswap V3 audits

Because the fork preserves upstream's protocol behaviour, every audit Uniswap V3 went through applies to Kumbaya's DEX. Both formal V3 audits live in the upstream `v3-core` repo:

| Auditor             | Report                                                                                                | Scope                                         |
| ------------------- | ----------------------------------------------------------------------------------------------------- | --------------------------------------------- |
| **Trail of Bits**   | [`v3-core/audits/tob/audit.pdf`](https://github.com/Uniswap/v3-core/blob/main/audits/tob/audit.pdf)   | Full V3 core protocol review (March 2021)     |
| **ABDK Consulting** | [`v3-core/audits/abdk/audit.pdf`](https://github.com/Uniswap/v3-core/blob/main/audits/abdk/audit.pdf) | Mathematical correctness, edge-case behaviour |

Index: [Uniswap/v3-core/audits](https://github.com/Uniswap/v3-core/tree/main/audits).

In addition to formal audits, Uniswap V3 has been live since **May 2021** with billions of dollars of cumulative TVL and trillions of dollars of cumulative volume. The protocol is among the most battle-tested AMMs in production.

> ⚠️ **Don't reference V2 or V4 audits** - those are different protocols and don't apply to Kumbaya.

## Trust model

What's enforced by the contracts (no admin can override):

* **User funds are non-custodial.** Tokens move directly between traders and pool contracts; LP positions are NFTs held by the user. Launchpad launches don't custody the token.
* **Creator address is immutable.** `FireLaunch.getLaunchState(token).creator` is set at `ignite` time and cannot be changed. The off-chain "Claim" flow on `kumbaya.xyz` is metadata-only.
* **Pool init code hash is fixed.** All Kumbaya V3 pools deploy with [`0x851d77a4…628a3da7`](/developers/dex-integration/pool-init-code-hash).
* **Streaming recipient list can be permanently locked.** `FireRegistry.lockStreamingRecipients()` is irreversible. Once called, the post-graduation creator share is fixed forever - see [**Fees and credits**](/developers/launchpad/fees-and-credits).

What's governance-tunable (you should read live values, not trust JSON snapshots):

* `gracePeriodDuration`, `forceGraduationDelay`, `burnCountdownDuration`, `fuelVestedBps`, the `requiredFeeTier` / `requiredSkimBps` / `requiredVestingDuration` registry pins.
* Streaming recipient list and total-bps share, *until locked*.
* Protocol fee-recipient and guardian addresses.

Read these on-chain via the snippets in [**Live registry config**](/developers/building-agents/registry-config) rather than trusting any documentation snapshot.

## What's *not* in scope

* **Arbitrary third-party tokens** that aren't deployed through the Kumbaya launchpad. **Tokens launched via `FireLaunch.ignite()`** ***are*****&#x20;in scope** - every launch is a `FireToken` with the audited bytecode, no custom admin powers, no per-launch divergence. The risk surface only opens up for ERC-20s deployed outside the launchpad (e.g. a token someone manually imports into the Kumbaya UI by pasting an address). See the [Unknown tokens warning](https://github.com/Kumbaya-xyz/documentation/tree/main/client/trading/swap.md#-unknown-tokens-carry-real-risk).
* **Off-chain services** (frontend, exchange-api, client-api, search-service, indexer). These are non-custodial - they can't sign transactions on a user's behalf - but bugs there can affect what data users see. They're outside the audit scope.

## Reporting a vulnerability

Email **<support@kumbaya.xyz>** with details and a proof of concept if you have one. Please don't disclose publicly until we've coordinated a fix.

## Where to next

* [**Contract Addresses**](/developers/networks-and-contracts/contract-addresses) - every audited contract's deployed address
* [**Differences from Uniswap V3**](/developers/dex-integration/differences-from-uniswap) - exactly what the fork changes
* [**Live registry config**](/developers/building-agents/registry-config) - read the tunable parameters live on-chain


# Overview

Kumbaya is a good fit for autonomous agents: every layer of the stack is permissionless, the chain is real-time (10ms blocks), and the Client API supports **Sign-In With Ethereum** so an agent can self-onboard with just a private key - no human, no Privy, no email.

This section is the integrator-grade guide for agent developers. It covers the auth flow agents use, the on-chain entry points they call, and the read paths that surface the freshest data.

> **Want the batteries-included path?** The [**Kumbaya Agent Kit**](/developers/building-agents/agent-kit) wraps everything below into MCP tools an LLM can call directly - two servers ([on-chain MCP](/developers/building-agents/agent-kit/onchain-mcp) for the wallet, [api-mcp](/developers/building-agents/agent-kit/mcp) for the app), a [signer](/developers/building-agents/agent-kit/signer) for keyless fleets, and a portable skill pack. This page is the from-scratch reference the kit is built on; read it to understand what the tools do under the hood, or skip to the kit if you just want an agent trading in minutes.

## What an agent typically does

| Capability                    | What it touches                                                                                                                     |
| ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| **Trade** (buy / sell tokens) | On-chain `SwapRouter02` or `UniversalRouter`, optionally via the [Exchange API quote endpoint](/developers/apis/exchange-api/quote) |
| **Provide liquidity**         | On-chain `NonfungiblePositionManager`                                                                                               |
| **Launch a token**            | On-chain `FireLaunch.ignite()`, optionally with `/v1/launch` for off-chain metadata                                                 |
| **Claim an unclaimed token**  | EIP-712 signature → `POST /v1/tokens/{mint}/claim`                                                                                  |
| **Trigger graduation**        | `FireLaunch.recordGraduationCondition` → wait grace → `FireGraduator.graduate`                                                      |
| **Sweep creator fees**        | `FireStream.claimFees(token)` and/or `FuelVault.withdraw(token)`                                                                    |
| **React to launches / swaps** | [Hasura GraphQL](/developers/resources/indexer) subscriptions or polling                                                            |
| **Comment, tip, be social**   | Client API after SIWE auth                                                                                                          |

You don't need most of those for a viable agent - pick what fits your strategy.

## Authentication: SIWE

The Client API accepts [EIP-4361](https://eips.ethereum.org/EIPS/eip-4361) signatures. This is the agent path; you don't need Privy.

```ts
import { privateKeyToAccount } from 'viem/accounts'

const account = privateKeyToAccount(process.env.AGENT_PRIVATE_KEY as `0x${string}`)

const BASE = 'https://clients.kumbaya.xyz'

// 1. Get a nonce for this address
const { nonce } = await fetch(
  `${BASE}/v1/session/wallet/nonce?address=${account.address}`,
).then(r => r.json())

// 2. Build a SIWE message (EIP-4361). The parser only requires `address` and `Nonce:` lines.
const issuedAt = new Date().toISOString()
const message = `kumbaya.xyz wants you to sign in with your Ethereum account:
${account.address}

Sign in to Kumbaya as an agent.

URI: https://kumbaya.xyz
Version: 1
Chain ID: 4326
Nonce: ${nonce}
Issued At: ${issuedAt}`

const signature = await account.signMessage({ message })

// 3. Exchange for a JWT
const session = await fetch(`${BASE}/v1/session/wallet/verify`, {
  method:  'POST',
  headers: { 'content-type': 'application/json' },
  body:    JSON.stringify({ message, signature }),
}).then(r => r.json())

// session.token - bearer JWT for subsequent requests
// session.expiresAt - ISO timestamp
// session.user.id, session.user.walletAddress, session.user.name, session.user.image
```

A few practical notes:

* **Nonces are single-use, 5-minute TTL.** Your agent must request a fresh nonce per login attempt.
* **First-time logins auto-create the user.** If the wallet has never signed in, a new user record is created with `privyDid: "wallet:<address>"`. No human approval needed.
* **Refresh proactively.** JWT lifetime is set by the backend (httpOnly cookie expiry); call `POST /v1/session/refresh` before expiry, or just re-do the SIWE flow whenever the agent restarts.
* **Some endpoints require a JWT, many don't.** Reads, quotes, search, and indexer queries are all fine without a session.

## Reading on-chain state

For freshest data:

* **Quotes** → [`/api/v1/quote`](/developers/apis/exchange-api/quote) on `exchange.kumbaya.xyz`. One HTTPS call, returns calldata.
* **Search** → [`/api/v1/search`](/developers/apis/search-service) on `search.kumbaya.xyz`. No auth.
* **Indexed history (analytics, time-series, joins)** → [Hasura](/developers/resources/indexer) at `https://ql.kumbaya.xyz/v1/graphql`. No auth.
* **Live RPC reads** → MegaETH RPC `https://mainnet.megaeth.com/rpc`. Use the [`@kumbaya_xyz/v3-sdk`](/developers/sdks/v3-sdk) for typed pool/position objects.

Hasura supports GraphQL subscriptions for live event streams. For example, this watches new launches:

```graphql
subscription NewFireLaunches {
  FireToken(
    where: { chainId: { _eq: 4326 } }
    order_by: { createdAt: desc }
    limit: 10
  ) {
    address
    creator
    createdAt
    pool_id
    isToken0
  }
}
```

## Trading

Use whichever path matches your latency/complexity needs:

* **Single pool, you know the route:** call `QuoterV2` then `SwapRouter02.exactInputSingle` directly. \~2 round trips.
* **Multi-pool optimal route:** call the [Exchange API quote endpoint](/developers/apis/exchange-api/quote), submit the returned calldata. \~1 round trip server-side.
* **Bundling Permit2 + swap or atomic batch:** use `UniversalRouter` via [`@kumbaya_xyz/universal-router-sdk`](/developers/sdks/universal-router-sdk).

End-to-end examples are in [**Quoting prices**](/developers/dex-integration/quoting) and [**Executing swaps**](/developers/dex-integration/swapping).

## Launching a token (programmatically)

```ts
import { fireLaunchAbi } from './abis/fireLaunch'
import { encodeFunctionData, parseUnits } from 'viem'

const FIRE_LAUNCH = '0x69FE0908F1211dE66F7067021998f28A5693ABbD' // mainnet
const WETH        = '0x4200000000000000000000000000000000000006'

// Mine a salt so the resulting token address is < numeraire (token must be token0).
// See "Token ordering and tick math" in the Launching a token guide.
const salt = mineToken0Salt({ deployer: FIRE_LAUNCH, numeraire: WETH, /* ... */ })

const txHash = await wallet.writeContract({
  address: FIRE_LAUNCH,
  abi: fireLaunchAbi,
  functionName: 'ignite',
  args: [{
    name:                 'My Agent Token',
    symbol:               'AGENT',
    totalSupply:          parseUnits('1000000000', 18),
    numeraire:            WETH,
    tickLower:            -219400,                 // canonical, indexed
    tickUpper:            -174800,                 // canonical, indexed
    feeTier:              10000,
    skimBps:              300,                    // 3%
    creatorAllocationBps: 0,
    maxShareToBeSoldBps:  7200,
    numPositions:         50,
    vestingDuration:      BigInt(90 * 24 * 60 * 60),
    salt,
  }],
})
```

> ⚠️ **Stick to canonical tick values** unless you have a strong reason not to. The indexer only indexes launches that match the canonical config (or its inverse). See [**Launching a token → Token ordering and tick math**](/developers/launchpad/launching#-token-ordering-and-tick-math).

To buy at launch, send a second transaction immediately after `ignite` confirms - typically a `SwapRouter02.exactInputSingle` against the freshly-created pool. Note this is **not atomic**: handle the case where the buy reverts but the token is already deployed.

### Off-chain listing metadata

If you want your agent's launch to appear with proper metadata (description, image, social links) on `kumbaya.xyz`, sign in via SIWE first, then either:

* Use **`POST /v1/launch`** to create a draft listing *before* deploying on-chain, then `POST /v1/launch/{id}/image` to upload the image, then `POST /v1/launch/{id}/submit` with `{ tokenAddress }` to verify and finalize, or
* Just deploy the token directly and **claim the listing afterwards** with the [EIP-712 claim flow](/developers/apis/client-api#token-claim--eip-712-signature).

The claim path is simpler for agents because it's a single signature per token.

## Watching your tokens & sweeping fees

Once you've launched (or graduated), you have ongoing earnings to collect.

**Pre-graduation - FuelVault gifts:**

```ts
const fuelVault = '0x5aFaB54ac28a3bd485751146470D053b4FF11c81' // mainnet

// Read your liquid bucket
const liquid = await client.readContract({
  address: fuelVault,
  abi: fuelVaultAbi,
  functionName: 'creatorBuckets',
  args: [account.address, tokenAddress],
})

// Withdraw if non-zero
if (liquid.liquid > 0n) {
  await wallet.writeContract({
    address: fuelVault,
    abi: fuelVaultAbi,
    functionName: 'withdraw',
    args: [tokenAddress],
  })
}
```

**Post-graduation - FireStream fees** (anyone can call; the contract pays your share to your address):

```ts
const fireStream = '0x94d9582130745d0e2a1757dDEd8e730F5CDAd759' // mainnet

await wallet.writeContract({
  address: fireStream,
  abi: fireStreamAbi,
  functionName: 'claimFees',
  args: [tokenAddress],
})
```

**Trigger graduation** when the price tick crosses the threshold - run this against any launchpad token whose `canGraduate(token)` returns `true`:

```ts
// 1. Anyone can record the condition (starts grace timer).
//    recordGraduationCondition lives on FireLaunch, not FireGraduator.
await wallet.writeContract({
  address: fireLaunch,
  abi: fireLaunchAbi,
  functionName: 'recordGraduationCondition',
  args: [tokenAddress],
})

// 2. After gracePeriodDuration elapses, anyone can graduate (FireGraduator).
await wallet.writeContract({
  address: fireGraduator,
  abi: fireGraduatorAbi,
  functionName: 'graduate',
  args: [tokenAddress],
})
```

A polling loop on `FireToken.graduationConditionRecordedAt` (from the indexer) plus a registry read for `gracePeriodDuration` is all you need to know when to act.

## Best practices for agents

* **Use SIWE, not Privy.** Privy is for human social-login flows; SIWE is for keys.
* **Fail loud, don't retry blindly.** A failed `ignite()` produces a stranded token. Check the txHash, parse the receipt, branch on success.
* **Stick to canonical launchpad params** unless you accept being unindexed - the launchpad UI won't show your launch otherwise.
* **Quote before swap.** Front-run-style "send and hope" is unreliable; always price the trade first.
* **Honor `expiresAt` on your JWT.** Keep a single shared session and refresh it; don't sign new SIWE messages on every request.
* **Don't gift to your own creator address.** `FuelVault` rejects `SelfGiftNotAllowed`.
* **Read the registry live.** Constants like `gracePeriodDuration`, `forceGraduationDelay`, and `streamingRecipientsTotalBps` are governance-tunable. See [**Live registry config**](/developers/building-agents/registry-config) for cast/viem snippets to read them on the fly.

## Where to next

* [**Kumbaya Agent Kit**](/developers/building-agents/agent-kit) - the whole stack as MCP tools + a skill pack, no glue code
* [**On-chain MCP**](/developers/building-agents/agent-kit/onchain-mcp) - the key-holding wallet server (swaps, liquidity, launches)
* [**Signer service**](/developers/building-agents/agent-kit/signer) - keyless signing for agent fleets
* [**Live registry config**](/developers/building-agents/registry-config) - read current registry values on-chain
* [**Auto-launch a token**](/developers/building-agents/launching-programmatically) - full programmatic launch recipe
* [**Sign in with Ethereum (SIWE)**](/developers/building-agents/siwe) - auth deep-dive
* [**Client API**](/developers/apis/client-api) - full API reference
* [**MCP Server**](/developers/building-agents/agent-kit/mcp) - the app APIs as MCP tools for LLM agents
* [**Hasura indexer**](/developers/resources/indexer) - subscription-friendly read path


# Kumbaya Agent Kit

The [**Kumbaya Agent Kit**](https://github.com/Kumbaya-xyz/kumbaya-agent-kit) is everything an agent needs to act on Kumbaya (MegaETH): two [Model Context Protocol](https://modelcontextprotocol.io) servers, a signing service, and a portable skill pack. Drop it into any MCP-capable client (Claude, Cursor, Hermes, or your own framework) and an LLM can trade, provide liquidity, launch tokens, tip creators, and read the whole platform - with no glue code.

If you're writing raw TypeScript against the contracts and APIs instead, start at [Building agents → Overview](/developers/building-agents/overview). The kit is the batteries-included path; everything it does, you can also do by hand.

## What's in the box

| Package                                                                          | npm                                                                                        | Role                                                                                                                                                                                                     |
| -------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **onchain-mcp**                                                                  | [`@kumbaya_xyz/onchain-mcp`](https://www.npmjs.com/package/@kumbaya_xyz/onchain-mcp)       | The wallet. Builds and broadcasts on-chain transactions (swaps, liquidity, launches, fee claims), signs with a key you control. See [On-chain MCP](/developers/building-agents/agent-kit/onchain-mcp).   |
| **api-mcp**                                                                      | [`@kumbaya_xyz/kumbaya-mcp`](https://www.npmjs.com/package/@kumbaya_xyz/kumbaya-mcp)       | The app. JWT-authenticated access to the Exchange, Client, and Search APIs as \~113 auto-generated tools. See [MCP Server](/developers/building-agents/agent-kit/mcp).                                   |
| **signer**                                                                       | [`@kumbaya_xyz/onchain-signer`](https://www.npmjs.com/package/@kumbaya_xyz/onchain-signer) | Key custody for fleets. Holds every agent's key server-side and signs token-authenticated requests, so agent processes stay keyless. See [Signer service](/developers/building-agents/agent-kit/signer). |
| [**skills/**](https://github.com/Kumbaya-xyz/kumbaya-agent-kit/tree/main/skills) | (source only)                                                                              | Portable `SKILL.md` procedures - one per activity - that turn the raw tools into step-by-step workflows.                                                                                                 |

Each package publishes independently to npm; the monorepo shares the docs and skills.

## The security boundary

The organizing principle: **whatever holds a private key does nothing else.**

* For a **single wallet**, that key-holder is onchain-mcp itself - you give it a `WALLET_PRIVATE_KEY` and it signs directly.
* For a **fleet**, the key-holder is the standalone **signer** service. Every agent's onchain-mcp runs keyless and delegates signing over HTTP with its own bearer token. Keys never enter an agent process, tokens are revocable without touching keys, and each token can carry a policy (allowed chains, value caps, recipient allowlists).

The api-mcp never holds a key at all. It holds only a session JWT.

## The auth bridge

Authenticated app actions (posting a comment, sending a gift, creating a launch draft) need a session that the wallet owns. The kit bridges the on-chain identity to the app session in two steps:

1. `siwe_login` (onchain-mcp) signs a [Sign-In-With-Ethereum](/developers/building-agents/siwe) message with the wallet key and returns a JWT.
2. Point both onchain-mcp and api-mcp at the same `KUMBAYA_JWT_FILE`. `siwe_login` writes the token there; api-mcp re-reads it on each request.

Now the agent can trade with its key and act socially with its session, using the same identity across both servers.

## Skills

The [`skills/`](https://github.com/Kumbaya-xyz/kumbaya-agent-kit/tree/main/skills) pack turns tools into procedures - including cross-server ones like tipping, which spans both servers in sequence. Every skill is a plain `SKILL.md` you can read or copy straight from the repo. Drop them into any agent that supports skills (Claude Code, Hermes, etc.).

**Activity skills** - one procedure each:

| Skill                                                                                                               | Spans         | What it does                                                                    |
| ------------------------------------------------------------------------------------------------------------------- | ------------- | ------------------------------------------------------------------------------- |
| [`kumbaya`](https://github.com/Kumbaya-xyz/kumbaya-agent-kit/blob/main/skills/kumbaya/SKILL.md)                     | both          | Overview and routing - which server to use for what, and the auth bridge.       |
| [`kumbaya-trade`](https://github.com/Kumbaya-xyz/kumbaya-agent-kit/blob/main/skills/kumbaya-trade/SKILL.md)         | onchain-mcp   | Quote first, then swap (including native ETH).                                  |
| [`kumbaya-liquidity`](https://github.com/Kumbaya-xyz/kumbaya-agent-kit/blob/main/skills/kumbaya-liquidity/SKILL.md) | onchain-mcp   | Provide, view, adjust, and collect V3 liquidity.                                |
| [`kumbaya-launch`](https://github.com/Kumbaya-xyz/kumbaya-agent-kit/blob/main/skills/kumbaya-launch/SKILL.md)       | onchain + api | Launch a token on the bonding curve, optionally seed a buy and post about it.   |
| [`kumbaya-tip`](https://github.com/Kumbaya-xyz/kumbaya-agent-kit/blob/main/skills/kumbaya-tip/SKILL.md)             | onchain + api | Tip a creator on a comment - the wallet signs a permit, the app API submits it. |
| [`kumbaya-earn`](https://github.com/Kumbaya-xyz/kumbaya-agent-kit/blob/main/skills/kumbaya-earn/SKILL.md)           | onchain-mcp   | Claim trading fees, withdraw tips, release vested creator allocation.           |

**Persona bundles** - quick-launch presets for a role. Each groups the activities it needs and scopes `KUMBAYA_MCP_SERVICES` down to a smaller, sharper tool set (the api-mcp exposes \~113 tools; a role rarely needs all of them). Pick one, paste its config, and go:

| Persona                                                                                                                 | Activities        | api-mcp services  | Credentials                       |
| ----------------------------------------------------------------------------------------------------------------------- | ----------------- | ----------------- | --------------------------------- |
| [`kumbaya-trader`](https://github.com/Kumbaya-xyz/kumbaya-agent-kit/blob/main/skills/kumbaya-trader/SKILL.md)           | trade, liquidity  | `exchange,search` | wallet key (partner key optional) |
| [`kumbaya-creator`](https://github.com/Kumbaya-xyz/kumbaya-agent-kit/blob/main/skills/kumbaya-creator/SKILL.md)         | launch, earn, tip | `client,exchange` | wallet key + JWT                  |
| [`kumbaya-contributor`](https://github.com/Kumbaya-xyz/kumbaya-agent-kit/blob/main/skills/kumbaya-contributor/SKILL.md) | tip, social       | `client,search`   | wallet key + JWT                  |
| [`kumbaya-observer`](https://github.com/Kumbaya-xyz/kumbaya-agent-kit/blob/main/skills/kumbaya-observer/SKILL.md)       | reads only        | `exchange,search` | none - no key, no JWT             |

## Quick start

The fastest path is to pick a [persona](#skills) and paste its preset. Every setup is the same two `mcpServers` entries - only the `env` changes: which services load, and which credentials you supply.

### 1. Register the two servers

```json
{
  "mcpServers": {
    "kumbaya-onchain": {
      "command": "npx",
      "args": ["-y", "@kumbaya_xyz/onchain-mcp"],
      "env": { }
    },
    "kumbaya-api": {
      "command": "npx",
      "args": ["-y", "@kumbaya_xyz/kumbaya-mcp"],
      "env": { }
    }
  }
}
```

### 2. Fill the `env` for your persona

| Persona         | `kumbaya-onchain` env                                | `kumbaya-api` env                                          |
| --------------- | ---------------------------------------------------- | ---------------------------------------------------------- |
| **Trader**      | `WALLET_PRIVATE_KEY`, `CHAIN_ID`                     | `KUMBAYA_MCP_SERVICES=exchange,search`                     |
| **Creator**     | `WALLET_PRIVATE_KEY`, `CHAIN_ID`, `KUMBAYA_JWT_FILE` | `KUMBAYA_MCP_SERVICES=client,exchange`, `KUMBAYA_JWT_FILE` |
| **Contributor** | `WALLET_PRIVATE_KEY`, `CHAIN_ID`, `KUMBAYA_JWT_FILE` | `KUMBAYA_MCP_SERVICES=client,search`, `KUMBAYA_JWT_FILE`   |
| **Observer**    | `CHAIN_ID` *(no key)*                                | `KUMBAYA_MCP_SERVICES=exchange,search`                     |

For example, a **creator** on testnet:

```json
"kumbaya-onchain": {
  "env": {
    "WALLET_PRIVATE_KEY": "0x...",
    "CHAIN_ID": "6343",
    "KUMBAYA_JWT_FILE": "/path/session.jwt"
  }
},
"kumbaya-api": {
  "env": {
    "KUMBAYA_MCP_SERVICES": "client,exchange",
    "KUMBAYA_JWT_FILE": "/path/session.jwt"
  }
}
```

The matching persona skill (`kumbaya-creator`, `kumbaya-trader`, …) carries the full role instructions and the same preset, so an agent that loads skills gets the procedure too.

### Two things to know

* **The shared `KUMBAYA_JWT_FILE` is the auth bridge.** onchain-mcp's `siwe_login` writes the session there; api-mcp reads it on each request. Personas that post socially (creator, contributor) need it; a trader or observer doesn't.
* **Scope the services.** api-mcp exposes \~113 tools across `exchange,client,search`. Trimming `KUMBAYA_MCP_SERVICES` to the persona's needs gives the model a smaller, sharper tool set - leave a service out and its tools don't load.

For a fleet, run the [signer](/developers/building-agents/agent-kit/signer) and swap onchain-mcp's `WALLET_PRIVATE_KEY` for `SIGNER_URL` + `SIGNER_TOKEN` (the address is derived from the signer).

## Networks

MegaETH testnet `6343` (the kit's default) and mainnet `4326`. onchain-mcp is testnet-first by design; set `CHAIN_ID=4326` (or pass `chainId` per tool call) to act on mainnet.

## Where to next

* [**On-chain MCP**](/developers/building-agents/agent-kit/onchain-mcp) - the wallet server and its 22 tools
* [**Signer service**](/developers/building-agents/agent-kit/signer) - keyless signing for agent fleets
* [**MCP Server**](/developers/building-agents/agent-kit/mcp) - the api-mcp and its \~113 API tools
* [**Sign in with Ethereum**](/developers/building-agents/siwe) - the auth bridge in detail
* [**Building agents → Overview**](/developers/building-agents/overview) - the raw integration path the kit is built on
* [**skills/ on GitHub**](https://github.com/Kumbaya-xyz/kumbaya-agent-kit/tree/main/skills) - the `SKILL.md` source for every activity and persona


# On-chain MCP

`@kumbaya_xyz/onchain-mcp` is the wallet half of the [Kumbaya Agent Kit](/developers/building-agents/agent-kit): an [MCP](https://modelcontextprotocol.io) server that builds, signs, and broadcasts on-chain transactions on MegaETH. It covers trading, liquidity, launchpad token launches, creator earnings, and the reads that support them. It signs either with a local key you control or by delegating to a remote [signer](/developers/building-agents/agent-kit/signer).

| Field         | Value                                                                                                                       |
| ------------- | --------------------------------------------------------------------------------------------------------------------------- |
| Package       | `@kumbaya_xyz/onchain-mcp` ([npm](https://www.npmjs.com/package/@kumbaya_xyz/onchain-mcp))                                  |
| Bin           | `kumbaya-onchain-mcp`                                                                                                       |
| Source        | [github.com/Kumbaya-xyz/kumbaya-agent-kit](https://github.com/Kumbaya-xyz/kumbaya-agent-kit/tree/main/packages/onchain-mcp) |
| Transport     | stdio                                                                                                                       |
| Default chain | `6343` (MegaETH testnet)                                                                                                    |
| License       | MIT                                                                                                                         |

Unlike the [api-mcp](/developers/building-agents/agent-kit/mcp), this server holds a key (or reaches one). It's built from contract calldata and the `@kumbaya_xyz` SDKs (`sdk-core`, `v3-sdk`, `router-sdk`) plus viem for signing and broadcasting.

## Install

Register it with an MCP client. Local-key mode (single wallet, testnet):

```json
{
  "mcpServers": {
    "kumbaya-onchain": {
      "command": "npx",
      "args": ["-y", "@kumbaya_xyz/onchain-mcp"],
      "env": {
        "WALLET_PRIVATE_KEY": "0x...",
        "CHAIN_ID": "6343",
        "KUMBAYA_JWT_FILE": "/path/session.jwt"
      }
    }
  }
}
```

Reads work without a key - omit `WALLET_PRIVATE_KEY` and the server runs read-only. For fleets, replace the key with the remote-signer variables below.

## Tools

22 tools, testnet-first. Most tools accept an optional `chainId` (`4326` mainnet, `6343` testnet) that overrides the configured default (`get_address` takes no inputs). Amounts are in human units (e.g. `"0.5"`), not wei.

### Reads (no key required)

| Tool             | Purpose                                                                                                                            | Key inputs                                       |
| ---------------- | ---------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------ |
| `get_address`    | Your wallet address (the account the signer holds).                                                                                | (none)                                           |
| `get_balance`    | ETH and optional ERC-20 balance for an address (defaults to the wallet).                                                           | `address?`, `token?`                             |
| `list_balances`  | All ERC-20 balances for an address plus ETH; discovered via the block explorer and verified on-chain.                              | `address?`, `tokens?`                            |
| `get_token`      | ERC-20 metadata: symbol, name, decimals, total supply.                                                                             | `token`                                          |
| `get_pool`       | V3 pool state for a pair + fee tier: address, price, tick, liquidity.                                                              | `tokenA`, `tokenB`, `fee`                        |
| `quote`          | Best route and amounts from `tokenIn` to `tokenOut`. Provide exactly one of `amountIn` / `amountOut`.                              | `tokenIn`, `tokenOut`, `amountIn?`, `amountOut?` |
| `list_positions` | V3 liquidity positions (NFTs) for an address: range, amounts, uncollected fees, in-range status.                                   | `address?`                                       |
| `get_tips`       | FuelVault balances for a token: your spendable tip credits and creator earnings (liquid / vested).                                 | `token`, `user?`                                 |
| `get_vesting`    | Creator vesting schedule for a launchpad token: total, vested, released, releasable-now.                                           | `token`                                          |
| `token_status`   | Your full stake in a token — balance, tip credits, creator earnings, liquidity, vesting — with `isCreator` / `isDisposable` flags. | `token`, `address?`                              |

### Writes (signing required, real transactions)

| Tool               | Purpose                                                                                                                                                                                                                                                                                   | Key inputs                                                                                  |
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| `swap`             | Swap tokens on the V3 DEX. Use the WETH address to pay/receive native ETH; ERC-20 inputs are auto-approved. Provide one of `amountIn` / `amountOut`.                                                                                                                                      | `tokenIn`, `tokenOut`, `amountIn?`, `amountOut?`, `slippageBps?`                            |
| `add_liquidity`    | Mint a V3 position. Full range by default; pass `tickLower`/`tickUpper` for a concentrated range.                                                                                                                                                                                         | `tokenA`, `tokenB`, `fee`, `amountA`, `amountB`, `tickLower?`, `tickUpper?`, `slippageBps?` |
| `remove_liquidity` | Withdraw `percent` of a position's principal plus all fees. `percent=100` also burns the NFT.                                                                                                                                                                                             | `tokenId`, `percent?`, `slippageBps?`                                                       |
| `collect_fees`     | Collect a position's accrued fees without removing liquidity.                                                                                                                                                                                                                             | `tokenId`                                                                                   |
| `ignite`           | Launch a token on the bonding curve. Mines a CREATE2 salt so the token sorts as token0 vs WETH, applies standard curve params, returns the token + pool addresses. No ETH required.                                                                                                       | `name`, `symbol`, `totalSupply?`                                                            |
| `claim_fees`       | Claim your creator trading fees. You earn only **post-graduation** via `stream` (FireStream pays the streaming recipients and sends you the remainder). The pre-graduation `graduator` path collects bonding-curve fees to the protocol, **not you**. `auto` tries graduator then stream. | `token`, `source?` (`auto`/`stream`/`graduator`)                                            |
| `withdraw_tips`    | Withdraw your unlocked creator tips from the FuelVault to your wallet.                                                                                                                                                                                                                    | `token`                                                                                     |
| `release_vested`   | Release your vested creator token allocation.                                                                                                                                                                                                                                             | `token`                                                                                     |

`swap`, `add_liquidity`, and `deposit_credits` operate only on launchpad, bluechip, or verified tokens (per the search directory), plus a token launched in the same session. Unrecognized tokens are rejected, and the check fails closed if the directory is unreachable. Set `KUMBAYA_TOKEN_ALLOWLIST=off` to operate on any token.

### Wallet (auth + signing)

| Tool               | Purpose                                                                                                                                                                                                                                                                     | Key inputs                |
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- |
| `siwe_login`       | Sign a [Sign-In-With-Ethereum](/developers/building-agents/siwe) message and return a JWT. If `KUMBAYA_JWT_FILE` is set, writes the token there so [api-mcp](/developers/building-agents/agent-kit/mcp) picks it up automatically. Run before any authenticated app action. | `chainId?`                |
| `sign_typed_data`  | Sign EIP-712 typed data (e.g. a gift permit from an api-mcp prepare step) so it can be submitted.                                                                                                                                                                           | `typedData`               |
| `sign_token_claim` | Sign the EIP-712 ClaimListing proof to claim an unclaimed token listing as its on-chain creator. Pass the returned fields straight to the api-mcp `app_post_tokens_by_mint_address_claim` tool.                                                                             | `mintAddress`, `chainId?` |
| `deposit_credits`  | Deposit a launched token into your FuelVault credit balance (approve + depositFrom). Credits are spent when tipping.                                                                                                                                                        | `token`, `amount`         |

Write and wallet tools return a `txHash`, `status`, and an `explorer` link where applicable.

## Configuration

### Signing: local key

| Env var              | Purpose                                                                                                     |
| -------------------- | ----------------------------------------------------------------------------------------------------------- |
| `WALLET_PRIVATE_KEY` | Hex private key used to sign. Omit for read-only mode. (`AGENT_WALLET_KEY` is accepted as a fallback name.) |

### Signing: remote signer (fleets)

Set these instead of `WALLET_PRIVATE_KEY` and the process holds no key - it delegates every signature to a [signer service](/developers/building-agents/agent-kit/signer).

| Env var          | Purpose                                                                                                 |
| ---------------- | ------------------------------------------------------------------------------------------------------- |
| `SIGNER_URL`     | Base URL of the signer service (e.g. `http://localhost:8787`).                                          |
| `SIGNER_TOKEN`   | This agent's bearer token at the signer.                                                                |
| `SIGNER_ADDRESS` | This agent's public address. Optional: derived from the signer's `/v1/address` at startup when omitted. |

### Chain and API

| Env var                   | Default                                         | Purpose                                                                               |
| ------------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------- |
| `CHAIN_ID`                | `6343`                                          | Default chain: `4326` mainnet or `6343` testnet. Overridable per tool via `chainId`.  |
| `KUMBAYA_JWT_FILE`        | (none)                                          | Path shared with api-mcp. `siwe_login` writes the session JWT here; api-mcp reads it. |
| `KUMBAYA_EXCHANGE_URL`    | `https://exchange.kumbaya.xyz`                  | Exchange API base, used for the public `pools/admitted` routing endpoint.             |
| `KUMBAYA_CLIENT_URL`      | `https://clients.kumbaya.xyz`                   | Client API base, used only for SIWE wallet auth.                                      |
| `KUMBAYA_TOKEN_ALLOWLIST` | `on`                                            | Set to `off` to disable the token allowlist and operate on any token.                 |
| `KUMBAYA_SEARCH_URL`      | `https://search.kumbaya.xyz`                    | Search API base, used to classify tokens as launchpad, bluechip, or verified.         |
| `KUMBAYA_BLOCKSCOUT_4326` | `https://megaeth.blockscout.com/api`            | Mainnet block explorer used to discover an address's token holdings.                  |
| `KUMBAYA_BLOCKSCOUT_6343` | `https://megaeth-testnet-v2.blockscout.com/api` | Testnet block explorer used to discover an address's token holdings.                  |

## Routing

`quote` and `swap` discover routes from the public `pools/admitted` endpoint on the Exchange API, and fall back to on-chain pool probing for freshly launched tokens that aren't admitted yet. Pool addresses are computed with Kumbaya's [custom init code hash](/developers/dex-integration/pool-init-code-hash), which is baked into `@kumbaya_xyz/v3-sdk`.

## Where to next

* [**Kumbaya Agent Kit**](/developers/building-agents/agent-kit) - how this fits with api-mcp, the signer, and skills
* [**Signer service**](/developers/building-agents/agent-kit/signer) - run keyless agents against a shared signer
* [**Sign in with Ethereum**](/developers/building-agents/siwe) - the JWT the auth bridge produces
* [**Launching a token (ignite)**](/developers/launchpad/launching) - what `ignite` does under the hood


# MCP Server (APIs)

`kumbaya-mcp` is a [Model Context Protocol](https://modelcontextprotocol.io) server that exposes the Kumbaya platform as tools for any MCP client (Claude, Cursor, Hermes, and custom AI agents). It turns the Exchange, Client, and Search APIs into callable tools an LLM can use directly, with no glue code.

| Field     | Value                                                                                                                   |
| --------- | ----------------------------------------------------------------------------------------------------------------------- |
| Package   | `@kumbaya_xyz/kumbaya-mcp` ([npm](https://www.npmjs.com/package/@kumbaya_xyz/kumbaya-mcp))                              |
| Source    | [github.com/Kumbaya-xyz/kumbaya-agent-kit](https://github.com/Kumbaya-xyz/kumbaya-agent-kit/tree/main/packages/api-mcp) |
| Transport | stdio                                                                                                                   |
| License   | MIT                                                                                                                     |

The server is generated directly from Kumbaya's OpenAPI specs, so its tools stay in lockstep with the live APIs.

> This is the **API** server - the app half of the [Kumbaya Agent Kit](/developers/building-agents/agent-kit). It reads and writes over HTTP and never holds a wallet. For on-chain actions (swaps, liquidity, launches) that need a key, pair it with the [on-chain MCP](/developers/building-agents/agent-kit/onchain-mcp).

## What it covers

| Service  | Tool prefix | Scope                                                                                  | Tools |
| -------- | ----------- | -------------------------------------------------------------------------------------- | ----- |
| Exchange | `dex_`      | Swap quotes, pools, tokens, stats, positions                                           | 29    |
| Client   | `app_`      | Launchpad, tokens, comments, gifts and fuel, feed, content, competition, badges, users | 76    |
| Search   | `search_`   | Token and pool full-text search                                                        | 8     |

Each tool maps one-to-one to an API endpoint and is named `<prefix>_<operation>`, for example `dex_get_quote`, `app_post_comments`, or `search_get_search_tokens`.

## Install

Run it directly:

```bash
npx @kumbaya_xyz/kumbaya-mcp
```

Or register it with an MCP client:

```json
{
  "mcpServers": {
    "kumbaya": {
      "command": "npx",
      "args": ["-y", "@kumbaya_xyz/kumbaya-mcp"],
      "env": {
        "KUMBAYA_API_KEY": "partner-key-for-quotes",
        "KUMBAYA_JWT": "user-jwt-for-authenticated-actions"
      }
    }
  }
}
```

## Configuration

| Env var                | Default                        | Purpose                                                                                               |
| ---------------------- | ------------------------------ | ----------------------------------------------------------------------------------------------------- |
| `KUMBAYA_MCP_SERVICES` | all                            | Comma-separated services to expose, e.g. `exchange,search`. Trims the tool count for focused clients. |
| `KUMBAYA_API_KEY`      | (none)                         | Partner API key, sent as `x-api-key`. Required for the quote endpoints.                               |
| `KUMBAYA_JWT`          | (none)                         | User JWT, sent as `Authorization: Bearer`. Required for authenticated client-api actions.             |
| `KUMBAYA_EXCHANGE_URL` | `https://exchange.kumbaya.xyz` | Exchange base URL                                                                                     |
| `KUMBAYA_CLIENT_URL`   | `https://clients.kumbaya.xyz`  | Client base URL                                                                                       |
| `KUMBAYA_SEARCH_URL`   | `https://search.kumbaya.xyz`   | Search base URL                                                                                       |

DEX and search tools take a `chainId` (`4326` mainnet, `6343` testnet).

## Credentials, by tier

Tool descriptions carry a tag telling the client which credential a tool needs.

* **Public.** Most reads (pools, tokens, stats, positions, feed, content, search). No credentials.
* **Partner key.** The four quote tools (`dex_get_quote`, `dex_post_quote`, `dex_post_quote_open`, `dex_get_quote_tokens`). Set `KUMBAYA_API_KEY`. Request a key from the Kumbaya team.
* **User JWT.** Authenticated client-api actions (create a launch, post a comment, send a gift, edit a profile). Set `KUMBAYA_JWT`.

The server is keyless by design: it never holds a wallet or signs. It uses a JWT you supply. Agents obtain that JWT with their own wallet via [Sign-In With Ethereum](/developers/building-agents/siwe) and pass it in as `KUMBAYA_JWT`. Internal and administrative endpoints are not exposed.

## Example

Once connected, a client can ask in natural language and the model selects the right tool:

> "What's trending on Kumbaya mainnet, and how deep is the top pool's liquidity?"

resolves to `dex_get_tokens_trending` followed by `dex_get_pools_metrics`, with the results returned as structured JSON.

## Staying in sync

The bundled OpenAPI specs are Kumbaya's live documents. Contributors refresh them with `npm run refresh-spec`, which re-pulls each spec so the generated tools match the current API.

## See also

* [**Kumbaya Agent Kit**](/developers/building-agents/agent-kit) - this server plus the on-chain wallet, the fleet signer, and the skill pack
* [**On-chain MCP**](/developers/building-agents/agent-kit/onchain-mcp) - the key-holding counterpart for swaps, liquidity, and launches
* [**Sign in with Ethereum**](/developers/building-agents/siwe) - how an agent obtains the JWT this server consumes
* [**Exchange API**](/developers/apis/exchange-api), [**Client API**](/developers/apis/client-api), [**Search Service**](/developers/apis/search-service) - the REST endpoints these tools are generated from


# Signer service

`@kumbaya_xyz/onchain-signer` is the key-custody component of the [Kumbaya Agent Kit](/developers/building-agents/agent-kit). It's a standalone HTTP service that holds every agent's private key and signs token-authenticated requests, so agent processes never hold a raw key. Run it when you're operating a **fleet** of agents in one framework; for a single wallet you don't need it - [onchain-mcp](/developers/building-agents/agent-kit/onchain-mcp) signs directly.

| Field        | Value                                                                                                                  |
| ------------ | ---------------------------------------------------------------------------------------------------------------------- |
| Package      | `@kumbaya_xyz/onchain-signer` ([npm](https://www.npmjs.com/package/@kumbaya_xyz/onchain-signer))                       |
| Bin          | `kumbaya-onchain-signer`                                                                                               |
| Source       | [github.com/Kumbaya-xyz/kumbaya-agent-kit](https://github.com/Kumbaya-xyz/kumbaya-agent-kit/tree/main/packages/signer) |
| Transport    | HTTP (Hono)                                                                                                            |
| Default port | `8787`                                                                                                                 |
| License      | MIT                                                                                                                    |

## Why it exists

The kit's security rule is that whatever holds a key does nothing else. With a fleet, you don't want every agent process carrying a raw key. The signer isolates key custody to one trusted host:

* **Keys never enter agent processes.** Each agent's onchain-mcp runs keyless and delegates signing over HTTP.
* **Tokens, not keys, are the agent credential.** A leaked token is revoked or rotated by editing the keystore; the underlying key is untouched.
* **Per-agent policy.** Each token can be scoped to allowed chains, a native-value cap, a recipient allowlist, and a typed-data allowlist. Requests that violate the policy are rejected before signing.
* **One identity per agent.** Each token maps to one address, so every agent signs as itself.

## Run it

```bash
SIGNER_KEYS_FILE=/secure/keys.json PORT=8787 npx @kumbaya_xyz/onchain-signer
```

Then point each agent's [onchain-mcp](/developers/building-agents/agent-kit/onchain-mcp) at it:

```json
{
  "mcpServers": {
    "kumbaya-onchain": {
      "command": "npx",
      "args": ["-y", "@kumbaya_xyz/onchain-mcp"],
      "env": {
        "SIGNER_URL": "http://kumbaya-signer.internal:8787",
        "SIGNER_TOKEN": "agent-official-token",
        "SIGNER_ADDRESS": "0xabcd...",
        "CHAIN_ID": "6343"
      }
    }
  }
}
```

## Keystore

The signer loads a JSON map of **token → key** (or **token → `{ key, label, policy }`**). Prefer `SIGNER_KEYS_FILE` over the inline `SIGNER_KEYS` so keys don't show up in the process list.

```json
{
  "agent-official-token": {
    "key": "0x<private-key>",
    "label": "official",
    "policy": {
      "allowChains": [6343],
      "maxValueWei": "50000000000000000",
      "allowTo": ["0x..swapRouter", "0x..positionManager"],
      "allowTypedData": [{ "primaryType": "GiftPermit", "name": "FuelVault", "version": "1" }]
    }
  },
  "agent-ronnie-token": "0x<private-key>"
}
```

A value can be a bare private-key string (no label, no policy) or an object. Policy fields are all optional:

| Field            | Effect                                                                                                                                                                                                                                          |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `allowChains`    | If set, `tx.chainId` must be in this list, else the request is rejected.                                                                                                                                                                        |
| `maxValueWei`    | If set, `tx.value` must not exceed this cap.                                                                                                                                                                                                    |
| `allowTo`        | If set, `tx.to` must be in this allowlist (case-insensitive).                                                                                                                                                                                   |
| `allowTypedData` | Allowed EIP-712 shapes for `/v1/sign/typed-data`, matched on `primaryType` / `name` / `version` / `verifyingContract` / `chainId`, each with an optional `spenderField` + `allowSpenders`. When unset, only the FuelVault GiftPermit is signed. |

## Configuration

| Env var            | Default | Purpose                                                      |
| ------------------ | ------- | ------------------------------------------------------------ |
| `SIGNER_KEYS_FILE` | (none)  | Path to the keystore JSON. Preferred.                        |
| `SIGNER_KEYS`      | (none)  | Inline keystore JSON. Use only where a file isn't practical. |
| `PORT`             | `8787`  | Listen port.                                                 |

If neither keystore variable is set, the signer starts empty and rejects every request.

## HTTP API

All signing endpoints require `Authorization: Bearer <token>`. Bigints in transaction and typed-data payloads are transported as hex strings and revived server-side.

| Method | Path                   | Body              | Returns                                                     |
| ------ | ---------------------- | ----------------- | ----------------------------------------------------------- |
| `GET`  | `/health`              | -                 | `{ ok, agents }` (count of loaded tokens)                   |
| `GET`  | `/v1/address`          | -                 | `{ address, label }` for the token                          |
| `POST` | `/v1/sign/transaction` | `{ transaction }` | `{ signedTransaction }` - policy-checked first              |
| `POST` | `/v1/sign/typed-data`  | `{ typedData }`   | `{ signature }` - policy-checked (default: GiftPermit only) |
| `POST` | `/v1/sign/message`     | `{ message }`     | `{ signature }`                                             |

Policy is enforced on `/v1/sign/transaction` and `/v1/sign/typed-data`; a violating request returns `403` with a `policy: <reason>` error and is never signed.

## How onchain-mcp delegates

When onchain-mcp sees `SIGNER_URL` set, it skips local key loading and builds a viem account whose signing methods call the signer:

* `signTransaction` → `POST /v1/sign/transaction`
* `signTypedData` → `POST /v1/sign/typed-data`
* `signMessage` → `POST /v1/sign/message`

The signer signs with the key mapped to the request's token and returns the signature; onchain-mcp then broadcasts the signed transaction itself. The signer only signs - it never touches the chain.

## Operating notes

* Run the signer on a trusted host and treat `SIGNER_KEYS_FILE` as a secret.
* Give each agent its own token and a policy scoped to what it actually needs (e.g. testnet-only, capped value, router + position-manager recipients).
* Rotate a token by replacing it in the keystore; the underlying key is unaffected.

## Where to next

* [**Kumbaya Agent Kit**](/developers/building-agents/agent-kit) - the full kit and its security model
* [**On-chain MCP**](/developers/building-agents/agent-kit/onchain-mcp) - the client that delegates to this signer


# Sign in with Ethereum (SIWE)

Kumbaya's Client API at [`clients.kumbaya.xyz`](https://clients.kumbaya.xyz) supports [EIP-4361 (Sign-In With Ethereum)](https://eips.ethereum.org/EIPS/eip-4361) as a first-class authentication path. This is the right path for **agents, bots, and any integration with its own private key** - no Privy account, no social login.

A wallet that signs in via SIWE for the first time is **automatically registered** as a user with a synthetic `privyDid: "wallet:<address>"`. From there it has the same JWT-authenticated access as any Privy-backed account.

## Endpoints

| Endpoint                    | Method | Auth | Purpose                                              |
| --------------------------- | ------ | ---- | ---------------------------------------------------- |
| `/v1/session/wallet/nonce`  | `GET`  | None | Issue a one-time nonce for an address (5-minute TTL) |
| `/v1/session/wallet/verify` | `POST` | None | Verify a signed SIWE message, issue a JWT            |

## Flow

```
agent                                       client-api
  │                                              │
  │  GET /v1/session/wallet/nonce?address=0x..   │
  ├─────────────────────────────────────────────▶│
  │                                              │   stores nonce in Redis
  │  { "nonce": "abc..." }                       │   under key siwe:nonce:0x...
  │◀─────────────────────────────────────────────┤
  │                                              │
  │  build SIWE message with the nonce           │
  │  personal_sign(message)                      │
  │                                              │
  │  POST /v1/session/wallet/verify              │
  │       { message, signature }                 │
  ├─────────────────────────────────────────────▶│
  │                                              │   parses message → recovers signer
  │                                              │   compares to nonce-bound address
  │                                              │   consumes nonce (one-shot)
  │                                              │   creates user if first-time
  │  { token, expiresAt, user }                  │   issues JWT (also as httpOnly cookie)
  │◀─────────────────────────────────────────────┤
```

## Required SIWE message format

The backend uses a **minimal parser**. It only requires:

* The signer's address (anywhere in the message, matched as the first `0x[0-9a-fA-F]{40}`)
* A line of the form `Nonce: <nonce>`

It accepts the standard EIP-4361 layout, e.g.:

```
kumbaya.xyz wants you to sign in with your Ethereum account:
0xYOUR_AGENT_WALLET

Sign in to Kumbaya as an agent.

URI: https://kumbaya.xyz
Version: 1
Chain ID: 4326
Nonce: <nonce-from-step-1>
Issued At: 2026-01-30T12:00:00Z
```

**Sign with `personal_sign`** (the EIP-191 prefixed-message variant). `viem`'s `signMessage`, `ethers`' `signer.signMessage`, and most wallet libraries default to this.

## End-to-end example (viem)

```ts
import { privateKeyToAccount } from 'viem/accounts'

const BASE = 'https://clients.kumbaya.xyz'

const account = privateKeyToAccount(process.env.AGENT_PRIVATE_KEY as `0x${string}`)

// 1. Get a nonce
const { nonce } = await fetch(
  `${BASE}/v1/session/wallet/nonce?address=${account.address}`,
).then(r => r.json())

// 2. Build SIWE message
const message = [
  `kumbaya.xyz wants you to sign in with your Ethereum account:`,
  account.address,
  ``,
  `Sign in to Kumbaya as an agent.`,
  ``,
  `URI: https://kumbaya.xyz`,
  `Version: 1`,
  `Chain ID: 4326`,
  `Nonce: ${nonce}`,
  `Issued At: ${new Date().toISOString()}`,
].join('\n')

// 3. Sign
const signature = await account.signMessage({ message })

// 4. Verify
const session = await fetch(`${BASE}/v1/session/wallet/verify`, {
  method:  'POST',
  headers: { 'content-type': 'application/json' },
  body:    JSON.stringify({ message, signature }),
}).then(r => {
  if (!r.ok) throw new Error(`SIWE verify failed: ${r.status}`)
  return r.json()
})

console.log('JWT:',         session.token)
console.log('Expires at:',  session.expiresAt)
console.log('Agent userId:', session.user.id)
```

## Using the JWT

Pass the token on subsequent requests via either:

* **Bearer header**: `Authorization: Bearer <jwt>`
* **httpOnly cookie**: the verify response also sets a session cookie automatically (use this if your agent shares a cookie jar)

```ts
const me = await fetch(`${BASE}/v1/session/current`, {
  headers: { Authorization: `Bearer ${session.token}` },
}).then(r => r.json())
```

## Refresh

JWTs are short-lived. Call `POST /v1/session/refresh` (with the existing JWT) to rotate, or simply re-run the SIWE flow when your token expires.

For long-running agents, the simplest pattern is: **request a fresh JWT on startup, refresh on a timer, re-do SIWE on any refresh failure.**

## Errors

| Status | Body                                           | Cause                                                   |
| ------ | ---------------------------------------------- | ------------------------------------------------------- |
| 400    | `{ "error": "Invalid SIWE message format" }`   | Couldn't parse address or nonce from the message        |
| 401    | `{ "error": "Invalid or expired nonce" }`      | Nonce doesn't match what was issued, or expired (5 min) |
| 401    | `{ "error": "Signature verification failed" }` | Recovered signer ≠ message address                      |
| 503    | `{ "error": "Service unavailable" }`           | Backend can't reach Redis (nonce store)                 |

## Notes for production

* **Nonce TTL is 5 minutes.** Don't pre-fetch nonces hours ahead.
* **Nonces are single-use.** Reusing a nonce after a successful verify will fail.
* **The address-in-message must match the recovered signer.** No "claim someone else's account" loophole.
* **Wallet-only users get a synthetic `privyDid`** of `wallet:<address>`. If a Privy user later imports the same wallet, the records merge implicitly - but for agent-only flows, this is usually moot.
* **You can sign in from any chain.** The `Chain ID` field in the SIWE message is informational; the parser doesn't enforce it. (That said, for clarity, set it to `4326` mainnet or `6343` testnet.)


# Auto-launch a token

End-to-end recipe for an agent that deploys a launchpad token, optionally buys at launch, and (optionally) attaches off-chain metadata so it shows up on `kumbaya.xyz`.

## Prerequisites

* An agent wallet with ETH on MegaETH mainnet for gas (and any initial buy).
* A SIWE session if you want to attach off-chain metadata. See [**Sign in with Ethereum**](/developers/building-agents/siwe).
* The launchpad contract addresses for your chain - see [Contract Addresses](/developers/networks-and-contracts/contract-addresses).

## Step 1: Mine the salt

`FireLaunch.ignite()` deploys via CREATE2, so the resulting token address is fully determined by `(deployer, salt, init code, constructor args)`. The Kumbaya indexer requires the new token to be **`token0`** (lower address than the numeraire). Mine until you find one:

```ts
import { getCreate2Address, keccak256, encodePacked } from 'viem'
import { randomBytes } from 'crypto'

const FIRE_LAUNCH = '0x69FE0908F1211dE66F7067021998f28A5693ABbD'  // mainnet
const WETH        = '0x4200000000000000000000000000000000000006'

// FireToken creation code hash - read once from a deployed FireToken or compute from its bytecode.
// Trust this from the launchpad repo's `forge build` output: keccak256(FireToken.creationCode + abi.encode(...constructorArgs))
function tokenAddrFor(salt: `0x${string}`): `0x${string}` {
  const initCodeHash = computeFireTokenInitCodeHash(/* args identical across launches */)
  return getCreate2Address({
    from: FIRE_LAUNCH,
    salt,
    bytecodeHash: initCodeHash,
  })
}

let salt: `0x${string}` | null = null
let tokenAddr: `0x${string}` | null = null

for (let i = 0; i < 10_000; i++) {
  const candidate = `0x${randomBytes(32).toString('hex')}` as `0x${string}`
  const addr = tokenAddrFor(candidate)
  if (BigInt(addr) < BigInt(WETH)) {
    salt = candidate
    tokenAddr = addr
    break
  }
}

if (!salt) throw new Error('salt mining exhausted - try more attempts')
```

> The constructor args (name, symbol, supply, etc.) feed into the init code hash, so technically the search has to fix those before mining. The frontend uses up to 10,000 attempts without a vanity suffix and 1,000,000 with one. See [Token ordering and tick math](/developers/launchpad/launching#-token-ordering-and-tick-math) for the constraints.

## Step 2: Call `ignite`

```ts
import { fireLaunchAbi } from './abis/fireLaunch'
import { parseUnits } from 'viem'

const txHash = await wallet.writeContract({
  address: FIRE_LAUNCH,
  abi: fireLaunchAbi,
  functionName: 'ignite',
  args: [{
    name:                 'My Agent Token',
    symbol:               'AGENT',
    totalSupply:          parseUnits('1000000000', 18),    // 1B
    numeraire:            WETH,
    tickLower:            -219400,                          // canonical, indexed
    tickUpper:            -174800,                          // canonical, indexed
    feeTier:              10000,                            // 1%
    skimBps:              300,                              // 3%
    creatorAllocationBps: 0,
    maxShareToBeSoldBps:  7200,
    numPositions:         50,
    vestingDuration:      BigInt(90 * 24 * 60 * 60),       // 90 days
    salt: salt!,
  }],
})

const receipt = await client.waitForTransactionReceipt({ hash: txHash })
// Parse `TokenIgnited(token, creator, pool, totalSupply, skimBps, tickLower, tickUpper)` from receipt.logs
// to confirm the deployed token address matches `tokenAddr` from your salt mining.
```

If the values you pass diverge from what `FireRegistry` requires (e.g. wrong `feeTier`, wrong `vestingDuration` range), the call reverts with a `*Mismatch` error. See [**Launching a token → Errors**](/developers/launchpad/launching#errors-to-handle).

## Step 3 (optional): Buy at launch

`ignite()` doesn't accept ETH, so the buy is a separate transaction immediately after.

```ts
import { swapRouter02Abi } from './abis/swapRouter02'
import { parseEther } from 'viem'

const SWAP_ROUTER_02 = '0xE5BbEF8De2DB447a7432A47EBa58924d94eE470e'

const buyHash = await wallet.writeContract({
  address: SWAP_ROUTER_02,
  abi:     swapRouter02Abi,
  functionName: 'exactInputSingle',
  args: [{
    tokenIn:           WETH,
    tokenOut:          tokenAddr!,
    fee:               10000,
    recipient:         account.address,
    amountIn:          parseEther('0.1'),
    amountOutMinimum:  0n,                  // tighten with a real quote in production
    sqrtPriceLimitX96: 0n,
  }],
  value: parseEther('0.1'),                 // SwapRouter02 wraps ETH for you
})
```

> ⚠️ Not atomic with `ignite`. If the buy reverts (e.g. slippage), the token still deployed. Quote first if you care about the price.

## Step 4 (optional): Attach off-chain metadata

If the agent wants its token to render with a description, image, and social links on `kumbaya.xyz`, attach metadata via the **claim** flow. If you use the on-chain MCP, the `sign_token_claim` tool builds and signs this exact proof for you (pass its output straight to `app_post_tokens_by_mint_address_claim`) - the raw viem below is for integrators not using the MCP:

```ts
import { signTypedData } from 'viem/accounts'

const CLAIM_DOMAIN = {
  name: 'Kumbaya Token Claim',
  version: '1',
  chainId: 4326,
}

const CLAIM_TYPES = {
  ClaimListing: [
    { name: 'mintAddress', type: 'address' },
    { name: 'chainId',     type: 'uint256' },
    { name: 'timestamp',   type: 'uint256' },
    { name: 'nonce',       type: 'string'  },
  ],
} as const

const signedAt = Math.floor(Date.now() / 1000)
const nonce    = crypto.randomUUID()

const signature = await account.signTypedData({
  domain: CLAIM_DOMAIN,
  types:  CLAIM_TYPES,
  primaryType: 'ClaimListing',
  message: {
    mintAddress: tokenAddr!,
    chainId:     BigInt(4326),
    timestamp:   BigInt(signedAt),
    nonce,
  },
})

await fetch(`https://clients.kumbaya.xyz/v1/tokens/${tokenAddr}/claim`, {
  method:  'POST',
  headers: {
    'content-type': 'application/json',
    'Authorization': `Bearer ${session.token}`,   // SIWE JWT from earlier
  },
  body: JSON.stringify({
    chainId:     4326,
    description: 'A token launched by my agent.',
    category:    'MEMES',         // or 'DARES'
    signature,
    signedAt,
    nonce,
    website:     'agent.example.com',
    xHandle:     'myagent',
    telegramUrl: 't.me/myagent',
  }),
})
```

You can optionally upload an image via `POST /v1/tokens/{tokenAddr}/claim/image` (multipart, JWT-authenticated).

## Watching the launch lifecycle

After deployment, the token is on-chain, indexed, and tradeable. Track its state via:

* **`FireLaunch.getLaunchState(token)`** - full per-launch state (creator, createdAt, graduationConditionMetAt, graduated, graduationFeeBps, isToken0, graduationTick, pool, etc.).
* **Hasura indexer** - subscribe to `FireToken(where: { address: { _eq: <addr-chainId> } })` for current-state changes; subscribe to `Swap(where: { pool_id: { _eq: <pool-addr-chainId> } })` for trade activity.

## Triggering graduation

When the price tick crosses the graduation threshold, `FireGraduator.canGraduate(token)` returns `true`. Anyone can then push graduation forward:

```ts
// Step 1 (one-shot, anyone): start the grace timer.
// recordGraduationCondition lives on FireLaunch, not FireGraduator.
await wallet.writeContract({
  address: FIRE_LAUNCH,
  abi: fireLaunchAbi,
  functionName: 'recordGraduationCondition',
  args: [tokenAddr],
})

// Step 2 (after gracePeriodDuration elapses): graduate (lives on FireGraduator)
const grace = await client.readContract({
  address: REGISTRY,
  abi: fireRegistryAbi,
  functionName: 'gracePeriodDuration',
})

// Wait grace seconds (or skip if you ARE the guardian - you can graduate immediately)
await wallet.writeContract({
  address: GRADUATOR,
  abi: fireGraduatorAbi,
  functionName: 'graduate',
  args: [tokenAddr],
})
```

After graduation, post-grad fees flow via `FireStream.claimFees(token)` and creators (or anyone) can call it to sweep their share.

## Common pitfalls

* **Salt mining mistake.** If your token address ≥ numeraire, the bonding curve runs the wrong way and the indexer skips your launch. Always assert `BigInt(tokenAddr) < BigInt(numeraire)` before broadcasting.
* **Wrong `feeTier` / `vestingDuration`.** `FireRegistry` enforces specific values. Read `requiredFeeTier`, `requiredSkimBps`, and the vesting bounds before constructing `IgniteParams`. See [**Live registry config**](/developers/building-agents/registry-config).
* **Skipping the grace period.** Only the guardian can graduate immediately. If your agent isn't the guardian, either wait `gracePeriodDuration` after `recordGraduationCondition`, or wait for `forceGraduationDelay` from `createdAt` which bypasses the grace check entirely. `forceGraduationDelay` is governance-set (currently \~180 days / 6 months; its floor is `MIN_FORCE_GRADUATION_DELAY = 90 days`) - read `FireRegistry.forceGraduationDelay()` rather than hardcoding a value.
* **Not parsing the receipt.** If `ignite()` reverts after gas was paid, you have no token but you spent gas. Check `receipt.status === 'success'` before assuming success.

## Where to next

* [**SIWE auth**](/developers/building-agents/siwe) - getting the JWT for the metadata-claim step
* [**Live registry config**](/developers/building-agents/registry-config) - reading current registry values
* [**Launching a token (full integrator reference)**](/developers/launchpad/launching) - every `IgniteParams` field, every error
* [**Fees and credits**](/developers/launchpad/fees-and-credits) - how earnings flow after launch


# Live registry config

`FireRegistry` holds protocol-level config that's read by every other launchpad contract at runtime: grace periods, fee splits, force-graduation delays, vesting bounds, etc. Some are constants; others are governance-tunable.

This page shows you how to read each field on-chain so your agent or service can adapt to whatever's currently in force.

## Mainnet address

```
FireRegistry = 0x286B4CB284270C6aE2844875BC92ed7E4C21c4C6   // chain 4326
FireRegistry = 0xaB31c1f84e9c7CcE928a27A8b77fC7De7C310EcA   // chain 6343 (testnet)
```

## Constants (immutable)

These are hardcoded in the contract and never change without a redeploy. Worth caching client-side after a single read:

| Field                        | Value      | Meaning                                    |
| ---------------------------- | ---------- | ------------------------------------------ |
| `MIN_POSITIONS`              | `5`        | Minimum bonding-curve positions per launch |
| `MAX_POSITIONS`              | `50`       | Maximum bonding-curve positions per launch |
| `MIN_VESTING_DURATION`       | `90 days`  | Lower bound for `vestingDuration`          |
| `MAX_VESTING_DURATION`       | `730 days` | Upper bound                                |
| `MAX_SKIM_BPS`               | `1500`     | Max 15% skim per buy                       |
| `MAX_CREATOR_ALLOCATION_BPS` | `2000`     | Max 20% on-token creator allocation        |
| `MIN_BURN_COUNTDOWN`         | `1 days`   | Lower bound for FuelVault burn timer       |
| `MAX_BURN_COUNTDOWN`         | `90 days`  | Upper bound                                |
| `MIN_GRACE_PERIOD`           | `1 hours`  | Lower bound for `gracePeriodDuration`      |
| `MAX_GRACE_PERIOD`           | `7 days`   | Upper bound                                |
| `MIN_FORCE_GRADUATION_DELAY` | `90 days`  | Lower bound for `forceGraduationDelay`     |

## Tunable values (governance-set)

These can change between launches. Always read live before depending on them.

| Field                              | Type                   | Used for                                                                                                                  |
| ---------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `gracePeriodDuration()`            | `uint256`              | Wait between `recordGraduationCondition` and permissionless `graduate`                                                    |
| `forceGraduationDelay()`           | `uint256`              | Time after `createdAt` after which anyone can force-graduate                                                              |
| `burnCountdownDuration()`          | `uint256`              | Wait between graduation and `executeBurn` eligibility                                                                     |
| `fuelVestedBps()`                  | `uint16`               | Pre-grad gift split - fraction that goes to vested bucket                                                                 |
| `requiredFeeTier()`                | `uint24`               | Forced fee tier for new launches (e.g. `10000`)                                                                           |
| `requiredSkimBps()`                | `uint16`               | Forced skim bps for new launches                                                                                          |
| `treasury()`                       | `address`              | Protocol fee recipient (pre-grad `graduationFeeBps` share). Same address as `integrator()` today.                         |
| `integrator()`                     | `address`              | Protocol fee recipient (pre-grad remainder). Same address as `treasury()` today, so all pre-grad fees go to the protocol. |
| `guardian()`                       | `address`              | Address that can graduate immediately and relay gifts                                                                     |
| `owner()`                          | `address`              | Registry admin                                                                                                            |
| `minGiftAmount()`                  | `uint256`              | Floor on `giftWithSig` amounts                                                                                            |
| `streamingRecipientsLocked()`      | `bool`                 | Whether the streaming-recipient list is permanently locked                                                                |
| `getStreamingRecipientsTotalBps()` | `uint16`               | Sum of bps across protocol recipients (creator gets `10000 - this`)                                                       |
| `getStreamingRecipients()`         | `StreamingRecipient[]` | Full list (max 10 entries) of `(address, bps)`                                                                            |
| `getStreamingRecipientsCount()`    | `uint256`              | List size                                                                                                                 |

## Reading with viem

```ts
import { createPublicClient, http } from 'viem'
import { fireRegistryAbi } from './abis/fireRegistry' // from fire/out after `forge build`

const REGISTRY = '0x286B4CB284270C6aE2844875BC92ed7E4C21c4C6'
const client = createPublicClient({ transport: http('https://mainnet.megaeth.com/rpc') })

const [
  gracePeriod,
  forceDelay,
  burnCountdown,
  treasury,
  integrator,
  guardian,
  totalRecipientBps,
  recipients,
] = await client.multicall({
  contracts: [
    { address: REGISTRY, abi: fireRegistryAbi, functionName: 'gracePeriodDuration' },
    { address: REGISTRY, abi: fireRegistryAbi, functionName: 'forceGraduationDelay' },
    { address: REGISTRY, abi: fireRegistryAbi, functionName: 'burnCountdownDuration' },
    { address: REGISTRY, abi: fireRegistryAbi, functionName: 'treasury' },
    { address: REGISTRY, abi: fireRegistryAbi, functionName: 'integrator' },
    { address: REGISTRY, abi: fireRegistryAbi, functionName: 'guardian' },
    { address: REGISTRY, abi: fireRegistryAbi, functionName: 'getStreamingRecipientsTotalBps' },
    { address: REGISTRY, abi: fireRegistryAbi, functionName: 'getStreamingRecipients' },
  ],
  allowFailure: false,
})

const creatorBps = 10_000 - Number(totalRecipientBps)
console.log(`Grace period: ${gracePeriod}s`)
console.log(`Force graduation: ${forceDelay}s after createdAt`)
console.log(`Burn countdown: ${burnCountdown}s after graduation`)
console.log(`Creator share: ${creatorBps} bps (${creatorBps / 100}%)`)
console.log(`Recipients:`, recipients)
```

## Reading with cast

If you've got Foundry installed, one-liner reads work too:

```bash
RPC=https://mainnet.megaeth.com/rpc
REG=0x286B4CB284270C6aE2844875BC92ed7E4C21c4C6

cast call $REG "gracePeriodDuration()(uint256)"          --rpc-url $RPC
cast call $REG "forceGraduationDelay()(uint256)"         --rpc-url $RPC
cast call $REG "burnCountdownDuration()(uint256)"        --rpc-url $RPC
cast call $REG "fuelVestedBps()(uint16)"                 --rpc-url $RPC
cast call $REG "treasury()(address)"                     --rpc-url $RPC
cast call $REG "integrator()(address)"                   --rpc-url $RPC
cast call $REG "guardian()(address)"                     --rpc-url $RPC
cast call $REG "getStreamingRecipientsTotalBps()(uint16)" --rpc-url $RPC
```

For `getStreamingRecipients()` you get a struct array - easier to handle in viem than `cast`.

## Reading per-launch state

For values snapshotted at `ignite()` time (per-token):

```ts
import { fireGraduatorAbi } from './abis/fireGraduator'

const GRADUATOR = '0xCCA4759167Ef4214dF98Eb7cBbCE47EB9B4F2585'

const launchState = await client.readContract({
  address: GRADUATOR,
  abi:     fireGraduatorAbi,
  functionName: 'launchState',
  args:    [tokenAddress],
})

// launchState includes: creator, createdAt, graduationConditionMetAt,
// graduated, graduationFeeBps, isToken0, graduationTick, etc.
```

`graduationFeeBps` here is the **pre-graduation** protocol share for that specific token, snapshotted at launch.

## Reading the recipient lock state

```ts
const locked = await client.readContract({
  address: REGISTRY,
  abi:     fireRegistryAbi,
  functionName: 'streamingRecipientsLocked',
})

if (locked) {
  // The creator's post-grad share is now permanently fixed.
  // (10_000 - getStreamingRecipientsTotalBps()) / 10_000 is forever.
}
```

This is a useful integrator/creator confidence check - once locked, the protocol cannot reduce the creator's cut later.

## What changes when

* **Constants** - never, without a redeploy.
* **Tunables** - by `owner()` only, via setter functions on the registry. Each setter emits an event (`GracePeriodDurationUpdated`, `ForceGraduationDelayUpdated`, etc.) - subscribe to those if you need to react in real time.
* **Streaming recipients** - by `owner()` until `lockStreamingRecipients()` is called, after which they're permanently fixed.


# Integration recipes

Short, copy-pasteable examples for the most common things integrators and agents do on Kumbaya. All of these are grounded in the actual API and contract surface - no pseudocode.

> Building an LLM agent rather than a script? Most of these flows already exist as one-call tools and skills in the [Kumbaya Agent Kit](/developers/building-agents/agent-kit) - e.g. buying a token (recipe 3) is the `swap` tool, and sweeping creator earnings (recipe 4) is `claim_fees` + `withdraw_tips`. Use these recipes when you want the raw calls; use the kit when you want the model to drive.

## 1. Watch for new launches (live)

Subscribe to the indexer and react as new tokens are deployed.

```ts
import { createClient } from 'graphql-ws'
import { WebSocket } from 'ws'

const client = createClient({
  url: 'wss://ql.kumbaya.xyz/v1/graphql',
  webSocketImpl: WebSocket,
})

const unsubscribe = client.subscribe(
  {
    query: `
      subscription NewLaunches($chainId: Int!) {
        FireToken(
          where: { chainId: { _eq: $chainId } }
          order_by: { createdAt: desc }
          limit: 10
        ) {
          address creator createdAt
          pool { id }
          marketCapUSD graduationProgress
        }
      }
    `,
    variables: { chainId: 4326 },
  },
  {
    next: ({ data }) => {
      const latest = data?.FireToken?.[0]
      if (latest && Date.now() / 1000 - Number(latest.createdAt) < 60) {
        console.log('New launch:', latest.address)
        // your hook: e.g. evaluate, snipe, post a tweet
      }
    },
    error: console.error,
    complete: () => console.log('subscription ended'),
  },
)
```

## 2. "Heating up" - find tokens with rising volume

The indexer tracks 30m/1h/2h-prior buy and sell counts plus velocity ratios. This is exactly how Kumbaya's "Heating up" feed is computed.

```graphql
query HeatingUp($chainId: Int!) {
  FireToken(
    where: {
      chainId:    { _eq: $chainId }
      graduated:  { _eq: false }
      buysLast1h: { _gte: 5 }
      buyVelocity: { _gt: 1.5 }
    }
    order_by: { buyVelocity: desc }
    limit: 20
  ) {
    address graduationProgress marketCapUSD
    buysLast1h buysPrev1h sellsLast1h
    buyVelocity sellVelocity
    netTokenFlowLast1h buyerCount
  }
}
```

## 3. Buy a launchpad token - quote then swap

Use the Exchange API for routing; submit calldata yourself.

```ts
import { createWalletClient, http, parseEther } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'

const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`)
const wallet  = createWalletClient({
  account,
  transport: http('https://mainnet.megaeth.com/rpc'),
})

const tokenIn  = '0x4200000000000000000000000000000000000006' // WETH
const tokenOut = '0xYOUR_FIRE_TOKEN'
const amountIn = parseEther('0.1')

// 1. Quote
const params = new URLSearchParams({
  chainId:         '4326',
  tokenInAddress:  tokenIn,
  tokenOutAddress: tokenOut,
  amount:          amountIn.toString(),
  slippageBps:     '100',                  // 1%
  recipient:       account.address,
  type:            'exactIn',
})
const quote = await fetch(
  `https://exchange.kumbaya.xyz/api/v1/quote?${params}`,
).then(r => {
  if (!r.ok) throw new Error(`quote failed: ${r.status}`)
  return r.json()
})

// 2. Submit
const txHash = await wallet.sendTransaction({
  to:    quote.methodParameters.to,
  value: BigInt(quote.methodParameters.value),
  data:  quote.methodParameters.calldata,
})
```

For native ETH ↔ token swaps the API handles wrapping for you. For pre-approved ERC-20 → ERC-20, do the `approve` once, then run the swap.

## 4. Sweep your creator earnings

For a graduated launchpad token where you (or an address you control) is the creator:

```ts
import { fireStreamAbi } from './abis/fireStream'
import { fuelVaultAbi }  from './abis/fuelVault'

const FIRE_STREAM = '0x94d9582130745d0e2a1757dDEd8e730F5CDAd759'
const FUEL_VAULT  = '0x5aFaB54ac28a3bd485751146470D053b4FF11c81'

// 1. Sweep streaming fees (anyone can call; you receive your share)
await wallet.writeContract({
  address: FIRE_STREAM,
  abi:     fireStreamAbi,
  functionName: 'claimFees',
  args:    [tokenAddress],
})

// 2. Withdraw FuelVault liquid bucket (only you can call; first call after grad also unlocks pre-grad vested)
await wallet.writeContract({
  address: FUEL_VAULT,
  abi:     fuelVaultAbi,
  functionName: 'withdraw',
  args:    [tokenAddress],
})
```

Run on a schedule (e.g. daily) for any token you've launched.

## 5. Find every token *I* created (and what they've earned)

```graphql
query MyTokens($creator: String!, $chainId: Int!) {
  FireToken(
    where: { creator: { _eq: $creator }, chainId: { _eq: $chainId } }
    order_by: { createdAt: desc }
  ) {
    address graduated graduationProgress marketCapUSD
    feesCollectedUSD volumeUSD
    pool { id token0 { symbol } token1 { symbol } }
  }
  FuelCreatorBucket(
    where: { creator: { _eq: $creator }, chainId: { _eq: $chainId } }
  ) {
    token { address }
    liquid vested unlocked
  }
}
```

## 6. Trigger graduation on tokens that are ready

```ts
import { fireLaunchAbi }     from './abis/fireLaunch'
import { fireGraduatorAbi }  from './abis/fireGraduator'

const FIRE_LAUNCH = '0x69FE0908F1211dE66F7067021998f28A5693ABbD'
const GRADUATOR   = '0xCCA4759167Ef4214dF98Eb7cBbCE47EB9B4F2585'

// Find all tokens whose tick crossed but no condition recorded yet
const { data } = await fetch('https://ql.kumbaya.xyz/v1/graphql', {
  method:  'POST',
  headers: { 'content-type': 'application/json' },
  body:    JSON.stringify({
    query: `query {
      FireToken(
        where: {
          chainId: { _eq: 4326 }
          readyToGraduate: { _eq: true }
          graduationConditionRecordedAt: { _is_null: true }
        }
        limit: 50
      ) { address }
    }`,
  }),
}).then(r => r.json())

for (const { address } of data.FireToken) {
  try {
    // Note: recordGraduationCondition lives on FireLaunch, not FireGraduator.
    await wallet.writeContract({
      address: FIRE_LAUNCH,
      abi:     fireLaunchAbi,
      functionName: 'recordGraduationCondition',
      args:    [address],
    })
    console.log('Recorded grad condition for', address)
  } catch (err) {
    console.warn('Skipped', address, (err as Error).message)
  }
}
```

After `gracePeriodDuration` elapses (read live from `FireRegistry.gracePeriodDuration()` - see [Live registry config](/developers/building-agents/registry-config)), call **`FireGraduator.graduate(token)`** to actually finalize.

## 7. Search-as-you-type token picker

Build a token-picker UI without running your own index:

```ts
async function autocomplete(prefix: string, chainId = 4326) {
  const params = new URLSearchParams({ q: prefix, chainId: String(chainId) })
  const res = await fetch(
    `https://search.kumbaya.xyz/api/v1/autocomplete?${params}`,
  )
  const json = await res.json()
  return json.suggestion // { completion, symbol, name, address, matchType } | null
}
```

For full search results (tokens + pools, multiple matches):

```ts
async function search(q: string, chainId = 4326) {
  const params = new URLSearchParams({
    q,
    chainId: String(chainId),
    limit:   '20',
    visibility: 'all',     // or 'verified' / 'trusted'
  })
  const res = await fetch(`https://search.kumbaya.xyz/api/v1/search?${params}`)
  return res.json() // { tokens: [...], pools: [...] }
}
```

Public, no auth, rate-limited per IP.

## 8. Newest swaps for a pool (REST, no GraphQL)

```ts
const swaps = await fetch(
  `https://exchange.kumbaya.xyz/api/v1/pools/${poolId}/swaps?limit=50`,
).then(r => r.json())
```

Where `poolId` is the `address-chainId` composite (e.g. `0xabc...-4326`).

## 9. Read protocol-level config live

Don't hardcode tunables. Read on demand:

```ts
import { fireRegistryAbi } from './abis/fireRegistry'

const REGISTRY = '0x286B4CB284270C6aE2844875BC92ed7E4C21c4C6'
const reads = await client.multicall({
  contracts: [
    { address: REGISTRY, abi: fireRegistryAbi, functionName: 'gracePeriodDuration' },
    { address: REGISTRY, abi: fireRegistryAbi, functionName: 'forceGraduationDelay' },
    { address: REGISTRY, abi: fireRegistryAbi, functionName: 'burnCountdownDuration' },
    { address: REGISTRY, abi: fireRegistryAbi, functionName: 'getStreamingRecipientsTotalBps' },
  ],
  allowFailure: false,
})
```

See [Live registry config](/developers/building-agents/registry-config) for the full field list.

## 10. Build a creator-fee dashboard

Combine indexer + on-chain reads:

```ts
// 1. From indexer: every token this creator has launched (and lifetime fees)
const indexed = await graphql(`
  query Creator($creator: String!, $chainId: Int!) {
    FireToken(where: { creator: { _eq: $creator }, chainId: { _eq: $chainId } }) {
      address graduated feesCollectedUSD marketCapUSD
    }
  }
`, { creator, chainId: 4326 })

// 2. From chain: claimable balances right now (FuelVault liquid + FireStream pending)
for (const token of indexed.FireToken) {
  const bucket = await client.readContract({
    address: FUEL_VAULT,
    abi:     fuelVaultAbi,
    functionName: 'creatorBuckets',
    args:    [creator, token.address],
  })
  // bucket.liquid (uint128), bucket.vested (uint128), bucket.unlocked (bool)
  // - for graduated tokens, bucket.vested unlocks on next withdraw
}
```

`FireStream` doesn't expose a "preview pending fees" view - to know what the next `claimFees` would pay out, you'd simulate it (e.g. with `eth_call`) or just call it and parse the `BeneficiaryPaid` event from the receipt.

## More

* [**Auto-launch a token**](/developers/building-agents/launching-programmatically) - full programmatic launch recipe
* [**SIWE auth**](/developers/building-agents/siwe) - get a JWT for the Client API
* [**Live registry config**](/developers/building-agents/registry-config) - every tunable + how to read it


# Contract Addresses

> **Source of truth for addresses:** DEX addresses are mirrored from [`integrator-kit/addresses/`](https://github.com/Kumbaya-xyz/integrator-kit/tree/main/addresses). Launchpad (`Fire*`) contract addresses are sourced from the Kumbaya frontend's `constants/fire.ts`, which is the canonical reference for currently-active deployments and parameters. If they disagree with this page, please open an issue.
>
> The `fire/addresses/megaETH-mainnet.json` snapshot in the [fire repo](https://github.com/Kumbaya-xyz/fire/tree/main/addresses) reflects **initial deployment state** - addresses are still correct, but other fields (`paused`, `requiredVestingDuration`, etc.) may have been updated by governance since deploy. **Read the registry on-chain for live config values** - see [Live registry config](/developers/building-agents/registry-config).

> 🛡 **All contracts on this page are audited.** The Kumbaya V3 fork inherits Uniswap V3's audits (Trail of Bits + ABDK) and is bytecode-verified against upstream. The Kumbaya launchpad - including `FireToken` - has a signed BlockSec audit. See [**Audit & security**](/developers/audit-and-security) for full reports and the trust model.

## MegaETH Mainnet (Chain ID `4326`)

* **RPC:** `https://mainnet.megaeth.com/rpc`
* **Explorer:** <https://mega.etherscan.io/>

### DEX (Uniswap V3 fork)

| Contract                   | Address                                      |
| -------------------------- | -------------------------------------------- |
| UniswapV3Factory           | `0x68b34591f662508076927803c567Cc8006988a09` |
| NonfungiblePositionManager | `0x2b781C57e6358f64864Ff8EC464a03Fdaf9974bA` |
| SwapRouter02               | `0xE5BbEF8De2DB447a7432A47EBa58924d94eE470e` |
| UniversalRouter            | `0xAAB1C664CeaD881AfBB58555e6A3a79523D3e4C0` |
| QuoterV2                   | `0x1F1a8dC7E138C34b503Ca080962aC10B75384a27` |
| TickLens                   | `0x9c22f028e0a1dc76EB895a1929DBc517c9D0593e` |
| V3Migrator                 | `0xE2702742F78b84F2032C5A36082b199E2d62aAB0` |
| UniswapV3Staker            | `0x9F393A399321110Fb7D85aCc812b8e48A7c569aC` |
| Multicall                  | `0xeeb4a1001354717598Af33f3585B66F9de7e7b27` |
| Multicall2                 | `0xf6f404ac6289ab8eB1caf244008b5F073d59385c` |
| Permit2                    | `0x000000000022D473030F116dDEE9F6B43aC78BA3` |
| WETH9                      | `0x4200000000000000000000000000000000000006` |

### Kumbaya Launchpad (`Fire*` contracts)

| Contract      | Address                                      |
| ------------- | -------------------------------------------- |
| FireRegistry  | `0x286B4CB284270C6aE2844875BC92ed7E4C21c4C6` |
| FireLaunch    | `0x69FE0908F1211dE66F7067021998f28A5693ABbD` |
| FireGraduator | `0xCCA4759167Ef4214dF98Eb7cBbCE47EB9B4F2585` |
| FuelVault     | `0x5aFaB54ac28a3bd485751146470D053b4FF11c81` |
| FireStream    | `0x94d9582130745d0e2a1757dDEd8e730F5CDAd759` |

> The `FireToken` contract is deployed per-launch by `FireLaunch` - there is no single canonical address.

## MegaETH Testnet (Chain ID `6343`)

* **RPC:** `https://carrot.megaeth.com/rpc`
* **Explorer:** <https://testnet-mega.etherscan.io/>
* **Faucet:** <https://testnet.megaeth.com>

### DEX (Uniswap V3 fork)

| Contract                   | Address                                      |
| -------------------------- | -------------------------------------------- |
| UniswapV3Factory           | `0x53447989580f541bc138d29A0FcCf72AfbBE1355` |
| NonfungiblePositionManager | `0x367f9db1F974eA241ba046b77B87C58e2947d8dF` |
| SwapRouter02               | `0x8268DC930BA98759E916DEd4c9F367A844814023` |
| UniversalRouter            | `0x7E6c4Ada91e432efe5F01FbCb3492Bd3eb7ccD2E` |
| QuoterV2                   | `0xfb230b93803F90238cB03f254452bA3a3b0Ec38d` |
| TickLens                   | `0x6D65B4854944Fd93Cd568bb1B54EE22Fe9BF2faa` |
| Multicall2                 | `0xc638099246A98B3A110429B47B3F42CA037BC0a3` |
| V3Migrator                 | `0xC6eB4Ad186F5A4a0184E95d10f00DCEf413D42Cf` |
| UniswapV3Staker            | `0x511f4EC90936450152895b2C2FD20AF6DC72663b` |
| Permit2                    | `0x000000000022D473030F116dDEE9F6B43aC78BA3` |
| WETH9                      | `0x4200000000000000000000000000000000000006` |

### Kumbaya Launchpad (testnet)

| Contract      | Address                                      |
| ------------- | -------------------------------------------- |
| FireRegistry  | `0xaB31c1f84e9c7CcE928a27A8b77fC7De7C310EcA` |
| FireLaunch    | `0xB710b3fe1002eeC1E4b451502f46bC384c823522` |
| FireGraduator | `0x88E0cC07b308FB07038Be48203a0619E281d2ac7` |
| FuelVault     | `0x37494B27b429b539a4048D19de4a015025B07662` |
| FireStream    | `0x65c62D7219Cc86E58636aA6589f60A43F389560A` |

## Pool init code hash

Same on both networks. Used for off-chain pool address derivation.

```
0x851d77a45b8b9a205fb9f44cb829cceba85282714d2603d601840640628a3da7
```

This is **different from upstream Uniswap V3** - see [**Pool Init Code Hash**](/developers/dex-integration/pool-init-code-hash).

## Programmatic access

Both repos publish the addresses as plain JSON, so you can import them directly:

```ts
import addresses from '../integrator-kit/addresses/megaETH-mainnet.json'
const factory = addresses.UniswapV3Factory
```

The launchpad addresses on this page are pinned for mainnet and testnet - those don't change once deployed. For other config values (grace period, vesting duration, recipient list, etc.), read the registry on-chain rather than trusting any JSON snapshot - see [Live registry config](/developers/building-agents/registry-config).


# MegaETH Mainnet

| Field        | Value                             |
| ------------ | --------------------------------- |
| Chain ID     | `4326`                            |
| RPC          | `https://mainnet.megaeth.com/rpc` |
| Explorer     | <https://mega.etherscan.io/>      |
| Native token | ETH                               |
| Block time   | \~10ms                            |

For all deployed contract addresses on this network see [**Contract Addresses**](/developers/networks-and-contracts/contract-addresses).


# MegaETH Testnet

| Field    | Value                                |
| -------- | ------------------------------------ |
| Chain ID | `6343`                               |
| RPC      | `https://carrot.megaeth.com/rpc`     |
| Explorer | <https://testnet-mega.etherscan.io/> |
| Faucet   | <https://testnet.megaeth.com>        |

For all deployed contract addresses on this network see [**Contract Addresses**](/developers/networks-and-contracts/contract-addresses).


# Overview

The Kumbaya DEX is a Uniswap V3 fork. If you've integrated Uniswap V3 before, almost everything you know transfers - same pool primitives, same `swap` / `mint` / `burn` flow, same `NonfungiblePositionManager`, same `SwapRouter02` / `UniversalRouter` / `QuoterV2` periphery.

Two things to be aware of:

1. **The pool init code hash is different.** If you compute pool addresses off-chain, you must use Kumbaya's hash, not Uniswap's. See [**Pool Init Code Hash**](/developers/dex-integration/pool-init-code-hash).
2. **The protocol-fee range is wider at the contract level.** Up to 50% (vs. Uniswap's 25%) - but the values currently set in production are within Uniswap's standard range (25% for 0.01% / 0.05% tiers; \~16.67% for 0.30% / 1.00% tiers). This only matters if you're reading or modifying protocol-fee state.

For everything else, **Uniswap's V3 docs are your reference**: <https://developers.uniswap.org/docs/protocols/v3/overview>. Do not use the V2 or V4 guides - they're for different protocols.

## Pages in this section

* [**Pool Init Code Hash**](/developers/dex-integration/pool-init-code-hash) - the value you need for off-chain pool address computation
* [**Quoting prices**](/developers/dex-integration/quoting) - using QuoterV2 / smart-order-router
* [**Pool routing eligibility**](/developers/dex-integration/routing-eligibility) - what makes a pool routable / a token tradeable in the UI
* [**Executing swaps**](/developers/dex-integration/swapping) - SwapRouter02 vs. UniversalRouter
* [**Reading pools and positions**](/developers/dex-integration/reading-pools) - slot0, ticks, liquidity, fee growth
* [**Differences from Uniswap V3**](/developers/dex-integration/differences-from-uniswap) - full diff summary


# Pool Init Code Hash

Kumbaya's V3 fork deploys pools with a **different init code hash** from upstream Uniswap V3. If you compute pool addresses off-chain (the standard CREATE2 derivation), you must use Kumbaya's hash:

```
0x851d77a45b8b9a205fb9f44cb829cceba85282714d2603d601840640628a3da7
```

This is the hash of the `UniswapV3Pool` deployment bytecode in [Kumbaya's v3-core fork](https://github.com/Kumbaya-xyz/v3-core), and it applies to **both MegaETH mainnet (4326) and testnet (6343)**.

## Using it through the SDK

`@kumbaya_xyz/v3-sdk` exposes two things:

```ts
import { POOL_INIT_CODE_HASH, poolInitCodeHash } from '@kumbaya_xyz/v3-sdk'
```

| Export                                 | What it is                                                                                                            | When to use             |
| -------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | ----------------------- |
| `POOL_INIT_CODE_HASH` (constant)       | **Deprecated** - returns the upstream Uniswap hash `0xe34f199b…87b8b54`. Kept for backwards compatibility.            | Don't use for new code. |
| `poolInitCodeHash(chainId)` (function) | Returns the chain-specific hash. For `ChainId.MEGAETH` and `ChainId.MEGAETH_TESTNET`, returns the Kumbaya hash above. | This one.               |

> ⚠️ **Don't import the constant.** It's named `POOL_INIT_CODE_HASH` but returns Uniswap's hash, not Kumbaya's. The SDK's `Pool.getAddress` and `computePoolAddress` use the function internally - they handle this correctly without the constant.

## In practice you don't have to think about this

`Pool.getAddress(tokenA, tokenB, fee)` and `computePoolAddress({ chainId, tokenA, tokenB, fee })` both call `poolInitCodeHash(chainId)` internally and compute the right CREATE2 address. So if you're using the SDK normally, you won't see this hash at all.

You only need the raw value if you're:

* Deriving pool addresses without the SDK (e.g. in Solidity, Rust, or another language).
* Pre-computing addresses across many chains in one go.
* Verifying behavior in tests.

## Self-check

If pool address derivation gives you a contract that doesn't exist on-chain - or worse, exists but has the wrong tokens - you're almost certainly using Uniswap's hash by mistake. Sanity-check against the [`integrator-kit`](https://github.com/Kumbaya-xyz/integrator-kit), which derives pool addresses on MegaETH and asserts equality.

## Used everywhere downstream

This hash is what every Kumbaya periphery contract uses internally to verify pool callbacks - `SwapRouter02`, `NonfungiblePositionManager`, `QuoterV2`, `UniversalRouter`, etc. So it's not just an SDK detail; it's how the entire stack derives pool addresses on Kumbaya's V3 fork.


# Quoting prices

You have three ways to get a swap quote on Kumbaya, depending on whether you want to run the routing logic yourself or have Kumbaya do it for you.

| Approach                              | When to use                            | Trade-off                                 |
| ------------------------------------- | -------------------------------------- | ----------------------------------------- |
| **`QuoterV2` directly (RPC)**         | Single-pool quotes, you know the route | Fast, simple. No multi-hop optimization.  |
| **`@kumbaya_xyz/smart-order-router`** | Multi-hop optimal routing, client-side | Heavier. Needs RPC + indexer access.      |
| **Exchange API `/api/v1/quote`**      | Most integrations - let us route       | One HTTPS call. Cached. Returns calldata. |

## Option 1: `QuoterV2` directly

For a single pool - fee tier and tokens known up front - the simplest path is calling `QuoterV2.quoteExactInputSingle()` via RPC.

```ts
import { createPublicClient, http, parseUnits } from 'viem'

const QUOTER_V2 = '0x1F1a8dC7E138C34b503Ca080962aC10B75384a27' // mainnet
const RPC = 'https://mainnet.megaeth.com/rpc'

const quoterAbi = [{
  type: 'function',
  name: 'quoteExactInputSingle',
  stateMutability: 'nonpayable',
  inputs: [{ type: 'tuple', name: 'params', components: [
    { type: 'address', name: 'tokenIn' },
    { type: 'address', name: 'tokenOut' },
    { type: 'uint256', name: 'amountIn' },
    { type: 'uint24',  name: 'fee' },
    { type: 'uint160', name: 'sqrtPriceLimitX96' },
  ]}],
  outputs: [
    { type: 'uint256', name: 'amountOut' },
    { type: 'uint160', name: 'sqrtPriceX96After' },
    { type: 'uint32',  name: 'initializedTicksCrossed' },
    { type: 'uint256', name: 'gasEstimate' },
  ],
}] as const

const client = createPublicClient({ transport: http(RPC) })

const quote = await client.simulateContract({
  address: QUOTER_V2,
  abi: quoterAbi,
  functionName: 'quoteExactInputSingle',
  args: [{
    tokenIn:  '0x4200000000000000000000000000000000000006', // WETH
    tokenOut: '0xYOUR_TOKEN',
    amountIn: parseUnits('1', 18),
    fee:      3000,    // 0.3% - pick the right tier
    sqrtPriceLimitX96: 0n,
  }],
})

console.log('Out:', quote.result[0])
console.log('Gas estimate:', quote.result[3])
```

Notes:

* `QuoterV2` does a real on-chain simulation, so the result is exact (modulo the obvious caveat that price can move before your tx lands).
* It's a `nonpayable` function despite being read-only - that's a Uniswap V3 quirk. Use `simulateContract` (viem) or `staticCall` (ethers) rather than `readContract`.
* For exact output, use `quoteExactOutputSingle` (same shape, swap `amountIn` → `amountOut`).
* For multi-hop, use `quoteExactInput(bytes path, uint256 amountIn)` with a packed path - but at that point you probably want option 2 or 3.

## Option 2: Smart Order Router (client-side)

For multi-hop and split routes, use [`@kumbaya_xyz/smart-order-router`](/developers/sdks/smart-order-router). It enumerates pools, scores routes, and accounts for gas:

```bash
npm install @kumbaya_xyz/smart-order-router @kumbaya_xyz/sdk-core ethers@^5
```

> Smart Order Router is built on **ethers v5** (`BaseProvider` from `@ethersproject/providers`). It does not currently support ethers v6 or viem providers.

```ts
import { AlphaRouter } from '@kumbaya_xyz/smart-order-router'
import { CurrencyAmount, Token, ChainId, TradeType, Percent } from '@kumbaya_xyz/sdk-core'
import { providers } from 'ethers'   // ethers v5

const provider = new providers.JsonRpcProvider('https://mainnet.megaeth.com/rpc')

const router = new AlphaRouter({
  chainId: ChainId.MEGAETH,
  provider,
})

const tokenIn  = new Token(ChainId.MEGAETH, '0x4200000000000000000000000000000000000006', 18, 'WETH')
const tokenOut = new Token(ChainId.MEGAETH, '0xYOUR_TOKEN', 18, 'TOKEN')

const route = await router.route(
  CurrencyAmount.fromRawAmount(tokenIn, '1000000000000000000'), // 1 WETH
  tokenOut,
  TradeType.EXACT_INPUT,
  {
    recipient: '0xYourWalletAddress',
    slippageTolerance: new Percent(50, 10_000), // 0.5%
    deadline: Math.floor(Date.now() / 1000) + 600,
  },
)

console.log('Quote:',  route?.quote.toExact())
console.log('Calldata:', route?.methodParameters?.calldata)
console.log('Value:',    route?.methodParameters?.value)
```

The returned `methodParameters` are ready to send to `SwapRouter02` or `UniversalRouter` (configurable on the `route` call). See [**Executing swaps**](/developers/dex-integration/swapping).

## Option 3: Exchange API

If you don't want to run the SOR client-side, hit the hosted endpoint:

```
GET https://exchange.kumbaya.xyz/api/v1/quote?
    chainId=4326
    &tokenInAddress=0x4200000000000000000000000000000000000006
    &tokenOutAddress=0xYOUR_TOKEN
    &amount=1000000000000000000
    &slippageBps=50
    &recipient=0xYourWalletAddress
    &type=exactIn                                  # default; or 'exactOut'
    &routerType=swap-router-02                     # default; or 'universal'
```

Response includes:

* `quote` and `quoteGasAdjusted` (human-readable decimal strings)
* `gasUseEstimate`, `gasUseEstimateUSD`
* `methodParameters: { to, value, calldata }` - ready to submit as a transaction
* `route` - the path the SOR picked

> The GET endpoint takes `amount` and `type` (`exactIn` / `exactOut`); the POST partner endpoint takes `fromAmount` and uses a decimal `slippage` instead. They're not interchangeable. See [**Quote endpoints**](/developers/apis/exchange-api/quote).

See [**Executing swaps**](/developers/dex-integration/swapping) for what to do with the returned calldata.

## Picking between them

* **Quick prototype, single pool:** `QuoterV2` direct.
* **Production frontend or bot, full routing:** Exchange API. Less to maintain, server-side caching.
* **Custom routing logic, alternative venues, or you want to inspect routes:** Smart Order Router locally.


# Pool routing eligibility

Not every Uniswap V3 pool on MegaETH is routable through the Kumbaya frontend. The swap UI quotes against an **admission-filtered** set of pools served by `GET /api/v1/pools/admitted`. A pool that exists on-chain - even one with healthy liquidity - will not be routed unless it clears every stage below.

This page documents what makes a pool routable, and by extension what makes a token tradeable in the Kumbaya UI. If you are integrating directly with the contracts (`QuoterV2`, `SwapRouter02`), none of this applies - these rules are a property of the *hosted routing layer*, not the protocol.

## The three stages

A pool must pass all three to appear in `/api/v1/pools/admitted` and be quoted by the frontend.

### 1. Indexer candidate fetch

The Exchange API builds its routable universe from the [Hasura indexer](/developers/resources/indexer), not from chain scans. To enter that universe a pool must satisfy:

| Requirement                        | Detail                                                                                                                                                                                                                    |
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Indexed**                        | The pool must exist in the indexer. If the indexer has not seen the pool's creation event, it is invisible to routing.                                                                                                    |
| **TVL or liquidity**               | `totalValueLockedETH` greater than the tracked threshold (**0.01 ETH** by default), *or* a pool with non-zero `liquidity` whose TVL has not yet been computed. Pools below the threshold are treated as dust and dropped. |
| **Not a pre-graduation fire pool** | Fire (bonding-curve) pools are excluded until the token graduates - see below.                                                                                                                                            |
| **Top 2000 by TVL**                | The candidate set is capped at the 2000 highest-TVL pools per chain.                                                                                                                                                      |

> **Most common reason a pool is missing:** its TVL sits below the 0.01 ETH tracked threshold. Small or freshly seeded pools - common on testnet - fall out here before any other rule is evaluated.

### 2. Initialized state

The pool must be **initialized** - it must have a current `tick` and a non-zero `sqrtPriceX96`. A pool that was created but never received its first mint / `initialize` call is skipped.

### 3. Admission filter (6 rules)

Surviving pools are run through a 6-rule filter. The pool is admitted if **any one** rule matches. "Trusted" means a token that is **verified** or **bluechip** in the Kumbaya token registry, or a graduated fire token.

| Rule | A pool is admitted when…                                                                          |
| ---- | ------------------------------------------------------------------------------------------------- |
| 1    | It is a Kumbaya fire pool whose token has **graduated**.                                          |
| 2–4  | **Both** sides of the pool are trusted (verified × verified, verified × fire, fire × fire).       |
| 5    | One side is the **token being swapped** and the other side is trusted (e.g. `YOUR_TOKEN / WETH`). |
| 6    | It is the **direct pool for the exact pair** the user is swapping.                                |

Rules 5 and 6 depend on the swap context, so `/api/v1/pools/admitted` accepts optional `tokenIn` / `tokenOut` query params. Omit them and only rules 1–4 can match - a brand-new token paired against WETH will *not* show up unless you pass its address.

## What this means for a token to be tradeable

A token is tradeable in the Kumbaya UI when it is one side of at least one **admitted** pool. In practice that means one of:

* The token is **verified or bluechip** in the registry - it routes against any other trusted token automatically (rules 2–4).
* The token has a **direct pool against a trusted token** (typically WETH) that is indexed, above the TVL threshold, and initialized - it routes via rule 5 once its address is passed as `tokenIn` / `tokenOut`.
* The token is a **graduated fire token** (rule 1).

A token with only an unverified-to-unverified pool, or only sub-threshold liquidity, is not tradeable through the hosted router even though the underlying contracts would happily execute the swap.

## Fire (bonding-curve) pools

Pre-graduation fire pools are deliberately excluded from the cached candidate set for scale. They are added back **per request** only when the pre-graduation fire token is the user's `tokenIn` or `tokenOut`, so a token still on its bonding curve can be traded directly but will not appear as an intermediate hop. After graduation the pool joins the normal candidate set (rule 1).

## Candidate trimming

Admission is necessary but not the last word. Before route search runs, the admitted set is trimmed to a bounded number of candidates **ranked by TVL** (top-N direct pools, top-N token-to-base pools, etc.). On a pair with many pools, a low-TVL admitted pool can still be left out of the final route search. This is a performance bound, not an eligibility rule.

## Debugging a missing pool

If a pool you expect is not being routed, check in order:

1. **Is it in the indexer?** Query the [Hasura indexer](/developers/resources/indexer) for the pool by address. No row → routing cannot see it.
2. **Is its `totalValueLockedETH` above 0.01?** If not, it was dropped at stage 1.
3. **Is `tick` / `sqrtPrice` populated?** Null `tick` → never initialized, dropped at stage 2.
4. **Are you passing `tokenIn` / `tokenOut`?** Without them, rules 5 and 6 cannot fire.
5. **Are the tokens trusted?** If neither side is verified / bluechip and you are not passing the swap context, no rule matches.

For raw request/response shapes, see [**Pools endpoints**](/developers/apis/exchange-api/pools) and the Swagger UI at [`exchange.kumbaya.xyz/docs`](https://exchange.kumbaya.xyz/docs).


# Executing swaps

Once you have a quote, you submit a transaction to one of two routers. This page covers when to use which and shows end-to-end calldata flows.

| Router                | When to use                                                       |
| --------------------- | ----------------------------------------------------------------- |
| **`SwapRouter02`**    | Standard V3 swaps. The simplest path.                             |
| **`UniversalRouter`** | Bundling Permit2 + swap, multi-protocol routes, atomic operations |

For Kumbaya mainnet addresses see [**Contract Addresses**](/developers/networks-and-contracts/contract-addresses).

## Path 1: SwapRouter02 - exact input single

For a single-pool exact-input swap, this is the minimum viable flow:

```ts
import { createWalletClient, createPublicClient, http, parseUnits, parseAbi } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'

const SWAP_ROUTER_02 = '0xE5BbEF8De2DB447a7432A47EBa58924d94eE470e' // mainnet
const RPC = 'https://mainnet.megaeth.com/rpc'

const account = privateKeyToAccount('0xYOUR_PRIVATE_KEY')
const wallet = createWalletClient({ account, transport: http(RPC) })

const swapAbi = parseAbi([
  'function exactInputSingle((address tokenIn, address tokenOut, uint24 fee, address recipient, uint256 amountIn, uint256 amountOutMinimum, uint160 sqrtPriceLimitX96) params) external payable returns (uint256 amountOut)',
])

// Step 1 - approve once per (token, router) if not already approved.
// (Skip for ETH; required for any ERC-20.)
// await tokenContract.write.approve([SWAP_ROUTER_02, MAX_UINT256])

// Step 2 - submit the swap.
const txHash = await wallet.writeContract({
  address: SWAP_ROUTER_02,
  abi: swapAbi,
  functionName: 'exactInputSingle',
  args: [{
    tokenIn:  '0xWETH_ADDRESS',
    tokenOut: '0xTOKEN_OUT',
    fee:      3000,
    recipient: account.address,
    amountIn: parseUnits('1', 18),
    amountOutMinimum: minimumOut,    // from your quote × (1 - slippageBps/10000)
    sqrtPriceLimitX96: 0n,
  }],
})
```

**Paying with native ETH:** pass `tokenIn = WETH`, set `value: parseEther('amount')` on the call (pulled from `msg.value`); SwapRouter02 wraps it for you.

**Receiving native ETH out:** set `recipient = address(2)` (the router's `ADDRESS_THIS` constant), then `multicall([exactInputSingle, unwrapWETH9(amountMinimum, userAddress)])` so the router holds the WETH and unwraps it to the user. Sending `recipient = msg.sender` directly gives you WETH (no unwrap).

## Path 2: SwapRouter02 - multi-hop exact input

For routes through more than one pool, use `exactInput` with a packed path:

```ts
import { encodePacked } from 'viem'

// Path: tokenIn -> WETH (3000) -> tokenOut (3000)
const path = encodePacked(
  ['address', 'uint24', 'address', 'uint24', 'address'],
  [tokenInAddress, 3000, WETH, 3000, tokenOutAddress],
)

await wallet.writeContract({
  address: SWAP_ROUTER_02,
  abi: parseAbi([
    'function exactInput((bytes path, address recipient, uint256 amountIn, uint256 amountOutMinimum) params) external payable returns (uint256 amountOut)',
  ]),
  functionName: 'exactInput',
  args: [{ path, recipient: account.address, amountIn, amountOutMinimum: minimumOut }],
})
```

The path is `tokenAddress, fee, tokenAddress, fee, ...` packed - fees go between adjacent token pairs.

## Path 3: Using the Exchange API's calldata

If you got your quote from the Exchange API, the response already contains the calldata. Just submit it.

**For a `GET /api/v1/quote` response** (`methodParameters.calldata`):

```ts
const quote = await fetch(
  'https://exchange.kumbaya.xyz/api/v1/quote?…'
).then(r => r.json())

const txHash = await wallet.sendTransaction({
  to:    quote.methodParameters.to,           // SwapRouter02 or UniversalRouter
  value: BigInt(quote.methodParameters.value),
  data:  quote.methodParameters.calldata,     // lowercase 'd'
})
```

**For a `POST /api/v1/quote` partner response** (`transaction.callData`):

```ts
const quote = await fetch(
  'https://exchange.kumbaya.xyz/api/v1/quote',
  { method: 'POST', headers: { 'x-api-key': KEY, 'content-type': 'application/json' }, body: ... }
).then(r => r.json())

const txHash = await wallet.sendTransaction({
  to:    quote.transaction.to,
  value: BigInt(quote.transaction.value),
  data:  quote.transaction.callData,          // ⚠ camelCase 'D'
})
```

> The two endpoints return different field names (`calldata` vs `callData`). Mind the casing.

This is the lowest-effort production path. Just do the approval first if needed.

## Path 4: UniversalRouter with Permit2

`UniversalRouter` lets you bundle a Permit2 signature + swap into one tx, so you don't need an upfront ERC-20 `approve`. Use [`@kumbaya_xyz/universal-router-sdk`](/developers/sdks/universal-router-sdk) to encode the commands:

```ts
import { SwapRouter, RoutePlanner, CommandType } from '@kumbaya_xyz/universal-router-sdk'

// Pseudocode - the SDK exposes high-level helpers that build the right RoutePlanner
// for a Trade object. Use SwapRouter.swapCallParameters({ trade, … }) to get
// `{ calldata, value }` ready to send to the UNIVERSAL_ROUTER address.
```

Use this when:

* You want to skip ERC-20 approvals via Permit2 signatures.
* You're combining swap + transfer + wrap/unwrap atomically.
* Your route spans multiple Uniswap protocols (V2 + V3).

## Recipient sentinels

SwapRouter02 accepts two special recipient values that resolve at execution time:

| `recipient`                   | Resolves to               | Use case                                                                                         |
| ----------------------------- | ------------------------- | ------------------------------------------------------------------------------------------------ |
| `address(1)` (`MSG_SENDER`)   | The original `msg.sender` | Direct send to the user (skip if you set `recipient` to the user explicitly)                     |
| `address(2)` (`ADDRESS_THIS`) | The router itself         | Hold tokens in the router for a follow-up call (`unwrapWETH9`, `sweepToken`, etc.) via multicall |

Source: `swap-router-contracts/contracts/libraries/Constants.sol`. Use these in calldata when chaining operations.

## Approvals

Before any non-ETH swap with `SwapRouter02`, the router needs allowance for `tokenIn`. Common patterns:

* **One-shot max approve** - `approve(SWAP_ROUTER_02, MAX_UINT256)`. Simple, but trusts the router with your full balance.
* **Per-trade approve** - approve exactly the amount you're swapping. Higher gas overhead, lower trust footprint.
* **Permit2** - sign instead of approve. Use `UniversalRouter` for this path.

## Slippage handling

`amountOutMinimum` is your slippage protection. Compute it from your quote:

```ts
const slippageBps = 50n // 0.5%
const minimumOut  = (quotedOut * (10_000n - slippageBps)) / 10_000n
```

If on-chain conditions move price more than this between your quote and execution, the swap reverts and you lose only the gas.

## Common reverts

| Revert reason               | Likely cause                                      |
| --------------------------- | ------------------------------------------------- |
| `Too little received`       | Slippage exceeded - bump tolerance or reduce size |
| `STF` (SafeTransferFrom)    | Token allowance is too low or zero                |
| `LOK`                       | Pool re-entered (extremely rare)                  |
| Plain revert with no reason | Path packed wrong or recipient mismatch           |

## Where to next

* [**Quoting prices**](/developers/dex-integration/quoting) - getting the input numbers above
* [**Reading pools and positions**](/developers/dex-integration/reading-pools) - checking on-chain state
* [**Quote endpoints (API)**](/developers/apis/exchange-api/quote) - the hosted alternative


# Reading pools and positions

Three ways to read pool state: directly via RPC (cheapest, freshest), via the Hasura indexer (best for ranges + history), and via the Exchange API (curated, cached).

## Pool address from token + fee tier

Always derive the address with Kumbaya's pool init code hash, not Uniswap's. The `@kumbaya_xyz/v3-sdk` does it for you:

```ts
import { Pool, FeeAmount } from '@kumbaya_xyz/v3-sdk'
import { Token, ChainId } from '@kumbaya_xyz/sdk-core'

const A = new Token(ChainId.MEGAETH, '0x...', 18, 'A')
const B = new Token(ChainId.MEGAETH, '0x...', 6,  'B')

const poolAddress = Pool.getAddress(A, B, FeeAmount.MEDIUM) // 0.3%
```

If you implement the derivation yourself, see [**Pool Init Code Hash**](/developers/dex-integration/pool-init-code-hash).

## Reading current state via RPC

The two most useful reads are `slot0` (current price + tick) and `liquidity` (active liquidity at the current tick).

```ts
import { createPublicClient, http, parseAbi } from 'viem'

const RPC = 'https://mainnet.megaeth.com/rpc'
const client = createPublicClient({ transport: http(RPC) })

const poolAbi = parseAbi([
  'function slot0() view returns (uint160 sqrtPriceX96, int24 tick, uint16 observationIndex, uint16 observationCardinality, uint16 observationCardinalityNext, uint8 feeProtocol, bool unlocked)',
  'function liquidity() view returns (uint128)',
  'function token0() view returns (address)',
  'function token1() view returns (address)',
  'function fee() view returns (uint24)',
  'function tickSpacing() view returns (int24)',
])

const [slot0, liquidity, token0, token1, fee] = await Promise.all([
  client.readContract({ address: poolAddress, abi: poolAbi, functionName: 'slot0' }),
  client.readContract({ address: poolAddress, abi: poolAbi, functionName: 'liquidity' }),
  client.readContract({ address: poolAddress, abi: poolAbi, functionName: 'token0' }),
  client.readContract({ address: poolAddress, abi: poolAbi, functionName: 'token1' }),
  client.readContract({ address: poolAddress, abi: poolAbi, functionName: 'fee' }),
])

const [sqrtPriceX96, tick] = slot0
console.log({ tick, sqrtPriceX96, liquidity })
```

`slot0()` returns `sqrtPriceX96` (a `Q64.96` fixed-point number). To convert to a human-readable price the standard formula is:

```ts
// price of token1 per token0, ignoring decimals:
const ratio = Number(sqrtPriceX96) / 2 ** 96
const rawPrice = ratio * ratio

// Adjust for token decimals to get human-readable token1 / token0 price:
const adjustedPrice = rawPrice * 10 ** (token0.decimals - token1.decimals)
```

You can also reach for the V3 SDK if you want typed `Price` / `Token` / `Pool` objects:

```ts
import { Pool } from '@kumbaya_xyz/v3-sdk'
import { TickMath } from '@kumbaya_xyz/v3-sdk'

// Returns the sqrt(price) * 2^96 at a given tick, as JSBI:
const sqrtPriceFromTick = TickMath.getSqrtRatioAtTick(tick)
```

## Tick liquidity via TickLens

For range UIs and depth charts you'll want the populated ticks around the current tick. Use `TickLens`:

```ts
const TICK_LENS = '0x9c22f028e0a1dc76EB895a1929DBc517c9D0593e' // mainnet

const tickLensAbi = parseAbi([
  'function getPopulatedTicksInWord(address pool, int16 tickBitmapIndex) view returns ((int24 tick, int128 liquidityNet, uint128 liquidityGross)[])',
])

const wordIndex = Math.floor(currentTick / tickSpacing / 256)
const populated = await client.readContract({
  address: TICK_LENS,
  abi: tickLensAbi,
  functionName: 'getPopulatedTicksInWord',
  args: [poolAddress, wordIndex],
})
```

Loop adjacent word indices to walk further from current tick.

## Reading positions

Each LP position is an NFT held by `NonfungiblePositionManager`:

```ts
const NFPM = '0x2b781C57e6358f64864Ff8EC464a03Fdaf9974bA' // mainnet

const positionAbi = parseAbi([
  'function positions(uint256 tokenId) view returns (uint96 nonce, address operator, address token0, address token1, uint24 fee, int24 tickLower, int24 tickUpper, uint128 liquidity, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128, uint128 tokensOwed0, uint128 tokensOwed1)',
  'function ownerOf(uint256 tokenId) view returns (address)',
])

const position = await client.readContract({
  address: NFPM,
  abi: positionAbi,
  functionName: 'positions',
  args: [tokenId],
})
```

`tokensOwed0` / `tokensOwed1` show fees that have been "settled" but not yet collected. To see *uncollected* fees (including unsettled fee growth), the cleanest path is to construct a `Position` object via the SDK with current pool state.

## Querying historical state via the indexer

For anything time-bounded - TVL over a week, swap volume, position changes - go through the [Hasura GraphQL endpoint](/developers/resources/indexer):

```graphql
query PoolDay($poolId: String!) {
  PoolDayData(
    where: { pool_id: { _eq: $poolId } }
    order_by: { date: desc }
    limit: 30
  ) {
    date
    volumeUSD
    feesUSD
    tvlUSD
    token0Price
    token1Price
  }
}
```

Or current pool state with relationships joined in:

```graphql
query Pool($id: String!) {
  Pool_by_pk(id: $id) {
    address
    feeTier
    liquidity
    sqrtPrice
    tick
    totalValueLockedUSD
    volumeUSD
    token0 { symbol decimals }
    token1 { symbol decimals }
  }
}
```

Pool IDs are `address-chainId` (e.g. `0xabc...-4326`).

## Curated reads via the Exchange API

If you don't want to run the queries yourself:

```
GET https://exchange.kumbaya.xyz/api/v1/pools/list?chainId=4326&limit=50
GET https://exchange.kumbaya.xyz/api/v1/pools/{poolId}
GET https://exchange.kumbaya.xyz/api/v1/pools/{poolId}/timeseries?timeframe=days&limit=30
GET https://exchange.kumbaya.xyz/api/v1/pools/{poolId}/ticks
```

Cached, paginated, and rate-limit friendly - use these for browser-grade UIs. See [**Pools endpoints**](/developers/apis/exchange-api/pools).

## Picking the right path

| Task                                              | Best path               |
| ------------------------------------------------- | ----------------------- |
| One-time pool state read                          | RPC                     |
| Range / depth chart for one pool                  | RPC + TickLens          |
| Time-series, aggregations, joins                  | Hasura indexer          |
| Browser-grade UI feeding pool list / token detail | Exchange API            |
| Position health for a known `tokenId`             | RPC `positions()` + SDK |


# Differences from Uniswap V3

Kumbaya's DEX is a **fork of Uniswap V3** - not V2, not V4. If you're reading Uniswap docs, stick to the [V3 protocol section](https://developers.uniswap.org/docs/protocols/v3/overview).

## What's different

| Area                                  | Upstream Uniswap V3                              | Kumbaya                                                                                                                                                                                      |
| ------------------------------------- | ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Protocol-fee contract range**       | `feeProtocol ∈ {0, 4..10}` - up to 25% of LP fee | `feeProtocol ∈ {0, 2..10}` - up to 50% of LP fee at the contract level                                                                                                                       |
| **Protocol-fee values currently set** | Per-deployment, capped at 25%                    | **0.01% / 0.05% tiers → 25%** (`feeProtocol = 4`); **0.30% / 1.00% tiers → \~16.67%** (`feeProtocol = 6`); **launchpad-originated pools → 0%**. These sit within Uniswap's standard 25% cap. |
| **Pool init code hash**               | Standard Uniswap hash                            | `0x851d77a4…628a3da7` ([details](/developers/dex-integration/pool-init-code-hash))                                                                                                           |
| **SDK fee tier enum**                 | 4 tiers: 100/500/3000/10000                      | 7 tiers: also includes 200/300/400 (see [v3-sdk](/developers/sdks/v3-sdk))                                                                                                                   |
| **Deployed addresses**                | Mainnet/L2 Uniswap deployments                   | Kumbaya deployments on MegaETH ([list](/developers/networks-and-contracts/contract-addresses))                                                                                               |
| **Network**                           | Multiple EVM chains                              | MegaETH (4326 mainnet, 6343 testnet)                                                                                                                                                         |

The protocol-fee mechanism is unchanged: collected fees are `step.feeAmount / feeProtocol` per swap, so `feeProtocol = 4` means ¼ of LP fees go to the protocol. Calling `setFeeProtocol` is owner-gated, same as upstream. The fork keeps the *option* of setting `feeProtocol = 2` (50%) at the contract level, but production currently uses the values shown above - keeping Kumbaya in line with the industry-standard Uniswap range.

## What's the same

Everything else. Pool math, tick spacing per fee tier, fee tiers (0.01% / 0.05% / 0.3% / 1%), the `IUniswapV3Pool` interface, position NFTs, periphery routers, quoter behavior - all unchanged.


# Overview

The Kumbaya launchpad uses **Uniswap V3 concentrated liquidity positions** to simulate a bonding curve, so a freshly-launched token can be traded immediately on the same DEX infrastructure as everything else - no separate AMM, no migration step, no surprises post-launch.

> 🔥 **A note on naming.** The launchpad's smart contracts are prefixed `Fire` (`FireLaunch`, `FireToken`, `FireGraduator`, `FireRegistry`, `FireStream`, `FuelVault`) - a campfire metaphor that fits the "Kumbaya" theme of people gathering around a fire. The contracts are part of Kumbaya, not a separate protocol; throughout these docs "the launchpad" and the `Fire*` contract names refer to the same thing.

> 🛡 **Audited.** Every launchpad contract - including `FireToken` - is covered by a signed BlockSec audit. No critical or high findings remain open. See [**Audit & security**](/developers/audit-and-security) for the full breakdown and the bundled report.

## How a launch works at a high level

```
┌─────────┐   ignite()    ┌──────────────┐   creates V3 pool,    ┌──────────────┐
│ Creator │──────────────▶│  FireLaunch  │──────────────────────▶│ Stacked seed │
└─────────┘               └──────┬───────┘  seed + tail positions │ + tail pos   │
                                 │                                └──────────────┘
                                 │ if creatorAllocationBps > 0:
                                 │   capture portion → FuelVault (creator liquid)
                                 │   remainder       → FireToken (creator vesting)
                                 ▼
                          ┌──────────────┐
                          │  FuelVault   │ ◀── on each buy: skimBps of token amount
                          │              │     credits the BUYER (not creator)
                          │              │ ◀── giftWithSig moves credits buyer→creator
                          └──────┬───────┘
                                 │ withdraw (creator)
                                 ▼
                              creator wallet

        trades happen ──▶ V3 Pool (bonding curve)  ──▶  pre-grad fees:
                                                       • protocol (100%)

   When price reaches graduation tick:
        ────────────────────────────────────────────────────────────
        recordGraduationCondition()  →  grace period (registry-set)
        ────────────────────────────────────────────────────────────
                       │
                       ▼
                 graduate()  ──▶  setGraduated() (skim off, vesting starts)
                                  burn all seed + tail positions
                                  distribute pre-grad fees
                                  mint full-range NFT to FireStream
                                  FuelVault.onGraduated() (burn countdown)

   After graduation:
        Trades go through the now-ordinary V3 pool
        FireStream.claimFees() splits NFT fees: registry recipients + creator
        FireToken.releaseVested() unlocks linearly (no-op if creatorAllocationBps=0)
        FuelVault.executeBurn() can permanently destroy ungifted user credits
```

## The contracts

| Contract          | What it does                                                                                                                                                                                        |
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **FireRegistry**  | Stores protocol-level config: fee splits, defaults, guardian, accepted numeraires/fee tiers                                                                                                         |
| **FireLaunch**    | Token factory. Entry point for creators (`ignite`)                                                                                                                                                  |
| **FireToken**     | ERC-20 with EIP-2612 `permit()` and infinite Permit2 allowance. Has a skim hook on buys (pre-graduation only) and a linear creator vesting schedule                                                 |
| **FireGraduator** | Position lifecycle: builds the bonding curve, collects pre-grad fees, graduates the pool                                                                                                            |
| **FireStream**    | Custodies the graduated NFT and distributes its fees to beneficiaries                                                                                                                               |
| **FuelVault**     | Tip-credit ledger (deposit, gift, withdraw, vest, burn-after-grace). User-facing this is the **Tip Jar** on `kumbaya.xyz`; on-chain functions still carry the `gift` / `Gifted` / `FuelVault` names |

For mainnet/testnet addresses see [**Contract Addresses**](/developers/networks-and-contracts/contract-addresses).

## Production defaults (frontend launches)

These are the values the Kumbaya frontend (`kumbaya.xyz/launchpad/create`) plugs into every standard launch on mainnet:

| Parameter              | Value                             | Note                                               |
| ---------------------- | --------------------------------- | -------------------------------------------------- |
| `defaultTotalSupply`   | `1,000,000,000` (1B, 18 decimals) | Fixed supply per launch                            |
| `feeTier`              | `10000` (1%)                      | V3 pool fee tier                                   |
| `tickSpacing`          | `200`                             | Derived from fee tier                              |
| `tickLower`            | `-219400`                         | Bonding curve start (token0 perspective)           |
| `tickUpper`            | `-174800`                         | Graduation tick                                    |
| `numPositions`         | `50`                              | Stacked seed positions                             |
| `skimBps`              | `300` (3%)                        | Skim on buys → FuelVault                           |
| `creatorAllocationBps` | `0`                               | **No on-token creator allocation in default flow** |
| `maxShareToBeSoldBps`  | `7200` (72%)                      | To bonding curve                                   |
| Tail liquidity         | `2800` (28%)                      | Remainder, full-range tail position                |
| `vestingDuration`      | `90 days`                         | Linear, starts at graduation                       |

> Because `creatorAllocationBps = 0` in the production frontend, default launches **don't have a creator vesting schedule to claim**. Creators earn from post-graduation `FireStream` fees and from FuelVault skim/gift inflows. See [**Fees and credits**](/developers/launchpad/fees-and-credits).
>
> A custom integration calling `FireLaunch.ignite()` directly *can* set a non-zero `creatorAllocationBps`, up to the registry's `MAX_CREATOR_ALLOCATION_BPS = 2000` (20%).

## Lifecycle states

A launchpad token has three distinct on-chain lifecycle states:

| State         | What's true                                                                                                                                                                             |
| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Bonding**   | `graduationConditionMetAt == 0`. Trades go through stacked seed positions. Skim hook active on transfers (taking `skimBps` to FuelVault). Creator vesting hasn't started.               |
| **Grace**     | `graduationConditionMetAt != 0` and `graduated == false`. Price hit the graduation tick. Anyone can `graduate()` after the grace period elapses; the guardian can graduate immediately. |
| **Graduated** | `graduated == true`. Seed positions are burned. A full-range NFT lives in `FireStream`. Skim hook disabled. Creator vesting clock started. FuelVault burn countdown started.            |

## Design notes for integrators

* **The bonding curve is just V3 positions.** All your existing Uniswap V3 tooling (QuoterV2, SwapRouter, indexer queries) works for launchpad tokens - pre-graduation and post-graduation. Buyers don't call the launchpad contracts directly; they swap on the V3 pool the way they would for any other token.
* **`ignite()` doesn't accept ETH.** The frontend's "buy at launch" feature is implemented as a 2-tx batch: `ignite()` plus an immediate swap on the freshly-created pool. See [**Launching a token**](/developers/launchpad/launching).
* **Allocation is parameterized at launch time**, capped by `FireRegistry`. Don't hardcode the 80/10/10 split - read it back from `IgniteParams` or the on-chain `LaunchState`.
* **Pre- and post-graduation fee splits are different.** Pre-grad fees go to the protocol. Post-grad fees stream through `FireStream`, with the creator getting whatever remains after registry beneficiaries. [**Fees and credits**](/developers/launchpad/fees-and-credits) has the full breakdown.

## Pages in this section

* [**Contracts at a glance**](/developers/launchpad/contracts) - file-level summary
* [**Launching a token (`ignite`)**](/developers/launchpad/launching) - full `IgniteParams` reference + how to do init-buy in the same batch
* [**Bonding curve and graduation**](/developers/launchpad/graduation) - `recordGraduationCondition`, grace period, `graduate()`, force-graduate
* [**Fees and credits**](/developers/launchpad/fees-and-credits) - pre/post-grad fee splits, `FireStream`, `FuelVault`


# Contracts at a glance

| Contract          | File                         | Purpose                                                                                                           | Notable functions                                           |
| ----------------- | ---------------------------- | ----------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- |
| **FireRegistry**  | `fire/src/FireRegistry.sol`  | Protocol config (fees, defaults, guardian)                                                                        | Admin setters                                               |
| **FireLaunch**    | `fire/src/FireLaunch.sol`    | Token factory + V3 pool initialization                                                                            | `ignite()`, `recordGraduationCondition()`                   |
| **FireToken**     | `fire/src/FireToken.sol`     | ERC-20 with EIP-2612 `permit()` and infinite Permit2 allowance, plus skim hook on buys and linear creator vesting | `releaseVested()`, `vestedAmount()`, `releasableAmount()`   |
| **FireGraduator** | `fire/src/FireGraduator.sol` | Bonding-curve lifecycle and fee collection                                                                        | `createPositions()`, `claimFees()`, `graduate()`            |
| **FuelVault**     | `fire/src/FuelVault.sol`     | Credit system (skim + gifts). Surfaced as the **Tip Jar** in `kumbaya.xyz`; gift calls = user-facing tips         | `deposit()`, `giftWithSig()`, `withdraw()`, `executeBurn()` |
| **FireStream**    | `fire/src/FireStream.sol`    | Fee distribution from graduated NFT                                                                               | `receiveNFT()`, `claimFees()`                               |

For deployed addresses, see [**Contract Addresses**](/developers/networks-and-contracts/contract-addresses).

> 🛡 **Audit:** All six contracts - including `FireToken` - are covered by the BlockSec audit. No critical or high findings remain open. See [**Audit & security**](/developers/audit-and-security) for the full report and Uniswap V3 audit lineage.

For per-contract function/error reference, read the source directly at [github.com/Kumbaya-xyz/fire/tree/main/src](https://github.com/Kumbaya-xyz/fire/tree/main/src). The contracts are short and well-named.


# Launching a token (ignite)

A launchpad token is created by a single call to **`FireLaunch.ignite(IgniteParams)`**. That one transaction:

1. Deploys a fresh `FireToken` ERC-20 (CREATE2, deterministic from `salt`)
2. Creates the corresponding Uniswap V3 pool at the starting tick if it doesn't exist
3. Transfers the bonding-curve allocation to `FireGraduator`
4. Calls `FireGraduator.createPositions()` to seed the curve with overlapping concentrated positions plus a tail
5. Splits the creator allocation across `FuelVault` (liquid bucket) and the on-token vesting schedule (which doesn't unlock until graduation)

The function returns the deployed token address.

## Function

```solidity
function ignite(IgniteParams calldata params)
    external
    nonReentrant
    whenNotPaused
    returns (address token);
```

> ⚠️ **`ignite()` does not accept `msg.value`.** It cannot bundle an ETH purchase. To buy tokens at launch, send a second transaction immediately after - see [**Buying at launch**](#buying-at-launch) below.

## `IgniteParams`

```solidity
struct IgniteParams {
    string  name;                  // Token name (must be non-empty)
    string  symbol;                // Token symbol (must be non-empty)
    uint256 totalSupply;           // Total fixed supply, must fit in uint128
    address numeraire;             // Quote token (must be approved in registry)
    int24   tickLower;             // Bonding-curve start tick (lower price)
    int24   tickUpper;             // Graduation tick (target price)
    uint24  feeTier;               // V3 pool fee tier (must be approved)
    uint16  skimBps;               // % of buys redirected to FuelVault as buyer credits
    uint16  creatorAllocationBps;  // Creator's % of total supply
    uint16  maxShareToBeSoldBps;   // % of supply allocated to bonding curve
    uint16  numPositions;          // Bonding-curve positions; must be in [minPositions, maxPositions]
    uint256 vestingDuration;       // Creator vesting duration in seconds
    bytes32 salt;                  // CREATE2 salt for deterministic token address
}
```

> **Don't pass `0` for `numPositions` or `vestingDuration` expecting a "registry default."** There is no fallback - `numPositions == 0` reverts with `ZeroPositions`, and `vestingDuration == 0` only passes when `creatorAllocationBps == 0` (because the bounds check is skipped) **and** the registry's `requiredVestingDuration` is also 0. Always pass the values you want.

## Validation: two phases

`_validateParams` runs two checks in sequence:

### Phase 1 - bounds

| Constraint                                                                                                                                       | Implementation                                                                     |
| ------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------- |
| `name`, `symbol` non-empty                                                                                                                       | `EmptyName` / `EmptySymbol`                                                        |
| `0 < totalSupply ≤ type(uint128).max`                                                                                                            | `ZeroSupply` / `SupplyExceedsUint128`                                              |
| Numeraire on the approved set                                                                                                                    | `registry.approvedNumeraires(numeraire)` (public mapping) → `NumeraireNotApproved` |
| Fee tier on the approved set                                                                                                                     | `registry.approvedFeeTiers(feeTier)` (public mapping) → `FeeTierNotApproved`       |
| Fee tier has tick spacing in V3 factory                                                                                                          | `factory.feeAmountTickSpacing(feeTier) != 0` → `InvalidFeeTier`                    |
| `tickLower < tickUpper`, both aligned to fee tier spacing                                                                                        | `InvalidTickRange` / `TickNotAligned`                                              |
| `skimBps ≤ registry.maxSkimBps()`                                                                                                                | `SkimTooHigh` (max 1500 = 15%)                                                     |
| `creatorAllocationBps ≤ registry.maxCreatorAllocationBps()`                                                                                      | `CreatorAllocTooHigh` (max 2000 = 20%)                                             |
| `creatorAllocationBps + maxShareToBeSoldBps ≤ 9000`                                                                                              | `AllocationsAbove90Percent` (≥10% tail)                                            |
| `maxShareToBeSoldBps ≥ registry.minMaxShareToBeSoldBps()`                                                                                        | `MaxShareToBeSoldTooLow`                                                           |
| `numPositions ≠ 0`                                                                                                                               | `ZeroPositions`                                                                    |
| `numPositions ∈ [registry.minPositions(), registry.maxPositions()]`                                                                              | `TooFewPositions` / `TooManyPositions` (5 … 50)                                    |
| `vestingDuration ∈ [minVestingDuration(), maxVestingDuration()]` *only if* `creatorAllocationBps > 0` and not all of it is captured to FuelVault | `VestingDurationTooShort` / `VestingDurationTooLong` (90 … 730 days)               |
| If `creatorAllocationBps > 0` and registry hasn't set `requiredVestingDuration` and not all is captured to FuelVault                             | `RequiredCreatorVestingUnset`                                                      |

### Phase 2 - registry pin matching

If the registry has set any `required*` value to a non-zero value, the corresponding parameter **must match exactly**. The registry-required tick range *also* accepts the inverse (negated and swapped):

| If registry has set…                                           | …user param must equal                     | Mismatch revert                                          |
| -------------------------------------------------------------- | ------------------------------------------ | -------------------------------------------------------- |
| `requiredTotalSupply`                                          | itself                                     | `TotalSupplyMismatch`                                    |
| `requiredTickLower` / `requiredTickUpper`                      | itself OR `(-reqTickUpper, -reqTickLower)` | `TickRangeMismatch`                                      |
| `requiredFeeTier`                                              | itself                                     | `FeeTierMismatch`                                        |
| `requiredSkimBps`                                              | itself                                     | `SkimBpsMismatch`                                        |
| `requiredCreatorAllocationBps` / `requiredMaxShareToBeSoldBps` | both must match (set atomically)           | `CreatorAllocationMismatch` / `MaxShareToBeSoldMismatch` |
| `requiredNumPositions`                                         | itself                                     | `NumPositionsMismatch`                                   |
| `requiredVestingDuration`                                      | itself                                     | `VestingDurationMismatch`                                |

> The registry pinning means production mainnet may force values tighter than the absolute caps. **Always read the registry's `required*` views before constructing `IgniteParams`** - see [Live registry config](/developers/building-agents/registry-config).
>
> The contract-level inverse-tick acceptance (line `bool reverseMatch = (params.tickLower == -reqTickUpper && params.tickUpper == -reqTickLower)` in `_validateRegistryRequirements`) is *separate* from the indexer's `(ticks, fireTokenPosition)` check - see [**Token ordering and tick math**](#-token-ordering-and-tick-math) below.

## Production defaults (Kumbaya frontend)

The Kumbaya frontend uses these values for every standard launch on mainnet:

```ts
{
  defaultTotalSupply:    1_000_000_000n * 10n ** 18n,  // 1B tokens
  tokenDecimals:         18,
  feeTier:               10000,                         // 1%
  tickSpacing:           200,
  tickLower:             -219400,
  tickUpper:             -174800,
  skimBps:               300,                           // 3%
  creatorAllocationBps:  0,                             // no on-token creator allocation
  maxShareToBeSoldBps:   7200,                          // 72% to bonding curve
  numPositions:          50,
  vestingDuration:       90n * 24n * 60n * 60n,         // 90 days
}
```

A few things worth calling out for integrators replicating the defaults:

* **`creatorAllocationBps = 0`.** The standard frontend launch grants **no on-token creator allocation**. The remaining `10000 - 7200 = 2800` bps (28%) goes to the **tail position**. Creators earn from post-grad `FireStream` fees and FuelVault skim/gifts - there's no vesting schedule to claim because there's nothing vesting.
* You *can* set `creatorAllocationBps` up to the registry cap (`MAX_CREATOR_ALLOCATION_BPS = 2000`, i.e. 20%) when calling `ignite` yourself - but make sure `creatorAllocationBps + maxShareToBeSoldBps ≤ 9000` so there's still tail liquidity.
* Always read `LaunchState` (returned by `FireLaunch.getLaunchState(token)`) to know the actual split for a *deployed* token - defaults can change.

## ⚠ Token ordering and tick math

**This is the easiest way to break a launch.** Read this section before you call `ignite`.

> 🚨 **The Kumbaya indexer only indexes launches that match an allowed `(tickLower, tickUpper, fireTokenPosition)` configuration for the chain.** A token launched with anything else still exists on-chain (the contract is permissionless), but it won't be picked up by the indexer - so it won't appear in the launchpad feed, search, or curated APIs, and Kumbaya pricing won't track it. Direct V3 trading against the pool still works for anyone with the contract address.

In Uniswap V3, every pool has a **`token0`** and a **`token1`**, sorted by address: the lower-address token is `token0`. Ticks measure the price of `token0` denominated in `token1` - so swapping which side your token is on **inverts the meaning of every tick value**.

### What the indexer actually accepts

The validator (`isValidFirePoolConfig`) accepts **both** the canonical config and its inverse:

| Side                                      | `fireTokenPosition` | `tickLower` | `tickUpper` |
| ----------------------------------------- | ------------------- | ----------- | ----------- |
| **Canonical** (Kumbaya frontend defaults) | `token0`            | `-219400`   | `-174800`   |
| **Inverse** (token is `token1`)           | `token1`            | `174800`    | `219400`    |

The inverse is the same curve from the pool's other side: ticks negated **and** swapped, position flipped. Fee tier, supply, skim, allocation, vesting duration, and position count are **not** checked by the indexer - only this `(tickLower, tickUpper, fireTokenPosition)` triple.

### Recommended path: mine for `token0`

The Kumbaya frontend always launches with token as `token0` and the canonical ticks above. Two reasons to follow that path:

1. It matches every screenshot and creator-facing UI in `kumbaya.xyz` - no surprises for users.
2. `FireLaunch.ignite()` deploys via CREATE2, so the token address is determined by the salt. Salt mining for `tokenAddr < numeraire` (i.e. token is `token0`) typically takes only a few thousand attempts.

The Kumbaya defaults (`tickLower = -219400`, `tickUpper = -174800`) **assume the new token is `token0`**:

* The negative ticks place the launched token at a *low* price relative to the numeraire - i.e. early buyers get cheap tokens.
* The graduation tick (`tickUpper = -174800`) is the price target the curve climbs *up* toward.
* The tail position extends from `tickUpper` outward toward `MAX_TICK`.

If your launched token ends up as `token1` (higher address than the numeraire), every one of those statements flips. Buys would walk price the wrong way, the "graduation" tick becomes a floor instead of a ceiling, and the tail position points the wrong direction.

### Always mine the salt for token0

`FireLaunch.ignite()` deploys the new token via CREATE2, so the resulting address is fully determined by `(deployer, salt, init code, constructor args)`. Pick salts in a loop until the resulting address is **strictly less than** the numeraire (WETH) address:

```ts
import { keccak256, encodePacked, getCreate2Address } from 'viem'

function mineToken0Salt({
  deployer,        // FireLaunch address
  initCodeHash,    // FireToken creation code hash
  numeraire,       // WETH address - the token your new token must beat
  vanitySuffix,    // optional: '069' | '420' | '888' from VANITY_SUFFIXES
  maxAttempts = vanitySuffix ? 1_000_000 : 10_000,
}) {
  for (let i = 0; i < maxAttempts; i++) {
    const salt = randomBytes32()
    const tokenAddr = getCreate2Address({
      from: deployer,
      salt,
      bytecodeHash: initCodeHash,
    })
    // Required: token must be token0 (strictly less than numeraire).
    if (BigInt(tokenAddr) >= BigInt(numeraire)) continue
    // Optional: filter for vanity ending.
    if (vanitySuffix && !tokenAddr.toLowerCase().endsWith(vanitySuffix)) continue
    return { salt, tokenAddr }
  }
  throw new Error('salt mining exhausted')
}
```

The Kumbaya frontend uses these defaults:

| Setting                                  | Value               |
| ---------------------------------------- | ------------------- |
| Max attempts (no vanity)                 | `10,000`            |
| Max attempts (with vanity, 3-hex suffix) | `1,000,000`         |
| Vanity suffixes available                | `069`, `420`, `888` |

Mining without a vanity is fast - it's a 50/50 coin flip per salt, so you typically find one in a handful of tries.

### Launching as `token1` (inverse config)

If you can't (or don't want to) mine for `token0`, you can launch with the new token as `token1` and the **inverse ticks** above. This *is* indexed correctly and will appear on the launchpad - the indexer treats canonical and inverse as equivalent.

To do it:

1. Mine a salt where `tokenAddr > numeraire`.
2. Pass `tickLower = 174800` and `tickUpper = 219400` (the negated and swapped values of the canonical config).
3. Everything else stays the same.

In practice, mining for `token0` is faster than the salt search needed to land on a memorable address while satisfying `tokenAddr > numeraire`, so the canonical config is what you'll see in production.

### What the indexer rejects

Any tuple `(tickLower, tickUpper, fireTokenPosition)` that doesn't match either row above. **The contract may still accept the call** (because `_validateRegistryRequirements` only checks tick *values*, not the resulting `token0`/`token1` ordering) - but the indexer skips the launch.

Examples and their outcomes:

| Ticks passed                            | Token side after deploy | Contract `ignite`                                    | Indexer               |
| --------------------------------------- | ----------------------- | ---------------------------------------------------- | --------------------- |
| `(-219400, -174800)` (canonical)        | `token0`                | accepts                                              | ✓ indexed             |
| `(-219400, -174800)` (canonical)        | `token1`                | accepts (matches required)                           | ✗ skipped (unindexed) |
| `(174800, 219400)` (inverse)            | `token1`                | accepts                                              | ✓ indexed             |
| `(174800, 219400)` (inverse)            | `token0`                | accepts (matches required inverse)                   | ✗ skipped             |
| Custom range, e.g. `(-150000, -100000)` | either                  | reverts (`TickRangeMismatch`) if registry pins ticks | n/a                   |

If the launch is unindexed, the token is permanent and tradeable but **invisible to Kumbaya**. Re-deploying with the correct ordering means a fresh address.

### Sanity check before submitting `ignite`

```ts
// Belt-and-braces assertion - run this client-side before calling writeContract.
if (BigInt(predictedTokenAddr) >= BigInt(numeraireAddr)) {
  throw new Error(
    'predicted token address is not less than numeraire - '
    + 'salt mining failed; do not submit ignite()',
  )
}
```

A failed launch costs gas and produces a stranded token. The 5-line check above prevents both.

## Example: deploy with viem

```ts
import { encodeFunctionData, parseUnits } from 'viem'
import { fireLaunchAbi } from './abis/fireLaunch'

const FIRE_LAUNCH = '0x69FE0908F1211dE66F7067021998f28A5693ABbD' // mainnet

const params = {
  name: 'My Token',
  symbol: 'MTK',
  totalSupply: parseUnits('1000000000', 18),    // 1B tokens
  numeraire: '0x4200000000000000000000000000000000000006', // WETH
  tickLower: -219400,                           // production frontend default
  tickUpper: -174800,                           // production frontend default
  feeTier:   10000,                             // 1%
  skimBps:    300,                              // 3% skim → FuelVault
  creatorAllocationBps: 0,                      // no on-token alloc (default)
  maxShareToBeSoldBps:  7200,                   // 72% bonding curve, 28% tail
  numPositions: 50,                             // production default
  vestingDuration: BigInt(90 * 24 * 60 * 60),   // 90 days
  salt: pickSaltSoTokenIsToken0(),              // see "Salt mining" below
}

const txHash = await wallet.writeContract({
  address: FIRE_LAUNCH,
  abi: fireLaunchAbi,
  functionName: 'ignite',
  args: [params],
})
```

After confirmation, parse the `TokenIgnited(address indexed token, address indexed creator, address indexed pool, uint256 totalSupply, uint16 skimBps, int24 tickLower, int24 tickUpper)` event to read the deployed token address, or compute it yourself from the salt + init code hash.

## Salt mining

The CREATE2 salt is significant: many integrations want **the new token to be `token0` of the V3 pool** (i.e. lower address than the numeraire). The Kumbaya frontend mines salts to satisfy that ordering, and additionally tries vanity suffixes like `069` and `420`. Mining is fast - a few thousand attempts is typical.

Reference logic from the frontend (simplified):

```ts
function mineSalt(deployer, ctorArgs, numeraire, vanitySuffix?) {
  for (let i = 0; i < 100_000; i++) {
    const salt = randomBytes(32)
    const tokenAddr = computeCreate2Address(deployer, salt, ctorArgs)
    if (BigInt(tokenAddr) >= BigInt(numeraire)) continue           // need token0
    if (vanitySuffix && !tokenAddr.endsWith(vanitySuffix)) continue
    return salt
  }
}
```

## Buying at launch

The Kumbaya frontend lets a creator buy from their own bonding curve in the same UX flow. The contract supports this only as a **two-transaction batch**, not a single call:

```
tx 1: FireLaunch.ignite(params)            → returns tokenAddress
tx 2: SwapRouter02.exactInputSingle({
        tokenIn:  numeraire (WETH),
        tokenOut: tokenAddress,
        fee:      params.feeTier,
        recipient: creator,
        amountIn: ethAmount,
        ...
      })
```

In practice both txs are signed up-front with sequential nonces and broadcast together via `eth_sendRawTransactionBatch` (or sequentially). The second tx executes against the fresh pool created by the first.

> ⚠️ **Not atomic.** If the second tx fails (e.g. slippage), the token is still deployed. Validate the bonding-curve price math client-side before submitting the buy.

## Errors to handle

Most validation failures revert with these custom errors:

| Error                                                                 | Cause                                                                                        |
| --------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
| `FeeTierNotApproved` / `InvalidFeeTier`                               | `feeTier` not in registry's accepted set / not a valid V3 fee tier                           |
| `NumeraireNotApproved`                                                | `numeraire` not in registry's accepted set                                                   |
| `ZeroSupply` / `SupplyExceedsUint128`                                 | Bad `totalSupply`                                                                            |
| `CreatorAllocTooHigh`                                                 | `creatorAllocationBps > MAX_CREATOR_ALLOCATION_BPS` (2000)                                   |
| `SkimTooHigh`                                                         | `skimBps > MAX_SKIM_BPS` (1500)                                                              |
| `AllocationsAbove90Percent`                                           | `creatorAllocationBps + maxShareToBeSoldBps > 9000`                                          |
| `MaxShareToBeSoldTooLow`                                              | `maxShareToBeSoldBps` below registry minimum                                                 |
| `InvalidTickRange` / `TickNotAligned`                                 | `tickLower`, `tickUpper` invalid or not aligned to fee tier's tick spacing                   |
| `TooFewPositions` / `TooManyPositions` / `ZeroPositions`              | `numPositions` outside `[5, 50]`                                                             |
| `VestingDurationTooShort` / `VestingDurationTooLong`                  | `vestingDuration` outside registry-allowed range                                             |
| `EmptyName` / `EmptySymbol`                                           | Missing metadata fields                                                                      |
| `*Mismatch` family (`TotalSupplyMismatch`, `TickRangeMismatch`, etc.) | An existing pool already exists for this pair but with different params than what you passed |
| `PoolAlreadyInitialized`                                              | Pool exists and is already at a non-zero price                                               |

## Where to next

* [**Bonding curve and graduation**](/developers/launchpad/graduation) - what happens after a launch
* [**Fees and credits**](/developers/launchpad/fees-and-credits) - how creators earn from launches
* [**Contracts at a glance**](/developers/launchpad/contracts) - function index across all launchpad contracts


# Bonding curve and graduation

A launchpad token's life on-chain has three states: **bonding**, **grace**, and **graduated**. This page describes how a token moves between them and what each transition does.

## The bonding curve

When `FireLaunch.ignite()` runs, `FireGraduator.createPositions()` deploys **`numPositions` concentrated V3 positions** (must be in `[5, 50]` per registry bounds) covering `[tickLower, tickUpper]`. Tokens-per-position is `tokensToSell / numPositions` (integer division; remainder goes to the last position). The bonding-curve effect emerges from the way these stacked positions are consumed in order as price moves through them.

There's also a **tail position** for post-graduation liquidity. It's anchored at the graduation tick and extends *outward*:

* **Token is `token0`** → tail covers `[graduationTick, MAX_TICK_aligned]`
* **Token is `token1`** → tail covers `[MIN_TICK_aligned, graduationTick]`

(Tick bounds are aligned inward to the fee tier's tick spacing using `TickMath.minUsableTick` / `maxUsableTick`.) Even before graduation, the tail provides liquidity outside the curve range so the pool never goes empty.

Trading on the pool is unmodified V3 - buyers swap via `SwapRouter02` like for any other token. The "curve" is just an emergent property of how the seed positions are stacked.

## Recording the graduation condition

```solidity
function recordGraduationCondition(address token) external;
```

Anyone can call this. It checks whether the pool's current tick has crossed the graduation tick (the comparison direction depends on `token0`/`token1` ordering: token0 launches need `currentTick ≥ graduationTick`, token1 launches need `currentTick ≤ graduationTick`). On success it sets `graduationConditionMetAt = block.timestamp`, which **starts the grace period**.

* **One-shot, not idempotent:** the function reverts with `ConditionAlreadyRecorded` if `graduationConditionMetAt != 0`. The grace timer is set once and never reset.
* **Pre-graduation only:** reverts with `AlreadyGraduated` if the token has already graduated.
* **Owner-disable switch:** the registry owner can flip `graduationRecordingEnabled` (via `FireLaunch.setGraduationRecordingEnabled`) to pause this entry point in an emergency. Disabled state reverts with `GraduationRecordingDisabled`.
* **Caller incentive:** none baked into the contract - but UIs and bots are motivated to call it because they want to trigger graduation downstream.

## The grace period

Configured in `FireRegistry`:

* **`gracePeriodDuration`** - bounded `[1 hour, 7 days]`. Read the live mainnet value with `FireRegistry.gracePeriodDuration()`.
* **`forceGraduationDelay`** - applied from the token's `createdAt`. Read with `FireRegistry.forceGraduationDelay()`.

Two roles can graduate during/after grace:

| Caller             | When they can graduate                                                                                                                      |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------- |
| **Guardian**       | Immediately, as long as `canGraduate(token)` returns `true` (no grace period required)                                                      |
| **Anyone**         | After `gracePeriodDuration` elapses since `graduationConditionMetAt`, *and* `canGraduate(token)` is `true`                                  |
| **Anyone (force)** | After `block.timestamp ≥ createdAt + forceGraduationDelay`, even if `recordGraduationCondition` was never called. Bypasses the grace check. |

The grace period exists to give the protocol a window to react to abnormal conditions before liquidity reshapes.

## `graduate()`

```solidity
function graduate(address token) external nonReentrant whenNotPaused;
```

What it does, in order - straight from `FireGraduator._graduate`:

1. **`FireToken.setGraduated()`** - runs first. Sets `graduated = true`, `vestingStart = block.timestamp`, and emits `Graduated()`. From this moment on the skim hook is disabled and the on-token vesting clock starts.
2. **Burn every bonding-curve and tail position.** Loops over `positions[token]`, calling `IUniswapV3Pool.burn` and `collect` for each. Tracks principal and fees separately.
3. **Distribute pre-graduation fees** via `_distributeFees`: all to the protocol (the contract splits `graduationFeeBps` to the registry `treasury` and the remainder to its `integrator`, but both are the same Kumbaya protocol address). See [**Fees**](/developers/launchpad/fees-and-credits).
4. **Mint a full-range NFT** via `NonfungiblePositionManager.mint` with `tickLower = TickMath.minUsableTick(tickSpacing)`, `tickUpper = TickMath.maxUsableTick(tickSpacing)`. Recipient is `FireStream`. Any unused principal (mint dust) is sent to the protocol.
5. **`FireStream.receiveNFT(token, nftId, creator)`** - registers the NFT for fee streaming and pins the creator address.
6. **`FuelVault.onGraduated(token)`** - sets `graduated[token] = true` and `burnTime[token] = block.timestamp + registry.burnCountdownDuration()`.
7. **`FireLaunch.setGraduated(token, nftId)`** - flips the launch state's `graduated` flag and stores `graduatedNftId`.

After `graduate` completes the pool is just an ordinary Uniswap V3 pool. There's nothing launchpad-specific in the trading path anymore.

## State you can read

Use **`FireLaunch.getLaunchState(token)`** to inspect a token's lifecycle state:

```solidity
function getLaunchState(address token) external view returns (LaunchState memory);

struct LaunchState {
    address  token;                       // FireToken address
    uint24   feeTier;
    int24    tickSpacing;
    uint16   graduationFeeBps;            // pre-grad protocol share, snapshotted at launch
    bool     isToken0;
    bool     graduated;
    address  numeraire;
    int24    graduationTick;              // = tickUpper if isToken0 else tickLower
    address  pool;
    address  creator;                     // ignite() caller
    uint256  graduatedNftId;              // 0 until graduation
    uint256  graduationConditionMetAt;    // 0 until recordGraduationCondition succeeds
    uint256  createdAt;
}
```

`FireGraduator.canGraduate(token)` returns `true` when graduation is possible (price reached, OR `forceGraduationDelay` elapsed since `createdAt`).

You can also read `FireToken.graduated` (public bool) and `FireToken.vestingStart` (public uint256) directly off the token contract.

## What happens to the tail liquidity?

The tail position is **burned at graduation**, just like the bonding-curve positions. It does not survive into the graduated state as a separate position. The full-range NFT held by `FireStream` is the only launchpad-related liquidity afterward - alongside whatever organic LPs add post-graduation.

## Errors to handle

`FireGraduator` errors:

| Error                   | When                                                                                                                  |
| ----------------------- | --------------------------------------------------------------------------------------------------------------------- |
| `AlreadyGraduated`      | `graduate()` called twice, or `claimFees()` called post-graduation on the graduator                                   |
| `ConditionNotMet`       | `graduate()` called but `canGraduate(token)` returns false (price not at tick AND force-graduation delay not elapsed) |
| `GracePeriodNotElapsed` | Non-guardian called `graduate()` after `recordGraduationCondition` but before `gracePeriodDuration` elapsed           |

`FireLaunch` errors relevant to graduation:

| Error                         | When                                                                                   |
| ----------------------------- | -------------------------------------------------------------------------------------- |
| `AlreadyGraduated`            | `recordGraduationCondition` called on a graduated token                                |
| `ConditionAlreadyRecorded`    | `recordGraduationCondition` called twice for the same token                            |
| `ConditionNotMet`             | `recordGraduationCondition` called when the price hasn't crossed the graduation tick   |
| `GraduationRecordingDisabled` | `recordGraduationCondition` called while the registry owner has paused the entry point |

> Note: `OnlyGuardian` is a **`FuelVault`** error (used for guardian-relayed gifts), not a graduation error. The guardian path on `graduate()` is gated by an `if (isGuardian || forceGraduationAvailable)` check rather than a single revert.

## What changes for integrators after graduation

* **Pool address is unchanged** - it's still the same V3 pool. Quotes, swaps, and existing tooling continue to work.
* **NFT ownership** - `NonfungiblePositionManager.ownerOf(positionTokenId) == FireStream`.
* **Fee claim path** - pre-grad fees came from `FireGraduator.claimFees()`; post-grad fees come from `FireStream.claimFees()`. See [**Fees and credits**](/developers/launchpad/fees-and-credits).
* **Vesting** - `FireToken.releaseVested()` becomes meaningful (the schedule unlocks linearly from graduation).
* **FuelVault burn timer** is now ticking. After it elapses, anyone can call `executeBurn(token)` to permanently destroy unclaimed user credits (creator-bucket credits are protected).


# Fee streaming and credits

The Kumbaya launchpad has two parallel earnings streams: **trading fees** (from the V3 pool) and **credits** (from skim + gifts in `FuelVault`). They behave differently before and after graduation.

## Pre-graduation: trading fees

Pre-graduation fees accumulate inside the bonding-curve positions just like any V3 liquidity. They are collected by:

```solidity
function claimFees(address token) external;
```

on **`FireGraduator`**. Anyone can call it. It pokes every active position (burns 0 liquidity to trigger fee accounting) and collects everything.

**Split:** pre-graduation trading fees go **100% to the protocol**; the creator's trading-fee income begins at graduation. (On-chain the contract splits `graduationFeeBps` to the registry's `treasury` and the remainder to its `integrator`, but both resolve to the same Kumbaya protocol address, so it all lands with the protocol.) The `graduationFeeBps` is snapshotted at launch, stored on the `LaunchState`, and immutable for the rest of the bonding phase - read it with **`FireLaunch.getLaunchState(token).graduationFeeBps`**.

> The creator's **trading-fee income begins at graduation** (via the post-graduation stream); pre-graduation trading fees flow to the protocol. Creators also earn throughout from FuelVault credits/gifts.

## At graduation

`graduate()` collects any remaining accumulated fees and splits them with the same snapshotted `graduationFeeBps`. Then ownership of all liquidity transfers to a single full-range NFT held by `FireStream`. From this point on, fee streams shift.

## Post-graduation: trading fees

```solidity
function claimFees(address token) external;
```

on **`FireStream`** - anyone can call. The function reads the current beneficiary list from `FireRegistry.getStreamingRecipients()` (a global, owner-controlled list of up to 10 recipients each with a `bps` share), and splits accumulated fees:

```
total fees collected from the NFT
   ├─ recipient[0]: bps[0] / 10000
   ├─ recipient[1]: bps[1] / 10000
   ├─ ...
   └─ creator: 10000 - sum(bps) / 10000
```

So **the creator receives whatever bps remain after the registry recipients**. If the registry totals to 7000 bps (70%), creators get 30%. The split applies separately to `token0` and `token1`, meaning creators receive both the launched token and the numeraire (e.g. WETH) proportionally.

**Important properties:**

* **Beneficiaries are dynamic, not snapshotted.** They're read from the registry at every `claimFees` call. If governance updates the recipient list, the new split applies immediately to the next claim.
* **The list can be locked permanently.** `FireRegistry.lockStreamingRecipients()` is an irreversible one-shot - after it's called, no future change is possible. Once locked, creators have a guaranteed permanent share of `10000 - totalBps` of all future fees on every graduated token.
* **Max 10 protocol recipients**, each with a `bps` share. Sum of bps cannot exceed 10000. Anything left over goes to the creator.
* **Creator address comes from `FireLaunch.getLaunchState(token).creator`** - set at `ignite` time, never changed by the contracts. (FireStream also pins the creator on its own `tokenInfo[token].creator` field at graduation when `receiveNFT(token, nftId, creator)` is called, so post-grad reads can also come from FireStream.) The "Claim" UI on `kumbaya.xyz` is purely an off-chain metadata association - it does not change the on-chain creator.
* **No per-token override.** All graduated tokens share the same registry recipient list.

## `FuelVault` - credits, gifts, and burn

`FuelVault` is a credit ledger that sits alongside the trading fee path. Two distinct flows feed it.

> 🪙 **Frontend naming note.** `FuelVault` is exposed on `kumbaya.xyz` as the **Tip Jar**. The `gift` / `giftWithSig` calls below are the user-facing **Tip** action. The skim deposit on each buy is shown as a **tip bonus**. Contract names (`FuelVault`, `gift`, `giftWithSig`, `Gifted`, `CreditsBurned`, `creatorBuckets`) haven't changed - only the UI vocabulary has.

### Skim flow - credits go to the *buyer*, not the creator

The `FireToken` ERC-20 has a transfer hook. **On every transfer FROM the pool (i.e. every buy)**, it deducts `skimBps` of the transferred amount and mints that as credits to the **buyer** inside `FuelVault`. Sells, P2P transfers, and post-graduation transfers don't trigger skim (the hook is disabled at graduation; `FireGraduator` also flips a `bypass` flag during fee collection so its own transfers aren't skimmed).

So skim does **not** flow directly to creators. It gives buyers a credit balance they can later gift.

### Gifting - how credits become creator income

```solidity
function giftWithSig(GiftPermit permit, bytes signature) external whenNotPaused;
```

The user signs an EIP-712 `GiftPermit` (`user → creator → token → amount → deadline → nonce`); the **guardian** submits the transaction so the user pays no gas. Internally:

* deducts from `credits[user][token]`
* increments `creatorBuckets[creator][token]`
* if **pre-graduation**: splits into `liquid` and `vested` portions per `registry.fuelVestedBps()`
* if **post-graduation**: 100% goes to `liquid` (no further locking)
* reverts with `SelfGiftNotAllowed` if `user == creator` - buyers can't route their own skim into their own creator bucket

> Direct `gift()` (without a signature) is **not** exposed publicly - all user-initiated gifting is signature-relayed via the guardian. There is also a `deposit(beneficiary, token, amount)` entry point that adds to a beneficiary's `credits` balance, used for protocol-level seeding.

### Two distinct vesting concepts (don't conflate them)

| Mechanism                    | Lives on                                           | When it locks                                         | When it unlocks                                                                                                                  |
| ---------------------------- | -------------------------------------------------- | ----------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| **FuelVault vested bucket**  | `creatorBuckets[creator][token].vested`            | Set on each pre-grad gift, fraction = `fuelVestedBps` | At graduation, on creator's next `withdraw` call (the function moves `vested` → `liquid` and sets `unlocked = true`)             |
| **FireToken linear vesting** | `FireToken.releasableAmount()` / `releaseVested()` | Defined on creation by `creatorAllocationBps`         | Linear from graduation over `vestingDuration` (default frontend value: 90 days). With `creatorAllocationBps = 0` this is a no-op |

The FuelVault "vested" bucket is **binary** (locked until graduation, then fully unlocked) - *not* a linear schedule. Linear vesting only applies to the on-token `FireToken` allocation, which is zero in default frontend launches.

### Withdrawing

```solidity
function withdraw(address token) external nonReentrant whenNotPaused;
```

Called by the creator. Reverts with `InsufficientLiquid` if the liquid bucket is empty. On the first post-graduation withdrawal it also unlocks any pre-grad vested portion (moves it to liquid before paying out).

### `executeBurn`

```solidity
function executeBurn(address token) external;
```

Burns any user credits that were never gifted into a creator bucket - those tokens are transferred to `0xdEaD`. Callable by anyone after the burn countdown elapses (set at graduation: `burnTime[token] = graduation_timestamp + burnCountdownDuration`, configurable 1–90 days). One-shot per token.

**Creator-bucket credits are not affected** - they're protected by the bookkeeping. Only credits that users still hold but never gifted before the deadline are at risk.

## Creator vesting (on the token itself, separate from FuelVault)

The portion of the creator allocation that didn't go into FuelVault sits on the `FireToken` contract under a linear vesting schedule.

> ⚠️ **In the production frontend default, this is zero.** Default launches set `creatorAllocationBps = 0`, which means **no creator vesting schedule is set up**. The vesting machinery below only matters if you call `ignite()` yourself with a non-zero `creatorAllocationBps`.

The schedule:

* **Starts at graduation** (not at launch). Pre-graduation, `vestedAmount() == 0` (because `vestingStart == 0`).
* **No cliff.** Linear from `vestingStart` over `vestingDuration` seconds. The frontend default is **90 days**; the registry-allowed range is `[90 days, 730 days]`.
* **`vestingDuration == 0`** is a special case: everything is fully vested *immediately* at graduation. (Only reachable for launches with `creatorAllocationBps > 0` and `creatorGiftBalanceCaptureBps < 10000` if registry's `requiredVestingDuration` is also 0.)
* **Beneficiary** is the `vestingBeneficiary` set at deployment (= original `msg.sender` of `ignite`).
* **Claim** with `FireToken.releaseVested()`. **Only the beneficiary** can call - reverts with `NotBeneficiary` otherwise. Reverts with `NothingToRelease` if `releasableAmount() == 0`.

```solidity
function vestedAmount() public view returns (uint256);     // total vested-to-date (incl. already released)
function releasableAmount() public view returns (uint256); // vestedAmount - vestingReleased
function releaseVested() external nonReentrant;            // beneficiary-only; transfers releasable
```

## Putting it all together - what a creator earns

This is what a creator's earnings look like for a **default frontend launch** (`creatorAllocationBps = 0`):

**Pre-graduation:**

* FuelVault `creatorBuckets[creator][token]` populated by **buyer-initiated gifts**. Each gift splits into `liquid` (withdrawable now) and `vested` (locked until graduation) per `fuelVestedBps`.
* *No trading-fee share* (those go to the protocol on `FireGraduator.claimFees`).
* *No direct skim income.* Skim credits the buyer, not the creator. Creators only see that value if buyers gift it.

**At graduation:**

* FuelVault burn countdown starts (`burnTime[token] = now + burnCountdownDuration`). Affects only ungifted user credits, not creator buckets.
* Creator's pre-grad vested bucket is *eligible* to unlock - actually unlocks on the creator's first post-grad `withdraw`.
* FireToken `vestingStart` is set. With `creatorAllocationBps = 0`, this is a no-op.

**Post-graduation:**

* Streaming share of trading fees via `FireStream.claimFees()` - creator receives `10000 - sum(registry.getStreamingRecipientsTotalBps())` of all fees, in both token0 and token1. **This is the dominant income stream for a successful launch.**
* New gifts go fully to liquid (no more vesting split).
* Creator can `withdraw` any time; first call after graduation also unlocks any prior vested bucket.

Custom launches with `creatorAllocationBps > 0` add a third stream: **linear FireToken vesting** unlocked via `releaseVested()` over `vestingDuration` from graduation.

## Errors to handle

| Error                                                 | When                                                                                        |
| ----------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| `AlreadyGraduated`                                    | Calling `FireGraduator.claimFees()` after graduation (use `FireStream.claimFees()` instead) |
| `NotGraduated`                                        | Calling FuelVault-graduation-gated functions too early                                      |
| `BurnNotReady` / `AlreadyBurned`                      | `executeBurn` timing                                                                        |
| `InsufficientLiquid`                                  | Withdrawing more from FuelVault than the liquid bucket                                      |
| `NotBeneficiary`                                      | Wrong account collecting `releaseVested()` proceeds                                         |
| `NothingToRelease`                                    | `releaseVested()` called when nothing has vested                                            |
| `InvalidSignature` / `PermitExpired` / `InvalidNonce` | EIP-712 issues on `giftWithSig`                                                             |


# Quickstart

This page gets you from zero to a real swap quote on MegaETH in about a dozen lines of TypeScript.

## What we're building

A script that:

1. Connects to MegaETH mainnet
2. Asks `QuoterV2` for the price of swapping 1 WETH → USDC through the 0.3% pool
3. Prints the result

## Install

```bash
npm install @kumbaya_xyz/sdk-core @kumbaya_xyz/v3-sdk viem
```

You don't strictly need viem - any RPC client works - but the example below uses it because it's the most ergonomic.

## The full script

```ts
import { createPublicClient, http, parseEther } from 'viem'
import { Token, ChainId } from '@kumbaya_xyz/sdk-core'
import { FeeAmount, Pool } from '@kumbaya_xyz/v3-sdk'

// 1. Network + RPC
const MEGAETH_MAINNET = {
  id: 4326,
  rpc: 'https://mainnet.megaeth.com/rpc',
}

// 2. Tokens (replace USDC with the real address from the token list / explorer)
//    Note: `ChainId.MEGAETH` (= 4326). The mainnet enum is just `MEGAETH`.
const WETH = new Token(
  ChainId.MEGAETH,
  '0x4200000000000000000000000000000000000006',
  18,
  'WETH',
)
const USDC = new Token(
  ChainId.MEGAETH,
  '0xYOUR_USDC_ADDRESS_HERE', // get it from default-token-list
  6,
  'USDC',
)

// 3. Compute the pool address (Kumbaya's POOL_INIT_CODE_HASH is wired in already)
const poolAddress = Pool.getAddress(WETH, USDC, FeeAmount.MEDIUM) // 0.3%

// 4. Quote 1 WETH -> USDC via QuoterV2
const QUOTER_V2 = '0x1F1a8dC7E138C34b503Ca080962aC10B75384a27'
const quoterAbi = [{
  type: 'function',
  name: 'quoteExactInputSingle',
  stateMutability: 'nonpayable',
  inputs: [{ type: 'tuple', name: 'params', components: [
    { type: 'address', name: 'tokenIn' },
    { type: 'address', name: 'tokenOut' },
    { type: 'uint256', name: 'amountIn' },
    { type: 'uint24', name: 'fee' },
    { type: 'uint160', name: 'sqrtPriceLimitX96' },
  ]}],
  outputs: [
    { type: 'uint256', name: 'amountOut' },
    { type: 'uint160', name: 'sqrtPriceX96After' },
    { type: 'uint32', name: 'initializedTicksCrossed' },
    { type: 'uint256', name: 'gasEstimate' },
  ],
}] as const

const client = createPublicClient({ transport: http(MEGAETH_MAINNET.rpc) })

const result = await client.simulateContract({
  address: QUOTER_V2,
  abi: quoterAbi,
  functionName: 'quoteExactInputSingle',
  args: [{
    tokenIn: WETH.address as `0x${string}`,
    tokenOut: USDC.address as `0x${string}`,
    amountIn: parseEther('1'),
    fee: FeeAmount.MEDIUM,
    sqrtPriceLimitX96: 0n,
  }],
})

console.log(`Pool address: ${poolAddress}`)
console.log(`1 WETH → ${result.result[0]} USDC (raw, 6 decimals)`)
```

## What just happened

* **`Pool.getAddress`** computed the deterministic pool address using **Kumbaya's** init code hash. If you used `@uniswap/v3-sdk` instead, the address would be wrong on MegaETH.
* **`QuoterV2`** is the standard V3 quoter, deployed at the address shown above. It does a real on-chain simulation and returns the exact output amount.
* No private key or signature involved - quoting is read-only.

## Where to go from here

* [**Executing swaps**](/developers/dex-integration/swapping) - turning this quote into a real on-chain trade
* [**Smart Order Router**](/developers/sdks/smart-order-router) - for multi-hop or multi-pool optimal routes
* [**Exchange API quote endpoint**](/developers/apis/exchange-api/quote) - if you'd rather not run the SOR client-side
* [**Reading pools and positions**](/developers/dex-integration/reading-pools) - querying live pool state directly
* [**Kumbaya Agent Kit**](/developers/building-agents/agent-kit) - building an LLM agent? Skip the SDK glue and use the ready-made MCP tools (`quote`, `swap`, `add_liquidity`, `ignite`, …)

## Don't do this

* **Don't use `@uniswap/v3-sdk` directly** for pool address derivation on MegaETH. It uses Uniswap's init code hash, not Kumbaya's. Use `@kumbaya_xyz/v3-sdk`.
* **Don't reference Uniswap V2 or V4 docs** when integrating Kumbaya. Stick to [V3 protocol docs](https://developers.uniswap.org/docs/protocols/v3/overview) plus this site.


# @kumbaya\_xyz/sdk-core

Core types, chain definitions, and shared primitives used by every other Kumbaya SDK.

```bash
npm install @kumbaya_xyz/sdk-core
```

## What's inside

* `Token`, `CurrencyAmount`, `Price`, `Percent` - basic value types
* `Ether`, `WETH9` - native and wrapped ETH helpers
* MegaETH chain definitions (`ChainId.MEGAETH = 4326`, `ChainId.MEGAETH_TESTNET = 6343`)
* Trade abstractions used by `router-sdk` and `smart-order-router`

## Use it when

* You need a `Token` instance to pass into the V3 SDK or router SDK.
* You need typed amounts, prices, or percentages.
* You're checking which chain you're on.

## Example

```ts
import { Token, ChainId } from '@kumbaya_xyz/sdk-core'

const USDC = new Token(
  ChainId.MEGAETH,
  '0x...USDC_ADDRESS_ON_MEGAETH...',
  6,
  'USDC',
  'USD Coin',
)
```

> *More reference coming soon.* For now, the upstream Uniswap [sdk-core docs](https://docs.uniswap.org/sdk/v3/overview) are 1:1 applicable except for chain IDs and addresses.


# @kumbaya\_xyz/v3-sdk

Concentrated liquidity (V3) primitives - `Pool`, `Position`, `Trade`, tick math, pool address computation.

```bash
npm install @kumbaya_xyz/v3-sdk @kumbaya_xyz/sdk-core
```

## Why this fork exists

The API matches Uniswap's `@uniswap/v3-sdk` 1:1, but this fork ships **chain-aware pool address derivation** for MegaETH. `Pool.getAddress()` and `computePoolAddress()` automatically use Kumbaya's pool init code hash (`0x851d77a4…628a3da7`) when called with `ChainId.MEGAETH` or `ChainId.MEGAETH_TESTNET`.

## Use it when

* Computing pool addresses from a `(tokenA, tokenB, feeTier)` triple
* Building `Pool` and `Position` objects from on-chain reads
* Doing local tick math / price ↔ tick conversions
* Encoding mint/burn calldata via the position manager

## Example: pool address

```ts
import { Pool, FeeAmount } from '@kumbaya_xyz/v3-sdk'
import { Token, ChainId } from '@kumbaya_xyz/sdk-core'

const tokenA = new Token(ChainId.MEGAETH, '0x...', 18, 'A')
const tokenB = new Token(ChainId.MEGAETH, '0x...', 6, 'B')

// Internally calls poolInitCodeHash(ChainId.MEGAETH) which returns the Kumbaya hash.
const poolAddress = Pool.getAddress(tokenA, tokenB, FeeAmount.MEDIUM)
```

## Fee amounts and tick spacing

The fork extends the canonical four V3 fee tiers with three intermediate ones:

```ts
enum FeeAmount {
  LOWEST  = 100,    // 0.01% - tickSpacing 1
  LOW_200 = 200,    // 0.02% - tickSpacing 4   (Kumbaya extension)
  LOW_300 = 300,    // 0.03% - tickSpacing 6   (Kumbaya extension)
  LOW_400 = 400,    // 0.04% - tickSpacing 8   (Kumbaya extension)
  LOW     = 500,    // 0.05% - tickSpacing 10
  MEDIUM  = 3000,   // 0.30% - tickSpacing 60
  HIGH    = 10000,  // 1.00% - tickSpacing 200
}
```

The four canonical tiers (100/500/3000/10000) are the most commonly used. The intermediate tiers exist for fine-grained control on stable-stable or correlated pairs but should be checked for actual on-chain pool existence before relying on them.

## Pool init code hash exports

Both exports exist; **only one returns the right value for MegaETH**:

```ts
import { POOL_INIT_CODE_HASH, poolInitCodeHash } from '@kumbaya_xyz/v3-sdk'
import { ChainId } from '@kumbaya_xyz/sdk-core'

POOL_INIT_CODE_HASH                       // ⚠ deprecated, returns upstream Uniswap hash
poolInitCodeHash(ChainId.MEGAETH)         // ✓ returns Kumbaya hash 0x851d77a4…628a3da7
```

You typically don't need either - `Pool.getAddress()` calls the function internally. See [**Pool Init Code Hash**](/developers/dex-integration/pool-init-code-hash) for context.

## Top-level exports

The package re-exports from these modules: `entities`, `utils`, `constants`, `multicall`, `nonfungiblePositionManager`, `payments`, `quoter`, `selfPermit`, `staker`, `swapRouter`. Pull from the package root:

```ts
import {
  Pool, Position, Trade, Route,
  TickMath, NonfungiblePositionManager,
  computePoolAddress, FeeAmount,
} from '@kumbaya_xyz/v3-sdk'
```

For everything not specific to Kumbaya, the upstream [V3 SDK docs](https://docs.uniswap.org/sdk/v3/overview) apply directly. **Do not** reference V2 or V4 SDK docs.


# @kumbaya\_xyz/router-sdk

Multi-pool route encoding for swaps. Builds the calldata you ultimately send to a router contract.

```bash
npm install @kumbaya_xyz/router-sdk
```

## Use it when

* You have a route (a sequence of pools) and need to turn it into calldata for `SwapRouter02` or `UniversalRouter`.
* You're aggregating routes from `smart-order-router` and need to execute them.

> *More reference coming soon.* See `@kumbaya_xyz/smart-order-router` for finding routes, and `@kumbaya_xyz/universal-router-sdk` for executing through UniversalRouter specifically.


# @kumbaya\_xyz/universal-router-sdk

Encodes calls for the **UniversalRouter** contract - the swiss-army-knife router that supports V2/V3 swaps, Permit2 token approvals, NFT trades, and arbitrary command sequences in a single transaction.

```bash
npm install @kumbaya_xyz/universal-router-sdk
```

## Use it when

* You want to bundle Permit2 approval + swap into one `execute()` call
* You're integrating Permit2 signature flows
* You need swap-and-do-something-else atomic batching

> *More reference coming soon.* For Permit2 specifics, the standard [`@uniswap/permit2-sdk`](https://github.com/Uniswap/permit2/tree/main/sdk) applies - Kumbaya uses the canonical Permit2 deployment at `0x000000000022D473030F116dDEE9F6B43aC78BA3`.


# @kumbaya\_xyz/smart-order-router

Finds the optimal route across Kumbaya's V3 pools for a given swap. Handles multi-hop, splits, and gas-cost-aware route selection.

```bash
npm install @kumbaya_xyz/smart-order-router @kumbaya_xyz/sdk-core ethers@^5
```

> **ethers v5 only.** SOR uses `BaseProvider` from `@ethersproject/providers` v5. Use ethers v5 (`providers.JsonRpcProvider`); v6 and viem providers are not directly compatible.

## Main exports

```ts
import {
  AlphaRouter,           // primary router class
  AlphaRouterParams,
  AlphaRouterConfig,
  // …plus the legacy router and lower-level providers/utilities
} from '@kumbaya_xyz/smart-order-router'
```

`AlphaRouter` is the entry point. It takes `{ chainId: ChainId.MEGAETH | ChainId.MEGAETH_TESTNET, provider: BaseProvider }`, and optionally a long list of provider overrides if you want to plug in your own data sources (subgraph, multicall, gas oracle, etc.).

## Use it when

* You're building a swap UI and need quotes that consider all pools, not just one
* You want the optimal route, not just *a* route
* You're OK with the SOR being heavier than `QuoterV2` - it does substantially more work

For one-shot quotes you don't want to compute client-side, use the hosted [**Exchange API `/api/v1/quote`**](/developers/apis/exchange-api/quote) endpoint instead.

## Quick example

See [**Quoting prices → Option 2**](/developers/dex-integration/quoting#option-2-smart-order-router-client-side) for a runnable example.

The upstream [`@uniswap/smart-order-router` docs](https://github.com/Uniswap/smart-order-router) cover the API surface in more detail - Kumbaya's fork is configured for MegaETH and uses Kumbaya's pool init code hash internally.


# Exchange API

The Exchange API is Kumbaya's hosted backend for trading-side data. It powers the Kumbaya frontend and is also available to integrators.

| Field             | Value                                                            |
| ----------------- | ---------------------------------------------------------------- |
| Base URL          | `https://exchange.kumbaya.xyz`                                   |
| OpenAPI / Swagger | [`exchange.kumbaya.xyz/docs`](https://exchange.kumbaya.xyz/docs) |
| Auth              | Mix of public, partner-keyed, and rate-limited (per route)       |

> **Building an LLM agent?** These endpoints are also available as MCP tools - 29 of them under the `dex_` prefix. See the [MCP Server](/developers/building-agents/agent-kit/mcp), part of the [Kumbaya Agent Kit](/developers/building-agents/agent-kit).

## What's available

| Endpoint group                                   | Purpose                                               |
| ------------------------------------------------ | ----------------------------------------------------- |
| [**Quote**](/developers/apis/exchange-api/quote) | Get swap quotes (single endpoint, multiple modes)     |
| [**Pools**](/developers/apis/exchange-api/pools) | Pool directory, metrics, ticks, admitted-pool lookups |
| [**Stats**](/developers/apis/exchange-api/stats) | Global TVL, volume, fees, timeseries                  |
| Tokens                                           | Trending tokens, history (see Swagger)                |
| Users                                            | Position lookups by address (see Swagger)             |
| Status                                           | Swap execution status by tx hash                      |

## Authentication

See [**Authentication**](/developers/apis/exchange-api/authentication) for the auth model. The short version:

* **Public, no auth** - health, status, pools, tokens, stats, and user-position endpoints
* **Partner key required** - every quote endpoint: `GET /api/v1/quote`, `POST /api/v1/quote`, `POST /api/v1/quote/open`, and `GET /api/v1/quote/tokens`. Without an `x-api-key` they return `401 { "error": "Invalid API key" }`. Request a key from the Kumbaya team.

## Live exploration

The Swagger UI at `/docs` is the source of truth for current parameters and response shapes. These docs link to it for anything we don't re-document here.


# Quote endpoints

The Exchange API exposes four quote endpoints. They all wrap Kumbaya's smart-order-router and **all require a partner API key**. They differ in **which tokens they cover** and the **request/response shape**.

| Endpoint               | Method | Auth                      | Token allowlist | Use when                                            |
| ---------------------- | ------ | ------------------------- | --------------- | --------------------------------------------------- |
| `/api/v1/quote`        | `GET`  | Partner key (`x-api-key`) | Off             | One-shot quote with calldata and full route details |
| `/api/v1/quote`        | `POST` | Partner key (`x-api-key`) | **Enforced**    | Partner integrations, transaction-ready response    |
| `/api/v1/quote/open`   | `POST` | Partner key (`x-api-key`) | Off             | Quotes for any token pair (allowlist off)           |
| `/api/v1/quote/tokens` | `GET`  | Partner key (`x-api-key`) | -               | Returns the partner allowlist                       |

> **All quote endpoints are partner-gated.** Every one requires a valid `x-api-key`; without it they return `401 { "error": "Invalid API key" }`. "Open" refers to the token **allowlist** being off, not authentication. To get a key, see [Authentication](/developers/apis/exchange-api/authentication) or email **<support@kumbaya.xyz>**.

## GET `/api/v1/quote`

Quote with full route details. Returned calldata targets the chosen router. Requires an `x-api-key` header.

### Query parameters

| Param              | Type    | Required                     | Notes                                                        |
| ------------------ | ------- | ---------------------------- | ------------------------------------------------------------ |
| `chainId`          | string  | yes                          | e.g. `4326`                                                  |
| `tokenInAddress`   | address | yes                          |                                                              |
| `tokenInDecimals`  | int     | no, default `18`             |                                                              |
| `tokenOutAddress`  | address | yes                          |                                                              |
| `tokenOutDecimals` | int     | no, default `18`             |                                                              |
| `amount`           | string  | yes                          | Base units of the input token (or output if `type=exactOut`) |
| `type`             | string  | no, default `exactIn`        | `exactIn` or `exactOut`                                      |
| `slippageBps`      | string  | no, default `50`             | basis points (50 = 0.5%)                                     |
| `recipient`        | address | yes                          | Where the swap output should land                            |
| `routerType`       | enum    | no, default `swap-router-02` | `swap-router-02` or `universal`                              |
| `useRouterBalance` | boolean | no, default `false`          | `universal` only - see [POST notes](#post-apiv1quote)        |

### Response (`GetQuoteResponse`)

```jsonc
{
  "quote": "0.000000107555379638",
  "quoteGasAdjusted": "-0.000000087444620362",
  "gasUseEstimate": "195000",
  "gasUseEstimateUSD": "0.000585",
  "methodParameters": {
    "to": "0xE5BbEF8De2DB447a7432A47EBa58924d94eE470e",   // SwapRouter02 or UniversalRouter
    "value": "0x00",
    "calldata": "0x3593564c..."
  },
  "route": { /* full route shape - pools, sub-routes, amounts */ }
}
```

The `methodParameters` block is ready to submit as a transaction.

## POST `/api/v1/quote`

Partner endpoint. Enforces a per-chain token allowlist; an off-list token returns HTTP 400 with `{ "error": "Token not permitted by allowlist", "details": { "addresses": [...] } }`. Pull the live allowlist via `GET /api/v1/quote/tokens?chainId=…`.

### Request body (`QuoteRequest`)

```jsonc
{
  "fromToken":         "0x4200000000000000000000000000000000000006",
  "toToken":           "0x021ee124cF23D302A7f725AE7a01B77A8ce9782B",
  "fromAmount":        "1000000000000000000",          // base units
  "slippage":          0.005,                          // decimal fraction (NOT bps)
  "recipient":         "0xfd3964c84a62692c347235edcf6477bb87de1e9a",
  "routerType":        "swap-router-02",               // or "universal"
  "useRouterBalance":  false                           // universal only
}
```

* **`slippage`** here is a **decimal fraction** (e.g. `0.005` for 0.5%) - not basis points like the GET endpoint.
* `useRouterBalance: true` is for aggregator integrations that pre-funded `UniversalRouter` and want it to swap from its own balance instead of pulling from `msg.sender` via Permit2.

### Header

```
x-api-key: <YOUR_PARTNER_KEY>
```

### Response (`PartnerQuoteResponse`)

```jsonc
{
  "fromAmount":      "1000000000000000000",
  "toAmount":        "107555379638",
  "toAmountMin":     "107017602740",        // post-slippage floor
  "slippage":        0.005,
  "approveToAddress": "0x7E6c4Ada91e432efe5F01FbCb3492Bd3eb7ccD2E",
  "gasEstimation":   "195000",
  "transaction": {
    "to":       "0xE5BbEF8De2DB447a7432A47EBa58924d94eE470e",
    "value":    "0x00",
    "callData": "0x3593564c..."             // ⚠ note camelCase 'D'
  }
}
```

> ⚠️ The transaction object uses **`callData`** (capital D) here, while the GET response uses `calldata` inside `methodParameters`. They're different schemas; don't mix them up.

## POST `/api/v1/quote/open`

Same body and response shape as `POST /api/v1/quote`, and the same `x-api-key` requirement, but with the token allowlist **off** - it quotes any pair and returns `meta.allowlistApplied: false`.

## GET `/api/v1/quote/tokens`

Returns the partner allowlist for a chain.

### Query

| Param     | Required | Notes       |
| --------- | -------- | ----------- |
| `chainId` | yes      | e.g. `4326` |

### Response (`TokensResponse`)

```jsonc
{
  "chainId": 4326,
  "tokens": [
    {
      "address":  "0x4200000000000000000000000000000000000006",
      "symbol":   "WETH",
      "name":     "Wrapped Ether",
      "decimals": 18,
      "logoURI":  "https://assets.kumbaya.xyz/tokens/weth.png"
    }
  ]
}
```

`x-api-key` header required.

## Error codes

All quote endpoints share a `QuoteErrorReason` enum:

| `reason`                          | HTTP | Meaning                                               |
| --------------------------------- | ---- | ----------------------------------------------------- |
| `NO_ROUTE`                        | 404  | No liquidity pools connect the pair                   |
| `INSUFFICIENT_LIQUIDITY`          | 500  | Route exists but insufficient liquidity for this size |
| `NO_POOLS_AVAILABLE`              | 500  | No pools connect the input/output                     |
| `ROUTE_MISSING_METHOD_PARAMETERS` | 500  | Route found but couldn't generate calldata            |
| `INVALID_TOKEN_ADDRESS`           | 400  | Malformed address                                     |
| `TOKEN_NOT_FOUND`                 | 500  | Address isn't a deployed ERC-20                       |
| `TOKEN_DECIMALS_FETCH_FAILED`     | 500  | Couldn't read `decimals()`                            |
| `INVALID_AMOUNT`                  | 400  | Malformed amount                                      |
| `AMOUNT_TOO_SMALL`                | 400  | Below minimum trade threshold                         |
| `CHAIN_NOT_CONFIGURED`            | 400  | Valid chain ID but not enabled on this server         |
| `CHAIN_NOT_SUPPORTED`             | 400  | Unrecognized chain ID                                 |
| `RPC_ERROR`                       | 500  | Network/RPC issue                                     |
| `QUOTE_TIMEOUT`                   | 500  | Request timed out                                     |
| `ROUTE_BUILD_FAILED`              | 500  | Unexpected route-build error                          |

Error response shape:

```jsonc
{
  "error":   "Human-readable message",
  "reason":  "NO_ROUTE",
  "details": { "tokenIn": "0x...", "tokenOut": "0x...", "amountRaw": "..." }
}
```

## Picking GET vs POST

Both require a partner key. The difference is shape and allowlist:

* **GET `/api/v1/quote`** - query params, `slippageBps` (basis points), returns full `route` details plus a `methodParameters` block (`calldata`, lowercase). No allowlist. Good for a one-shot quote where you want the route.
* **POST `/api/v1/quote`** - JSON body, `slippage` as a decimal fraction, enforced allowlist, returns a transaction-ready `transaction` object (`callData`, capital D). The production path for partner integrations.
* **POST `/api/v1/quote/open`** - same as POST but with the allowlist off (any pair).


# Pools endpoints

Reading pool data without running your own indexer. All endpoints are `GET` and **public** (no auth).

## Low-level (paginated, raw)

| Endpoint                     | Purpose                                                         |
| ---------------------------- | --------------------------------------------------------------- |
| `GET /api/v1/pools/list`     | Paginated pool directory with filters (chain, fee tier, tokens) |
| `GET /api/v1/pools/admitted` | Admitted-pool lookups for a trading pair                        |

## UI-curated

| Endpoint                                | Purpose                           |
| --------------------------------------- | --------------------------------- |
| `GET /api/v1/pools`                     | Pool summary with metrics         |
| `GET /api/v1/pools/metrics`             | Aggregate metrics across pools    |
| `GET /api/v1/pools/{poolId}`            | Single pool detail                |
| `GET /api/v1/pools/{poolId}/timeseries` | Historical metrics by hour/day    |
| `GET /api/v1/pools/{poolId}/activity`   | Recent swaps, mints, burns        |
| `GET /api/v1/pools/{poolId}/ticks`      | Tick-level liquidity distribution |
| `GET /api/v1/pools/{poolId}/positions`  | Active LPs (paginated)            |
| `GET /api/v1/pools/{poolId}/swaps`      | Swap history                      |

`poolId` is the indexer's `address-chainId` composite (e.g. `0xabc...-4326`).

## When to use these vs. the indexer directly

* **Use the API** for typical UI needs: a paginated pool list, recent swaps, headline metrics. Cached and rate-limit friendly.
* **Use the** [**Hasura indexer**](/developers/resources/indexer) **directly** for custom analytics, historical aggregations, joins, or anything outside the curated endpoints. The indexer also supports GraphQL subscriptions.

For exact request/response shapes consult the Swagger UI at [**`exchange.kumbaya.xyz/docs`**](https://exchange.kumbaya.xyz/docs).


# Stats endpoints

Global aggregate stats across the Kumbaya DEX. All endpoints are `GET` and **public** (no auth).

| Endpoint                     | Purpose                                                       |
| ---------------------------- | ------------------------------------------------------------- |
| `GET /api/v1/stats/global`   | Global TVL, 24h volume, fees, and timeseries                  |
| `GET /api/v1/indexer/status` | Envio indexer sync progress (height, lag, last block indexed) |

## Token-level stats

Token-level data lives under `/api/v1/tokens/*`:

| Endpoint                               | Purpose                                                           |
| -------------------------------------- | ----------------------------------------------------------------- |
| `GET /api/v1/tokens/trending`          | Trending tokens by recent activity                                |
| `GET /api/v1/tokens/{tokenId}`         | Token overview (name, symbol, price, TVL, launchpad metadata)     |
| `GET /api/v1/tokens/{tokenId}/history` | Price/TVL timeseries (`timeframe` ∈ `days` / `hours` / `minutes`) |
| `GET /api/v1/tokens/bluechip`          | Verified/trusted token list                                       |
| `GET /api/v1/tokens/prices`            | Batch token prices (up to 100 addresses)                          |
| `GET /api/v1/tokens/fire-meta`         | Launchpad-specific metadata for tokens                            |

`tokenId` is the indexer's `address-chainId` composite.

## Other helpers

| Endpoint                                     | Purpose                                                                |
| -------------------------------------------- | ---------------------------------------------------------------------- |
| `GET /api/v1/users/{owner}/positions/active` | Active LP positions for a wallet address                               |
| `GET /api/v1/status?chainId=&txHash=`        | Swap-execution status for a tx hash (`pending` / `success` / `failed`) |
| `GET /api/health`                            | Liveness probe                                                         |

For exact request/response shapes see the Swagger UI at [**`exchange.kumbaya.xyz/docs`**](https://exchange.kumbaya.xyz/docs).


# Authentication

The Exchange API has two auth tiers: public read endpoints, and partner-keyed quote endpoints.

## 1. Public (no auth)

Most read-only endpoints - health, status, pools, tokens, stats, and user positions - work with no credentials. They're rate-limited per IP. Note: the quote endpoints are **not** public; they all require a partner API key (see below).

## 2. Partner API key

All four quote endpoints require a partner API key: `GET /api/v1/quote`, `POST /api/v1/quote`, `POST /api/v1/quote/open`, and `GET /api/v1/quote/tokens`. Request a partner API key from the Kumbaya integrations team.

* **Header:** `x-api-key: <YOUR_KEY>` by default. (The header name is configurable server-side via the `PARTNER_API_KEY_HEADER` env var; production uses the default.)
* Keys are per-partner and rate-limited.
* Failed validation returns `401 { "error": "Invalid API key" }`.
* If no partner keys are configured server-side at all, the partner-key guard becomes a no-op - useful for local dev.

> Don't pass the partner key as `Authorization: Bearer …`. That header is reserved for JWT-based auth on other Kumbaya APIs (e.g. the Client API).

### Allowlist-off quotes

`POST /api/v1/quote/open` uses the same partner API key as the other quote endpoints, but with the token allowlist **disabled** - it quotes any pair. It is not origin-restricted; a valid `x-api-key` is all it needs.

## Getting a key

Email **<support@kumbaya.xyz>** with:

* Your project name and a one-line description
* The endpoints you need
* Expected request volume
* A contact email for revocation/rotation


# Client API

The Client API powers the social side of Kumbaya: user accounts, sessions, comments, fuel/tipping, launches, and the **token claim** flow that links on-chain creators to Kumbaya profiles.

| Field             | Value                                                                                                                |
| ----------------- | -------------------------------------------------------------------------------------------------------------------- |
| Base URL          | `https://clients.kumbaya.xyz`                                                                                        |
| Base path         | `/v1`                                                                                                                |
| OpenAPI / Swagger | [`clients.kumbaya.xyz/docs`](https://clients.kumbaya.xyz/docs)                                                       |
| Auth              | Mostly JWT (Privy-issued) via `Authorization: Bearer` or cookies. Public for some reads. Signed-message for `claim`. |

> **Building an LLM agent?** These endpoints are also available as MCP tools - 76 of them under the `app_` prefix. See the [MCP Server](/developers/building-agents/agent-kit/mcp), part of the [Kumbaya Agent Kit](/developers/building-agents/agent-kit).

## Endpoint groups

### Sessions (`/v1/session/*`)

| Endpoint                             | Auth            | Purpose                                     |
| ------------------------------------ | --------------- | ------------------------------------------- |
| `POST /v1/session/create`            | Privy `idToken` | Create a session from a Privy login         |
| `GET /v1/session/current`            | JWT             | Fetch current session                       |
| `POST /v1/session/refresh`           | JWT             | Rotate token                                |
| `POST /v1/session/logout`            | JWT             | Revoke session                              |
| `POST /v1/session/wallet-state`      | Optional JWT    | Update wallet state metadata                |
| **`GET /v1/session/wallet/nonce`**   | None            | **SIWE: get a one-time nonce for a wallet** |
| **`POST /v1/session/wallet/verify`** | None            | **SIWE: verify signed message, return JWT** |

#### Sign-In With Ethereum (SIWE) - for agents and wallet-only users

The Client API supports **EIP-4361 (Sign-In With Ethereum)** as a first-class auth path alongside Privy. This is the right path for AI agents, bots, and any integration that has its own private key and doesn't want to go through a social login flow.

**1. Request a nonce**

```http
GET /v1/session/wallet/nonce?address=0xYOUR_WALLET
```

Response: `{ "nonce": "abc123…" }`. The nonce is valid for **5 minutes** and is single-use.

**2. Build a SIWE message** (EIP-4361). The parser is minimal - it requires the address and `Nonce: …` fields:

```
kumbaya.xyz wants you to sign in with your Ethereum account:
0xYOUR_WALLET

Sign in to Kumbaya.

URI: https://kumbaya.xyz
Version: 1
Chain ID: 4326
Nonce: <nonce-from-step-1>
Issued At: 2026-01-30T12:00:00Z
```

Sign it with `personal_sign` (most wallets/SDKs do this by default for raw strings).

**3. Verify**

```http
POST /v1/session/wallet/verify
Content-Type: application/json

{
  "message":   "<the SIWE string above, verbatim>",
  "signature": "0x..."
}
```

Response:

```jsonc
{
  "token":     "<jwt>",
  "expiresAt": "2026-02-29T...",
  "user": {
    "id":             "cuid...",
    "walletAddress":  "0xYourWallet",
    "name":           null,
    "image":          null
  }
}
```

The same JWT is also set as an httpOnly cookie. Use either path on subsequent requests.

**Wallet-only user creation.** If the wallet has never logged in before (no Privy user exists with that address), the backend creates a new user record with a synthetic `privyDid: "wallet:0x..."`. This means **agents can self-onboard** - no admin, no Privy account, no email needed. From there the wallet has the same access as any Privy-backed account except for features that need Privy-specific data (which are rare).

### Users (`/v1/users/*`)

| Endpoint                                                         | Auth         | Purpose                  |
| ---------------------------------------------------------------- | ------------ | ------------------------ |
| `GET /v1/users/me`                                               | JWT          | Current user's profile   |
| `GET /v1/users/profile/address/{wallet}`                         | Public       | Public profile by wallet |
| `GET /v1/users/username/check?username=`                         | Public       | Username availability    |
| `PATCH /v1/users/profile`                                        | JWT          | Update name/bio/image    |
| `POST /v1/users/profile/image`                                   | JWT          | Upload avatar            |
| `GET /v1/users/stats` / `activity` / `yaps` / `fuels` / `trades` | Optional JWT | Profile widgets          |
| `PATCH /v1/users/admin/{userId}` and similar                     | Admin JWT    | Moderation               |

### Tokens (`/v1/tokens/*`)

The token endpoints back the launchpad detail page **and** the claim flow.

| Endpoint                                    | Auth                        | Purpose                                               |
| ------------------------------------------- | --------------------------- | ----------------------------------------------------- |
| `GET /v1/tokens/{mintAddress}`              | Public                      | Token detail (creator, holders, metadata)             |
| `GET /v1/tokens/{mintAddress}/buyers`       | Public                      | Holder leaderboard                                    |
| `GET /v1/tokens/{mintAddress}/buyers/chart` | Public                      | Buyer distribution by time                            |
| `GET /v1/tokens/{mintAddress}/fuels`        | Public                      | Fuel tip history                                      |
| `GET /v1/tokens/{mintAddress}/position`     | JWT                         | Caller's position                                     |
| `POST /v1/tokens/batch/images`              | Public                      | Batch fetch icons                                     |
| `POST /v1/tokens/{mintAddress}/report`      | JWT                         | Report a token                                        |
| **`POST /v1/tokens/{mintAddress}/claim`**   | **JWT + EIP-712 signature** | **Claim an unclaimed token (see below)**              |
| `POST /v1/tokens/{mintAddress}/claim/image` | JWT                         | Upload claim image (separate from the metadata claim) |

#### Token claim - EIP-712 signature

The user must be **authenticated** (JWT) *and* present an **EIP-712 signature** that recovers to the on-chain `creator` of the token. The signature is verified server-side; only on success does the backend create the `TokenMetadata` record linking the token to the authenticated user.

**Request body** (`ClaimTokenInputSchema`):

```jsonc
{
  "chainId":     4326,
  "description": "What this token is about (1–500 chars)",
  "category":    "MEMES",                 // 'MEMES' | 'DARES'
  "signature":   "0x...",                  // EIP-712 signature
  "signedAt":    1740000000,               // Unix timestamp the user signed at
  "nonce":       "random-string",          // Unique per signing session
  "website":     "yourcoin.com",           // optional
  "xHandle":     "yourcoin",               // optional, ≤15 chars
  "telegramUrl": "t.me/yourcoin"           // optional
}
```

`name`, `symbol`, and the image are **not** in this body. Name and symbol are read from the on-chain token; the image is uploaded via the separate `POST /v1/tokens/{mintAddress}/claim/image` endpoint after the metadata claim succeeds.

**EIP-712 typed data** the user signs:

```ts
domain = {
  name:    'Kumbaya Token Claim',
  version: '1',
  chainId,                         // numeric chain id
}
types = {
  ClaimListing: [
    { name: 'mintAddress', type: 'address' },
    { name: 'chainId',     type: 'uint256' },
    { name: 'timestamp',   type: 'uint256' },   // matches `signedAt` in the body
    { name: 'nonce',       type: 'string'  },
  ],
}
primaryType = 'ClaimListing'
message = { mintAddress, chainId, timestamp: signedAt, nonce }
```

The signature must be produced within the **last hour** - `signedAt` older than 3600 seconds rejects with `SIGNATURE_EXPIRED`.

**Error codes** (from `ClaimErrorCodes`):

| Code                | When                                                      |
| ------------------- | --------------------------------------------------------- |
| `UNAUTHORIZED`      | No valid session                                          |
| `TOKEN_NOT_FOUND`   | No on-chain token at this address                         |
| `ALREADY_CLAIMED`   | Listing already has a `creatorId`                         |
| `TOKEN_DELETED`     | Token was removed by an admin                             |
| `INVALID_SIGNATURE` | Signature doesn't validate against the EIP-712 typed data |
| `NOT_CREATOR`       | Recovered signer ≠ on-chain `creator`                     |
| `SIGNATURE_EXPIRED` | `signedAt` older than 1h                                  |
| `IMAGE_REQUIRED`    | (image endpoint) no image provided                        |

For the user-facing version of this flow see [**client docs › Unclaimed tokens**](https://github.com/Kumbaya-xyz/documentation/tree/main/client/launchpad/unclaimed-tokens.md).

### Launches (`/v1/launch/*`)

Used by the frontend to track launch state across drafts → on-chain deployment. The actual `FireLaunch.ignite()` transaction is signed and sent from the user's wallet; the Client API tracks the metadata flow around it.

| Endpoint                      | Auth | Purpose                                                                                      |
| ----------------------------- | ---- | -------------------------------------------------------------------------------------------- |
| `POST /v1/launch`             | JWT  | Create a draft launch (body: `name`, `symbol`, `description?`, `category`, `chainId`)        |
| `GET /v1/launch/pending`      | JWT  | Caller's launches not yet `COMPLETED` or `FAILED`                                            |
| `GET /v1/launch/{id}`         | JWT  | Launch detail                                                                                |
| `POST /v1/launch/{id}/image`  | JWT  | Upload launch image (transitions `DRAFT` → `IMAGE_UPLOADED`)                                 |
| `POST /v1/launch/{id}/submit` | JWT  | Submit on-chain `tokenAddress` for verification (transitions `IMAGE_UPLOADED` → `COMPLETED`) |
| `POST /v1/launch/{id}/fail`   | JWT  | Mark a launch `FAILED`                                                                       |
| `DELETE /v1/launch/{id}`      | JWT  | Delete a draft                                                                               |

#### Status state machine

```
DRAFT ──────► IMAGE_UPLOADED ──────► COMPLETED
                    │                    
                    ▼                    
                 FAILED   (or via /fail)
```

`TokenLaunchStatus = 'DRAFT' | 'IMAGE_UPLOADED' | 'COMPLETED' | 'FAILED'`.

#### Create launch body (`CreateLaunchInputSchema`)

```jsonc
{
  "name":        "My Token",                // 1–32 chars
  "symbol":      "MTK",                     // 1–10 chars, uppercase alphanumeric only
  "description": "A short prompt...",       // 8–500 chars, optional
  "category":    "MEMES",                    // 'MEMES' | 'DARES', default MEMES
  "chainId":     4326
}
```

A user can have at most one in-flight launch per `(chainId, symbol)` pair - duplicates return `400 { code: 'DUPLICATE_LAUNCH' }`.

#### Submit launch body (`SubmitLaunchInputSchema`)

```jsonc
{ "tokenAddress": "0x..." }   // the on-chain FireToken address from FireLaunch.ignite() receipt
```

The backend verifies that the authenticated user matches the on-chain `creator` for the supplied address before creating a `TokenMetadata` record. The launch must be in `IMAGE_UPLOADED` status, and the corresponding token must not already have a `TokenMetadata` record.

### Comments (`/v1/comments/*`)

Standard CRUD for token comment threads (Yaps).

| Endpoint                                                  | Auth   | Purpose                            |
| --------------------------------------------------------- | ------ | ---------------------------------- |
| `GET /v1/comments/tokens/{mintAddress}`                   | Public | Comments on a token (paginated)    |
| `GET /v1/comments/{id}`                                   | Public | Single comment + replies           |
| `GET /v1/comments/tokens/{mintAddress}/post/{postNumber}` | Public | Comment by 4chan-style post number |
| `POST /v1/comments`                                       | JWT    | Create comment                     |
| `PUT /v1/comments/{id}`                                   | JWT    | Edit own comment                   |
| `DELETE /v1/comments/{id}`                                | JWT    | Delete own comment                 |
| `POST /v1/comments/{id}/report`                           | JWT    | Report comment                     |

### Engagement (`/v1/likes`, `/v1/dislike`, `/v1/favorites`)

`POST` toggles for likes/dislikes/favorites on posts and comments.

### Fuel (`/v1/fuel/*`)

| Endpoint                | Auth | Purpose               |
| ----------------------- | ---- | --------------------- |
| `GET /v1/fuel/credits`  | JWT  | Caller's fuel balance |
| `GET /v1/fuel/received` | JWT  | Inbound transactions  |
| `GET /v1/fuel/given`    | JWT  | Outbound transactions |

### Gifts (`/v1/gifts/*`)

| Endpoint                     | Auth  | Purpose                    |
| ---------------------------- | ----- | -------------------------- |
| `GET /v1/gifts/status`       | JWT   | Caller's gift cycle status |
| `GET /v1/gifts/prepare`      | JWT   | Prepare gift claim         |
| `POST /v1/gifts`             | JWT   | Claim gift                 |
| `POST /v1/gifts/reset-cycle` | Admin | Reset gift cycle           |

### Notifications & push (`/v1/notifications/*`, `/v1/push/*`)

Standard endpoints for notification list, unread count, mark-read, and Web Push subscription registration. `GET /v1/push/vapid-key` returns the public VAPID key for browser registration.

### Feed (`/v1/feed/*`)

| Endpoint                   | Auth         | Purpose                                                                          |
| -------------------------- | ------------ | -------------------------------------------------------------------------------- |
| `GET /v1/feed`             | Optional JWT | Social feed (`type=ALL\|LAUNCHES\|YAPS\|FUELS`), personalized when authenticated |
| `GET /v1/feed/content`     | None         | Content feed (yaps and shills)                                                   |
| `GET /v1/feed/dares/viral` | None         | Trending dare tokens                                                             |
| `GET /v1/feed/sidebar`     | None         | Sidebar widgets                                                                  |

### Content & discovery (`/v1/content/*`, `/v1/competition`, `/v1/badges`, `/v1/shares`)

| Endpoint                                                      | Auth | Purpose                         |
| ------------------------------------------------------------- | ---- | ------------------------------- |
| `GET /v1/content/yaps` / `shills` / `tips` / `landing-ticker` | None | Public content streams          |
| `GET /v1/competition/stats`                                   | None | Competition leaderboards        |
| `GET /v1/badges` / `GET /v1/badges/{badgeId}`                 | None | Badge catalog                   |
| `POST /v1/shares`                                             | JWT  | Create a share link for a token |

### Misc

* `GET /healthz` - liveness
* `GET /v1/ip` - return caller IP
* `POST /v1/x-auth/store-tokens` - store X OAuth tokens

## Auth model

* **JWT** is issued by `POST /v1/session/create` (validating a Privy idToken or wallet signature) and stored as an httpOnly cookie. Subsequent requests use the cookie or `Authorization: Bearer <jwt>`.
* **Signed-message auth** for `POST /v1/tokens/{mintAddress}/claim` is independent of the session - the request must include a wallet-signed payload that recovers to the on-chain creator address.
* **Admin endpoints** require `isAdmin: true` on the authenticated user and otherwise behave like any other JWT-protected route.

For complete request/response shapes consult the OpenAPI Swagger UI at [`clients.kumbaya.xyz/docs`](https://clients.kumbaya.xyz/docs).


# Search Service

Public, no-auth full-text search for Kumbaya tokens and pools. Backed by an in-memory index (MiniSearch) that's continuously synced.

| Field             | Value                                                        |
| ----------------- | ------------------------------------------------------------ |
| Base URL          | `https://search.kumbaya.xyz`                                 |
| Base path         | `/api/v1`                                                    |
| OpenAPI / Swagger | [`search.kumbaya.xyz/docs`](https://search.kumbaya.xyz/docs) |
| Auth              | None. Rate-limited per IP.                                   |

> **Building an LLM agent?** These endpoints are also available as MCP tools - 8 of them under the `search_` prefix. See the [MCP Server](/developers/building-agents/agent-kit/mcp), part of the [Kumbaya Agent Kit](/developers/building-agents/agent-kit).

## Endpoints

### Health

* `GET /health` - `{ status, indexed, tokenCount, poolCount }`
* `GET /ready` - readiness probe (`200` if synced, `503` until then)

### Search (`/api/v1/search*`)

#### `GET /api/v1/search` - combined token + pool search

Query parameters:

| Param                        | Type                         | Notes                                                          |
| ---------------------------- | ---------------------------- | -------------------------------------------------------------- |
| `q`                          | string (1–256)               | The search query - fuzzy matched on symbol, name, and address. |
| `chainId`                    | int                          | `4326` mainnet, `6343` testnet                                 |
| `limit`                      | int (1–100, default 20)      | Max results                                                    |
| `isBluechip`                 | bool                         | Filter to blue-chip tokens/pools                               |
| `isTrending`                 | bool                         | Filter to trending                                             |
| `isFireToken` / `isFirePool` | bool                         | Filter to launchpad-originated tokens / their pools            |
| `isVerified`                 | bool                         | Filter to verified                                             |
| `visibility`                 | `verified \| trusted \| all` | Visibility level                                               |

Response: `{ tokens: Token[], pools: Pool[], query, chainId }`.

#### `GET /api/v1/search/tokens` - tokens only

Same query params; returns `{ tokens, query, chainId }`.

#### `GET /api/v1/search/pools` - pools only

Same query params; returns `{ pools, query, chainId }`.

#### `GET /api/v1/autocomplete` - fast prefix completion

For typeahead UIs. Returns either `null` or a single best `suggestion`:

```jsonc
{
  "query": "kum",
  "suggestion": {
    "completion": "Kumbaya",
    "symbol": "KUM",
    "name": "Kumbaya Token",
    "address": "0x…",
    "matchType": "name"        // or "symbol"
  }
}
```

Stricter rate limits than the main search endpoints.

### Index introspection

* `GET /api/v1/search/stats` - `{ tokenCount, poolCount, lastSyncTime, syncInProgress }`
* `GET /api/v1/tokens` - list all indexed tokens with the same filter params (no `q` required)

## Token / pool fields

Tokens include: `id, address, symbol, name, chainId, decimals, totalValueLockedUSD, volumeUSD, isBluechip, isTrending, isFireToken, isVerified, fireCreator, fireGraduated, imageUrl`.

Pools include: `id, address, chainId, feeTier, token0 { symbol, name, address }, token1 { … }, totalValueLockedUSD, volumeUSD, isBluechip, isFirePool, isVerified`.

## When to use this vs. the indexer

* **Search service** - typeahead, full-text matching, filtering for "show me launchpad tokens that are trending" style queries. Optimized for browser-grade latency.
* [**Hasura indexer**](/developers/resources/indexer) - anything analytical, time-series, or filtered on numeric ranges. The search index doesn't do range queries on TVL or time-bounded counts.
* [**Exchange API**](/developers/apis/exchange-api) - when you need quote pricing or detailed pool state, not search.

For complete schemas see Swagger at [`search.kumbaya.xyz/docs`](https://search.kumbaya.xyz/docs).


# Integrator Kit

The [**`integrator-kit`**](https://github.com/Kumbaya-xyz/integrator-kit) repo is the operational source of truth for integrators: addresses, ABIs, and Foundry tests that exercise the live deployments on MegaETH.

## What's in it

| Path             | Contents                                                                                                                                                                                                                                                                              |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `addresses/`     | One JSON file per chain (`megaETH-mainnet.json`, `megaETH-testnet.json`) with all deployed addresses                                                                                                                                                                                  |
| `abis/`          | 13 contract ABIs: ERC-20, V3 Factory & Pool, NonfungiblePositionManager, Permit2, QuoterV2, SwapRouter02, UniversalRouter, TickLens, etc.                                                                                                                                             |
| `scripts/`       | `fetch-abis.ts` (regenerate ABIs from artifacts) and `compare-bytecode.ts` (validate against Uniswap V3)                                                                                                                                                                              |
| `tests/megaETH/` | Foundry Solidity integration tests that hit the live MegaETH deployments - useful as reference for quoting, swapping, pool address derivation, WETH wrapping                                                                                                                          |
| `audits/`        | Signed audit reports - currently the [BlockSec audit of the Kumbaya launchpad contracts](https://github.com/Kumbaya-xyz/integrator-kit/blob/main/audits/blocksec_Kumbaya-xyz_Fire_v1.0-signed.pdf). See [**Audit & security**](/developers/audit-and-security) for the full breakdown |
| `README.md`      | Full integration guide with installation and example commands                                                                                                                                                                                                                         |

## When to reach for it

* **Confirming a contract address** - `addresses/megaETH-mainnet.json` is canonical
* **Pulling an ABI for off-chain encoding** - copy from `abis/`
* **Sanity-checking your integration** - clone the repo, run the Foundry tests, compare against your own behavior
* **Verifying our V3 fork is bytecode-equivalent to upstream** (where it should be) - `compare-bytecode.ts`

## Launchpad addresses

Note: integrator-kit currently holds **DEX addresses only**. The Kumbaya launchpad (`Fire*`) contract addresses are listed in the consolidated table in [**Contract Addresses**](/developers/networks-and-contracts/contract-addresses).

> The `fire/addresses/megaETH-mainnet.json` file in the [`fire`](https://github.com/Kumbaya-xyz/fire) repo is an **initial-deployment snapshot** - addresses are still accurate, but other fields like `paused` and `requiredVestingDuration` may have been changed by governance since deploy. For live config values, read the registry on-chain (see [Live registry config](/developers/building-agents/registry-config)).


# ABIs

ABIs for every DEX-side contract you might call live in [**`integrator-kit/abis/`**](https://github.com/Kumbaya-xyz/integrator-kit/tree/main/abis) (13 files):

| Contract                   | File                              |
| -------------------------- | --------------------------------- |
| ERC-20                     | `ERC20.json`                      |
| Uniswap V3 Factory         | `UniswapV3Factory.json`           |
| Uniswap V3 Pool            | `UniswapV3Pool.json`              |
| NonfungiblePositionManager | `NonfungiblePositionManager.json` |
| SwapRouter02               | `SwapRouter02.json`               |
| UniversalRouter            | `UniversalRouter.json`            |
| QuoterV2                   | `QuoterV2.json`                   |
| TickLens                   | `TickLens.json`                   |
| Permit2                    | `Permit2.json`                    |
| Multicall                  | `Multicall.json`                  |
| Multicall2                 | `Multicall2.json`                 |
| V3 Migrator                | `V3Migrator.json`                 |
| UniswapV3Staker            | `UniswapV3Staker.json`            |

For launchpad contract ABIs (the `Fire*` contracts), build the [`fire`](https://github.com/Kumbaya-xyz/fire) repo with `forge build` - the artifacts land in `fire/out/`.

## Regenerating

The integrator-kit has a script that regenerates ABIs from upstream artifacts:

```bash
cd integrator-kit
pnpm fetch-abis
```

The same repo also has `scripts/compare-bytecode.ts` for verifying that the deployed contracts match the upstream Uniswap V3 bytecode (where they should match).


# Default Token List

Kumbaya publishes a curated token list at [**`@kumbaya_xyz/default-token-list`**](https://www.npmjs.com/package/@kumbaya_xyz/default-token-list) (also at [github.com/Kumbaya-xyz/default-token-list](https://github.com/Kumbaya-xyz/default-token-list)).

## What it is

A standard [Uniswap-format token list](https://tokenlists.org/) - JSON describing every curated token across MegaETH mainnet and testnet, with addresses, decimals, symbol, name, logo URI, and chain ID.

## Use it when

* Building a token picker that shouldn't show everything ever deployed
* Validating that a token symbol matches a known address
* Bootstrapping a wallet with sensible defaults

## Install

```bash
npm install @kumbaya_xyz/default-token-list
```

```ts
import tokenList from '@kumbaya_xyz/default-token-list'
const megaethTokens = tokenList.tokens.filter(t => t.chainId === 4326)
```

## Adding a token

Open a PR against the [default-token-list repo](https://github.com/Kumbaya-xyz/default-token-list) with the token entry and follow the contribution guidelines in its README.


# Hasura Indexer

Kumbaya runs a public **Hasura GraphQL** endpoint - the layer you query - on top of an **Envio** indexer that ingests every Uniswap V3 event *and* every Kumbaya launchpad event on MegaETH and publishes it as queryable data.

| Field        | Value                                               |
| ------------ | --------------------------------------------------- |
| **Endpoint** | `https://ql.kumbaya.xyz/v1/graphql`                 |
| **Auth**     | None - public, read-only                            |
| **Networks** | MegaETH mainnet (chain `4326`) and testnet (`6343`) |

> The indexer's source code is not public. You consume the GraphQL API directly. Schema introspection is enabled - point your IDE/codegen tools at the URL above to explore.

## Entity catalogue

### Core (V3) entities

| Entity                               | What it is                                                                                                                                                                                                               |
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `Factory`                            | Per-chain factory state (pool count, total volume)                                                                                                                                                                       |
| `Bundle`                             | Per-chain ETH/USD price bundle (used to derive USD prices)                                                                                                                                                               |
| `Token`                              | Current token state - `priceUSD`, `lastSwapTimestamp`, `totalValueLockedUSD`, `volumeUSD`, `derivedETH`                                                                                                                  |
| `Pool`                               | Pool state. Includes `firePool` (boolean) and `fireToken` (relationship) for launchpad-originated pools, and `poisonedPool` (boolean) for invalid launchpad configs. Also `feeProtocol0` and `feeProtocol1` (0 or 2..10) |
| `Tick`                               | Per-tick liquidity bookkeeping                                                                                                                                                                                           |
| `RawPosition`                        | Pool-level liquidity position                                                                                                                                                                                            |
| `UserPosition`                       | NFT-level LP position (NonfungiblePositionManager-owned)                                                                                                                                                                 |
| `Transaction`                        | Per-tx envelope for related events                                                                                                                                                                                       |
| `Mint` / `Burn` / `Swap` / `Collect` | Per-event records                                                                                                                                                                                                        |

### Time-series aggregates

| Entity                                                                             | Resolution      | Purpose                     |
| ---------------------------------------------------------------------------------- | --------------- | --------------------------- |
| `TokenMinuteData`                                                                  | 1m              | Token price/volume snapshot |
| `TokenHourData`                                                                    | 1h              | Token snapshot              |
| `TokenDayData`                                                                     | 1d              | Token snapshot              |
| `PoolHourData` / `PoolDayData` / `PoolWeekData` / `PoolMonthData` / `PoolYearData` | Hourly → yearly | Pool aggregates             |
| `UniswapHourData` / `UniswapDayData`                                               | Hourly / daily  | Chain-wide totals           |

### Launchpad-specific entities

| Entity                                                       | What it is                                                                                                                             |
| ------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
| `FireToken`                                                  | Per-launch state - see fields below. Includes graduation progress, market cap, fees collected, time-windowed buy/sell counts, velocity |
| `FirePosition`                                               | Per-position record inside a bonding-curve pool                                                                                        |
| `FireFeeClaim`                                               | Pre-grad fee-claim events (`FireGraduator.claimFees`)                                                                                  |
| `FireSkim`                                                   | Per-buy skim events (`FireToken.Skimmed`)                                                                                              |
| `FireVestingRelease`                                         | On-token vesting unlocks (`FireToken.releaseVested`)                                                                                   |
| `FuelDeposit` / `FuelGift` / `FuelWithdrawal` / `FuelBurn`   | Lifecycle of credits in `FuelVault`                                                                                                    |
| `FuelCreatorBucket`                                          | Current per-creator-per-token bucket state (liquid + vested + unlocked flag)                                                           |
| `FireStreamNFT`                                              | The graduated-NFT custody record                                                                                                       |
| `FireStreamFeeDistribution` / `FireStreamBeneficiaryPayment` | Post-grad fee distributions and per-recipient payouts                                                                                  |
| `FireUser` / `FireUserParticipation`                         | Per-user / per-user-per-token aggregates (tokens created, pools participated, total volume, realized PnL)                              |
| `VestedUnlock`                                               | When a creator's pre-grad vested gifts unlocked at graduation                                                                          |
| `FireMigration`                                              | Position migration events                                                                                                              |

### Notable `FireToken` fields

```graphql
type FireToken {
  address: Bytes
  creator: Bytes                # the on-chain ignite() caller
  pool:    Pool                 # nested relationship
  totalSupply: BigInt
  skimBps: Int
  tickLower: Int
  tickUpper: Int
  isToken0: Boolean

  # Graduation
  graduated: Boolean
  readyToGraduate: Boolean       # tick crossed boundary, condition not yet recorded
  graduatedAt: BigInt
  graduationConditionRecordedAt: BigInt
  graduationProgress: Int        # 0..100
  graduationTargetNumeraire: BigDecimal
  totalNumeraireCollected: BigDecimal
  fuelBurnTime: BigInt           # when unclaimed user credits become burnable
  fuelBurned: Boolean

  # Metrics
  volumeUSD: BigDecimal
  marketCapUSD: BigDecimal
  feesCollectedUSD: BigDecimal
  lastSwapAt: BigInt

  # Time-windowed activity
  buysLast30m: Int
  buysLast1h: Int
  sellsLast30m: Int
  sellsLast1h: Int
  buyVelocity: BigDecimal        # ratio of buys this hour vs last
  sellVelocity: BigDecimal
  tokensBoughtLast1h: BigDecimal
  tokensSoldLast1h: BigDecimal
  netTokenFlowLast1h: BigDecimal
  buyToSellRatio1h: BigDecimal
  buyerCount: Int
}
```

The time-windowed counters are exactly what powers the launchpad feed's "trending" sort and "heating up" sections.

## ID conventions

Most entities use **`address-chainId`** composite IDs, e.g. `0xabc...-4326`. Use that format anywhere you reference an entity by id.

## Examples

### Latest token swaps

```graphql
query LatestSwaps($poolId: String!, $limit: Int!) {
  Swap(
    where: { pool_id: { _eq: $poolId } }
    order_by: { timestamp: desc }
    limit: $limit
  ) {
    id timestamp sender recipient
    amount0 amount1 amountUSD
    sqrtPriceX96 tick
    transaction { id }
  }
}
```

### 24h price change

```graphql
query TokensWithHistory($chainId: Int!, $targetPeriodStart: Int!, $minTvl: String!) {
  TokenHourData(
    where: {
      chainId: { _eq: $chainId }
      periodStartUnix: { _eq: $targetPeriodStart }
      token: { totalValueLockedUSD: { _gt: $minTvl } }
    }
  ) {
    priceUSD
    token { address symbol priceUSD lastSwapTimestamp }
  }
}
```

`targetPeriodStart` = `Math.floor((now - 86400) / 3600) * 3600`.

### Newest launches

```graphql
query NewLaunches($chainId: Int!, $since: BigInt!, $limit: Int!) {
  FireToken(
    where: {
      chainId:   { _eq: $chainId }
      createdAt: { _gte: $since }
    }
    order_by: { createdAt: desc }
    limit: $limit
  ) {
    address creator createdAt graduationProgress marketCapUSD
    pool { id firePool poisonedPool }
  }
}
```

### "Heating up" - recent activity buckets

```graphql
query HeatingUp($chainId: Int!) {
  FireToken(
    where: {
      chainId:    { _eq: $chainId }
      graduated:  { _eq: false }
      buysLast1h: { _gte: 5 }
    }
    order_by: { buyVelocity: desc }
    limit: 20
  ) {
    address creator graduationProgress marketCapUSD
    buysLast1h buysPrev1h buyVelocity
    sellsLast1h sellVelocity
  }
}
```

## Calling from JavaScript

Plain `fetch` works:

```ts
const res = await fetch('https://ql.kumbaya.xyz/v1/graphql', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    query: `query { Token(where: { chainId: { _eq: 4326 } }, limit: 5) { address symbol priceUSD } }`,
  }),
})
const { data } = await res.json()
```

Codegen-friendly SDKs (`graphql-request`, `urql`, `apollo`) work without modification.

## Live subscriptions

Hasura supports GraphQL **subscriptions** over WebSocket. Useful for agents watching new launches, swaps, or graduation events:

```graphql
subscription OnNewSwap($poolId: String!) {
  Swap(
    where: { pool_id: { _eq: $poolId } }
    order_by: { timestamp: desc }
    limit: 1
  ) {
    id timestamp amount0 amount1 amountUSD
  }
}
```

## When to use the indexer vs. the Exchange API

| Use the **indexer** for                             | Use the [**Exchange API**](/developers/apis/exchange-api) for          |
| --------------------------------------------------- | ---------------------------------------------------------------------- |
| Custom analytics, joins, aggregations               | Curated UI data: pool list, trending tokens, headline stats            |
| Historical time-series at minute or hour resolution | Quote pricing for a swap                                               |
| Live subscriptions on raw events                    | Anything with caching / rate-limit-friendly defaults                   |
| Anything outside the curated REST endpoints         | The hosted `/api/v1/quote` endpoint when you don't want to run the SOR |

## Notes

* Hasura's nested `where` clauses become SQL JOINs - feel free to filter on nested fields.
* `*_aggregate` queries are available for `count`, `sum`, `avg`, etc.
* Pagination: prefer cursor-style on `timestamp` or `id` rather than large `offset` values.
* The `Pool.poisonedPool` flag marks launchpad-originated pools that were launched with non-canonical params and aren't price-tracked. Filter by `poisonedPool: { _eq: false }` if you only want healthy pools.


