> For the complete documentation index, see [llms.txt](https://docs.kumbaya.xyz/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.kumbaya.xyz/developers/launchpad/fees-and-credits.md).

# 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`                                                             |
