# fees post bond

After a token graduates, all swaps move to Uniswap V2. The fee model adapts.

## V1 vs V2 token (huge difference)

|                    | FactoryV2 tokens (V1)                       | FactoryV3 tokens (V2) — **default** |
| ------------------ | ------------------------------------------- | ----------------------------------- |
| Post-grad swap fee | **0%** to basefun (only 0.3% Uni V2 LP fee) | **1% to basefun** (+ 0.3% LP)       |
| Fee asset          | n/a                                         | Token (skimmed via FOT)             |
| Distribution       | n/a                                         | Keeper sweeps to USDC, splits 50/50 |

**All current launches use FactoryV3**, so the rest of this page assumes V2 tokens.

## On-chain skim (the easy part)

`BasefunTokenV2._update` runs on every transfer. For any transfer touching a registered pair, between non-exempt addresses:

```solidity
uint256 fee = (value * FEE_BPS) / 10_000;   // 1%
super._update(from, feeCollector, fee);     // skim 1%
value -= fee;                                // remaining 99%
super._update(from, to, value);
```

`feeCollector` is the **keeper EOA**, immutable, set at deploy.

So a buy and a sell both generate **two** Transfer events:

```
buy:   pair → user   (99%)    +    pair → keeper (1%)
sell:  user → pair   (99%)    +    user → keeper (1%)
```

Net effect: the keeper's token balance ticks up by exactly 1% of every swap's gross.

## Off-chain dispatch (the keeper part)

{% stepper %}
{% step %}

### Keeper tick 1

```
keeperBal = token.balanceOf(keeper)
if keeperBal == 0: skip
```

{% endstep %}

{% step %}

### Resolve the pair

```
resolve uniV2Pair (cached after first hit)
```

{% endstep %}

{% step %}

### Resolve recipients

```
resolve (treasury, creator) by reading token.treasury() / token.creatorFeeRecipient()
(immutable, cached forever per process)
```

{% endstep %}

{% step %}

### Quote USDC value

```
estUSDC = router.getAmountsOut(keeperBal, [token, USDC])
if estUSDC < $5: skip (dust gate, avoid burning gas on micro-balances)
```

{% endstep %}

{% step %}

### Approve once

```
ensure token.approve(router, MAX)  ← one-time
```

{% endstep %}

{% step %}

### Record balance before swap

```
usdcBefore = USDC.balanceOf(keeper)
```

{% endstep %}

{% step %}

### Swap to USDC

```
router.swapExactTokensForTokensSupportingFeeOnTransferTokens(
   keeperBal,
   minOut (3% slippage of estUSDC),
   [token, USDC],
   keeper,                  // proceeds → keeper, we dispatch below
   now + 5 min              // deadline
)
```

{% endstep %}

{% step %}

### Measure received USDC

```
received = USDC.balanceOf(keeper) - usdcBefore
if received <= 0: log & skip
```

{% endstep %}

{% step %}

### Split and transfer

```
USDC.transfer(treasury, received / 2)
USDC.transfer(creator,  received - received / 2)
```

{% endstep %}
{% endstepper %}

### Why this can't pay the wrong wallet

`treasury` and `creator` are read **from each token's bytecode**, per sweep. The keeper has a per-token in-memory cache (cleared on process restart) and one DB-driven loop that iterates tokens by `address`. Two different tokens never share the same `recipientCache` entry. The transfer destinations come from the immutables that were burned into the token at deploy.

The post-swap accounting also uses `usdcBefore`/`usdcAfter` measured on the keeper itself, so even if the keeper has unrelated USDC sitting around from other LT redemptions, the dispatch only routes the **delta** caused by this swap.

### Dust gate ($5)

Sweeping a $0.50 balance would burn $0.20 in gas. The keeper requires a minimum estimated USDC out of **$5** before swapping. Balances under that accumulate across ticks until they cross the threshold or the keeper is poked manually.

### Slippage (3%)

The minOut is `estUSDC × 97/100`. Conservative for fast-moving pairs. Failures (deeper sell pressure between quote and execution) are logged and retried next tick with the now-larger keeper balance.

## Worked example

Three swaps happen on a graduated FUN token in one minute:

| # | User action              | Gross moved    | Skimmed to keeper (1%) |
| - | ------------------------ | -------------- | ---------------------- |
| 1 | Bought $500 worth of FUN | 12,345,678 FUN | 123,456.78 FUN         |
| 2 | Sold 5,000,000 FUN       | 5,000,000 FUN  | 50,000 FUN             |
| 3 | Bought $20 worth of FUN  | 493,827 FUN    | 4,938.27 FUN           |

Keeper bal jumps from 27,348,652 → 27,527,047 FUN.

Next tick:

* Router quotes \~$7.10 USDC out for 178,395 FUN.
* Above threshold; sweep proceeds.
* Swap returns $7.10 USDC (after 1% FOT on the keeper→pair leg too, which the SupportingFeeOnTransfer variant accepts).
* Treasury gets $3.55, creator gets $3.55.

## Verifying creator earnings

For any creator wallet, on-chain USDC `Transfer` events with `from = keeper EOA` since the token graduated == creator's accumulated post-grad earnings. Same for the treasury.

The basefun UI's **Earnings** tab shows the *theoretical* creator take (1/2 of 1% of every swap, valued in USDC) so the number is visible even between sweeps. The actual USDC arrives whenever the keeper next sweeps that token above the $5 threshold.

## When sweeps don't happen

| Reason                                                         | Symptom                                                                          | Fix                                                                         |
| -------------------------------------------------------------- | -------------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
| Keeper service down                                            | Token balance on keeper grows; no USDC transfers from keeper to treasury/creator | Restart keeper service                                                      |
| Token not yet in DB as `graduated=true AND uniV2Pair NOT NULL` | Sweep loop skips it                                                              | Indexer eventually catches up; the `Migrated` event is what flips this      |
| FOT swap reverts (low pair liquidity)                          | Logged in keeper; next tick retries                                              | Usually self-resolves as more liquidity arrives or with manual `swapExact…` |
| `token.approve(router, MAX)` missing                           | First sweep on a token fails once                                                | Keeper does the approve on-demand and retries next tick                     |

## Comparing pre vs post

|             | Pre-bond                  | Post-bond (V2 token)                                   |
| ----------- | ------------------------- | ------------------------------------------------------ |
| Fee rate    | 1.00%                     | 1.00% (+ 0.30% LP)                                     |
| Asset       | USDC                      | Token (swept to USDC by keeper)                        |
| Split       | 50/50 protocol/creator    | 50/50 protocol/creator                                 |
| Settlement  | Inline, same tx           | Async, ≤ one keeper tick after the swap (sub-minute)   |
| Audit trail | USDC Transfers from curve | Token Transfers to keeper + USDC Transfers from keeper |


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://basefun.gitbook.io/basefun-docs/fees-post-bond.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
