# YeetAMM — DEX Integration Spec

**For indexers, aggregators, wallets, portfolio trackers, and explorers**
(DexScreener, Birdeye, GeckoTerminal, Jupiter, GMGN, Solscan, …)

YeetAMM is a native Solana AMM + bonding-curve launchpad. Every token launched
on YeetLaunch trades **exclusively** in a YeetAMM pool — there is no external
Raydium/Orca/Meteora pool to index. This document is everything required to
detect pools, decode swaps, price them, and track volume **directly from
on-chain state**, with an optional REST path for fast bootstrap.

Everything here is derivable from public on-chain data. No API key or
partnership is required to index YeetAMM.

---

## 1. Program

| Network | Program ID |
|---------|-----------|
| **Mainnet** | `yeetaecvxpd7DFzZAYTEYracRt1WYJ7DfMVjEeEt2Cp` |
| Devnet  | `yeetMcJ7nBfMZiQV8ns4h5m3WeVekT1ySq27bMWWfio` |

- **One program, two modes.** DBC (bonding curve) and AMM are not separate
  programs. They are the `pool_mode` field of the *same* `Pool` account:
  `0 = DBC`, `1 = AMM`. A token starts in DBC mode and atomically graduates to
  AMM mode in the same transaction that crosses its graduation threshold.
- Anchor program. All accounts and events are Anchor-encoded (8-byte
  discriminator prefix, Borsh body).
- The canonical Anchor **IDL** is published at
  [`/idl/yeet-amm.json`](https://yeetlaunch.io/idl/yeet-amm.json). It is not
  adjacent to this file; there is no `/docs/yeet-amm-idl.json`. The IDL shipped
  for a given release matches the deployed bytecode (verifiable via
  `solana-verify`).

---

## 2. Pool discovery

Two equivalent ways to enumerate all pools.

### 2a. `getProgramAccounts` (full history, no dependency on us)

Filter by account size and the `Pool` discriminator:

- **`dataSize` = 593** (8-byte discriminator + 585-byte account body).
- **memcmp @ offset 0** = Pool account discriminator
  `[241, 154, 109, 4, 17, 177, 109, 188]`
  (`sha256("account:Pool")[0..8]`).

```
connection.getProgramAccounts(PROGRAM_ID, {
  filters: [
    { dataSize: 593 },
    { memcmp: { offset: 0, bytes: <base58 of the 8 discriminator bytes> } },
  ],
})
```

### 2b. `PoolInitialized` event (real-time)

Subscribe to program logs and decode the `PoolInitialized` event (see §5) to
catch new pools as they are created. Use `PoolGraduated` to catch DBC→AMM
migrations.

### Pool PDA derivation

A pool address is a PDA of the program, so you can also verify any pool
deterministically:

```
seeds = [ b"yeet_amm_pool", token_mint_a, token_mint_b ]
programId = <YeetAMM program id>
```

**Invariant:** `token_mint_a < token_mint_b` (lexicographic byte order of the
32-byte mint pubkeys). The pool is keyed on the sorted pair.

---

## 3. Pool account layout

Total account data = **593 bytes** = 8-byte Anchor discriminator + 585-byte
account body (577 bytes of fields + 8 reserved trailing bytes). Fields are
packed in declaration order, little-endian, no padding between them. Offsets
below are **absolute** (byte 0 = first discriminator byte).

**Fields an indexer needs:**

| Offset | Field | Type | Meaning |
|-------:|-------|------|---------|
| 0   | *(discriminator)* | `[u8;8]` | `[241,154,109,4,17,177,109,188]` |
| 10  | `token_mint_a` | `Pubkey` | first mint (sorted) |
| 42  | `token_mint_b` | `Pubkey` | second mint (sorted) |
| 74  | `vault_a` | `Pubkey` | SPL token account holding token-A reserves |
| 106 | `vault_b` | `Pubkey` | SPL token account holding token-B reserves |
| 138 | `lp_mint` | `Pubkey` | LP token mint |
| 266 | `reserve_a` | `u64` | on-chain reserve of token A |
| 274 | `reserve_b` | `u64` | on-chain reserve of token B |
| 282 | `fee_bps` | `u16` | total swap fee, bps (always `100` = 1.00%) |
| 414 | `is_initialized` | `bool` | `true` once the pool is live/tradeable |
| 520 | `pool_mode` | `u8` | `0 = DBC`, `1 = AMM` |
| 521 | `grad_threshold` | `u64` | quote level that triggers graduation (`0` = pure AMM pool, never DBC) |
| 529 | `grad_slot` | `u64` | slot the pool graduated; `0` = not graduated |
| 537 | `quote_mint` | `Pubkey` | the pool's quote mint (see §4) |

> The full field set (LP lock/vesting vaults, creator, creator-fee vaults,
> internal slot accounting, etc.) is in the IDL. The table above is the subset
> needed to list, price, and classify a pool. **Do not treat any undocumented
> field as a pricing input** — see §6.

**Ignore uninitialized pools:** skip any account where `is_initialized == false`
(offset 414). These are partially set-up pools that are not yet tradeable.

---

## 4. Quote side

- `quote_mint` (offset 537) names the pool's quote asset. For every token
  launched on YeetLaunch this is **wSOL**
  (`So11111111111111111111111111111111111111112`).
- The **base** token is whichever of `token_mint_a` / `token_mint_b` is *not*
  the `quote_mint`.
- Prices should be expressed as **quote per base** (i.e. SOL per token), then
  optionally multiplied by the SOL/USD reference for USD display.
- Match `quote_mint` against `token_mint_a`/`token_mint_b` to learn which
  reserve/side is the quote; do not assume A or B.

---

## 5. Events

All events are Anchor events emitted in program logs (`Program data:` lines),
8-byte event discriminator + Borsh body. Discriminators
(`sha256("event:<Name>")[0..8]`):

| Event | Discriminator | Emitted on |
|-------|---------------|-----------|
| `SwapEvent` | `[64,198,205,232,38,8,113,226]` | every swap |
| `PoolInitialized` | `[100,118,173,87,12,198,254,229]` | pool goes live |
| `PoolGraduated` | `[133,36,99,83,207,210,174,93]` | DBC → AMM migration |
| `LiquidityAdded` | `[154,26,221,108,238,64,217,161]` | `add_liquidity` |
| `LiquidityRemoved` | `[225,105,216,39,124,116,169,189]` | `remove_liquidity` |
| `FeeCollected` | `[12,28,17,248,244,36,8,73]` | fees taken on a swap |
| `LpLocked` | `[231,255,40,229,17,147,106,125]` | graduation LP split |

### `SwapEvent` (the one you need for price & volume)

| Field | Type | Meaning |
|-------|------|---------|
| `pool` | `Pubkey` | pool address |
| `user` | `Pubkey` | trader |
| `token_in` | `Pubkey` | mint paid in |
| `token_out` | `Pubkey` | mint received |
| `amount_in` | `u64` | raw amount in (base units of `token_in`) |
| `amount_out` | `u64` | raw amount out (base units of `token_out`) |
| `price_q64` | `u128` | executed rate = `(amount_out << 64) / amount_in`, Q64.64 |
| `pool_mode` | `u8` | `0 = DBC`, `1 = AMM` at execution time |
| `quote_mint` | `Pubkey` | pool quote mint |
| `reserve_a` | `u64` | post-swap reserve A |
| `reserve_b` | `u64` | post-swap reserve B |

### `PoolInitialized`
`pool, creator, lp_mint, token_a, token_b, reserve_a, reserve_b, quote_mint, grad_threshold, pool_mode`

### `PoolGraduated`
`pool, grad_slot, reserve_a, reserve_b, quote_mint, quote_reserve, grad_threshold`

### `LiquidityAdded` / `LiquidityRemoved`
`pool, user, token_a, token_b, amount_a, amount_b, lp_tokens, reserve_a, reserve_b`

### `FeeCollected`
`pool, pool_mode, yeet_fee, creator_fee, lp_fee`

### `LpLocked`
`pool, lp_mint, total_lp, locked_lp, vesting_lp`

---

## 6. Price & volume (authoritative method)

**Price a YeetAMM market from executed trades — the `SwapEvent`.** This is the
recommended and supported method, and it is exact for both DBC and AMM modes.

For each `SwapEvent`:

1. **Direction:** compare `token_in` / `token_out` against `quote_mint` to know
   whether the trade was a buy (quote → base) or sell (base → quote).
2. **Executed price (quote per base):**
   - If selling base for quote: `price = amount_out / amount_in` (quote out per
     base in).
   - If buying base with quote: `price = amount_in / amount_out` (quote in per
     base out).
   - Both are exact from the raw fill amounts. `price_q64` is provided as a
     convenience: it always equals `(amount_out << 64) / amount_in`, i.e. the
     out-per-in rate as Q64.64 — divide by `2^64`, then **invert only when the
     input side is the quote** (a buy). On a sell the input is the base, so
     out-per-in is already quote-per-base and must NOT be inverted.
   - Sanity check against the two formulas above: on a sell they give
     `amount_out / amount_in`, which is exactly what `price_q64` encodes; on a
     buy they give `amount_in / amount_out`, which is its reciprocal.
3. **Decimal-normalize:** convert raw base-unit amounts to human units using
   each mint's `decimals` (read from the SPL mint account; wSOL = 9). Human
   price = `(amount_quote / 10^quote_dec) / (amount_base / 10^base_dec)`.
4. **Volume:** the trade's quote volume is the quote-side amount, which is
   `amount_in` on a **buy** (quote goes in) and `amount_out` on a **sell**
   (quote comes out). Do not take the base-side amount: it is denominated in
   the token, and no decimal normalization can turn it into quote volume. Sum
   per candle interval.

   Determine the side from the mints, never from the direction alone:
   `quoteAmount = token_in === quote_mint ? amount_in : amount_out`.

   Worked example — finalized sell
   [`336fdd…Ric34`](https://solscan.io/tx/336fddWQ8MXDNGG9bBxkB8yRczaT9UFUi4jfsnJmK9vGm8XftXTd4WbHV4WmXvdzFAntwTwdqLM5nqL9hpyRic34)
   on pool `22PkJRrc5VMgY6WmAr4PxNRdG23LWSeyuJG95oNJG1bH`: `amount_in` =
   9,883,650,385,039,889 raw base units, `amount_out` = 188,719,761 lamports.
   The quote volume is **0.188719761 SOL** (`amount_out / 1e9`) — not
   `amount_in / 1e9`.

**Do not price from raw `reserve_a`/`reserve_b` alone.** YeetAMM pools are not
pure constant-product; a naive `reserve_quote / reserve_base` will misprice.
Use executed trade prices from `SwapEvent`. A pool with no swaps yet simply has
no market price until its first trade — the same as any DEX.

---

## 7. Fee model

- **Total swap fee: 1.00% flat** (`fee_bps = 100`), enforced on-chain — pools
  cannot be created at any other value. Charged on the input amount.
- Split (informational; `FeeCollected` reports the realized split per swap):
  - **DBC mode:** 0.70% protocol, 0.30% creator, 0.00% LP.
  - **AMM mode:** 0.50% protocol, 0.30% creator, 0.20% LP-retained.
- For quoting/execution, the only number that matters is the **1.00% total**.

---

## 8. Lifecycle: DBC → AMM

- New tokens launch in **DBC mode** (`pool_mode = 0`).
- When cumulative quote reserves reach `grad_threshold`, the pool **graduates
  atomically** in the same transaction — no separate migration tx, no delay.
  `pool_mode` flips to `1`, `grad_slot` is set, and a `PoolGraduated` event is
  emitted.
- A pool with `grad_threshold = 0` is a pure AMM pool that never had a DBC
  phase.
- **Canonical migration signal:** the `PoolGraduated` event, or a transition of
  the `pool_mode` byte (offset 520) from `0` to `1`.

---

## 8.1 Adaptive sell cap (routers: read this)

Graduated (AMM-mode) pools cap how much of the input reserve a single sell may consume. A router that quotes naive constant-product output and ignores this will hand users transactions that revert with `AdaptiveSellCapExceeded` (**6042** — 6019 is `InvalidSetupAuthority`, an unrelated error). **The whole cap is computable from the Pool account — no RPC round trip and no API call**, which matters because `Amm::quote()` implementations cannot make network calls mid-route-search.

Three fields carry it, all inside the existing 585-byte account body (593 bytes on the wire, including the 8-byte discriminator — the `dataSize` filter above):

| field | offset | type | meaning |
|---|---|---|---|
| `creator_fee_share_bps` | 284 | u16 | **the live cap in bps** — see the warning below |
| `slot_sell_window` | 512 | u64 | packs `(slot << 16) \| consumed_bps` — same-slot aggregate |
| `last_sell_cap_update_slot` | 577 | u64 | anchor for lazy recovery |

> ⚠ **`creator_fee_share_bps` is not a fee share.** It is a legacy field from the pre-mode-based launch config, reused to persist the adaptive sell-cap state (`state.rs:86`). Reading the IDL name literally will mis-model it. Our TypeScript SDK exposes it as `adaptiveSellCapBps` for exactly this reason.

**Constants** (`state.rs:14-23`): ceiling 500 bps (5%), floor 50 bps (0.5%), recovery +50 bps per 32 slots, and a per-slot aggregate ceiling also of 500 bps.

**Applies when** `pool_mode == 1` (AMM) **and** `creator_fee_share_bps != 0`. Pools that pre-date the cap carry 0 and are uncapped — treat 0 as "no cap", not as "zero allowance", or you will reject every sell on a legacy pool.

**Recovery** is lazy — nothing is written between trades. Given `current_slot`:

```
elapsed   = current_slot - last_sell_cap_update_slot
intervals = elapsed / 32
cap_bps   = min(500, stored_cap_bps + intervals * 50)
```

**Maximum sell input:**

```
max_amount_in = cap_bps * reserve_in / 10_000        // floor division
```

**Consumption**, if you are modelling post-trade state:

```
consumed_bps = ceil(amount_in * 10_000 / reserve_in)   // ceiling
next_cap     = max(50, cap_bps - consumed_bps)
```

**Per-slot aggregate.** Unpack `slot_sell_window` as `anchor_slot = value >> 16` and `consumed = value & 0xFFFF`. If `anchor_slot == current_slot`, the slot has already used `consumed` bps and the remaining same-slot allowance is `500 - consumed`; otherwise the accumulator resets. This is what stops a dump being split across many wallets inside one block, so a router batching fills into a single slot must respect it.

Sizing a sell under `min(max_amount_in, per-slot remaining)` is sufficient. Pricing itself is unaffected by the cap — it is purely an upper bound on input size.

## 9. Canonical / anti-imposter check

Because pool addresses are deterministic PDAs, you can prove a pool is the
genuine YeetAMM market for a mint and reject lookalikes:

1. Re-derive the PDA: `findProgramAddress(["yeet_amm_pool", mintA, mintB], programId)`
   with `mintA < mintB` — it must equal the pool address.
2. The pool account's **owner** must be the YeetAMM program id.
3. `is_initialized == true` (offset 414).
4. `quote_mint == wSOL` for a YeetLaunch token market.

A pool passing all four is the canonical, program-owned YeetAMM pool for that
pair. (YeetLaunch surfaces this same check to traders as a "Verified Mint"
badge.)

---

## 10. REST API (optional fast bootstrap)

For polling-friendly integration without running your own RPC indexer. Live,
JSON, wildcard-CORS (`Access-Control-Allow-Origin: *`, GET) — callable directly
from a browser.

**Base URL:** `https://api.yeetlaunch.io`

| Endpoint | Description |
|----------|-------------|
| `GET /pools` | all active pools: real, virtual and effective reserves, fee, mode, graduation state. **No price, volume or TVL fields** — derive price from reserves, volume from `SwapEvent`. |
| `GET /pools/:id` | single pool by address |
| `GET /api/pools` | pools in aggregator schema |
| `GET /api/tokens` | indexed tokens with metadata |
| `GET /api/price?tokenA=&tokenB=` | current price for a pair |
| `GET /api/trades?limit=&poolId=` | swaps **this API instance routed and confirmed** — in-memory, newest 200, cleared on restart. Not an on-chain trade history; see the note below. |
| `GET /quote?inputMint=&outputMint=&amount=&slippageBps=` | execution quote |

> The `getProgramAccounts` / event path in §2–§6 is the source of truth; REST is
> a convenience mirror and may lag chain head slightly.

> **`/api/trades` is not a trade index.** It is an in-memory buffer of the most
> recent 200 swaps that were quoted and confirmed *through this aggregator*. It
> holds nothing that traded directly against the program or through anyone else,
> and it is empty after every restart of the API. For complete, verifiable swap
> history — volume, charts, or detecting trades — subscribe to or backfill the
> on-chain `SwapEvent` (§6). That is the only source that can answer those
> questions.

The on-chain path is always authoritative. Prefer it for full fidelity; use
REST to bootstrap or for lightweight polling.

---

## 11. Integration checklist

| Capability | On-chain | REST |
|-----------|----------|------|
| Enumerate pools | `getProgramAccounts` (dataSize 593 + discriminator) | `GET /pools` |
| Detect new pools | `PoolInitialized` event | poll `GET /pools` |
| Read reserves / mode / quote | `Pool` account (offsets in §3) | `GET /pools/:id` |
| Price a market | `SwapEvent` executed price (§6) | `GET /api/price` |
| Track volume | sum `SwapEvent` quote amounts | — (REST cannot: see `/api/trades` note) |
| Detect swaps | `SwapEvent` | — (REST cannot: see `/api/trades` note) |
| Detect DBC→AMM migration | `PoolGraduated` / `pool_mode` 0→1 | `kind` field on `GET /pools` |
| Verify a canonical pool | PDA + owner + `is_initialized` + wSOL quote (§9) | — |

---

## 12. Contact

Integration questions, IDL updates, or listing support:
**support@yeetlaunch.io** · docs: `https://yeetlaunch.io/dev`

*This spec describes on-chain interfaces and public endpoints only. It does not
disclose internal pricing/curve derivation, and none is required to index or
price YeetAMM markets — executed trade prices from `SwapEvent` are exact.*
