> For the complete documentation index, see [llms.txt](https://degen-games-2.gitbook.io/degen-games-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://degen-games-2.gitbook.io/degen-games-docs/developers/trading-and-quoting.md).

# Trading & Quoting

Trade against the curve directly, quote trades exactly, and route to Uniswap v4 after graduation.

Before graduation, trades go **directly to the coin's curve**. There's no router in between. After graduation, the coin is an ordinary Uniswap v4 pool that any v4-aware router can trade.

## Buying and selling on the curve

```js
import { parseAbi } from "viem";

const curveAbi = parseAbi([
  "function buy(uint256 quoteIn, uint256 minTokensOut, address recipient) payable returns (uint256 tokensOut)",
  "function sell(uint256 tokensIn, uint256 minQuoteOut, address recipient) returns (uint256 quoteOut)",
  "function isNativeQuote() view returns (bool)",
  "function pairToken() view returns (address)",
]);

// Native (ETH) coin: quoteIn must equal the value sent.
await wallet.writeContract({
  address: curve, abi: curveAbi, functionName: "buy",
  args: [quoteIn, minTokensOut, recipient],
  value: quoteIn,
});

// ERC-20 raise asset: approve the curve first, send no value.
await wallet.writeContract({
  address: pairToken,
  abi: parseAbi(["function approve(address spender, uint256 amount) returns (bool)"]),
  functionName: "approve",
  args: [curve, quoteIn],
});
await wallet.writeContract({
  address: curve, abi: curveAbi, functionName: "buy",
  args: [quoteIn, minTokensOut, recipient],
});
```

* Sending value on an ERC-20 coin reverts with `UnexpectedNativeValue`. Sending the wrong value on a native coin reverts with `NativeValueMismatch`.
* **Partial fills:** a buy that would cross the curve's sellable allocation is filled to the edge and the rest is refunded in the same transaction. Read `tokensOut` from the return value or the `CurveBuy` event, and expect a `CurveBuyRefunded` event alongside it.
* **`minTokensOut` bounds the price, not the quantity.** A clamped fill is checked against the rate you asked for, so a partial fill at your price still succeeds. Size it from the quoted rate.

## Getting a quote

The curve has no quote function. Pricing is deterministic and cheap to reproduce, so compute it from the curve's reserves and fee rates. The math below uses the curve's own integer order, so a quote matches the settled trade if nothing moves in between.

```js
const quoteAbi = parseAbi([
  "function getReserves() view returns (uint256 quoteReserve, uint256 tokenReserve)",
  "function sellableTokens() view returns (uint256)",
  "function feeBps() view returns (uint256)",
  "function creatorTaxBps() view returns (uint256)",
  "function currentSnipeTaxBps(address recipient) view returns (uint256)",
]);

const BPS = 10_000n;
const ceilDiv = (a, b) => (a + b - 1n) / b;

// Constant product. Fees are applied outside this step.
const amountOut = (amtIn, rIn, rOut) => (amtIn * rOut) / (rIn + amtIn);
const amountIn = (amtOut, rIn, rOut) => (amtOut * rIn) / (rOut - amtOut) + 1n;

const readCurve = (curve, functionName, args) =>
  client.readContract({ address: curve, abi: quoteAbi, functionName, args });

/** Raise asset in, coin out. */
async function quoteBuy(curve, quoteIn, recipient) {
  const [[qR, tR], sellable, feeBps, taxBps, rawSnipe] = await Promise.all([
    readCurve(curve, "getReserves"),
    readCurve(curve, "sellableTokens"),
    readCurve(curve, "feeBps"),
    readCurve(curve, "creatorTaxBps"),
    readCurve(curve, "currentSnipeTaxBps", [recipient]),
  ]);

  // The snipe tax is capped so at least 1% of the spend always reaches the curve.
  let snipeBps = rawSnipe;
  if (snipeBps > 0n) {
    const cap = BPS - feeBps - taxBps - 100n;
    if (snipeBps > cap) snipeBps = cap;
  }

  // All fees come off the input before the curve prices the trade.
  let spent = quoteIn;
  const fee = (spent * feeBps) / BPS;
  const tax = (spent * taxBps) / BPS;
  const snipe = (spent * snipeBps) / BPS;
  let tokensOut = amountOut(spent - fee - tax - snipe, qR, tR);

  // Crossing the sellable allocation: fill to the edge, reprice, refund the rest.
  if (tokensOut > sellable) {
    tokensOut = sellable;
    const net = amountIn(sellable, qR, tR);
    const gross = ceilDiv(net * BPS, BPS - feeBps - taxBps - snipeBps);
    spent = gross < quoteIn ? gross : quoteIn;
  }
  return { tokensOut, spent, refund: quoteIn - spent };
}

/** Coin in, raise asset out. No snipe tax on sells. */
async function quoteSell(curve, tokensIn) {
  const [[qR, tR], feeBps, taxBps] = await Promise.all([
    readCurve(curve, "getReserves"),
    readCurve(curve, "feeBps"),
    readCurve(curve, "creatorTaxBps"),
  ]);
  const gross = amountOut(tokensIn, tR, qR);
  return gross - (gross * feeBps) / BPS - (gross * taxBps) / BPS;
}
```

### Quoting rules of thumb

* **Price against `getReserves()`.** It includes the virtual reserve and excludes fees waiting to be swept. `realQuoteReserve()` is what the curve physically holds, and it's the wrong input for a quote.
* **Buys and sells aren't mirror images.** Buys pay fees on the input, sells pay fees on the output. Quoting a sell as an inverted buy overstates the proceeds.
* **Snipe tax is keyed to the recipient.** Call `currentSnipeTaxBps(recipient)` with the wallet that will *receive* the tokens. It returns 0 once the window has passed, which covers almost every trade.
* **Spot price** is `quoteReserve ÷ tokenReserve`. Use it for display only. It carries no slippage.

## When the curve closes

* **Buys close** when `sellableTokens()` reaches 0.
* **Sells close earlier**, as soon as `readyToGraduate()` returns `true`, because the curve is then holding the pool's reserves. Gate sells on `readyToGraduate()`, not `graduated()`.
* After graduation, the curve reverts with `CurveGraduated`. Route both directions to the pool.

## Pushing a stalled graduation

Graduation normally completes inside the buy that fills the curve. If that transaction runs short of gas, the curve emits `AutoGraduationFailed` and the coin waits in the **Swept** phase. Anyone can finish it:

```js
const gradAbi = parseAbi([
  "function createGraduatedPool(address token) returns (uint256 positionId)",
]);

// Phase 1 (Swept): create and seed the pool. Permissionless and retryable.
await wallet.writeContract({
  address: factory, abi: gradAbi, functionName: "createGraduatedPool", args: [token],
});
```

`createGraduatedPool` reverts with `WrongGraduationPhase` if the coin isn't waiting to be seeded. Treat `AutoGraduationFailed` events as a work queue.

## Trading after graduation (Uniswap v4)

A graduated coin is a normal Uniswap v4 pool, so any v4-aware router or aggregator can trade it without integrating Degen Games. Build the pool key from the launch record:

```js
import { encodeAbiParameters, keccak256 } from "viem";

const memeHook = "0x0f3DdB1D7bfc3469036C5F393FF47dafF0f72044";

// Currencies are sorted by address. Native ETH is the zero address, so it's always currency0.
const [currency0, currency1] =
  launch.pairToken.toLowerCase() < launch.token.toLowerCase()
    ? [launch.pairToken, launch.token]
    : [launch.token, launch.pairToken];

const poolKey = {
  currency0,
  currency1,
  fee: launch.poolFee,          // 0: the hook charges the fee, not the pool
  tickSpacing: launch.tickSpacing, // 200
  hooks: memeHook,
};

const poolId = keccak256(encodeAbiParameters(
  [{ type: "address" }, { type: "address" }, { type: "uint24" }, { type: "int24" }, { type: "address" }],
  [poolKey.currency0, poolKey.currency1, poolKey.fee, poolKey.tickSpacing, poolKey.hooks],
));
```

The hook only accepts pools registered by the Degen Games factory, so nobody can attach an unrelated pool to it, or initialize a coin's pool at a bad price before graduation. It doesn't restrict who can swap and doesn't tax wallet-to-wallet transfers. It charges the 1% trading fee and any creator tax on each swap.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## 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, and the optional `goal` query parameter:

```
GET https://degen-games-2.gitbook.io/degen-games-docs/developers/trading-and-quoting.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

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.
