# flexdex: full reference for AI assistants > flexdex is a bin-liquidity DEX (Liquidity Book / DLMM style) for XPR Network with native limit orders, a 1 bp dollar hub, an integrated money market that accepts LP bin positions as collateral, and perps in progress. One contract account holds every token and keeps an internal ledger. This file summarises the design document `docs/DEX.md`, the contract ABI and the app's contract layer. Claims are marked Built (in the contract and tested) or Design (specified, not built) where it matters. Last reviewed: 2026-10-09. ## 1. Status - Runs on XPR Network TESTNET. Contract account: `flexdex`. Testnet tokens have no value. - Mainnet: after an audit (roadmap phase D4: audit, then guarded mainnet with TVL caps). The operator registered `flexdex`, `fdxadmin`, `fdxguard`, `flexmm`, `flexarb`, `flexliq`, `flexlend` and `flexvault` on mainnet as reserved names: no code and no funds until after the audit. - Testnet chain id: 71ee83bcf52142d61019d95f9cc5427ba6a0d7ff8accd9e2088ae2abeaf3d3dd. Testnet RPC endpoints the app uses: https://tn1.protonnz.com, https://testnet.protonchain.com, https://test.proton.eosusa.io. Explorer: https://testnet.explorer.xprnetwork.org - The pure engine was audited internally on 2026-09-29 (five parallel reviewers, 55 findings, all fixed with regression tests). An external audit is planned before mainnet. - Built in the contract: ledger, pools, liquidity, limit orders (pots), merged multi-pool routing, `exec` batches with `settle` and `soft`, Alcor-format memos, exact-out, read-only `quote`, farms, one-tap session keys, lending (single-asset markets and LP positions as collateral), order types (GTC / IOC / FOK / post-only, GTT expiry, cancel-replace). The ABI also contains perps actions and exec ops (section 9). - Design only (not built): soft-liquidation bands in the router, Dutch-auction liquidation lots with a keeper priority window, a stability pool, cross-margin between perps and lending, deposit-and-trade from bridges, routing legs through Alcor / proton.swaps inside `exec`. ## 2. Accounts | Account | Role | |---|---| | `flexdex` | The contract: ledger, pools, orders, lending, perps | | `fdxadmin` | Admin (config, listing tokens, creating pools) | | `fdxguard` | Guardian (pauses, reduce-only switch for perps) | | `flexmm` | Operator's LP / market-making bot account | | `flexarb` | Operator's arbitrage bot: keeps flexdex in line with Alcor, MetalX, proton.swaps | | `flexliq` | Operator's liquidation keeper | The bot accounts are public, named, and have no exclusive role. ## 3. Pools and bins - A pool pairs token X with token Y; each token is keyed by (symbol, contract). Several pools per pair are allowed (different bin steps). - Bin `id` has price P(id) = (1 + bin_step/10000)^(id - 2^23), in raw Y units per raw X unit. Inside a bin the price is constant (no slippage inside a bin). - Layout: bins below the active bin hold only Y (and bid orders); bins above hold only X (and ask orders); only the active bin mixes both LP tokens. - Per-bin liquidity L = P*x + y. Swaps never decrease a bin's L; only fees add to it. Shares are per bin and fungible within it. - Bin steps and base fees (defaults, operator may change): 1 bp step / 0.01% fee (dollars), 5 bp / 0.05% (majors), 25 bp / 0.125% (mid caps), 100 bp / 0.5% (memes), 200 bp / 1% (launches, wide collateral ranges). - Dynamic fee (LB v2.1): fee = min(base + variable, 10%), where the variable part grows with bins crossed in a short window (filter 30 s, decay 600 s). Protocol share default 10% of the fee (cap 25%); the rest goes to LPs. - Composition fee: adding to the active bin with an X:Y ratio different from the bin's is an implicit swap and is charged a fee that goes to the bin's existing LPs. - A swap moves at most 64 bins per pool; beyond that it is a partial fill that min_out accepts or rejects. ## 4. Swaps ### Merged routing (Built) A hop can name several pools of one pair; the contract walks them as one merged ladder, best price first, which is the optimal split. At most 4 pools per pair in a hop. ### From the wallet: transfer memos (Built, Alcor-compatible) Transfer the input token to `flexdex`: ``` swapexactin#### swapexactout#### ``` - ``: pool ids separated by `,` (one entry per hop, up to 4 hops); a hop may merge pools of one pair with `+`, e.g. `12+13,40`. - `` / ``: extended asset `" @"`, e.g. `1.234500 XMD@xmd.token`. - ``: unix seconds, 0 = none. - Output goes to `` as a transfer; unspent input is refunded to the sender. No ledger row is needed. - Exact-out takes one hop with one pool. ### From the ledger: exec op_swap `op_swap{token_in, amount, hops[][], min_out}` inside `exec`, settled to the ledger. ### Quotes Read-only action `quote(token_in, token_out, pools[], amount, exact_out)` returns `quote_view{amount_in, amount_out, complete, pools[], pool_in[], pool_out[], active_after[]}`. Run it with `send_read_only_transaction` (no signature, no CPU billed). Only nodes with read-only threads enabled accept it; many public API nodes do not. The result is exact for the state it reads; the swap still needs min_out. ## 5. The ledger (internal balances) - `open(owner, token, ram_payer)` creates a balance row (a deposit notification cannot pay RAM, so deposits into unopened rows are refused). - Transfer to `flexdex` with memo `deposit` (or `deposit:`) credits the ledger. - `withdraw(owner, extended_asset)` sends ledger funds back to the wallet. `close(owner, token)` removes an empty row and returns its RAM. - Liquidity, limit orders, lending and perps all use the ledger. A wallet user can do `transfer(deposit)` + `exec` in one transaction. - Deposits check that the contract's real balance covers everything owed plus the deposit, so tokens with transfer taxes fail closed. Some flextokens (e.g. EASY) charge the SENDER an extra 2% per transfer unless the sender opted out with the token's `noflexzone` action; the app shows this. ## 6. Batches: exec ``` exec(owner, ops[], settle[], soft) auth: owner settle = [{token, min_delta}] e.g. "end at least 0.10 XMD up" ``` - Ops accumulate a signed delta per token in memory; deltas may go negative mid-batch (free flash credit) as long as the end is covered. At the end: negative deltas debit the owner's ledger, positive deltas credit it, `settle` is checked, health checks run if needed, and dirty rows are written once. - `soft = true`: a failed settle or min_out makes `exec` a no-op instead of failing. Lending and perps ops are refused in soft batches. - Up to 16 ops per `exec`. - Op types in the ABI: `op_swap`, `op_addliq`, `op_remliq`, `op_order`, `op_cancel`, `op_limit`, `op_replace`, `op_send`, `op_supply`, `op_redeem`, `op_borrow`, `op_repay`, `op_collat`, `op_liquidate`, `op_pledge`, `op_liqlp`, `op_pmargin`, `op_ptrade`, `op_pvault`, `op_pclaim`, `op_ptrig`, `op_pcancel`. - One-tap sessions: `sessopen(owner, ttl_s, max_slip_bps, caps[])` and `sexec(owner, ops, settle)` let a session key run swaps, orders and cancels only, within per-token spending caps and a slippage limit against the pool TWAP; it cannot send or withdraw. `sessclose` ends it. ## 7. Liquidity - `addliq(owner, pool, bins[{bin, x, y}], min_shares_each)`: 1 to 64 bins per action; the app sends up to 4 actions (256 bins) in one transaction. Also `op_addliq{pool, bins}` in `exec`. - `remliq(owner, pool, bins[{bin, shares}], min_x, min_y)` returns to the ledger; `remliqwd` sends everything removed to the wallet. Also `op_remliq` in `exec`. - Rebalancing (remove + add over a new range) can be one `exec` with flash accounting. - Shapes in the app: spot (uniform), curve (concentrated in the middle), bid-ask (heavier at the edges); one-sided ranges act like a ladder of limit orders. - Fees compound into the bins (they raise L); there is no separate fee claim for swap fees. - Farms (Built): `farmcreate(funder, pool, token, amount, days)` streams rewards to the ACTIVE bin's liquidity only; `claim(owner, pool)` pays into the ledger. ## 8. Limit orders - Each bin has an ask pot (X for sale) and a bid pot (Y for X). Fills are pro-rata in O(1) CPU across all orders in a pot. The pot is filled before LP liquidity in that bin. - Makers earn the swap fee on the part their order fills (a maker rebate paid by takers), less the protocol share. - `placeorder(owner, pool, bin, sells_x, amount)` and `cancelorder(owner, pool, order_id)`; orders that would cross are refused (post-only). - `op_limit{pair_pools[], sells_x, amount, limit_id, tif, rest_pool, expires, min_out}`: tif 0 GTC (take up to the limit, rest the remainder), 1 IOC, 2 FOK, 3 POST (post-only). `expires` = GTT expiry in unix seconds (GTC / POST only; between now + 60 s and now + 90 days; 0 = none). An expired order keeps filling until cancelled or removed with the permissionless `expire(actor, owner, pool, ids[])`. - `op_replace{pool, order_id, new_bin, new_amount, expires}`: cancel-replace keeping the order id. - There is no separate CLOB: the bin pots are the order book; fine-tick (1 bp) pools serve as fine-tick books. ## 9. Lending (Built: D2, live on testnet) - One market per listed token (`lmarkets`), positions in `lpos` (scope owner). - Exec ops: `op_supply{token, amount}`, `op_redeem` (0 = all), `op_borrow`, `op_repay` (0 = all), `op_collat{token, on}`, `op_liquidate{borrower, debt_token, coll_token, repay, min_seize}`. - LP positions as collateral: `op_pledge{pool, on}` pledges the owner's whole position in an enabled pool (up to 512 bins). The position is valued at a stressed reference price (the lower of TWAP and spot, minus a stress drop), each leg at its market's collateral factor. Removing pledged liquidity re-checks health. LP liquidation: `op_liqlp{borrower, debt_token, pool, repay}` transfers a fraction of every pledged bin to the liquidator; nothing is dumped into the pool. - Prices: XPR `oracles` fresh median, fixed pegs, or a pool TWAP (collateral uses the lower of TWAP and spot, debt the higher). Pool prices come from committed state, so a batch cannot inflate its own collateral. - Health is checked once at the end of a batch. Health factor = liquidation-threshold-weighted collateral / debt; below 1 the account can be liquidated. - Some markets are collateral-only (for example EASY at launch). ## 10. Perps (in progress) - Design: each perp market is a bin pool of virtual tokens priced on chain; a protocol vault per market is the counterparty; the oracle index anchors funding, margin and liquidation, not the trade price. Engine Built and fuzz-tested. - The contract ABI has perps exec ops `op_pmargin{market, amount}`, `op_ptrade{market, size, limit}`, `op_pvault{market, kind, amount, min}`, `op_pclaim{market}`, `op_ptrig{...}` (stop-loss, take-profit, limit and stop entries, reduce-only, OCO, expiry) and `op_pcancel{id}`, plus read-only `pacct` and `pvnav`. - The wiring plan (2026-10-09) for v1: isolated XMD margin per (owner, market), markets BTC, ETH and XPR, two-step vault withdrawals, keeper-executed triggers. It was tested on a local chain; check the app's Perps page for what is live on testnet. Cross-margin with lending and an EASY market are deferred. ## 11. Dollar hub - 1 bp pools between dollars (XMD, XUSDC, XUSDT), kept on peg by the operator's market maker. - XMD <-> other stables can also go through `xmd.treasury` (Metal Dollar) 1:1 when that pays more; the app's swap box compares both. The treasury is reserve- and mint-cap-gated. - On testnet the hub uses XUSDC@xtokens, XUSDT@xtokens, XMD@xmd.token (older flextoken-issued dollar copies are legacy, exit-only). ## 12. Tables (code `flexdex`) | Table | Scope | Contents | |---|---|---| | `config` | flexdex | admin, guardian, pause flags | | `tokens` | flexdex | id, extended symbol, tracked (everything owed), paused, min order, min farm | | `pools` | flexdex | id, tx, ty, bin_step, fee params, protocol_share, active bin, oracle ring, paused | | `bins` | pool id | id, rx, ry, shares | | `binmap` | pool id | bitmap of non-empty bins | | `pots` | pool id | order pots; key = (bin << 1) or ask flag | | `balances` | owner | ledger: token id, amount | | `lpshares` | owner | LP shares, chunked per 64 bins: key = (pool << 32) or (bin >> 6), entries keep the low 6 bits | | `orders` | owner | id, pool, bin, sells_x, r0, expires | | `farms`, `farmacc` | pool id | farm streams | | `farmpos` | owner | banked farm rewards | | `lmarkets` | flexdex | lending markets | | `lpos` | owner | lending positions (supply shares, scaled debt, collateral flag) | | `pledges` | owner | pools pledged as collateral | | `lpcoll` | flexdex | pools enabled as LP collateral | | `perpcfg`, `perpmkts` | flexdex | perps config and markets | | `pmargin`, `pvault` | owner | perps margin and vault shares | | `ptrigs` | flexdex | perps trigger orders | | `sessions` | flexdex | one-tap session keys | Amounts in tables are raw integers (divide by 10^precision). Market data: the contract emits an inline `logswap(pool, trader, token_in, token_out, amount_in, amount_out, active_before, active_after, fee_in, ts_ms)` per pool per swap; Hyperion history nodes index it. ## 13. Risks and safety - Testnet only; contracts are pre-audit for mainnet. - Solvency guard: for every token, the contract's real balance must cover `tracked` (ledger + pools + orders + markets); deposits and payouts are checked (`chkbal`). - Pausing a pool blocks swaps, new orders and new liquidity; exits (remove liquidity, cancel or expire orders, withdraw) keep working. - LPs bear divergence (impermanent) loss; out-of-range positions hold one token and earn no fees. - Borrowers can be liquidated when health drops below 1. - Prices shown in the app are estimates; min_out / settle protect every trade. - Token look-alikes exist: check the contract, not just the symbol. ## 14. Links - App routes: /trade, /pools, /dollars, /lend, /perps, /portfolio, /portfolio?account= (read-only view of any account), /solvency, /widget - Summary: /llms.txt - Design document: `docs/DEX.md` in the flexdex repository (no public URL yet)