> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lunya.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Trading the Curve

> Buy and sell on a launch: entry points, units, quoting, errors

All trading happens **on the launch clone** — resolve it once via `factory.launchOf(token)`. There is no router: approvals go to the launch itself, and no function takes a token argument.

## The four entry points

Two payment shapes, four distinct selectors — not overloads. All take a `deadline` and a mandatory `recipient`.

**Quote-token (ERC-20) entries** — approve the launch first:

```solidity theme={null}
function buy(uint256 amountIn, uint256 minTokensOut, uint256 deadline, address recipient)
    external returns (uint256 tokensOut);

function sell(uint256 tokensIn, uint256 minAmountOut, uint256 deadline, address recipient)
    external returns (uint256 amountOut);
```

**Native entries** — only where the launch accepts native (`acceptsNative()`):

```solidity theme={null}
function buyWithNative(uint256 minTokensOut, uint256 deadline, address recipient)
    external payable returns (uint256 tokensOut);

function sellForNative(uint256 tokensIn, uint256 minAmountOut, uint256 deadline, address recipient)
    external returns (uint256 nativeOut);
```

`buyWithNative` has no `amountIn` — the amount is `msg.value`.

## Units — read this twice

Everything is denominated in the **quote token's own decimals**: `amountIn`, `minAmountOut` on `sell`, quotes, reserves, event amounts. One exception, deliberate:

<Warning>
  **`sellForNative`'s `minAmountOut` is denominated in the NATIVE coin**, because that is the unit the caller receives. On a chain whose quote has 6 decimals and whose native view has 18, mixing the two is a silent 10¹² error, not a revert.
</Warning>

`minTokensOut` is always in launch-token units (18 decimals).

## Recipient and refund semantics

* **Buys**: tokens go to `recipient`; any refund goes to the **payer** (`msg.sender`). Only the curve-completing buy refunds — it takes exactly what is left on the curve and returns the overshoot (native refunds come back unwrapped, plus any sub-unit dust).
* **Sells**: tokens always come from `msg.sender` — the transfer gate means nobody can hold pre-graduation tokens on another's behalf. `recipient` redirects only the **proceeds**.

## Quoting

On-chain views on each launch:

```solidity theme={null}
function quoteBuy(uint256 amountIn) external view returns (uint256 tokensOut, uint256 fee, uint256 refund);
function quoteSell(uint256 tokensIn) external view returns (uint256 amountOut, uint256 fee);
function quoteBuyWithNative(uint256 nativeIn) external view returns (uint256 tokensOut, uint256 fee, uint256 nativeRefund);
function quoteSellForNative(uint256 tokensIn) external view returns (uint256 nativeOut, uint256 fee);
```

* `quoteBuy`/`quoteSell` **return zeros instead of reverting** outside the `Trading` phase — treat `(0,0)` as "not tradable", not as a price.
* `quoteBuyWithNative` is **not** `quoteBuy(nativeIn / divisor)`: native→quote truncation is returned as dust in native units. Use the native quoter for native trades.

**Reproducing the curve off-chain** costs no RPC per quote once you hold the state. The curve is constant-product over virtual reserves:

```
x = virtualQuote + reserve          // quote side
y = virtualToken − sold             // token side

buy:  fee = amountIn · tradeFeeBps / 10000 ;  net = amountIn − fee
      tokensOut = y · net / (x + net)
sell: gross = x · tokensIn / (y + tokensIn) ;  fee = gross · tradeFeeBps / 10000
      amountOut = gross − fee
```

The buy that would exceed the remaining curve supply takes exactly what is left and refunds the overshoot (fees recomputed on the gross actually needed, rounding up). Read `reserve()` and `sold()` from storage and the constants (`virtualQuote`, `virtualToken`, `curveSupply`, `tradeFeeBps`, `nativeDivisor`) from `config()` — the interface deliberately omits them, so bind the concrete ABI.

## Errors you will hit

| Error                              | When                                                                                                                             |
| ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `Expired()`                        | `block.timestamp > deadline`                                                                                                     |
| `SlippageExceeded()`               | output below your minimum — also when `tokensOut == 0`                                                                           |
| `WrongPhase()`                     | trading in `ReadyToGraduate` (curve full, graduation pending) or after `Graduated` — no arguments; read `phase()` to distinguish |
| `NativeNotAccepted()`              | a native entry on a launch whose quote has no native representation                                                              |
| `TransfersLockedUntilGraduation()` | on the **token**: any transfer that isn't launch↔wallet before graduation                                                        |

<Note>
  **The frozen window is a real state**: between the curve filling (`CurveCompleted`) and someone calling `graduate()`, all four entries revert `WrongPhase()`. Surface it distinctly — the token is neither tradable on the curve nor on the exchange yet. Factory `creationPaused` never affects trading on existing curves.
</Note>
