# bonding curve

One curve per token. Holds the bonding-curve math, the LT shares, the graduation trigger, and the inline fee split. \~300 lines of Solidity.

## Constants

```solidity
TOTAL_SUPPLY             = 1_000_000_000e18
CURVE_SUPPLY             = 757_500_000e18      // sold via the curve
LP_SUPPLY                = 242_500_000e18      // seeded to Uni V2 at graduation

GRADUATION_USDC          = 10_000e6            // $10k → triggers graduation
GRADUATION_LP_USDC       = 9_700e6             // $9.7k → Uni V2 LP
GRADUATION_TREASURY_USDC = 300e6               // $300 → protocol treasury

SWAP_FEE_BPS             = 100                 // 1.00%
PROTOCOL_FEE_BPS         = 50                  // 0.50% → treasury (rest = creator)

VIRTUAL_USDC             = 4_000_000_000       // $4,000 (1e6 scale)
VIRTUAL_TOKENS           = 303_000_000e18      // 303M (1e18 scale)
```

The virtual reserves are sized so the curve's "sellout" point (all 757.5M sold) coincides exactly with `reserveUSDC = $10,000`:

```
VIRTUAL_USDC × CURVE_SUPPLY / VIRTUAL_TOKENS = GRADUATION_USDC
4_000e6      × 757_500_000  / 303_000_000   = 10_000e6  ✓
```

## State

```solidity
IERC20  public token;                  // bound by factory after deploy
uint256 public tokensSoldFromCurve;
bool    public graduated;
uint256 public collateralDeposited;    // defensive fallback when LT.nav() is stale
```

## Pricing math

The constant-product AMM with virtual reserves:

```
x_total = VIRTUAL_USDC + getReserveUSDC()        (USDC, 1e6)
y_total = VIRTUAL_TOKENS + CURVE_SUPPLY - sold   (tokens, 1e18)

spot = x_total × 1e18 / y_total                  (1e6 USDC per 1e-18 token)
mcap = x_total × TOTAL_SUPPLY / y_total          (1e6 USDC)
```

`getReserveUSDC()` reads the live USD value of the LT shares the curve owns:

```
reserveUSDC = ltBalance × LT.nav() / LT.totalSupply()
```

## buy(usdcAmount, minTokensOut)

{% stepper %}
{% step %}

### Transfer USDC

```
usdc.transferFrom(buyer, curve, usdcAmount)
```

{% endstep %}

{% step %}

### Split the fee

```
split: protoFee = 0.5%, creatorFee = 0.5%, netUSDC = 99%
```

* `protoFee` → `protocolFeeRecipient` (inline transfer)
* `creatorFee` → `creatorFeeRecipient` (inline transfer)
  {% endstep %}

{% step %}

### Quote tokens out

```
tokensOut = _quoteBuy(netUSDC)
newY = (x_total × y_total) / (x_total + netUSDC)
tokensOut = y_total - newY
```

{% endstep %}

{% step %}

### Cap the output

```
cap tokensOut at CURVE_SUPPLY - tokensSoldFromCurve
```

{% endstep %}

{% step %}

### Check slippage

```
require tokensOut >= minTokensOut, else SlippageExceeded
```

{% endstep %}

{% step %}

### Mint LT shares

```
usdc.approve(LT, netUSDC); LT.mint(netUSDC, 0)
```

→ LT pulls USDC into keeper EOA
{% endstep %}

{% step %}

### Update sold amount

```
tokensSoldFromCurve += tokensOut
```

{% endstep %}

{% step %}

### Transfer tokens to buyer

```
token.transfer(buyer, tokensOut)
```

{% endstep %}

{% step %}

### Emit event

```
emit Bought(buyer, usdcAmount, tokensOut, getReserveUSDC())
```

{% endstep %}

{% step %}

### Maybe graduate

```
_maybeGraduate()
```

{% endstep %}
{% endstepper %}

## sell(tokenAmount, minUSDCOut)

{% stepper %}
{% step %}

### Quote USDC out

```
usdcGross = _quoteSell(tokenAmount)
newX = (x_total × y_total) / (y_total + tokenAmount)
usdcGross = x_total - newX
```

{% endstep %}

{% step %}

### Pull tokens in

```
token.transferFrom(seller, curve, tokenAmount)
```

{% endstep %}

{% step %}

### Update sold amount

```
tokensSoldFromCurve -= tokenAmount
```

{% endstep %}

{% step %}

### Compute LT burn

```
ltBurn = ltBalance × usdcGross / reserveUSDC
```

(capped at `ltBalance` to prevent over-redemption)
{% endstep %}

{% step %}

### Redeem LT shares

```
LT.redeem(ltBurn, 0)
```

→ LT pulls USDC back from keeper, forwards to curve
{% endstep %}

{% step %}

### Split the fee

```
split: 0.5% proto, 0.5% creator, 99% net
```

→ inline transfers\
→ require `netUSDC >= minUSDCOut`, else `SlippageExceeded`
{% endstep %}

{% step %}

### Transfer USDC to seller

```
usdc.transfer(seller, netUSDC)
```

{% endstep %}

{% step %}

### Emit event

```
emit Sold(seller, tokenAmount, netUSDC, getReserveUSDC())
```

{% endstep %}
{% endstepper %}

## \_maybeGraduate

Called at the end of every `buy`:

```
reserveHit = getReserveUSDC() >= GRADUATION_USDC
soldOut    = tokensSoldFromCurve >= CURVE_SUPPLY
if neither: return

LT.redeem(ltBalance, 0)        // pull ALL remaining USDC back
graduated = true
emit Graduated(reserve)

if balance >= 300: usdc.transfer(protocolFeeRecipient, 300)
```

After this, every `notGraduated` modifier (on `buy` / `sell` / `initialBuy`) reverts. The curve holds the residual `$9.7k USDC + 242.5M tokens` waiting for the keeper to call `factory.migrateToUniV2(curve)`.

## withdrawForGraduation(to)

Factory-only. Drains the residual USDC + tokens to the caller (the factory's `migrateToUniV2`), which then seeds the Uni V2 LP.

## Errors

| Error                                | When                                                                   |
| ------------------------------------ | ---------------------------------------------------------------------- |
| `AlreadyGraduated()`                 | Any write after `graduated = true`                                     |
| `NotEnoughCurveSupply()`             | (unused — capped via `if` instead)                                     |
| `SlippageExceeded()`                 | `tokensOut < minTokensOut` or `netUSDC < minUSDCOut`                   |
| `MinBuyNotMet()` / `MinSellNotMet()` | Both currently 0 — placeholder for future config                       |
| `OnlyFactory()`                      | `bindToken`, `initialBuy`, `withdrawForGraduation` if caller ≠ factory |
| `InvalidLeverage()`                  | Constructor: leverage ∉ {2e10, 3e10, 5e10}                             |
| `InvalidRecipient()`                 | Constructor: creatorFeeRecipient = `address(0)`                        |


---

# 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/bonding-curve.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.
