# AeX402 C Program Reference

Complete reference for the on-chain Solana program (`aex402.c`). This document covers instruction handlers, state structures, security audit trails, math, and advanced features.

For project-level overview and build/deploy instructions, see the root `CLAUDE.md`.

## Simulation Framework (sim/)

Declarative scenario-based testing for the on-chain program:

```bash
cd sim && zig build run -- scenarios/swap-basic.scenario    # Run single scenario
cd sim && zig build run -- scenarios/                        # Run all scenarios
```

**300+ scenario files** covering: swaps, liquidity, farming, governance, circuit breakers, rate limiting, oracle integration, CL, flash loans, virtual pools, migration, ML brain, N-token pools, orderbook, and 40+ cross-feature integration tests.

**Critical:** `sim/cli.zig` must put global flags BEFORE command args (e.g., `aex --network localhost pool create`, NOT `aex pool create --network localhost`).

**Recent Security Fixes (Virtual Pool Graduation - 2026-01-14):**

*Initial Implementation (Morning):*
- **C-1 RESOLVED**: Moved founder/early supporter determination from bonding phase to post-graduation (eliminates lock-out, MEV, buy-and-dump attacks)
- **C-2 RESOLVED**: Added graduation fallback mechanism - anyone can trigger after 1 hour if original triggerer doesn't (prevents pool bricking)
- **C-3 RESOLVED**: Added clock validation in vesting handlers to prevent time manipulation attacks
- **H-2 RESOLVED**: Zero founder/early arrays in vpcreate to prevent garbage data exploitation

*Deep Audit Fixes (Afternoon):*
- **D-1 RESOLVED**: Fixed zero value bypass in graduation_eligible_at check (prevented auth bypass when pool hasn't reached threshold)
- **D-2 RESOLVED**: Removed unused founder/early arrays from VirtualPool struct (saves ~28KB per pool, cannot reconstruct pubkeys from 6-byte hashes)
- **D-3 RESOLVED**: Implemented missing triggerer reset in vpsell handler when pool drops below threshold (Bug #50 - was documented as fixed but code missing)
- **D-4 RESOLVED**: Added overflow protection for graduation_eligible_at + GRADUATION_DEADLINE calculation (prevents integer wrap-around)
- **D-5 RESOLVED**: Added defensive underflow check in AMM token calculation (prevents future bugs if percentages change)
- **D-6 RESOLVED**: Fixed vpflush front-running graduation fallback (most critical - prevented holders from losing funds if triggerer delays 30+ min)

**V2 Governance Security Audit (2026-02-24) — 20 issues, ALL RESOLVED:**

*Critical (6):*
- **C-1**: veunstk had NULL PDA seeds (no seed derivation) → Added global_pda account + slot_index in data for full PDA derivation
- **C-2**: veclmfe had no pool-to-position binding → Added pool_mint validation against pool's t0/t1_mint + vault validation
- **C-3**: vpfcmt/vpfrev had no real CPI transfers (state-only) → vpfcmt does CPI transfer for IDL deposit, vpfrev mints rewards via vpmint PDA
- **C-4**: vpfrev used `secret XOR 0` as random seed (fully predictable) → Mixed with slothash: `seed = secret ^ slothash_seed`
- **C-5**: vpgrad required only 3 accounts (no CPI) → Now requires 7 accounts with mandatory minting
- **C-6**: vpclaim/claimcrt CPI was conditional (`if ka_num >= N`) → Made unconditional (mandatory)

*High (6):*
- **H-1**: vestake didn't verify token mint matches graduated pool → Added slot_index + real_mint validation
- **H-2**: vestake/veunstk lacked account size checks → Added `data_len >= 128` check
- **H-3**: VPoolSlot lacked packed attribute (padding risk) → Added `__attribute__((packed))` + `_Static_assert`
- **H-4**: veclmfe mixed admin0+admin1 (different token denominations) → Single-token fee model based on ve->pool_mint
- **H-5**: veclmfe had no pool account validation → Added pool data_len >= 1024 check
- **H-6**: vpfrev committer could self-deal as winner → Committer hash excluded from winner selection

*Medium (5):*
- **M-1**: vpgrad minted `sold` tokens (double-minting with vpclaim) → Only mints amm_liq + dao_alloc; holder/creator/farming mint on-demand
- **M-2**: vpflush didn't clear v2 fields → Now zeros tokens_bought/sold_back/farming_extracted/creator_pct/delegated
- **M-3**: vpfinit didn't store PDA derivation fields → Added vp_pool_id and vp_mint_bump to VPoolFarmingState
- **M-4**: vestake/veunstk account layout had signer at wrong index → Signer moved from ka[3] to ka[4]
- **M-5**: vpfrev required only 5 accounts → Now requires 6 minimum (added token_program)

*Low (3):*
- **L-1**: VPoolFarmingState _pad2 wasted space → Repurposed for vp_pool_id(4) + vp_mint_bump(1) + _pad2[2]
- **L-2**: vpdelegt should require GRADUATED status → Already present (confirmed)
- **L-3**: ERR_LIMIT (ERR_BASE+20) collided with ERR_RATE → Changed to ERR_BASE+34

**V2 Self-Roast Round 2 (2026-02-24) — 14 issues, ALL RESOLVED:**

*Critical (4):*
- **C-1r2**: vpfinit farming_alloc cap wrong — capped farming_extracted against unsold budget (separate budgets!) → Fixed: only cap unsold_farming against avail_unsold; farming_extracted never capped
- **C-2r2**: veclmfe fee model broken — used `pool->admin0` as absolute value without dividing by total staked → Rewritten: accumulated-reward-per-weight model with ve_acc_fee0/1 (1e12 scaled), lazy update from admin fee deltas, per-position debt tracking
- **C-3r2**: vpfrev winner ATAs unverified — caller could steal all rewards → Added: ATA owner hash verification against `top_hashes[winner_slot]` via `vp_caesar_hash`
- **C-4r2**: vpclaim/claimcrt/vpfrev don't verify mint matches slot->real_mint → Added: `pk_eq(mint_key, &slot->real_mint)` in all three handlers

*High (3):*
- **H-5r2**: vpgrad didn't verify mint_auth_pda matches derived PDA → Added: `sol_create_program_address` derivation + `pk_eq` check before InitializeMint2
- **H-6r2**: vpfcmt deposits never returned → Added: lamport transfer back to committer on successful reveal in vpfrev
- **H-7r2**: veclmfe competes with wdrawfee for same admin fees → Resolved: veclmfe decrements pool->admin0/1 after claiming, keeping accounting consistent

*Medium (4):*
- **M-8r2**: vestake ka[5] unused (burn_authority) → Repurposed as farming_state(writable) for ve_total_weight tracking
- **M-9r2**: vpfrev integer rounding leaks tokens → Fixed: remainder added to first winner; farming_rewards_left decremented by actually_minted
- **M-10r2**: veclmfe blocks claims after lock expires → Fixed: removed ERR_EXP check; stakers can claim earned fees even after unlock
- **M-11r2**: vpfinit no farming_state PDA derivation check → Farming state created by caller, validated by disc check on use

*Low (3):*
- **L-12r2**: vpgrad unused global_bump parameter → Documented as required for future PDA derivation
- **L-13r2**: veunstk position not closeable → Position disc zeroed on unstake; client can close account
- **L-14r2**: VPoolFarmingState extended 664→704 bytes with ve_total_weight, ve_acc_fee0/1, ve_last_admin0/1

**Struct changes in round 2:**
- VPoolFarmingState: 664→704 bytes (added ve_total_weight, ve_acc_fee0, ve_acc_fee1, ve_last_admin0, ve_last_admin1)
- VePosition._reserved[0..7]: stores weight; [8..15]: stores fee1 debt; [16..23]: reserved
- vestake account layout: ka[5] changed from burn_authority to farming_state(writable)
- veunstk account layout: expanded to 8 accounts (added farming_state at ka[6])
- veclmfe account layout: completely redesigned — 9 accounts (ve_position, pool, farming_state, vault0, vault1, ata0, ata1, user, token_program)

**V2 Self-Roast Round 3 (2026-02-24) — 8 issues, ALL RESOLVED:**

*Critical (3):*
- **C-1r3**: veclmfe pool->admin0 decrement breaks accumulated model — claiming decrements pool->admin0 but ve_last_admin0 was set to pre-claim value, causing future fee distributions to "miss" fees equal to past claims. Progressive fee loss. → Fixed: added ve_total_claimed0/1 counters to VPoolFarmingState; lazy update uses virtual_admin = pool->admin + ve_total_claimed (always monotonic)
- **C-2r3**: vpfrev actually_minted overcounts skipped winners — pre-computed actually_minted includes winners skipped by ATA hash mismatch, over-decrementing farming_rewards_left → Fixed: track actually_minted dynamically in the minting loop; only count successful mints
- **C-3r3**: veclmfe wrong-pool drain attack — farming_state binding only checked graduated_mint, not specific pool address; attacker could pass a fee-rich pool with same token mint → Fixed: added `pk_eq(ka[1], &fs->pool)` verification

*High (3):*
- **H-1r3**: veunstk doesn't auto-claim pending fees — user loses accumulated fees when position is zeroed on unstake → Fixed: added warning log VE:WARN:UNCLAIMED; documented that client SDKs MUST batch veclmfe + veunstk in same TX
- **H-2r3**: vpfinit missing data_len >= 720 check — comment said 664, struct now 720 bytes; writes to ve_* fields at offset 664+ without bounds check → Fixed: added `data_len < 720` check
- **H-3r3**: vpfinit ve_last_admin0/1 = 0 causes windfall for first stakers — pre-existing AMM admin fees get distributed to first veToken stakers → Fixed: vpfinit now accepts amm_pool (ka[3]) and snapshots pool->admin0/1 into ve_last_admin0/1

*Medium (2):*
- **M-1r3**: vpfrev/vpfcmt missing is_writable check on farming_state (ka[0]) → Fixed: added `is_writable` checks in both handlers
- **M-2r3**: vestake weight .hi not checked after u128 division — could silently truncate for extreme amounts → Fixed: added `wd.hi > 0` overflow check returning ERR_MATH

**Struct changes in round 3:**
- VPoolFarmingState: 704→720 bytes (added ve_total_claimed0, ve_total_claimed1)
- vpfinit account layout: 4→5 accounts (added amm_pool at ka[3] for admin fee snapshot)

**Binary:** ~250KB, ~10,333 lines

**V2 Self-Roast Round 12 (2026-02-24) — 3 issues found (2 RESOLVED, 1 FLAGGED):**

- **H-1 FLAGGED**: `vp_update_top10` is dead code — defined but never called from any handler. The farming top-10 buyer leaderboard is never populated, so `vpfrev` always finds `valid_count == 0` and skips to cleanup. Farming lottery rewards are permanently locked and never distributed. Fix requires wiring `vp_update_top10` into AMM swap handlers when farming state account is passed — deferred to integration phase (not a simple patch).
- **H-2 RESOLVED**: `vp_calc_tokens_out` bonding curve math has premature integer division — `sol_term = (2*slope*effective_sol) / VP_SCALE` truncates to 0 when `2*slope*effective_sol < 1e9`. With slope=200, buys below ~0.0025 SOL always fail with ERR_MATH despite VP_MIN_BUY being 0.001 SOL. Fix: restructured to use the direct conjugate formula `tokens = 2*SOL*sqrt(SCALE) / (sqrt(disc_scaled) + b*sqrt(SCALE))` which avoids premature division and maintains full u128 precision throughout.
- **M-1 RESOLVED**: vpsell SOL accounting gap — `sol_raised -= sol_out_gross` over-deducts by `fee_to_pool` (0.5%) on every sell. The pool's share of the sell fee should stay in `sol_raised` (matching vpbuy's symmetric treatment). This trapped ~0.5% of every sell as phantom lamports in the PDA and made graduation artificially harder. Fix: changed to `sol_raised -= (sol_out_net + fee_to_balance)` which only deducts what actually leaves the pool.

**Verified safe (no issues):** Vesting math convergence (5%/2% rates with dust threshold guarantee full drain), vesting token conservation (`claimed + unclaimed = initial` invariant).

**V2 Self-Roast Round 11 (2026-02-24) — 3 issues, ALL RESOLVED:**

- **H-1 RESOLVED**: Caesar hash collision enables double-claiming in `vpclaim`. The 6-byte caesar hash (`vp_wallet_matches`) has ~2^48 collision space. An attacker who grinds a wallet matching a holder's hash creates a SEPARATE claim PDA (derived from full 32-byte pubkey) initialized with the same holder balance — double-minting tokens. Cost: ~3 GPU-days (~$384). Fix: zero `h->balance = 0` atomically after claim PDA initialization, so second collision wallet finds balance=0 → ERR_ZERO. Also added `ka[0].is_writable` check.
- **M-1 RESOLVED**: `claimcrt` modifies `slot->creator_unclaimed` and `slot->creator_last_claim` in ka[0] data without checking `ka[0].is_writable`. Added writability gate. Not exploitable (Solana runtime enforces writability post-execution) but provides early failure with clear error code.
- **L-2 RESOLVED**: 7 virtual pool handlers (`initglobal`, `vpcreate`, `vpbuy`, `vpsell`, `vpflush`, `vpgrad`, `vpfinit`) all write to ka[0] (global PDA) without `is_writable` check. Solana runtime enforces this at VM level, but explicit checks give clearer errors (ERR_IMMUT vs generic runtime rejection) and save CU on early exit. Added `ka[0].is_writable` check to all 7.

**V2 Self-Roast Round 10 (2026-02-24) — 3 issues, ALL RESOLVED:**

- **H-1 RESOLVED**: vpgrad CPI minting has a chicken-and-egg flaw — `InitializeMint2` creates the token mint, then `MintTo` tries to send AMM/DAO tokens to ka[4]/ka[5]. But Token Program's `MintTo` requires destination to be an *initialized* token account for the matching mint, which can't exist before the mint was just created in the same instruction. vpgrad doesn't call `InitializeAccount` between the two CPIs → MintTo always fails → graduation CPI non-functional. Fixed: removed MintTo calls from vpgrad (keep InitializeMint2), deferred AMM/DAO minting to vpfinit which already recomputes allocations from frozen slot fields. Reduced min accounts from 7 to 5.
- **M-1 RESOLVED**: Slot 0 can only hold 1390 holders, not 1400. `initglobal` creates 10240-byte account, `VP_HEADER_SIZE=64` leaves 10176 bytes for slot 0. After 440-byte slot header, 9736 bytes remain → `floor(9736/7) = 1390` holders. But `VP_SLOT_MAX_HOLDERS=1400`, so the guard allows writing holder 1391+ → OOB Access Violation at byte 10240. Fixed: added dynamic bounds check (`hptr + 7 > data + data_len → ERR_FULL`) before writing new holder.
- **M-2 RESOLVED**: vpcreate realloc path is dead code. When slot 0 is occupied, vpcreate increments `num_slots` and tries slot 1 at offset `64 + 10240 = 10304` — 64 bytes past the 10240-byte account → Access Violation → TX rollback. The fee_balance deduction is harmless (rolled back) but confusing. Fixed: added `needed > data_len → ERR_FULL` check before incrementing num_slots.

**V2 Self-Roast Round 9 (2026-02-24) — 2 issues, ALL RESOLVED:**

- **C-1 RESOLVED**: vpgrad has NO graduation fallback — `CLAUDE.md` documented "C-2 RESOLVED: graduation fallback" from early implementation but the code was never added. If `vp_can_graduate(slot)` is true and the triggerer is unresponsive: vpgrad returns ERR_AUTH (only triggerer can call), vpflush returns ERR_INV (`vp_can_graduate` blocks it) → pool permanently bricked. Fixed: added time-based fallback — after `VP_FLUSH_SECS` (1 hour), anyone can call vpgrad (0.1% reward incentivizes crankers).
- **L-1 RESOLVED**: vpflush and vpcreate use unguarded `hdr->fee_balance += X` while vpbuy/vpsell use `vp_safe_add`. Practically safe (need 18.4B SOL to overflow) but inconsistent. Fixed: both now use `vp_safe_add` for defense-in-depth.

**V2 Self-Roast Round 8 (2026-02-24) — 2 issues, ALL RESOLVED:**

- **H-1 RESOLVED**: vpbuy missing `tokens_sold <= total_supply` cap. Repeated buy-sell cycles inflate `tokens_sold` past `total_supply` via 80% extraction (each cycle nets +0.8X to tokens_sold). Can happen organically in actively-traded pools. When `tokens_sold > total_supply`, vpgrad returns ERR_MATH at the `unsold = total_supply - sold` calculation — permanently blocking graduation. Fixed: added `if (new_sold > slot->total_supply) return ERR_MATH;` after the safe_add. Correct economic behavior: supply exhausted → no more buys until sellers return 20% to curve.
- **L-1 RESOLVED**: `vp_sqrt_u128` has UB when `n.hi` has bit 63 set — `1ULL << 64` is undefined in C. Unreachable with current parameter caps (base/slope/sold ≤ 1e18) but latent UB that aggressive optimization could miscompile. Fixed: clamp initial guess to `0x8000000000000000ULL` when shift would reach 64.

**V2 Self-Roast Round 7 (2026-02-24) — 0 issues (clean):**

Systematic audit of: vpbuy tau_B, vpsell extraction math, vp_calc_grad_target formula, vpfcmt/vpfrev farming rewards, vp_update_top10 bounds, build_pool_seeds PDA, vpclaim first-claim, vpgrad allocation token conservation proof, vp_calc_tokens_out u128 precision, vestake/veunstk/veclmfe edge cases, wdrawfee ve-awareness, farming_state cross-verification.

**V2 Self-Roast Round 6 (2026-02-24) — 2 issues, ALL RESOLVED:**

- **C-1 RESOLVED**: vpfinit can be called multiple times with different farming_state accounts, overwriting `slot->real_pool`. The ERR_INIT check (line 8279) only prevents re-initializing the SAME farming_state — a second call with a NEW farming_state + fake AMM pool overwrites real_pool → veclmfe validates against fake pool → fee drain. Fixed: added `if (!vp_is_zero_pubkey(&slot->real_pool)) return ERR_INIT;` — vpfinit can only link once.
- **H-1 RESOLVED**: vpfinit has NO authority check — `ka[2]` (signer) is never verified against creator or graduation_triggerer. Anyone can front-run legitimate vpfinit with a fake AMM pool (that contains the graduated mint as t0/t1). Combined with C-1, enables permanent farming/veToken subsystem hijack. Fixed: added `if (!pk_eq(ka[2], &slot->creator) && !pk_eq(ka[2], &slot->graduation_triggerer)) return ERR_AUTH;`.

**V2 Self-Roast Round 5 (2026-02-24) — 4 issues, ALL RESOLVED:**

- **C-1 RESOLVED**: vpgrad never sets `slot->real_pool` — it's zeroed in vpcreate and never updated. vpfinit checks `pk_eq(ka[3], &slot->real_pool)` which is always the zero address → vpfinit ALWAYS fails → entire farming + veToken subsystem bricked post-graduation. Fixed: vpfinit now verifies AMM pool contains graduated mint (t0_mint or t1_mint == real_mint), then sets `slot->real_pool = *ka[3].key`.
- **H-1 RESOLVED**: flashrepy adds flash loan fees to bal0/bal1 but NOT to admin0/admin1. veToken fee distribution and wdrawfee both read admin fee growth — flash loan fees were invisible to both. Fixed: added `s->admin0 += fee0; s->admin1 += fee1;` in flashrepy.
- **M-1 RESOLVED**: vpfrev `actually_minted` initialized to `committer_cut` unconditionally, but committer mint is conditional on `ka_num > 8`. If committer doesn't provide enough accounts, mint is skipped but `farming_rewards_left` still decremented by unminted amount. Fixed: `actually_minted = 0`, committer_cut added only after successful CPI mint.
- **L-1 RESOLVED**: veclmfe per-claim calculation `u128_div64(c0, VE_ACC_SCALE).lo` silently truncates when result overflows u64. Added `.hi > 0` check, caps at u64_max (vault balance cap at lines 8720-8727 already prevents over-withdrawal).

**V2 Self-Roast Round 4 (2026-02-24) — 4 issues, ALL RESOLVED:**

- **C-1 RESOLVED**: Flash loan MSB sentinel in pool->admin0 inflated veclmfe virtual_admin by ~9.2e18, enabling vault drain via flashloan→veclmfe→flashrepy TX. Fixed by masking with `FLASH_FLAG_MASK (0x7FFFFFFFFFFFFFFF)` before fee accounting. Also applied in wdrawfee.
- **C-2 RESOLVED**: vpfrev deposit return used SOL lamport manipulation instead of CPI token transfer. Amount (1T) exceeded farming_state lamport balance (~5M), so return always failed — committers permanently lost IDL deposits. Fixed: CPI token transfer via vpmint PDA signing. Added deposit_vault authority verification in vpfcmt. vpfrev account layout shifted: ka[6]=deposit_vault, ka[7]=committer_idl_ata, ka[8]=committer_reward_ata, ka[9+]=winners.
- **H-1 RESOLVED**: ve_acc_fee accumulator overflow when `u128_div64(...)` result > u64. Silent truncation via `.lo` corrupted accumulator, permanently blocking all staker claims. Fixed: check `.hi > 0` and cap increment to prevent wrap.
- **M-1 RESOLVED**: wdrawfee could front-run veToken fee claims by depleting vaults. Stakers' debt updated to full acc_fee on capped claims, permanently losing difference. Fixed: optional farming_state account (ka[7]) in wdrawfee — when ve_total_weight > 0, reserves 50% for ve stakers and only withdraws remaining 50%.

**Account changes in round 4:**
- vpfrev: ka[6]=deposit_vault, ka[7]=committer_idl_ata added (was: ka[6]=committer_reward_ata). Min accounts for committer reward: 9 (was 7)
- wdrawfee: optional ka[7]=farming_state for ve-aware fee reservation

## Key Features

### Core AMM
- **Dual Pool Types**: Stable pools (high amp, pegged assets) and volatile pools (amp=1, any assets)
- **Mixed Token Support**: Pools can contain both SPL Token (original) and Token-2022 tokens
- **TWAP Oracle**: On-chain manipulation-resistant price feed with confidence scores
- **On-chain Analytics**: 24 hourly + 7 daily OHLCV candles, trade counts, TWAP
- **N-Token Pools**: Support for 2-8 token pools with AeX402 math
- **Pool Registry**: On-chain enumeration of all pools
- **Farming**: LP staking with time-locked rewards
- **Lottery**: LP-based lottery system

### Security & Hardening
- **Circuit Breakers**: Auto-pause on abnormal price deviation or volume spikes
- **Rate Limiting**: Per-epoch swap volume and count limits (5-minute epochs)
- **Oracle Integration**: Pyth/Switchboard price validation with staleness checks
- **Flash Loan Protection**: Reentrancy guard, dynamic fees based on size/volatility
- **Governance Security**: Vote weight snapshots prevent flash loan attacks
- **MEV Protection**: Proportional slippage requirements for large LP operations
- **CL JIT Protection**: Minimum 5-minute position duration prevents JIT liquidity attacks

### Advanced Features
- **LP Token Governance**: Pools as DAOs with proposal/vote/execute system
- **Limit Orderbook Hybrid**: On-chain limit orders + AMM liquidity
- **On-Chain ML Brain (V2)**: Q-Learning with volatility-aware states, configurable reward weights, explicit action tracking (see ML section below)
- **Concentrated Liquidity**: Uniswap v3-style tick ranges for capital efficiency
- **Virtual Pool Graduation**: Zero-rent token launches with bonding curves, extraction-based dynamic targets, Pool DAO, veToken staking (see `graduation-plan.md`, `paper/aex-402-v2.pdf` §6)

## Documentation

**Key Files:**
- `CLAUDE.md` (this file): Complete architecture and development guide
- `INTEGRATION.md`: TypeScript integration guide with full examples
- `SECURITY_AUDIT.md`: Initial security audit findings (all resolved)
- `SECURITY_AUDIT_DEEP.md`: Deep audit covering all 35 issues (all resolved)
- `SECURITY_AUDIT_TOKEN22.md`: Token-2022 specific security review
- `WHITEPAPER.md`: Mathematical foundations and AeX402 curve design
- `paper/aex-402-v2.tex` / `paper/aex-402-v2.pdf`: Formal whitepaper v2 — GBC theory, RL adaptation, mechanism design, virtual pool graduation specs, governance extensions
- `graduation-plan.md`: Virtual pool graduation system design
- `tests/COVERAGE_REPORT.md`: Test coverage report (72/72 handlers covered)
- `fuzz/README.md`: Fuzzing guide and invariant documentation
- `docs/TOKEN22_TRANSFER_HOOKS.md`: Transfer hook implementation details
- `docs/UI_INTEGRATION.md`: Frontend integration patterns

## Build & Test Commands

### Build

```bash
# Find your Solana toolchain version (may differ from v1.52)
ls ~/.cache/solana/

# Compile C to object file (adjust v1.52 to your installed version)
SOLANA_LLVM=~/.cache/solana/v1.52/platform-tools/llvm/bin
$SOLANA_LLVM/clang -target sbf -O2 -fno-builtin -c aex402.c -o aex402.o

# Link with official Solana linker script (REQUIRED - see bpf_official.ld)
$SOLANA_LLVM/ld.lld -z notext -shared --Bdynamic bpf_official.ld --entry entrypoint -o aex402.so aex402.o

# Verify build succeeded
ls -lh aex402.so  # Should be ~248KB
llvm-nm aex402.o | grep " T " | wc -l  # Should show 70+ functions

# Deploy (use existing program keypair for upgrades)
solana program deploy aex402.so \
  --upgrade-authority keypair.json \
  --program-id 3AMM53MsJZy2Jvf7PeHHga3bsGjWV4TSaYz29WUtcdje \
  --url devnet
```

**Build Troubleshooting:**

```bash
# If functions are missing from .o file:
# - Function names MUST be ≤10 chars (Solana LLVM limitation)
# - Verify: llvm-nm aex402.o | grep " T "

# If deploy fails with "ELF error":
# - Must use bpf_official.ld (not standard ld.lld)
# - Standard linker adds GNU_RELRO/GNU_STACK headers that Solana rejects

# If you see "memcpy undefined":
# - No struct assignment (e.g., `s1 = s2`)
# - Copy fields manually: `s1.field = s2.field`
```

### Test

The real integration harness for this program is **`aex-sim`** — hundreds of Zig
scenarios driving the actual CLI against the actual handlers. It was invisible in
this document until 2026-08-26, which is part of why the strategy handlers below
were never tested.

```bash
cd sim && zig build && ./zig-out/bin/aex-sim --list
```

Static audits of the C source (no devnet, no network):

```bash
aex audit handlers aex402.c   # decision data read from unverified accounts
aex audit offsets  aex402.c   # a guard one handler sets and another clears
aex audit sweep    aex402.c   # error codes never returned; numeric collisions
```

> **The `tests/*.ts` layer below is abandoned** (since 2026-03-31) and its
> dependencies are not installed, so every command in it fails. It is recorded
> rather than deleted because the files still exist and may be worth reviving.
> Its "all 72 handlers" claim was wrong regardless: `entrypoint()` dispatches 117.
> See `tests/CLAUDE.md` for what actually runs.


```bash
# Install dependencies
npm install

# Run comprehensive handler tests (all 72 handlers)
npx ts-node tests/comprehensive-test.ts

# Run handler tests (requires devnet SOL)
npx ts-node tests/test-all-handlers.ts

# Run analytics tests
npx ts-node tests/test-analytics.ts

# Run full E2E flow
npx ts-node tests/test-full-flow.ts

# Inspect pool state
npx ts-node tests/check-pool.ts

# Stress tests
npx ts-node tests/stress-test-swaps.ts        # High-frequency swap stress test
npx ts-node tests/stress-100k-random.ts       # 100k random operations
npx ts-node tests/create-random-pools.ts      # Pool creation stress test

# Test real pools (testnet/mainnet)
npx ts-node tests/test-real-pool.ts
```

### Fuzz Testing

The `fuzz/` directory contains honggfuzz-based fuzz tests for mathematical invariants:

```bash
# Install honggfuzz
cargo install honggfuzz

# Fuzz StableSwap math functions
cd fuzz
cargo hfuzz run fuzz_calc_d       # Invariant D calculation
cargo hfuzz run fuzz_calc_y       # Swap output calculation
cargo hfuzz run fuzz_swap         # Full swap simulation
cargo hfuzz run fuzz_liquidity    # Add/remove liquidity

# Debug crash artifacts
cargo hfuzz run-debug fuzz_calc_d hfuzz_workspace/fuzz_calc_d/CRASH_FILE
```

**Invariants tested:**
- D monotonicity and symmetry
- Swap output bounds and D preservation
- No arbitrage conditions
- LP token proportionality

## TypeScript Client

The repository includes TypeScript client libraries for interacting with the program:

**Files:**
- `client.ts`: Query helpers for pools, farms, lotteries, registry
- `types.ts`: Complete type definitions and discriminators
- `INTEGRATION.md`: Comprehensive integration guide with examples

**Quick Start:**

```typescript
import { Connection, PublicKey } from '@solana/web3.js';
import { PROGRAM_ID, DISCRIMINATORS } from './types';

// Query all 2-token pools
const connection = new Connection('https://api.devnet.solana.com');
const pools = await connection.getProgramAccounts(PROGRAM_ID, {
  filters: [
    { dataSize: 1024 },  // Pool size
    { memcmp: { offset: 0, bytes: DISCRIMINATORS.POOL } }
  ]
});

// Parse pool data (see types.ts for full Pool structure)
const poolData = pools[0].account.data;
// First 8 bytes: discriminator "POOLSWAP"
// Bytes 8-40: authority (PublicKey)
// Bytes 40-72: token0_mint (PublicKey)
// ... (see State Structures section)
```

**Building Instructions:**

```typescript
import { TransactionInstruction } from '@solana/web3.js';

// Swap instruction (token 0 -> token 1)
const swapIx = new TransactionInstruction({
  programId: PROGRAM_ID,
  keys: [
    { pubkey: poolPDA, isSigner: false, isWritable: true },
    { pubkey: vault0, isSigner: false, isWritable: true },
    { pubkey: vault1, isSigner: false, isWritable: true },
    { pubkey: userToken0, isSigner: false, isWritable: true },
    { pubkey: userToken1, isSigner: false, isWritable: true },
    { pubkey: userWallet, isSigner: true, isWritable: false },
    { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
  ],
  data: Buffer.concat([
    Buffer.from([0xf9, 0xe3, 0xa7, 0xc8, 0xd1, 0xe4, 0xb9, 0xf2]),  // swapt0t1
    new BN(amountIn).toArrayLike(Buffer, 'le', 8),
    new BN(minOut).toArrayLike(Buffer, 'le', 8),
  ])
});
```

See `INTEGRATION.md` for complete examples of all instruction types.

## Architecture

### Solana C ABI Requirements

This program uses the **correct Solana C ABI**, which differs from Rust:

```c
// CORRECT - C entrypoint receives raw serialized buffer
uint64_t entrypoint(const uint8_t *input);

// Must manually deserialize using sol_deserialize pattern
// See deser() function in aex402.c
```

**Critical:** The `bpf_official.ld` linker script is required. Standard `ld.lld` adds GNU_RELRO/GNU_STACK headers that Solana's BPF loader rejects.

### Instruction Dispatch

8-byte discriminators with switch-based dispatch (function pointer tables don't work in BPF data sections):

| Discriminator | Handler | Description |
|---------------|---------|-------------|
| `0xf2b9e4d1c8a7e3f9` | createpool | Create 2-token pool |
| `0x27c933bce5c77c1b` | createpn | Create N-token pool (2-8 tokens) |
| `0x82c69e91e17587c8` | swap | Generic swap with from/to indices |
| `0x642af2b7e0f14e2a` | swapt0t1 | Token 0 to Token 1 swap |
| `0x3a0e131bac75c4c8` | swapt1t0 | Token 1 to Token 0 swap |
| `0xf1a8e3c7b2d9e5f8` | swapn | N-token pool swap |
| `0xa2e7c4f8b3d1e5a9` | addliq | Add liquidity (2-token) |
| `0xe3f7a2c8d1b9e4f6` | addliqn | Add liquidity (N-token) |
| `0x2e54bc2c75c9f902` | remliq | Remove liquidity (2-token) |
| `0xb3f8e2a5c7d9e1b4` | remliqn | Remove liquidity (N-token) |
| `0xd2e4f1a8c3b7e9d5` | migt0t1 | Migration swap (1:1 with 0.1337% fee) |
| `0x1888779426393db8` | migt1t0 | Migration swap reverse |
| `0x6d7b0c8e2f1a3d5c` | createfarm | Create farming period |
| `0xf8d4e1a7c3b9e2f7` | stakelp | Stake LP tokens |
| `0x4166bf654e34f8bc` | unstakelp | Unstake LP tokens |
| `0x075762b7e0d6ec9b` | claimfarm | Claim farming rewards |
| `0x6c6f74746572793c` | createlot | Create lottery for pool |
| `0xe795383a4eef48fc` | enterlot | Enter lottery |
| `0x1361225a4d7cbc11` | drawlot | Draw lottery winner |
| `0x7e7b5e3f15f93cf4` | claimlot | Claim lottery prize |
| `0x5e8c3b0d0f3e4a9f` | initt0v | Initialize token 0 vault |
| `0x7a4e9f1c3b2d5e8a` | initt1v | Initialize token 1 vault |
| `0xf4d1e9a3c5b8e7f2` | initlpm | Initialize LP mint |
| `0x51c98b4e3c2e12e6` | addliq1 | Single-sided add liquidity |
| `0xe075762b7e0d6ec9` | setpause | Pause/unpause pool |
| `0x8f3a2e5b7c9d1f4a` | updfee | Update swap fee |
| `0xf9e5d3a2c8b1e7f8` | wdrawfee | Withdraw admin fees |
| `0xc1d9e3f7a5b8e2c4` | commitamp | Commit amp change (timelock) |
| `0x9a1c5e3f7b2d8e6a` | rampamp | Start amp ramping |
| `0x3c9427bb15a21053` | stopramp | Stop amp ramping |
| `0xf5e2a7c9d3b1e8f4` | initauth | Initiate authority transfer |
| `0xf6e8d2a4c7b9e1f5` | complauth | Complete authority transfer |
| `0xf7e3a9c1d5b2e8f6` | cancelauth | Cancel authority transfer |
| `0xfefb83015f028cec` | locklp | Lock LP tokens |
| `0xca8593f45ce88b1e` | claimulp | Claim unlocked LP |
| `0xa1b2c3d4e5f60718` | initreg | Initialize pool registry |
| `0xb2c3d4e5f6071829` | regpool | Register pool in registry |
| `0xc3d4e5f607182930` | unregpool | Unregister pool from registry |
| `0xd4e5f60718293041` | initrega | Initiate registry authority transfer |
| `0xe5f6071829304152` | complrega | Complete registry authority transfer |
| `0xf60718293041526` | cancelrega | Cancel registry authority transfer |
| `0x7477617067657401` | gettwap | **TWAP Oracle** - Get manipulation-resistant price |
| `0x1a66fb4bc5652569` | th_exec | **Transfer Hook Execute** - Called on every LP transfer |
| `0xebeb58a7310d222b` | th_init | **Transfer Hook Init** - Initialize ExtraAccountMetaList |
| `0xcb01cb01cb01cb01` | setcb | Configure circuit breaker parameters |
| `0xcb02cb02cb02cb02` | resetcb | Reset triggered circuit breaker |
| `0x726c01726c01726c` | setrl | Configure rate limiting |
| `0x6f72636c01020304` | setoracle | Configure Pyth/Switchboard oracle validation |
| `0x676f7670726f7000` | govprop | Create governance proposal |
| `0x676f76766f746500` | govvote | Vote on proposal (LP-weighted) |
| `0x676f7665786563` | govexec | Execute passed proposal |
| `0x676f76636e636c` | govcncl | Cancel proposal (proposer only) |
| `0x696e6974626f6f6b` | initbook | Initialize limit orderbook |
| `0x706c6163656f7264` | placeord | Place limit order |
| `0x63616e63656c6f72` | cancelord | Cancel limit order |
| `0x66696c6c6f726465` | fillord | Fill limit order (keeper) |
| `0x696e697461696665` | initaifee | Initialize AI fee manager |
| `0x757064616966656` | updaifee | Update AI fee (authority required) |
| `0x636667616966656` | cfgaifee | Configure AI fee bounds |
| `0x636c706f6f6c0101` | initclpl | Initialize concentrated liquidity pool |
| `0x636c6d696e740101` | clmint | Mint CL position (add liquidity to range) |
| `0x636c6275726e0101` | clburn | Burn CL position (remove liquidity) |
| `0x636c636f6c6c6563` | clcollect | Collect accumulated CL fees |
| `0x636c737761700101` | clswap | Swap through concentrated liquidity |
| `0x666c6173686c6f61` | flashloan | Initiate flash loan |
| `0x666c61736872657` | flashrepy | Flash loan repay callback |
| `0x6d756c7469686f70` | multihop | Multi-pool swap route (2-4 hops) |
| `0x696e69746d6c6272` | initml | **ML Brain V2** - Initialize Q-learning brain for pool |
| `0x6366676d6c627261` | cfgml | Configure ML brain (V2: reward weights, 3 accounts) |
| `0x747261696e6d6c00` | trainml | Batch Q-learning training (V2: max_iters + rand_seed) |
| `0x6170706c796d6c00` | applyml | Apply ML-suggested action manually |
| `0x6c6f676d6c737461` | logml | Log ML state for monitoring |
| `0x6d69676372656174` | migcreate | Create migration pool (token conversion) |
| `0x6d69676465706f00` | migdepo | Deposit new tokens into migration pool |
| `0x6d69677377617000` | migswap | Execute migration (convert old→new tokens) |
| `0x6d6967636c6d0000` | migclm | Claim vested migration tokens |
| `0x6d6967636c6f7365` | migclose | Close migration pool + reclaim |
| `0x6d69677061757365` | migpause | Pause/unpause migration pool |
| `0x6d69676175746800` | migauth | Transfer migration pool authority |
| `0x0066657274696e69` | initref | Create referral link PDA |
| `0x00006665726d6c63` | clmref | Claim referral rewards |
| `0x7670666e69740000` | vpfinit | **V2** - Initialize farming state post-graduation |
| `0x767064656c656774` | vpdelegt | **V2** - Irrevocable creator authority delegation |
| `0x76657374616b6500` | vestake | **V2** - Lock tokens for veToken fee share |
| `0x7665756e73746b00` | veunstk | **V2** - Unlock veToken position after expiry |
| `0x7665636c6d666500` | veclmfe | **V2** - Claim AMM fee share from veToken position |
| `0x696e69746e760101` | initnv | Initialize N-pool vault for a token index |
| `0x6e706c6d696e6974` | initnlpm | Initialize N-pool LP mint |
| `0x7265636c61696d76` | reclaimvt | Reclaim locked veToken after voting ends |

### Token-2022 Support

The AMM supports both SPL Token (original) and Token-2022:
- **Mixed pools**: N-Pools can contain tokens from both programs
- **Auto-detection**: CPI calls automatically use the correct token program
- **Fee-on-transfer**: Parses TransferFeeConfig extension to calculate net amounts
- **Transfer hooks**: Implements the `spl-transfer-hook-interface` for LP token hooks

**Extension parsing**: Uses TLV (Type-Length-Value) format starting at offset 83 in mint data.

**Supported extensions**:
- `TransferFeeConfig` (type 1): Fee-on-transfer tokens
- `TransferHook` (type 14): Transfer hook configuration

### TWAP Oracle

The `gettwap` handler returns time-weighted average prices with confidence scores:

```
Accounts: [pool]
Data: [window(1)]  - 0=1h, 1=4h, 2=24h, 3=7d

Returns (encoded in u64):
  - Bits 0-31:  TWAP price (scaled 1e6)
  - Bits 32-47: Sample count
  - Bits 48-63: Confidence (0-10000 = 0-100%)
```

**Confidence score** based on:
- Sample count (more candles = higher confidence)
- Trade count (more trades = higher confidence)
- Price variance (lower variance = higher confidence)

### State Structures

**Pool (1024 bytes):** 2-token AeX402 pool with on-chain OHLCV analytics
- 24 hourly candles + 7 daily candles (delta-encoded, 12 bytes each)
- Trade count, sum, max/min price tracking
- Authority, vaults, balances, fees, amp ramping state

**NPool (2048 bytes):** N-token pool supporting 2-8 tokens

**Candle (12 bytes):** Delta-encoded OHLCV
```c
typedef struct {
    uint32_t open;    // Base price (scaled 1e6)
    uint16_t high_d;  // High = open + high_d
    uint16_t low_d;   // Low = open - low_d
    int16_t close_d;  // Close = open + close_d
    uint16_t volume;  // Volume in 1e9 units
} Candle;
```

### AeX402 Math (StableSwap Invariant)

The AeX402 curve is based on Curve Finance's StableSwap invariant, which blends constant product (xy=k) and constant sum (x+y=k) formulas via an amplification coefficient A.

**Core Invariant (2-token pools):**
```
4A(x + y) + D = 4AD + D³/(4xy)
```

where:
- `x`, `y` = token balances
- `A` = amplification coefficient (1 to 100,000)
- `D` = invariant (total value when prices are 1:1)

**Curve behavior:**
- `A = 1`: Acts like constant product (Uniswap) - good for volatile pairs
- `A = 100,000`: Acts like constant sum - near-zero slippage for pegged assets
- `A` in between: Hybrid behavior optimized for stable pools

**Newton's Method Computation:**

All swap and liquidity operations require solving for D (invariant) and y (output amount) using Newton-Raphson iteration:

```c
// Algorithm: Compute D given balances x, y, amp A
S = x + y
D = S  // Initial guess
for i = 0 to 255:
    D_P = D³ / (4xy)
    D_new = (4A·S + 2·D_P) · D / (4A·D + 3·D_P - D)
    if |D_new - D| < epsilon: return D_new
    D = D_new
return D
```

Convergence typically occurs in 6-10 iterations. Max 255 iterations prevents infinite loops.

**N-Token Generalization (3-8 tokens):**
```
Anⁿ Σᵢ xᵢ + D = ADnⁿ + D^(n+1) / (nⁿ Πᵢ xᵢ)
```

This allows pools like 3pool (USDC/USDT/DAI) or 4pool setups.

**Manual u128 arithmetic implementation (no stdlib):**
- `u128_add`, `u128_sub`, `u128_mul`, `u128_mul64`, `u128_div64`

The sBPF VM lacks native 128-bit integers, so all operations are implemented manually with carry propagation to prevent overflow during D³ calculations.

### CPI Token Transfers

`cpi_xfer()` helper for SPL Token transfers with PDA signing via `sol_invoke_signed_c`.
`cpi_mint()` for minting LP tokens to users.
`cpi_burn()` for burning LP tokens from users.

Pool PDA seeds for signing: `["pool", t0_mint(32 bytes), bump(1 byte)]`

### Security Validations

Each handler performs these security checks (see SECURITY_AUDIT.md for details):

1. **Discriminator check** (`chk_disc`): Verifies account type via 8-byte magic header
2. **Owner validation** (`is_prog_owner`, `is_token_acc`): Verifies account owners
3. **PDA verification** (`vrfy_pool`): Derives PDA from seeds and compares to account
4. **Vault validation**: Verifies vault pubkeys match pool state
5. **Actual token transfers**: CPI calls before state updates

Account discriminators:
- `POOL_DISC`: "POOLSWAP" (0x504f4f4c53574150)
- `NPOOL_DISC`: "NPOOLSWA" (0x4e504f4f4c535741)
- `FARM_DISC`: "FARMSWAP" (0x4641524d53574150)
- `UFARM_DISC`: "UFARMSWA" (0x554641524d535741)
- `LOT_DISC`: "LOTTERY!" (0x4c4f545445525921)
- `LOTE_DISC`: "LOTENTRY" (0x4c4f54454e545259)
- `MLBRAIN_DISC`: "MLBRAIN!" (0x4d4c425241494e21)
- `MIGPOOL_DISC`: "MIGPOOL!" (0x4d49475041534521)
- `MIGCLM_DISC`: "MIGCLM!!" (0x4d494743434d2121)
- `REFLINK_DISC`: "REFLINK!" (0x214b4e494c464552)

### Timelocked Operations

Two-step commit-reveal pattern with `COMMIT_DELAY` (3600s = 1 hour):

1. **Amp changes:** `commitamp` (sets pending + timestamp) → wait 1hr → `rampamp` (executes over `RAMP_MIN` 86400s)
2. **Authority transfer:** `initauth` (sets pending + timestamp) → wait 1hr → `complauth` (new authority signs)

Both can be cancelled: `stopramp` / `cancelauth`

### Instruction Data Formats

All instructions are prefixed with 8-byte discriminator, then:

| Handler | Data after discriminator |
|---------|--------------------------|
| swap | `from:u8, to:u8, amt:u64, min:u64, deadline:i64` |
| swapt0t1/swapt1t0 | `amt:u64, min:u64` |
| addliq | `amt0:u64, amt1:u64, min_lp:u64` |
| remliq | `lp_amt:u64, min0:u64, min1:u64` |
| createpool | `amp:u64, bump:u8` |
| setpause | `paused:u8` |
| updfee | `fee_bps:u64` |
| rampamp | `target_amp:u64, duration:i64` |
| stakelp/unstakelp | `amount:u64` |
| locklp | `amount:u64, duration:i64` |
| createlot | `ticket_price:u64, end_time:i64` |
| enterlot | `ticket_count:u64` |
| drawlot | `random_seed:u64` |
| createpn | `amp:u64, n_tokens:u8, bump:u8` |
| addliqn | `amt0:u64, amt1:u64, ..., amtN:u64, min_lp:u64` |
| remliqn | `lp_amt:u64, min0:u64, min1:u64, ..., minN:u64` |
| swapn | `from_idx:u8, to_idx:u8, amt:u64, min_out:u64` |
| vpfinit | `slot_index:u32` |
| vpdelegt | `slot_index:u32` |
| vestake | `amount:u64, duration:i64, slot_index:u32` |
| veunstk | `slot_index:u32` |
| veclmfe | (no data after discriminator) |

### Account Ordering (All Handlers)

**Pool Setup:**
- `createpool`: `[pool, mint0, mint1, authority(signer), system_program]`
- `initt0v/initt1v`: `[pool, vault, authority(signer), system_program]`
- `initlpm`: `[pool, lp_mint, authority(signer), system_program]`

**Swaps (7 accounts):**
- `swap/swapt0t1/swapt1t0`: `[pool, vault0, vault1, user_t0, user_t1, user(signer), token_program]`
- `migt0t1/migt1t0`: same as swap

**Liquidity (9 accounts):**
- `addliq/remliq`: `[pool, vault0, vault1, lp_mint, user_t0, user_t1, user_lp, user(signer), token_program]`
- `addliq1`: `[pool, vault_in, lp_mint, user_in, user_lp, user(signer), token_program]` (8 accounts)

**Admin (2-6 accounts):**
- `setpause/updfee/commitamp/stopramp/cancelauth`: `[pool, authority(signer)]`
- `rampamp`: `[pool, authority(signer)]`
- `initauth`: `[pool, authority(signer), new_authority]`
- `complauth`: `[pool, new_authority(signer)]`
- `wdrawfee`: `[pool, vault0, vault1, dest0, dest1, authority(signer), token_program]`

**Farming:**
- `createfarm`: `[farm, pool, reward_mint, authority(signer), system_program]`
- `stakelp/unstakelp`: `[user_position, farm, user_lp, lp_vault, user(signer), token_program]`
- `claimfarm`: `[user_position, farm, pool, reward_vault, user_reward, user(signer), token_program]`
- `locklp`: `[user_position, farm, user(signer), system_program]`
- `claimulp`: `[user_position, farm, user(signer), system_program]`

**Lottery:**
- `createlot`: `[lottery(writable), pool, lottery_vault, authority(signer), system_program]`
- `enterlot`: `[lottery, user_entry, user(signer), user_lp, lottery_vault, token_program]`
- `drawlot`: `[lottery, authority(signer), recent_slothashes]`
- `claimlot`: `[lottery, user_entry, user(signer), user_lp, lottery_vault, pool, token_program]`

**N-Token Pools:**
- `createpn`: `[pool, mint0, mint1, ..., mintN, authority(signer), system_program]`
- `addliqn`: `[pool, vault0..vaultN, lp_mint, user_t0..user_tN, user_lp, user(signer), token_program]`
- `remliqn`: same as addliqn
- `swapn`: `[pool, vault_in, vault_out, user_in, user_out, user(signer), token_program]`

**V2: Graduation + veToken + Delegation (post-audit account layouts):**
- `vpgrad`: `[global_pda, triggerer(signer), mint_pda, mint_auth_pda, token_program]` (5 accounts, AMM/DAO minting deferred to vpfinit)
- `vpclaim`: `[global_pda, claim_pda, mint_pda, user_ata, user(signer), mint_auth_pda, sysvar_clock, token_program]` (8 accounts)
- `claimcrt`: `[global_pda, mint_pda, user_ata, user(signer), mint_auth_pda, token_program]` (6 accounts)
- `vpfinit`: `[global_pda, farming_state, authority(signer), system_program]`
- `vpdelegt`: `[global_pda, creator(signer), new_authority]`
- `vestake`: `[ve_position, global_pda, token_mint, user_ata, user(signer), burn_authority, token_program]` (7 accounts)
- `veunstk`: `[ve_position, global_pda, token_mint, user_ata, user(signer), mint_auth_pda, token_program]` (7 accounts)
- `veclmfe`: `[ve_position, pool, fee_vault, user_ata, user(signer), token_program]`
- `vpfcmt`: `[farming_state, committer(signer), global_pda, sysvar_clock, ?slothashes, ?graduated_mint, ?committer_ata, ?farming_vault]`
- `vpfrev`: `[farming_state, caller(signer), graduated_mint, slothashes, mint_auth_pda, token_program, ?committer_ata, ?winner_atas...]` (6+ accounts)

## Constants

```c
MIN_AMP: 1              MAX_AMP: 100000
FEE_BPS: 30             ADMIN_FEE: 50%
MIN_SWAP: 100,000       MIN_DEPOSIT: 100,000,000
NEWTON: 255             RAMP_MIN: 86400 (1 day)
COMMIT_DELAY: 3600      MIG_FEE: 1337 (0.1337%)
MAX_TOKENS: 8           POOL_SIZE: 1024
NPOOL_SIZE: 2048
VP_EXTRACT_PCT: 80      VP_EXTRACT_RETURN: 20
VP_MAX_CREATOR_PCT: 5   VP_DEFAULT_CREATOR_PCT: 5
VP_DAO_ALLOC_PCT: 5     VP_RHO_BPS: 8000
VE_MIN_LOCK_SECS: 1209600   VE_MAX_LOCK_SECS: 7776000
VE_MAX_PCT_BPS: 8000
```

## Error Codes

| Code | Name | Description |
|------|------|-------------|
| 6000 | ERR_PAUSED | Pool is paused |
| 6001 | ERR_AMP | Invalid amplification |
| 6002 | ERR_MATH | Math overflow |
| 6003 | ERR_ZERO | Zero amount |
| 6004 | ERR_SLIP | Slippage exceeded |
| 6005 | ERR_INV | Invalid invariant / PDA mismatch |
| 6006 | ERR_LIQ | Insufficient liquidity |
| 6007 | ERR_VAULT | Vault mismatch |
| 6008 | ERR_EXP | Expired/ended |
| 6009 | ERR_INIT | Already initialized |
| 6010 | ERR_AUTH | Unauthorized |
| 6011 | ERR_RAMP | Ramp constraint |
| 6012 | ERR_LOCK | Locked |
| 6013 | ERR_FARM | Farming error |
| 6014 | ERR_OWNER | Invalid account owner |
| 6015 | ERR_DISC | Invalid discriminator |
| 6016 | ERR_CPI | CPI call failed |
| 6017 | ERR_FULL | Orderbook/registry full |
| 6018 | ERR_BREAKER | Circuit breaker triggered |
| 6019 | ERR_ORACLE | Oracle price validation failed |
| 6020-6024 | (reserved) | Reserved for future use |
| 6025 | ERR_FLASH | Flash loan error |
| 6026 | ERR_COOLDOWN | Operation on cooldown |
| 6027 | ERR_MEV | MEV protection triggered |
| 6028 | ERR_STALE | Stale data (oracle, etc) |
| 6029 | ERR_BIAS | Lottery bias detected |
| 6030 | ERR_DURATION | Minimum duration not met |
| 6031 | ERR_MIG | Migration error (invalid type, ended, etc) |
| 6032 | ERR_CAP | Migration cap exceeded |
| 6033 | ERR_CLOSED | Migration pool closed |
| 6034 | ERR_LIMIT | Wallet 2.5% limit exceeded (virtual pools) |

## Solana LLVM Constraints

**Function names must be ≤10 characters.** Longer names are silently dropped during `.s` → `.o` conversion. Verify with:

```bash
# Check all functions survive compilation
llvm-nm aex402.o | grep " T " | wc -l  # Should show 34+
```

**No struct assignment.** Direct struct copy generates `memcpy` calls which fail linking. Copy fields manually.

**No function pointer tables.** BPF loader doesn't process data section relocations. Use switch statements.

## Binary Size

The compiled `.so` is ~248KB for 90+ handlers - significantly smaller than equivalent Rust/Anchor programs (typically 200-500KB for similar functionality). This is achieved through:
- No Rust runtime/stdlib
- Manual u128 math (~1.5KB)
- Switch dispatch instead of vtables
- `-O2` optimization with `-fno-builtin`

## Key Patterns

### Deserialization

The `deser()` function parses Solana's account serialization format:
- First 8 bytes: account count
- Per account: 1-byte duplicate marker (0-254 = reference to earlier account, 255 = new account)
- New accounts: signer/writable/executable flags, key, owner, lamports, data_len, data, rent_epoch
- After accounts: instruction data length + data + program ID

### Time-Based Checks

Several handlers use `sol_get_clock_sysvar()` for timestamps:
- `rampamp` / `stopramp`: Amp ramping with linear interpolation via `get_amp()`
- `complauth`: Authority transfer delay enforcement
- `claimfarm`: Reward accumulation based on elapsed time
- `enterlot` / `drawlot`: Lottery timing enforcement
- `claimulp`: LP unlock timing

### Analytics Update Flow

`updstats()` is called on every swap to update on-chain analytics:
1. Get current slot from clock sysvar
2. Calculate price from `amt_out * 1e6 / amt_in`
3. Update trade count, sum, max/min prices
4. Roll hourly candles when `slot / SLOTS_PER_HOUR` changes
5. Roll daily candles when `slot / SLOTS_PER_DAY` changes
6. Update current candle's high_d, low_d, close_d, volume

### Farming Reward Math

Uses accumulated reward per share model (scaled by 1e12 for precision):

```
On each claim/stake/unstake:
  time_elapsed = min(now, end_time) - last_update
  new_rewards = time_elapsed * reward_rate
  acc_reward += (new_rewards * 1e12) / total_staked

Per-user pending rewards:
  total_earned = (user.staked * farm.acc_reward) / 1e12
  pending = total_earned - user.reward_debt

After claim:
  user.reward_debt = total_earned
```

### Return Values

All handlers return `uint64_t`:
- `0` (SUCCESS): Transaction succeeded
- `3` (ERR_KEYS): Wrong number of accounts
- `4` (ERR_SIG): Missing required signature
- `5` (ERR_DATA): Invalid instruction data
- `6` (ERR_IMMUT): Account not writable
- `6000-6013`: Custom errors (see Error Codes table)

### Reserved Fields

- `Pool.bloom[128]`: Reserved for future use. Originally intended for unique trader bloom filter but removed (can't track 500k+ traders on-chain). Use off-chain indexing for unique trader counts.

## Advanced Features

### Circuit Breakers

Auto-pause pool when abnormal activity detected:

```c
CB_PRICE_DEV_BPS: 1000     // 10% price deviation triggers
CB_VOLUME_MULT: 10         // Volume > 10x avg triggers
CB_COOLDOWN_SLOTS: 9000    // 1 hour cooldown
CB_AUTO_RESUME_SLOTS: 54000 // 6 hour auto-resume
```

**Handlers:**
- `setcb`: Configure thresholds (admin only)
- `resetcb`: Manual reset after investigation

### Rate Limiting

Per-epoch volume and swap count limits:

```c
RL_SLOTS_PER_EPOCH: 750    // ~5 minute epochs (M-1 fix)
rl_max_vol: u64            // Max volume per epoch (0 = unlimited)
rl_max_swaps: u32          // Max swaps per epoch (0 = unlimited)
```

**Handler:** `setrl` (admin only)

### Oracle Integration

Validates swap prices against Pyth/Switchboard:

```c
ORACLE_MAX_STALENESS: 300   // 5 minute max staleness
ORACLE_MAX_DEV_BPS: 500     // 5% max deviation
```

Swap fails with `ERR_ORACLE` if execution price deviates too far from oracle price.

**Handler:** `setoracle` - Configure oracle addresses and thresholds

### LP Token Governance

Pools can enable on-chain governance:

```c
GOV_VOTE_DURATION: 259200   // 3 days voting
GOV_TIMELOCK: 86400         // 1 day execution delay
GOV_QUORUM_BPS: 1000        // 10% quorum
GOV_THRESHOLD_BPS: 5000     // 50%+ to pass
```

**Proposal Types:**
- `PROP_FEE_CHANGE` (1): Change swap fee
- `PROP_AMP_CHANGE` (2): Change amplification
- `PROP_ADMIN_FEE` (3): Change admin fee percentage
- `PROP_PAUSE` (4): Pause/unpause pool
- `PROP_AUTHORITY` (5): Transfer pool authority

**Handlers:**
- `govprop`: Create proposal (requires LP tokens)
- `govvote`: Vote for/against (LP-weighted)
- `govexec`: Execute passed proposal (after timelock)

### Limit Orderbook

Hybrid AMM + limit orders:

```c
MAX_ORDERS: 64              // Max orders per book
ORDER_EXPIRY: 604800        // 7 day default expiry
```

**Order Types:** `ORDER_BUY` (t0→t1), `ORDER_SELL` (t1→t0)

**Handlers:**
- `initbook`: Initialize orderbook for pool
- `placeord`: Place limit order with price/amount
- `cancelord`: Cancel own order
- `fillord`: Keeper fills order when AMM price crosses limit

### LP Governance (Fee Voting)

On-chain governance allowing LP holders to vote on pool parameters.

#### Governance Constants

```c
GOV_VOTE_SLOTS: 518400      // ~3 days voting period
GOV_TIMELOCK_SLOTS: 172800  // ~1 day execution delay
GOV_QUORUM_BPS: 1000        // 10% of LP supply must vote
GOV_THRESHOLD_BPS: 5000     // 50%+ of votes to pass
```

#### Proposal Types

| Type | Value | Description |
|------|-------|-------------|
| PROP_FEE_CHANGE | 1 | Change swap fee (0-1000 bps) |
| PROP_AMP_CHANGE | 2 | Change amplification (1-100000) |
| PROP_ADMIN_FEE | 3 | Change admin fee % (0-100) |
| PROP_PAUSE | 4 | Pause/unpause pool (0/1) |
| PROP_AUTHORITY | 5 | Transfer pool authority |

#### Usage Flow

```typescript
// 1. Create a proposal (must hold 0.1% LP to propose)
await program.govprop(proposal, pool, lpMint, proposerLP, proposer, systemProgram, {
  prop_type: 1,          // PROP_FEE_CHANGE
  value: 50,             // New fee: 0.5%
  description: "Lower fees to attract volume"
});

// 2. LP holders vote (voting power = LP balance)
await program.govvote(voteRecord, proposal, pool, lpMint, voterLP, voter, {
  vote_for: 1            // 1 = for, 0 = against
});

// 3. After voting ends + timelock, anyone can execute
await program.govexec(proposal, pool, executor);

// 4. Proposer can cancel before voting ends
await program.govcncl(proposal, proposer);
```

#### Governance Lifecycle

```
Create Proposal → Voting (3 days) → [Pass/Fail] → Timelock (1 day) → Execute
                      │                  │
                      └── Cancel ────────┘
```

**Handlers:**
- `govprop`: Create governance proposal (256 bytes account)
- `govvote`: Vote on proposal (LP-weighted)
- `govexec`: Execute passed proposal after timelock
- `govcncl`: Cancel proposal (proposer only)

### On-Chain ML Brain (Q-Learning V2)

**Real machine learning on Solana** - not heuristics. The ML Brain uses Q-Learning to optimize pool parameters for multiple objectives. V2 adds volatility-aware state encoding, configurable reward weights, and explicit action tracking.

#### Architecture

```
┌─────────────────────────────────────────────────────────────┐
│                    ML BRAIN ACCOUNT (~6.8KB)                 │
├─────────────────────────────────────────────────────────────┤
│  Config: pool type, bounds, V2 reward weights (5×u16)       │
│  Metrics: EMA price, volume, TVL, volatility, last_price    │
│  Q-Table: 27 states × 9 actions = 243 Q-values              │
│  Observation Buffer: 200 samples (circular, ~5.6KB)         │
└─────────────────────────────────────────────────────────────┘
                              │
              ┌───────────────┴───────────────┐
              ▼                               ▼
       During Swaps:                   Bot Triggers:
    - Record observation               - Batch Q-learning
    - Update EMAs + volatility          - Apply best action
    - Track last_action + vol_impact    - Log decisions
```

#### Multi-Objective Optimization (V2 - Configurable Weights)

The ML brain optimizes differently based on pool type. **V2 makes all weights configurable** via `cfgml` — the defaults below can be tuned per-pool.

**Stable Pools** (is_stable=1) - e.g., USDC/USDT:
| Goal | Default Weight | How |
|------|----------------|-----|
| Price Stability | 30% | Reward closeness to 1:1 peg, penalize deviation |
| Volume | 25% | Reward for larger swap sizes |
| New Traders | 20% | Bonus when bloom filter indicates new trader |
| TVL Growth | 15% | Reward for increasing pool balances |
| Fee Efficiency | 10% | Reward fee×volume product |

**Volatile Pools** (is_stable=0) - e.g., SOL/USDC:
| Goal | Default Weight | How |
|------|----------------|-----|
| Fee Efficiency | 30% | Maximize fee×volume (trading revenue) |
| Volume | 25% | Reward for larger swap sizes |
| TVL Growth | 25% | Reward for increasing pool balances |
| New Traders | 15% | Bonus when bloom filter indicates new trader |
| Price Stability | 5% | Light penalty for extreme moves only |

**V2 weight storage:** 5 u16 values in MLBrain struct (w_stability, w_volume, w_traders, w_tvl, w_fees). Sum must equal 100 (checked by cfgml handler).

#### State Space (27 States) - MDP Formulation

The ML brain formulates AMM optimization as a **Markov Decision Process (MDP)**: (S, A, P, R, γ)

where:
- **S**: State space (27 discrete states)
- **A**: Action space (9 parameter adjustments)
- **P**: Transition probability (determined by market)
- **R**: Reward function (multi-objective, V2 configurable)
- **γ**: Discount factor (0.9 - balances short/long term)

States are encoded as 3-dimensional: `dim0 × vol_trend × tvl_trend`

**V2 change:** The first dimension differs by pool type:
- **Stable pools:** `price_trend` (±5% EMA bands) — penalizes depeg
- **Volatile pools:** `volatility_trend` (±15% EMA bands) — tracks realized vol for fee optimization

Each dimension has 3 values relative to EMA (Exponential Moving Average):
- `0` = Declining (current < threshold of EMA)
- `1` = Stable (within threshold bands)
- `2` = Growing (current > threshold of EMA)

State index = `dim0 * 9 + vol_trend * 3 + tvl_trend`

**Thresholds:** Stable pools use ±5% bands. Volatile pools use ±15% bands for the volatility dimension — wider bands prevent thrashing during normal vol swings.

**Why EMA-relative thresholds?** The bands provide **hysteresis** - preventing rapid state oscillation during normal market fluctuations. Without this, the system would thrash between states on minor price movements.

#### Action Space (9 Actions)

| Action | Code | Effect |
|--------|------|--------|
| HOLD | 0 | No change |
| FEE+ | 1 | Increase swap fee by `fee_step` bps |
| FEE- | 2 | Decrease swap fee by `fee_step` bps |
| AMP+ | 3 | Increase amplification by `amp_step` |
| AMP- | 4 | Decrease amplification by `amp_step` |
| FRM+ | 5 | Increase farming reward rate |
| FRM- | 6 | Decrease farming reward rate |
| LOT+ | 7 | Increase lottery ticket price (more exclusive) |
| LOT- | 8 | Decrease lottery ticket price (more accessible) |

#### Q-Learning Algorithm

**Tabular Q-learning with ε-greedy exploration:**

```c
// Q-Learning Update (Bellman equation)
Q[s][a] ← Q[s][a] + α × (r + γ × max_a' Q[s'][a'] - Q[s][a])

where:
  s = current state
  a = action taken
  r = reward received
  s' = next state
  α = learning rate (0.1)
  γ = discount factor (0.9)
```

**Action Selection:**
```c
if random() < ε:
    return random_action()  // Exploration (10% of time)
else:
    return argmax_a Q[s][a]  // Exploitation (90% of time)
```

#### Q-Learning Parameters

```c
ML_GAMMA: 0.9            // Discount factor (long-term vs short-term)
ML_ALPHA: 0.1            // Learning rate (how fast to adapt)
ML_EPSILON: 0.1          // Exploration rate (10% random actions)
ML_ACTION_COST: 25       // V2: penalty for non-HOLD actions (reduces churn)
ML_EMA_DECAY: 950/1000   // V2: EMA smoothing (α=0.05 per observation)
```

#### Reward Function (V2 - 5 Components, Configurable)

The reward signal balances five competing objectives with **per-pool configurable weights** (V2):

```
R(obs_t, obs_t+1) = w_stability × r_price
                  + w_volume × r_volume
                  + w_traders × r_new
                  + w_tvl × r_tvl
                  + w_fees × r_fees

where:
  r_price  = clip((price_t+1 - price_t) / price_t, -1, 1) × 1000
  r_volume = min(volume_t+1 / 1000, 250)
  r_new    = 250 × indicator[trader is new]
  r_tvl    = clip((tvl_t+1 - tvl_t) / tvl_t, -1, 1) × 1000
  r_fees   = min(fee_bps × volume / 10000 / 1000, 250)

Default weights (stable):   (30, 25, 20, 15, 10)  → sum = 100
Default weights (volatile): (5, 25, 15, 25, 30)   → sum = 100
```

**V2 improvements:**
- Fee efficiency component rewards the product of fee × volume (not just volume alone)
- Weights stored as u16 percentages in MLBrain struct, tunable via `cfgml`
- Action cost subtracted: each non-HOLD action incurs -25 penalty to discourage unnecessary churn
- Explicit `last_action` tracking in observations eliminates fragile parameter-delta inference

**Reward Bounds:** R ∈ [-575, 750] (after action cost)

This bounded reward prevents extreme Q-value divergence.

#### Cold Start Behavior

New pools initialize with **conservative defaults**:
- Q-table initialized to zeros with slight bias toward HOLD (+50)
- First 10 observations required before training can begin
- EMAs initialize from first observation (no history = stable state assumed)
- Default state is (1,1,1) = stable dim0, stable volume, stable TVL
- V2 weights default to stable (30/25/20/15/10) or volatile (5/25/15/25/30) based on `is_stable`
- V2 `ema_volatility` and `last_price` initialize from first observation

**Warm-up period**: ~10 swaps before any ML action can be taken, ~100 swaps for meaningful Q-values.

#### MEV Protection

The ML brain includes multiple protections against manipulation:

```c
ML_MIN_OBS: 10           // Minimum observations before training
ML_TRAIN_COOLDOWN: 2250  // ~15 min between training calls
ML_ACTION_COOLDOWN: 9000 // ~1 hour between auto-apply actions
ML_MAX_FEE_CHANGE: 10    // Max 10 bps fee change per action
ML_MAX_AMP_CHANGE: 50    // Max 50 amp change per action
```

**Attack vectors mitigated:**
1. **Flash manipulation**: Can't trigger training immediately after swaps
2. **Fee griefing**: Limited to 0.1% fee change per hour
3. **Amp manipulation**: Limited to 50 amp change per hour
4. **Observation stuffing**: Training cooldown prevents rapid state changes

#### Delegated Learning Architecture

**Key innovation:** Users don't pay for ML compute during swaps!

1. **During swaps:** Light observation recording (~32 bytes, ~200 CU)
2. **Bot-triggered batch:** Process 100+ observations per tx (~100K CU)
3. **Fees cover learning:** Bot uses accumulated admin fees

#### Usage Flow

```typescript
// 1. Initialize ML Brain for pool
const brain = Keypair.generate();
await program.initml(brain, pool, {
  is_stable: 1,                   // 1 = stable pool (USDC/USDT), 0 = volatile (SOL/USDC)
  min_fee: 10, max_fee: 100,      // 0.1% - 1%
  min_amp: 100, max_amp: 5000,
  fee_step: 5, amp_step: 50,
  min_farm_rate: 0, max_farm_rate: 1e9,
  farm_step: 1e6,
  min_lot_price: 1e6, max_lot_price: 1e9,
  lot_step: 1e6
});

// 2. Enable brain with V2 configurable reward weights
await program.cfgml(brain, pool, {
  enabled: true, auto_apply: true,
  // V2 reward weights (must sum to 100)
  w_stability: 30, w_volume: 25, w_traders: 20, w_tvl: 15, w_fees: 10
});

// 3. Swaps automatically record observations (add brain as 8th account)
await program.swap(pool, vaults, user, { from: 0, to: 1, amt, min, deadline }, brain);

// 4. Bot triggers training periodically (V2: explicit max_iters + rand_seed)
await program.trainml(brain, pool, { max_iters: 50, rand_seed: Date.now() }, farm?, lottery?);
// Logs: "ML:TRAIN:A=3,S=14,Q=1250" = best action is AMP+ in state 14
```

#### Monitoring

All ML decisions are logged via `sol_log_`:

- `ML:INIT` - Brain initialized
- `ML:CFG` - Configuration updated
- `ML:TRAIN:A=X,S=Y,Q=Z` - Training result: action X, state Y, Q-value Z
- `ML:FEE+`, `ML:FEE-` - Fee adjustment applied
- `ML:AMP+`, `ML:AMP-` - Amp adjustment applied
- `ML:FRM+`, `ML:FRM-` - Farming rate adjusted
- `ML:LOT+`, `ML:LOT-` - Lottery price adjusted
- `ML:HOLD` - No action taken

#### Security

- Only `authority` can trigger training or apply actions
- All parameters bounded by configurable min/max
- Actions fail safely if at bounds (returns `ERR_RAMP`)
- Observation buffer has fixed size (no memory exhaustion)

**Handlers:**
- `initml`: Initialize ML brain for pool (~6.8KB account)
- `cfgml`: Configure bounds, enable/disable, auto_apply, V2 reward weights (3 accounts: brain, pool, authority)
- `trainml`: Batch Q-learning + optional action application (data: 8-byte disc + u16 max_iters + u64 rand_seed)
- `applyml`: Manually apply a specific action
- `logml`: Log current Q-table state for debugging

### Concentrated Liquidity

Uniswap v3-style tick ranges with proper math (H-1 fix):

```c
CL_TICK_MIN: -887272        // Full price range
CL_TICK_MAX: 887272
CL_MIN_DURATION: 300        // 5 min minimum position (E-3 JIT protection)
```

**Position NFT tracks:**
- `tick_lower` / `tick_upper`: Price range
- `liquidity`: Virtual liquidity amount (Q63.64)
- `fee_inside_0` / `fee_inside_1`: Position snapshot of the pool `fee_growth_0` / `fee_growth_1` accumulators
- `created_at`: Position creation timestamp

**Handlers:**
- `initclpl`: Initialize CL extension for pool
- `clmint`: Mint position (add liquidity to range)
- `clburn`: Burn position (remove liquidity, enforces min duration)
- `clcollect`: Collect accumulated fees
- `clswap`: Swap through concentrated liquidity

**Math (Uniswap v3 compatible):**
- `tick_to_sqrt()`: Lookup table + interpolation for 1.0001^(tick/2)
- `sqrt_to_tick()`: Binary search inverse
- `calc_liq_from_amts()`: L = sqrt(x * y * P) calculation
- `calc_amts_from_liq()`: Token amounts from liquidity + price range
- Fee growth uses u128 to prevent overflow (H-3 fix)
- Iteration limit prevents infinite loops (H-2 fix)

### Flash Loans

Borrow pool liquidity within a single transaction:

```c
FLASH_BASE_FEE_BPS: 9       // 0.09% base fee
FLASH_MAX_PCT: 50           // Max 50% of pool per loan
```

**Dynamic Fee (E-4 fix):**
- Base: 0.09% (9 bps)
- Size multiplier: +1 bps per 1% of pool borrowed
- Volatility multiplier: +10 bps if price moved >5% in last day
- Capped at 1% maximum

**Security model:**
- Reentrancy guard prevents nested flash loans (C-2 fix)
- Atomic transaction ensures repayment
- State stored in dedicated fields (not bloom, C-1 fix)
- Events emitted for indexing (M-7 fix)

**Handlers:**
- `flashloan`: Borrow tokens (transfers to receiver)
- `flashrepy`: Repay borrowed + fee (must be in same tx)

**Usage pattern:**
1. Call `flashloan` with borrow amounts
2. Execute arbitrage/liquidation via CPI
3. Call `flashrepy` to repay

### Multi-Hop Routing

Chain multiple pool swaps in one instruction:

```c
Max hops: 3                 // 2-3 pools per route (M-5 fix)
Account layout: [pool, vault0, vault1] × n_hops + [user_in, user_out, user, token_prog]
```

**Instruction data:** `[amount_in(8), min_out(8), deadline(8), n_hops(1), directions[n_hops]]`

**Handler:** `multihop`
- Executes all swaps atomically
- Updates all pool states
- Enforces slippage on final output only

### Virtual Pool Graduation System

Zero-rent token launches with bonding curves. Tokens exist as "virtual" entries in a single global PDA until they graduate to real AMM pools. See `graduation-plan.md` for full design.

**Architecture:**
```
┌─────────────────────────────────────────────────────────────┐
│                    GLOBAL PDA (~10MB max)                   │
├─────────────────────────────────────────────────────────────┤
│  GlobalHeader (64 bytes): slot count, fee balance, stats    │
│  VPoolSlot[0..1023] (10KB each): virtual pool + holders     │
└─────────────────────────────────────────────────────────────┘
```

**State Structures:**
- `GlobalHeader` (64 bytes): num_slots, next_pool_id, fee_balance, volume, pool counts
- `VPoolSlot` (10KB): metadata, bonding curve params, holder tracking (up to 1400 holders)
- `VPoolHolder` (7 bytes packed): caesar-hashed wallet + balance in 100K token units
- `VPoolClaimPDA` (96 bytes): per-holder vesting tracker post-graduation
- `VPoolFarmingState` (720 bytes): 90-day farming rewards with commit-reveal lottery. Contains `vp_pool_id` + `vp_mint_bump` for CPI minting, plus veToken fee accounting (`ve_total_weight`, `ve_acc_fee0/1`, `ve_last_admin0/1`, `ve_total_claimed0/1`)
- `VePosition` (128 bytes, disc "VESTAKE!"): veToken lock — pool_mint, owner, amount, lock_start, lock_duration, fee_debt

**Bonding Curve Mathematics:**

Linear bonding curve with quadratic cost integral:

```
price(sold) = base_price + slope × sold / SCALE
```

**Buy calculation** (given SOL input, find tokens received):
```
effective_sol = sol_in × (10000 - FEE_BPS) / 10000
b = base_price + slope × sold / SCALE
discriminant = b² + 2 × slope × effective_sol / SCALE
tokens_out = (√discriminant - b) × SCALE / slope
```

This solves the integral: ∫[sold to sold+t] price(x) dx = effective_sol

**Sell calculation** (given tokens, find SOL received):
```
avg_price = base_price + slope × (sold - tokens/2) / SCALE
sol_out_gross = tokens × avg_price
sol_out_net = sol_out_gross × (10000 - FEE_BPS) / 10000
```

Uses average price at the midpoint of the range.

**Fee structure:**
- 1% total fee (FEE_BPS = 100)
- Split: 0.5% to global fee_balance, 0.5% stays in pool

**Sell Extraction Mechanism:**
- 80% of sold tokens → Extracted for farming pool (permanently removed)
- 20% of sold tokens → Returns to bonding supply (available for repurchase)
- More selling = larger farming pool = better post-graduation incentives

**Extraction-Based Dynamic Graduation Target (v2):**

Replaces the v1 churn formula. Uses sell ratio to lower target for active communities:

```c
R_sell = min(tau_S / tau_B, 1.0)   // tau_S = cumulative tokens sold, tau_B = cumulative tokens bought
T* = clip(T_max × (1 - rho × R_sell), T_min, T_max)
// T_max = 200 SOL, T_min = 10 SOL, rho = 0.8
```

**Supply counters:** `σ` (outstanding supply), `τ_B` (cumulative bought, monotonic), `τ_S` (cumulative sold, monotonic).

**Why 200 SOL base?** Buy-only patterns (scammer) face maximum target. Selling builds farming pool AND lowers the target for everyone — manipulation benefits the community. Farming pool at graduation: `F = min(0.8 × τ_S + 0.2 × U, U - A_c - A_DAO)` where U = unsold supply.

**Game-Theoretic Properties:**

1. **Sell Extraction**: Each sell extracts 80% of tokens for farming
   - Scammer sells → builds farming pool (benefits community)
   - More trading → lower graduation target → faster graduation
   - Each sell permanently removes tokens from supply

2. **Wallet Concentration Limit**: 2.5% max per holder (b_max=250, Q=100K tokens)
   - Prevents whale dominance
   - Forces wider distribution — Sybil requires ⌈α/0.025⌉ wallets but total fees are identical
   - Creates Nash equilibrium: can't profitably manipulate alone

3. **Flush-or-Graduate Lifecycle (v2)**:
   - 1-hour pool lifetime from creation; trading continues until terminal event
   - Anyone can call `vpgrad` when `s ≥ T*` (0.1% reward)
   - After 1 hour, `vpflush` checks: if `s ≥ T*` → graduate; if `s < T*` → flush (99.9% to protocol, 0.1% to cranker)
   - Atomic check: `s` and `T*` evaluated once at execution time (no race conditions)

4. **Exponential Vesting (v2)** (prevents immediate dumps):
   - Holders: `claimable(t) = A₀ × (1 - 0.95^t)` — 50% at hour 14, 95% by hour 59
   - Creator: `claimable(t) = A₀ × (1 - 0.98^t)` — 50% at hour 35, 95% by hour 149
   - Creator cannot dump faster than community (slower decay rate)

5. **Price Continuity**: AMM seeded at exact graduation price
   - No arbitrage opportunity at transition
   - Smooth handoff from bonding curve to AMM
   - Initial liquidity = graduation SOL raised

6. **Dual-Source Farming Pool** (capped by token conservation):
   - Extracted tokens: 80% of all sells during bonding (0.8 × τ_S)
   - Plus 20% of unsold tokens (0.2 × U)
   - Capped at `U - A_c - A_DAO` (preserves creator + DAO allocations)
   - Larger farming pool = better LP incentives post-graduation

7. **Scammer's Dilemma (v2)**: Buy-only scammer faces T*=200 SOL maximum target + exponential vesting (50% at hour 14+). Recruiting accomplices to sell lowers T* but builds the farming pool (community benefit). No profitable unilateral deviation exists.

**Token Distribution at Graduation (v2 — on-demand minting model):**

| Source | Recipient | Amount | Minted by | Vesting |
|--------|-----------|--------|-----------|---------|
| Sold tokens | Holders | σ - farming_extracted | vpclaim (on-demand) | 1 - 0.95^t (50% hr 14) |
| Unsold (5%) | Pool DAO | 0.05 × U (always) | vpgrad (immediate CPI) | Immediate (DAO-governed) |
| Unsold (0-5%) | Creator | α_c × U (optional) | claimcrt (on-demand) | 1 - 0.98^t (50% hr 35) |
| Unsold + extraction | Farming | ≤ U - A_c - A_DAO | vpfrev (on-demand) | 90-day distribution |
| Unsold (remainder) | AMM liquidity | U - A_c - A_DAO - F | vpgrad (immediate CPI) | Immediate |

**CRITICAL**: vpgrad only mints `amm_liq` + `dao_alloc` at graduation time. Holder tokens, creator tokens, and farming rewards are minted on-demand by their respective handlers (vpclaim, claimcrt, vpfrev) using the `["vpmint", pool_id, bump]` PDA. This prevents double-minting and ensures token conservation.

Token conservation: (σ - farming_extracted) + A_c + A_DAO + F + L_AMM = T_total

**Governance Extensions (v2 — from whitepaper Section 6):**

- **Optional Creator Allocation**: α_c ∈ [0, 0.05], set at pool creation (immutable). Encoded as single byte (0.5% granularity). α_c = 0 = "fair launch"
- **Pool DAO Treasury**: Always 5% of unsold supply. Proposal-vote-execute governance (10% quorum, >50% majority, 24h timelock)
- **veToken Staking**: Lock tokens 14-90 days for AMM fee share. Multiplier m(Δ) = Δ/Δ_min (max 6.43×). 80% cap per staker prevents monopoly. Unclaimed fees flow to Pool DAO
- **Creator Ownership Delegation**: Single-step irrevocable transfer to DAO/multisig. Can occur at creation or post-graduation. Vesting unaffected (separates operational control from economic interest)
- **Progressive Decentralization**: 4-stage path — Creator-led (0-7d) → Community-governed (7-30d) → Staker-aligned (30-90d) → Fully decentralized (90d+)

**Competitive Farming (90-day Volume Flywheel):**

Solves Pump.fun's post-graduation death spiral:
- 90 days = 25,920 windows (5 min each)
- Per-window reward = remaining tokens / remaining windows (constant incentive)
- Track top 10 buyers by SOL spent per window
- 3 random winners from top 10, each receives 30% of window reward
- Committer provides randomness (commit-reveal), receives 10% reward
- Anti-sniping: 30% win probability discourages last-second displacement

**Why This Works:**

The system creates a **cooperative game** where:
- Trading activity benefits the community (builds farming pool)
- Whales can't monopolize (2.5% limit)
- Dumping is delayed (exponential vesting)
- Active trading communities graduate faster with better farming programs
- Post-graduation farming sustains volume for 90 days (vs 24-48h on Pump.fun)

**Handlers:**
| Discriminator | Handler | Description |
|---------------|---------|-------------|
| `0x696e6974676c6f62` | initglobal | Bootstrap global PDA (one-time) |
| `0x7670637265617465` | vpcreate | Create virtual pool (0.1 SOL fee) |
| `0x7670627579000000` | vpbuy | Buy tokens via bonding curve |
| `0x767073656c6c0000` | vpsell | Sell tokens back |
| `0x7670666c75736800` | vpflush | Flush stale pool (0.1% reward) |
| `0x7670677261640000` | vpgrad | Graduate to real AMM |
| `0x7670636c61696d00` | vpclaim | Claim vested tokens |
| `0x636c61696d637274` | claimcrt | Creator vesting claim |
| `0x7670666d63740000` | vpfcmt | Farming commit (IDL stake) |
| `0x7670667265760000` | vpfrev | Farming reveal (rewards) |
| `0x7670666e69740000` | vpfinit | Init farming state for graduated pool |
| `0x767064656c656774` | vpdelegt | Irrevocable creator delegation |
| `0x76657374616b6500` | vestake | Lock tokens in veToken position |
| `0x7665756e73746b00` | veunstk | Unlock veToken after expiry |
| `0x7665636c6d666500` | veclmfe | Claim veToken fee share |
| `0x76706578706e6400` | vpexpand | Grow account data by SLOT_SIZE (10KB). Permissionless; payer covers rent |
| `0x76706d657461` | vpmeta | Create Metaplex metadata for a slot mint |
| `0x767077666565` | vpwfee | Withdraw fee_balance from the global PDA (admin only) |

> **The strategy / QVM family is not in this file.** `streg`, `stfund`, `stfork`,
> `stbet`, `stsettle`, `stclaim`, `stfexec`, `rtquote` and `rtexec` are dispatched
> by `entrypoint()` like everything above, but documented in `QVM.md` and
> `STRATEGY_VM.md`. They are also unreachable from the native CLI (WASM DEX only),
> so `aex-sim` structurally cannot exercise them — undocumented here, untested
> there, and reachable only through one client is a single population, not three
> coincidences.

**Account Discriminators:**
- `GPOOLS_DISC`: "GPVOOLS!" (0x4750564f4f4c5321)
- `VPCLAIM_DISC`: "VPCLAIM!" (0x5650434c41494d21)
- `FARMSTATE_DISC`: "FARMSTAT" (0x4641524d53544154)
- `PCONFIG_DISC`: "PCONFIG!" (0x50434f4e46494721)
- `VESTAKE_DISC`: "VESTAKE!" (DISC_VESTAKE constant)

**TypeScript SDK:** `sdk/typescript/src/vpool/` with `VPoolClient` class

### Migration Pools

Token conversion/migration system supporting 13 distinct scenarios. Standalone conversion facilities with rich configuration — separate from the existing `migt0t1`/`migt1t0` simple 1:1 swaps on AMM pools.

**Account Structures:**
- `MigrationPool` (512 bytes, disc "MIGPOOL!"): Full migration configuration, vault addresses, stats
- `MigrationClaimPDA` (128 bytes, disc "MIGCLM!!"): Per-user vesting tracker

**PDA Seeds:**
```
MigrationPool:  ["migpool", old_mint(32), new_mint(32), bump(1)]
MigrationClaim: ["migclm", mig_pool_pubkey(32), wallet(32)]
```

**13 Migration Types:**
| Type | Name | Key Params |
|------|------|------------|
| 0 | Simple 1:1 | — |
| 1 | Fixed Ratio | p0=num, p1=den |
| 2 | Ratio + Fee | p2=fee_bps |
| 3 | Vesting | p2=vest_dur, p3=interval |
| 4 | Time-Decay Bonus | p2=bonus_bps |
| 5 | Burn-and-Mint | BURN flag |
| 6 | Multi-Token Merge | p0/p1=ratio0, p2/p3=ratio1 |
| 7 | Capped (FCFS) | p2=cap |
| 8 | Gated (Whitelist) | GATED flag |
| 9 | Per-Wallet Cap | p2=max_per_wallet |
| 10 | Vest + Bonus | p2=bonus, p3=vest_dur, p4=interval |
| 11 | Burn + Vest | p2=vest_dur, p3=interval |
| 12 | SOL-for-Token | p0=tokens_per_sol, p2=cap |

**Flags Bitfield:** `VEST=0x01, DECAY=0x02, BURN=0x04, GATED=0x08, WLCAP=0x10, SOL=0x20, REF=0x40`

**9 Handlers:**
- `migcreate`: Create migration pool (8-10 accounts, type+flags+params in data)
- `migdepo`: Deposit new tokens into vault (5 accounts)
- `migswap`: Execute migration/conversion (7-9 accounts + 1-3 RefLink PDAs when REF flag set)
- `migclm`: Claim vested tokens via claim PDA (6 accounts)
- `migclose`: Close pool + reclaim remaining tokens (8 accounts)
- `migpause`: Pause/unpause (2 accounts)
- `migauth`: Transfer authority — step 0=init, 1=complete, 2=cancel (2-3 accounts)
- `initref`: Create referral link PDA (4-5 accounts: reflink_pda, mig_pool, referrer, system_program, ?parent_reflink)
- `clmref`: Claim referral rewards from RefLink PDA (6 accounts: mig_pool, reflink_pda, new_vault, user_new, referrer, token_program)

**Output calculation** uses u128 arithmetic for overflow safety. Supports ratio conversions, fee deductions, time-decay bonuses, and cap enforcement.

#### Referral System

On-chain referral tracking for migration pools via **RefLink PDAs** (128 bytes, disc "REFLINK!"). Enabled by setting `MIG_FLAG_REF` (0x40) on a migration pool.

**Account Structure — RefLinkPDA (128 bytes):**
```
0-7:      discriminator ("REFLINK!")
8-39:     mig_pool (Pubkey)
40-71:    referrer (Pubkey)
72-103:   referred_by (Pubkey — parent referrer for multi-level chains)
104-111:  total_earned (u64)
112-119:  total_claimed (u64)
120-123:  referral_count (u32)
124:      level (u8 — 0=L1 direct, 1=L2, 2=L3)
125-127:  reserved
```

**PDA Seeds:** `["reflink", mig_pool_pubkey(32), referrer_wallet(32)]`

**MigrationPool referral fields** (offset 504-510, repurposed from reserved):
- `ref_fee_bps` (u16, offset 504): Referral reward in basis points (max 2000 = 20%)
- `ref_levels` (u8, offset 506): Number of referral levels (1-3)
- `ref_decay` (u8[3], offset 507): Percentage of parent reward per level (e.g., [50, 25, 0])

**Multi-level reward calculation** (in `migswap`):
```
L1 reward = swap_output × ref_fee_bps / 10000
L2 reward = L1_reward × ref_decay[0] / 100
L3 reward = L2_reward × ref_decay[1] / 100
```

**Security:**
- Self-referral prevention: referrer must differ from migrating user
- Max referral cap: `REF_MAX_BPS = 2000` (20%)
- Vault sufficiency checked before crediting rewards
- RefLink PDA verified via seed derivation

**Constants:**
```c
MIG_FLAG_REF:   0x40
REFLINK_SIZE:   128
REF_MAX_BPS:    2000
D_INITREF:      0x0066657274696e69  /* "initref\0" */
D_CLMREF:       0x00006665726d6c63  /* "clmref\0\0" */
```

#### 20 Migration Strategy Templates

Predefined configurations combining migration types with referral settings:

| # | Name | Type | Flags | Ref BPS | Levels | Decay |
|---|------|------|-------|---------|--------|-------|
| 1 | Basic Referral | 0 (1:1) | REF | 500 | 1 | 100/0/0 |
| 2 | Tiered Affiliate | 0 (1:1) | REF | 500 | 3 | 60/30/0 |
| 3 | Squad Migrate | 0 (1:1) | REF | 800 | 3 | 50/25/0 |
| 4 | Leaderboard | 0 (1:1) | REF | 1000 | 1 | 100/0/0 |
| 5 | Cross-DEX LP | 1 (Ratio) | REF | 500 | 1 | 100/0/0 |
| 6 | Diamond Hands | 3 (Vest) | VEST+REF | 500 | 2 | 50/25/0 |
| 7 | Referral Farming | 3 (Vest) | VEST+REF | 500 | 2 | 50/0/0 |
| 8 | Quest Chain | 3 (Vest) | VEST+REF | 300 | 2 | 50/0/0 |
| 9 | Staking Direct | 3 (Vest) | VEST+REF | 500 | 1 | 100/0/0 |
| 10 | Early Bird | 4 (Decay) | DECAY+REF | 300 | 1 | 100/0/0 |
| 11 | Flash Window | 4 (Decay) | DECAY+REF | 500 | 1 | 100/0/0 |
| 12 | Burn & Premium | 5 (Burn) | BURN+REF | 500 | 2 | 50/0/0 |
| 13 | Multi-Merge | 6 (Merge) | REF | 500 | 2 | 50/0/0 |
| 14 | Lottery Migration | 7 (FCFS) | REF | 300 | 1 | 100/0/0 |
| 15 | KOL Boost | 8 (Gated) | GATED+REF | 1000 | 2 | 50/0/0 |
| 16 | Vote-to-Migrate | 8 (Gated) | GATED+REF | 300 | 1 | 100/0/0 |
| 17 | Ambassador | 8 (Gated) | GATED+REF | 1500 | 3 | 60/30/0 |
| 18 | Whale Cap | 9 (WlCap) | WLCAP+REF | 300 | 1 | 100/0/0 |
| 19 | LP Bootstrap | 12 (SOL) | SOL+REF | 500 | 2 | 50/0/0 |
| 20 | SOL Pairing | 12 (SOL) | SOL+REF | 300 | 2 | 50/25/0 |

Available via `aex migrate templates` CLI command.

#### Composable Presale Extension (MigConfig)

Structured presale system that overlays daily caps, 3-way SOL split, exponential vesting, total supply tracking, and auto LP seeding on existing migration pools. Enabled by `MIG_FLAG_EXT = 0x80` on any migration pool — composable with all 13 migration types.

**Design:** A companion PDA (`MigConfig`, 512 bytes) shared across multiple migration pools for the same new token. Each pool with `MIG_FLAG_EXT` references the shared config for centralized cap tracking and SOL distribution.

**Account Structure — MigConfig (512 bytes, disc "MIGCFG!!"):**
```
0-7:      discriminator ("MIGCFG!!")
8-39:     new_mint (Pubkey — PDA seed)
40-71:    authority (Pubkey — presale admin)
72-79:    daily_mig_cap (u64 — migration tokens/day, 0=unlimited)
80-87:    daily_sol_cap (u64 — SOL sale tokens/day, 0=unlimited)
88-95:    mig_today (u64 — migration distributed today)
96-103:   sol_today (u64 — SOL sale distributed today)
104-107:  current_day (u32)
108-111:  num_pools (u32 — linked pool count)
112-119:  creation_time (i64 — day boundary reference)
120-127:  end_time (i64 — presale end, 0=until supply)
128-135:  total_supply (u64 — max total tokens, 0=unlimited)
136-143:  total_distributed (u64 — cumulative tokens out)
144:      creator_pct (u8 — % SOL to creator)
145:      dao_pct (u8 — % SOL to DAO)
146:      lp_pct (u8 — % SOL to LP)
147:      status (u8 — 0=ACTIVE, 1=COMPLETED, 2=LP_SEEDED)
148-149:  exp_decay_bps (u16 — per-claim vesting rate, 500=5%, 0=linear)
150-151:  _pad1 (u16)
152-155:  vest_cooldown (u32 — seconds between claims, default 3600)
156-163:  vest_dust (u64 — dust threshold for full drain)
164-195:  creator_wallet (Pubkey)
196-227:  dao_treasury (Pubkey — SOL accumulator)
228-259:  lp_accumulator (Pubkey — SOL accumulator for LP)
260-291:  lp_pool (Pubkey — AMM pool, zero until LP_SEEDED)
292-299:  sol_to_creator (u64)
300-307:  sol_to_dao (u64)
308-315:  sol_to_lp (u64)
316-323:  total_sol (u64)
324-331:  total_mig_tokens (u64)
332-339:  user_count (u64)
340-347:  tokens_per_sol (u64)
348:      bump (u8)
349:      mint_auth (u8 — 1=config PDA is mint authority)
350-511:  _reserved[162]
```

**PDA Seeds:**
```
MigConfig: ["migcfg"(6), new_mint(32), bump(1)]
```

**Flags (updated):**
```
MIG_FLAG_VEST:  0x01
MIG_FLAG_DECAY: 0x02
MIG_FLAG_BURN:  0x04
MIG_FLAG_GATED: 0x08
MIG_FLAG_WLCAP: 0x10
MIG_FLAG_SOL:   0x20
MIG_FLAG_REF:   0x40
MIG_FLAG_EXT:   0x80    ← NEW (composable presale extension)
```

**Status Lifecycle:**
```
ACTIVE (0) → COMPLETED (1) → LP_SEEDED (2)
                  ↑
                  │ total_distributed >= total_supply
                  │ OR clock >= end_time
```

**2 New Handlers:**
- `migcfg` (D_MIGCFG = `0x6d69676366670000`): Create MigConfig PDA (7 accounts, 64 bytes data). Validates `creator_pct + dao_pct + lp_pct == 100`, creates 512-byte account via CPI.
- `seedlp` (D_MIGSEEDLP = `0x6d69677365656400`): Seed AMM LP pool after COMPLETED (12+ accounts). Transfers accumulated SOL → AMM WSOL vault, optionally mints tokens, locks 99.9% LP forever, rewards cranker 0.1%.

**2 Modified Handlers:**
- `migswap` (EXT extension): When `MIG_FLAG_EXT` set, additional accounts appended:
  - SOL path: config_pda(w) + dao_treasury(w) + lp_accumulator(w) — daily cap + 3-way SOL split + total supply tracking + auto-complete
  - Token path: config_pda(w) — daily cap + total supply tracking (inserted before REF accounts, shifts ref_start)
- `migclm` (exponential vesting): When `MIG_FLAG_EXT` set and config `exp_decay_bps > 0`, overrides linear vesting with `claimable = unclaimed * exp_decay_bps / 10000` per claim. Optional config_pda at ka[6]. Falls back to linear if config absent.

**SOL Split (in migswap SOL path):**
```c
creator_sol = sol_amt * config->creator_pct / 100
dao_sol     = sol_amt * config->dao_pct / 100
lp_sol      = sol_amt - creator_sol - dao_sol  // remainder to LP (no rounding loss)
```

**Exponential Vesting (in migclm):**
```c
unclaimed = c->total_entitled - c->claimed;
if (unclaimed <= cfg->vest_dust) { claimable = unclaimed; }  // drain dust
else { claimable = (unclaimed * cfg->exp_decay_bps) / 10000; }
// With 500 bps (5%): ~50% claimed by hour 14, ~95% by hour 59
```

**Backward Compatibility:** Pools without `MIG_FLAG_EXT` are completely unaffected. No changes to MigrationPool or MigrationClaimPDA structs. All 13 migration types + referral system continue to work unchanged.

**Constants:**
```c
MIG_FLAG_EXT:       0x80
MIGCFG_SIZE:        512
DISC_MIGCFG:        "MIGCFG!!" (0x4d49474346472121)
D_MIGCFG:           0x6d69676366670000  /* "migcfg\0\0" */
D_MIGSEEDLP:        0x6d69677365656400  /* "migseed\0" */
PS_ACTIVE:          0
PS_COMPLETED:       1
PS_LP_SEEDED:       2
```

## Development Workflow

### Adding New Handlers

When adding a new instruction handler to `aex402.c`:

1. **Choose discriminator**: Use 8-byte unique value (avoid collisions)
   ```c
   #define MYNEW_DISC 0x6e6577686e646c21ULL  // "newhandl!"
   ```

2. **Add to dispatch**: Update `entrypoint()` switch statement
   ```c
   case MYNEW_DISC: return mynew(accs, n_accs, data, data_len);
   ```

3. **Function naming**: MUST be ≤10 chars (LLVM constraint)
   ```c
   // GOOD: mynew, setfoo, updbar
   // BAD: myNewHandler (too long - will be dropped silently)
   ```

4. **Account validation**: Always perform security checks
   ```c
   if (!chk_disc(acc_data, EXPECTED_DISC)) return ERR_DISC;
   if (!is_token_acc(acc)) return ERR_OWNER;
   ```

5. **Update IDL**: Add to `types.ts` and `INTEGRATION.md`

6. **Write tests**: Add test in `tests/comprehensive-test.ts`

7. **Verify**: Check function survived compilation
   ```bash
   llvm-nm aex402.o | grep mynew
   ```

### Debugging On-Chain

```bash
# View program logs
solana logs 3AMM53MsJZy2Jvf7PeHHga3bsGjWV4TSaYz29WUtcdje --url devnet

# Add debug logging in C
sol_log_("DEBUG: value=%llu", my_value);

# Simulate transaction locally
solana program dump 3AMM53MsJZy2Jvf7PeHHga3bsGjWV4TSaYz29WUtcdje program.so --url devnet
```

### Common Patterns

**Manual deserialization** is required (no Anchor):
```c
// Parse instruction data
uint64_t amt = *(uint64_t*)(data + 8);  // After 8-byte discriminator
uint64_t min = *(uint64_t*)(data + 16);
```

**PDA derivation** for pool authority:
```c
// Seeds: ["pool", token0_mint(32 bytes), bump(1 byte)]
uint8_t seeds[38];
memcpy(seeds, "pool", 4);
memcpy(seeds + 4, pool->t0_mint, 32);
seeds[37] = pool->bump;
```

**CPI token transfers** via `sol_invoke_signed_c`:
```c
cpi_xfer(vault, user_ata, authority_pda, amount, seeds, 3);
```

### Key Architectural Decisions

**Why single-file C?**
- Binary size: ~248KB vs 200-500KB (Rust/Anchor)
- No runtime overhead
- Direct control over memory layout
- Faster compilation (2 seconds vs 30+ seconds)

**Why switch dispatch instead of function pointers?**
- BPF loader doesn't process data section relocations
- Function pointer tables would require runtime patching

**Why manual u128 math?**
- No libc/stdlib available in eBPF
- AeX402 curve requires 128-bit precision
- Custom implementations: `u128_add`, `u128_mul`, `u128_div64`

**Why delta-encoded candles?**
- 12 bytes per candle (vs 32 bytes uncompressed)
- 24h + 7d OHLCV fits in ~372 bytes
- Decompression is trivial: `high = open + high_delta`

**Why bloom filter removed?**
- Can't track 500k+ unique traders on-chain (4KB limit)
- Use off-chain indexing for trader metrics
- Field reserved for future use (128 bytes)

## Design Philosophy & Key Insights

### Why Pure C for Solana Programs?

This project demonstrates that **complex DeFi protocols can be more efficiently implemented in C than Rust/Anchor:**

**Benefits:**
- **Binary size**: 189KB (C) vs 200-500KB (Rust/Anchor) with fewer features
- **Compile time**: 2 seconds vs 30+ seconds
- **Memory control**: Direct struct layout, no runtime overhead
- **Determinism**: No hidden allocations or panic handlers

**Trade-offs:**
- Manual account deserialization (no Anchor magic)
- No automatic IDL generation
- Manual u128 arithmetic implementation
- Requires understanding Solana C ABI

**When to use C:**
- Compute-intensive programs (AMM math, ML training)
- Programs with strict CU budgets
- Programs requiring precise memory layout
- When binary size matters (deployment cost)

### The Delegated Learning Pattern

**Problem:** On-chain ML is expensive (100K+ CU per training step)

**Solution:** Split into two phases:
1. **User transactions**: Lightweight observation recording (~200 CU)
2. **Bot transactions**: Heavy batch training (funded by protocol fees)

**Result:** Users get ML-optimized pools without paying for ML compute!

This pattern generalizes to any on-chain computation that:
- Requires significant compute but infrequent execution
- Can be batched across multiple events
- Benefits from protocol-wide optimization

### State Space Design for On-Chain RL

**Challenge:** Q-learning needs |S| × |A| Q-values in memory

**Solution:**
- Discretize continuous metrics into 3 bins (declining/stable/growing)
- Use EMA-relative thresholds for hysteresis
- Result: 3³ = 27 states (tractable for on-chain storage)

**Key Insight:** The exact price/volume/TVL values don't matter - only their **trend relative to recent history** matters for parameter decisions.

**V2 Enhancement:** Volatile pools replace the price dimension with a **volatility dimension** (±15% EMA bands). This captures realized volatility directly — high vol → raise fees to capture spread, low vol → lower fees to attract volume. This avoids penalizing healthy price discovery in volatile pairs.

### Virtual Pool Graduation as a Mechanism Design Problem

**Challenge:** Token launches attract manipulation (pump-and-dump, wash trading)

**Solution:** Design the game so that:
- Manipulation is **more expensive** than the expected payoff
- Honest organic growth is **cheaper** and **more profitable**
- No single entity can profitably deviate from honest behavior

**Mechanisms (v2 — extraction-based, replaces v1 churn formula):**
1. **Sell extraction**: 80% of sold tokens permanently go to farming pool — manipulation builds community rewards
2. **Wallet limits**: 2.5% cap prevents monopoly control
3. **Exponential vesting**: Concave unlock curve (1-0.95^t) removes "instant dump" from payoff matrix
4. **Extraction-based dynamic target**: T* = T_max × (1 - 0.8 × R_sell), clamped to [10, 200] SOL
5. **Flush penalty**: 99.9% of failed pool SOL goes to protocol — no risk-free parking

**Game-Theoretic Outcome:** Unique subgame-perfect equilibrium where manipulation is unprofitable (Theorem in whitepaper v2, Section 6). The "Scammer's Dilemma": buy-only faces maximum target; cycling incurs fees while building farming pool for community.

**v2 Governance Extensions:** Progressive decentralization via optional creator allocation (0-5%), Pool DAO treasury (5% always), veToken staking (lock-weighted fee share), and irrevocable creator delegation. See whitepaper v2 Section 6.3.

### On-Chain Analytics as First-Class Data

Most AMMs treat analytics as off-chain indexing problems. AeX402 embeds analytics directly:

**Why?**
- **Composability**: Other programs can read TWAP oracle, candle data
- **Trustlessness**: No reliance on centralized APIs or indexers
- **ML training**: Q-learning needs historical metrics on-chain
- **Transparency**: All metrics verifiable in program state

**How?**
- Delta encoding: 12 bytes per candle vs 32 bytes uncompressed
- Circular buffers: Fixed 24h + 7d storage
- Slot-based updates: No timestamp reliance

### The StableSwap Invariant Intuition

**Constant Product** (xy = k):
- Large slippage for similar assets
- Good for volatile pairs (SOL/USDC)
- High capital inefficiency for stables

**Constant Sum** (x + y = k):
- Zero slippage until reserves depleted
- Unstable: any price deviation drains pool

**StableSwap** (AeX402):
- Blends both via amplification coefficient A
- A=1: Acts like constant product
- A=100k: Acts like constant sum
- Tunable for asset correlation

**Physical Analogy:** Imagine a marble rolling in a valley:
- A=1: Deep V-shaped valley (constant product)
- A=100k: Flat valley with steep walls (constant sum)
- A=100: Gentle bowl that's flat in the middle

The math ensures the marble (price) stays stable for similar assets but can move when needed.

### Generalized Bonding Curve (GBC) — Whitepaper v2 Theory

The whitepaper v2 introduces a **theoretical** GBC family that unifies constant product and constant sum under a single curvature parameter κ ∈ [0,1]:

```
I_κ(x,y) = (xy/D²)^(1-κ) × ((x+y)/2D)^(2κ) = 1
```

- κ=0: Constant product (xy=k), κ=1: Constant sum (x+y=k)
- Curvature at equilibrium: K(κ) = (1-κ)/D²
- Capital efficiency scales as Θ(ε^(1/(1-κ))) — higher κ = flatter curve = more efficient near peg

**Static AMM Lower Bound (Theorem):** Any static curve accumulates Ω(T) welfare loss under regime-switching markets. This motivates Q-learning adaptation.

**Note:** The on-chain implementation uses StableSwap (amp-based), not GBC directly. GBC is the theoretical framework motivating why adaptive curves are needed. The Q-learning brain adapts amp/fees based on market conditions.

## Summary for Future Claude Instances

This is a **production-grade AMM implementation in pure C** demonstrating:

1. **Mathematical rigor**: Newton's method for StableSwap invariant, GBC theoretical framework (whitepaper v2)
2. **ML innovation**: First on-chain Q-learning for DeFi (V2: volatility-aware, configurable weights)
3. **Mechanism design**: Game-theoretically sound virtual pool graduation with extraction-based targets, exponential vesting, Pool DAO, veToken staking
4. **Systems engineering**: Delegated learning, delta encoding, manual u128 math
5. **Security**: 85+ handlers tested, all audit issues resolved
6. **Governance**: Progressive decentralization via optional creator allocation, Pool DAO treasury, veToken fee sharing, creator delegation

**When working on the C program:**
- Treat the math as ground truth (Newton's method, bonding curves)
- Respect the 10-char function name limit (LLVM constraint)
- Always use `bpf_official.ld` for linking (standard ld.lld fails)
- Test new handlers in `tests/comprehensive-test.ts`
- Update `types.ts` and `INTEGRATION.md` for any new instructions

**Per-directory guides:**
- `cli/CLAUDE.md` — Zig CLI, TUI, WASM DEX, MCP server, Telegram bot, daemon
- `rpc/CLAUDE.md` — RPC proxy, x402 payments, MCP server, WebSocket relay
- `facilitator/CLAUDE.md` — x402 payment facilitator
- `site/dex/CLAUDE.md` — WASM DEX build pipeline, JS architecture

**Key references:**
- Math: WHITEPAPER.md sections 2-3, paper/aex-402-v2.pdf (GBC theory, RL proofs, mechanism design)
- Security: SECURITY_AUDIT_DEEP.md (all 35 issues resolved)
- Virtual pools: graduation-plan.md (implementation), paper/aex-402-v2.pdf §6 (formal specs + governance extensions)
- Coverage: tests/COVERAGE_REPORT.md (366 test cases)
- Migration tutorials: tutorials/
- Feature specs: specs/001-005

