diff --git a/RFPs/RFP-004-privacy-preserving-dex.md b/RFPs/RFP-004-privacy-preserving-dex.md index 1e8111e..2b3087b 100644 --- a/RFPs/RFP-004-privacy-preserving-dex.md +++ b/RFPs/RFP-004-privacy-preserving-dex.md @@ -88,10 +88,21 @@ participants. to LPs. 7. Implement slippage protection with user-configurable tolerance and minimum output guarantees. -8. Use Associated Token Accounts (ATAs) for all token interactions — - pool token accounts, LP token accounts, and trader token accounts - must use the deterministic ATA derivation per `(owner, mint)` pair - (see [LP-0014](https://github.com/logos-co/lambda-prize/blob/main/prizes/LP-0014.md)). +8. The DEX program must be compatible with Associated Token Accounts + (ATAs) for user-facing token accounts: when a trader or LP supplies + an ATA derived per `(owner, mint)` pair (see + [LP-0014](https://github.com/logos-co/lambda-prize/blob/main/prizes/LP-0014.md)), + the program must accept it without requiring an alternative + derivation. ATAs must not be forced on users; the program must also + accept any valid SPL token account owned by the caller. Pool-side + vault accounts may use program-derived addresses (PDAs) rather than + ATAs, matching Solana DEX practice. +9. Implement a permissionless `sync()` function that updates a pool's + cached reserves to match the actual vault token balances, absorbing + any surplus from unsolicited transfers into the pool for the benefit + of LPs. See + [Appendix: DEX Ecosystem Behaviour, section 10](../appendix/dex-ecosystem-behaviour.md#10-reserve-reconciliation-sync-and-skim) + for rationale and ecosystem precedent. #### Usability @@ -105,25 +116,28 @@ participants. 2. Provide a Logos mini-app GUI with local build instructions, downloadable assets, and loadable in Logos app (Basecamp) via git repo. -3. Provide a pool analytics view showing aggregate volume, TVL, and +3. Provide a CLI that covers core functionality of the program (pool + creation, swapping, LP management). The CLI may have fewer features + than the GUI mini-app but must support all essential operations. +4. Provide an IDL for the DEX program, preferably using the + [SPEL framework](https://github.com/logos-co/spel). +5. Provide a pool analytics view showing aggregate volume, TVL, and fee revenue without revealing individual positions. -4. Documentation must clearly explain what information is public vs. +6. Documentation must clearly explain what information is public vs. private for each action (trade size and pool used are visible on-chain; the private account that originated or receives the funds is not traceable). -5. Failed or rejected swaps must return clear, actionable error messages. -6. Provide an IDL for the DEX program, preferably using the - [SPEL framework](https://github.com/logos-co/spel). -7. Before each swap or liquidity operation, the mini-app must show the +7. Failed or rejected swaps must return clear, actionable error messages. +8. Before each swap or liquidity operation, the mini-app must show the estimated transaction fee. When the user interacts from a private account, it must also confirm that the shielded balance covers both the operation amount and fees within the single deshield action; a clear, actionable error must be shown if the balance is insufficient - — preventing partial deshields that could leave funds stranded in - an ephemeral account. -8. The mini-app must display a swap preview before the user confirms: + (preventing partial deshields that could leave funds stranded in + an ephemeral account). +9. The mini-app must display a swap preview before the user confirms: estimated output amount, effective price, price impact, and fee - taken — so the user can evaluate the trade before confirming. + taken, so the user can evaluate the trade before confirming. #### Reliability @@ -134,27 +148,30 @@ participants. 1. A swap against an existing pool completes within a single LEZ transaction. -2. The transaction size of each operation (swap, add/remove - liquidity, pool creation) must be documented; LEZ's block size is - limited and this budget may change during testnet. -3. Pool creation and liquidity operations complete within a single +2. Pool creation and liquidity operations complete within a single transaction each. +3. Compute unit usage and transaction size of each operation (swap, + add liquidity, remove liquidity, pool creation) must be documented + and benchmarked against LEZ devnet limits; LEZ's per-transaction + compute budget and block size may change during testnet. #### Supportability 1. The DEX program is deployed and tested on LEZ devnet/testnet. 2. End-to-end integration tests run against a LEZ sequencer (standalone - mode) and are included in CI — CI must be green on the default - branch. -3. Every hard requirement in Functionality, Usability, Reliability, + mode) and are included in CI. +3. CI must be green on the default branch. +4. Every hard requirement in Functionality, Usability, Reliability, and Performance has at least one corresponding test. -4. A README documents end-to-end usage: deployment steps, program +5. A README documents end-to-end usage: deployment steps, program addresses, and step-by-step instructions for interacting with the DEX via CLI and front-end (pool creation, swapping, LP management). -5. Submit a [doc packet](https://github.com/logos-co/logos-docs/issues/new?template=doc-packet.yml) - for the SDK, covering the developer integration journey for swapping, - pool creation, and liquidity management. -6. Provide Figma designs or equivalent for the mini-app GUI. +6. Submit a [doc packet](https://github.com/logos-co/logos-docs/issues/new?template=doc-packet.yml) + for the SDK, covering the developer integration journey for pool + creation, swapping, and liquidity management. +7. Submit a [doc packet](https://github.com/logos-co/logos-docs/issues/new?template=doc-packet.yml) + for the CLI, covering the core operator/user journey. +8. Provide Figma designs or equivalent for the mini-app GUI. #### + Privacy @@ -179,6 +196,29 @@ participants. or liquidity operation from a private account must use a freshly generated account with no prior on-chain history. +### Soft Requirements + +If possible. + +#### Functionality + +1. Support multi-hop routing across multiple pools within a single + transaction (e.g. flash-accounting style settlement of intermediate + hops), reducing slippage on token pairs without a direct pool. + +### Out of Scope + +The following are explicitly excluded from this RFP: + +- A `skim()` or `recoverSurplus()` instruction that extracts surplus + tokens from a pool's vault to a caller-specified address. Surplus + reconciliation is handled exclusively by the permissionless `sync()` + function (Functionality requirement F.9), which folds surplus into + the pool to benefit LPs. Among surveyed protocols, only Uniswap V2 + exposes a `skim()`-style instruction; Uniswap V4, Balancer V3, Curve + StableSwapNG, Raydium, and Orca Whirlpools do not. See + [Appendix: DEX Ecosystem Behaviour, section 10](../appendix/dex-ecosystem-behaviour.md#10-reserve-reconciliation-sync-and-skim). + ### Privacy Architecture All DEX liquidity pools are public on-chain state. User privacy is diff --git a/appendix/dex-ecosystem-behaviour.md b/appendix/dex-ecosystem-behaviour.md new file mode 100644 index 0000000..f146fe9 --- /dev/null +++ b/appendix/dex-ecosystem-behaviour.md @@ -0,0 +1,357 @@ +# Appendix: DEX Ecosystem Behaviour + +This appendix surveys how existing decentralised exchanges implement +behaviours required by +[RFP-004](../RFPs/RFP-004-privacy-preserving-dex.md). Each section +maps an RFP requirement to the equivalent mechanism in production +protocols, showing that the requirement reflects established industry +practice. All data is sourced from the +[research-dex](https://github.com/marclawclaw/research-dex) vault; +individual project notes contain full citations. + +## Protocols considered + +TVL figures are DeFiLlama snapshots as of 2026-04-27. Volume figures +cite each protocol's own dashboards or DeFiLlama where available. + +| Protocol | Ecosystem | Type | TVL | Cumulative volume | +|----------|-----------|------|-----|-------------------| +| Uniswap V2 | Ethereum (multi-chain) | Constant-product AMM | ~$970M | $604B+ | +| Uniswap V4 | Ethereum (~16 chains) | Singleton AMM with hooks | ~$720M | >$190B (2025) | +| Balancer V3 | Ethereum (9 chains) | Vault-based AMM with hooks | ~$80M (post 2025-11 V2 exploit) | N/A (fee data only) | +| Curve Finance (StableSwapNG and earlier) | Ethereum (multi-chain) | StableSwap AMM | ~$1.7B | ~$126B (2025) | +| CoW Protocol | Ethereum (~9 chains) | Intent-based batch auction | N/A (no custody) | $87B (2025) | +| Raydium | Solana | AMM + CLMM | ~$1.0B | $695.7B all-time | +| Orca Whirlpools | Solana (+ Eclipse) | CLMM | ~$255M | $36B+ all-time (Orca dashboards) | + +## 1. Constant-product AMM with public pool state + +**RFP-004 requirement:** Implement an AMM program with public liquidity +pools (requirement F.1). + +**Ecosystem practice:** The constant-product invariant (`x * y = k`) is +the most widely deployed AMM mechanism. Uniswap V2 is the reference +implementation, with major forks including PancakeSwap, SushiSwap, and +Aerodrome (DeFiLlama tracks dozens of V2-derived deployments). Pool +state (reserves, price, cumulative volume) is fully public on-chain in +every surveyed protocol. No production AMM stores pool state privately. + +On Solana, both Raydium and Orca implement the same constant-product +invariant for their base pools, adapted for the SVM account model: +token balances reside in SPL Token Accounts whose authority is a +program-derived address (PDA), and pool state is stored in a separate +data account. + +RFP-004's specification of a public-state AMM with the +deshield/swap/re-shield privacy layer applied at the UX level is +consistent with the universal industry pattern: pool state is public; +privacy, where it exists, is enforced outside the pool contract. + +## 2. Immutable fee tiers with multiple pools per pair + +**RFP-004 requirement:** The pool creator selects a fee tier at +creation (e.g. 0.01%, 0.05%, 0.3%, 1%); the tier is immutable. +Multiple pools for the same pair with different tiers can coexist +(requirement F.6). + +**Ecosystem practice:** + +| Protocol | Fee tier model | Immutable | Multiple pools per pair | +|----------|---------------|-----------|------------------------| +| Uniswap V2 | Fixed 0.3% (all pools) | Yes | No (one pool per pair) | +| Uniswap V4 | Configurable per pool; hooks can add dynamic fees | Yes (base tier) | Yes | +| Balancer V3 | Configurable per pool; StableSurge hook adds dynamic fee | Yes (base tier) | Yes | +| Curve StableSwapNG | Configurable per pool; `offpeg_fee_multiplier` adds dynamic scaling | Yes (base tier) | Yes | +| Raydium CLMM | 8 fee tiers (0.01% to 2%) | Yes | Yes | +| Orca Whirlpools | 6 tick spacings mapping to fee tiers (0.01% to 2%) | Yes (adaptive pools add a variable component) | Yes | + +Immutable base fee tiers are the norm. Every protocol launched after +Uniswap V2 supports multiple pools per pair with different fee tiers. +The RFP-004 fee model (0.01%, 0.05%, 0.3%, 1%) mirrors the standard +Uniswap V3/V4 tier set, which is also the set used by Raydium and Orca +(with additional tiers at 0.02% and 2%). + +## 3. Trading fees paid by trader, distributed to LPs + +**RFP-004 requirement:** Trading fees are paid by the trader and +distributed to LPs (requirement F.6). + +**Ecosystem practice:** + +| Protocol | Fee payer | LP share | Protocol share | Other | +|----------|-----------|----------|----------------|-------| +| Uniswap V2 | Trader (input token) | 0.25% (post-UNIfication) | 0.05% (1/6 of 0.3%) | UNIfication proposal passed 2025-12-26; pre-activation LPs received the full 0.3% | +| Uniswap V4 | Trader (input token) | Majority | Configurable per pool | Hook can adjust | +| Balancer V3 | Trader (input token) | ~50% (varies) | ~50% (split with pool creator) | Yield fee: 10% on boosted pools | +| Curve StableSwapNG | Trader (output token) | Majority | Admin fee (fraction of swap fee) | Dynamic fee on imbalanced pools | +| Raydium CLMM | Trader | 84% | 12% RAY buyback + 4% treasury | | +| Orca Whirlpools | Trader | 87% | 12% DAO treasury + 1% Climate Fund | | + +In every surveyed protocol, the trader pays fees as part of the swap. +Fees accrue to LPs implicitly by growing the pool invariant (Uniswap +V2/V4), or via fee accumulator tracking (CLMM protocols, Curve). + +The RFP-004 model (trader pays, LPs receive) is universal. + +## 4. Slippage protection with minimum output guarantees + +**RFP-004 requirement:** Implement slippage protection with +user-configurable tolerance and minimum output guarantees (requirement +F.7). + +**Ecosystem practice:** Every surveyed protocol enforces slippage +protection via a `minimum_amount_out` (or equivalent) parameter that +reverts the transaction if the output falls below the user's threshold: + +| Protocol | Parameter | Enforcement | +|----------|-----------|-------------| +| Uniswap V2 | `amountOutMin` on Router | Router reverts if output < minimum | +| Uniswap V4 | `amountSpecified` + `sqrtPriceLimitX96` | PoolManager reverts on limit breach | +| Balancer V3 | `limit` in swap params | Vault reverts if output < limit | +| Curve StableSwapNG | `_min_dy` on `exchange()` | Pool reverts if output < minimum | +| Raydium | `minimum_amount_out` | Program reverts if output < minimum | +| Orca Whirlpools | `other_amount_threshold` | Program reverts if output < threshold | + +This is a universal safety mechanism. No production AMM omits it. + +## 5. Pool creation for arbitrary token pairs + +**RFP-004 requirement:** Support creation of liquidity pools for +arbitrary token pairs (requirement F.2). + +**Ecosystem practice:** Permissionless pool creation is the default: + +| Protocol | Permissionless | Factory pattern | +|----------|---------------|-----------------| +| Uniswap V2 | Yes | `createPair()` on Factory contract | +| Uniswap V4 | Yes | `initialize()` on PoolManager | +| Balancer V3 | Yes | Pool registration on Vault | +| Curve StableSwapNG | Yes | `CurveStableSwapFactoryNG.vy` using `create_from_blueprint()` | +| Raydium | Yes | `initialize()` instruction per pool type | +| Orca Whirlpools | Yes | `initialize_pool()` instruction | + +Every protocol uses a factory or registry pattern where any user can +create a pool for any token pair. Curve restricts some parameters +(asset type classification) but pool creation itself is open. + +## 6. Single-transaction operations + +**RFP-004 requirement:** A swap, pool creation, and liquidity +operations each complete within a single LEZ transaction (requirements +P.1, P.3). + +**Ecosystem practice:** Every surveyed protocol executes swaps, pool +creation, and liquidity add/remove as atomic single-transaction +operations. On Ethereum, this is a single EVM transaction. On Solana, +this is a single Solana transaction (which may contain multiple +instructions). + +Uniswap V4 and Balancer V3 go further: multi-hop swaps across multiple +pools settle in a single transaction with only two token transfers +(input and output) regardless of the number of intermediate pools, +using flash accounting. On Solana, Raydium and Orca achieve multi-hop +routing through Jupiter aggregation within a single transaction. + +The RFP-004 single-transaction requirement is consistent with +every production protocol. + +## 7. Pool analytics: aggregate volume, TVL, and fee revenue + +**RFP-004 requirement:** Provide a pool analytics view showing +aggregate volume, TVL, and fee revenue without revealing individual +positions (requirement U.3). + +**Ecosystem practice:** All surveyed protocols expose pool-level +aggregate metrics on-chain or via indexers: + +| Metric | On-chain | Typical source | +|--------|----------|----------------| +| TVL | Derived from pool token balances (public) | DeFiLlama, protocol subgraphs | +| Volume | Cumulative swap counters stored on-chain (Uniswap V2: `kLast`, TWAP accumulators; Raydium CLMM: `swap_in_amount` / `swap_out_amount` fields) | Subgraph indexing of Swap events | +| Fee revenue | Derived from volume x fee rate, or accumulated in on-chain fee counters | Protocol dashboards | + +Individual LP positions are public on-chain in every protocol (LP token +balances in Uniswap V2, NFT positions in CLMMs, `admin_balances` in +Curve). RFP-004's privacy model does not change this: LP positions +remain public, but the private account that originated the funds is not +traceable when the deshield pattern is used. + +## 8. Token account derivation on Solana DEXes + +**Ecosystem practice on Solana:** Solana DEX programs split token +account handling between pool-side and user-side: + +- **Pool-side vaults are PDA-derived, not ATAs.** Both Raydium and Orca + use deterministic program-derived addresses for pool token accounts. + Raydium CLMM uses the seed `["pool_vault", pool_state, token_mint]`; + Orca Whirlpools uses similar per-pool, per-mint derivation. Pool + vaults are not ATAs because the pool program (not a wallet) is the + owning authority. +- **User-side accounts are conventionally ATAs but not required by the + program.** Raydium and Orca instructions accept any valid SPL token + account owned by the caller, validating ownership and mint rather + than derivation. Frontends and SDKs default to ATAs and create them + on-demand via the SPL Associated Token Account program, so users + encounter ATAs as the path of least resistance, but non-ATA token + accounts (e.g. delegated accounts, Token-2022 accounts with extensions) + can also be used. + +The SPL Associated Token Account program itself derives one token +account per `(wallet, mint)` pair, providing a predictable address that +any sender can compute without coordination. + +## 9. Vault accounting and balance reconciliation + +**RFP-004 context:** The DEX program must maintain correct pool +balances under concurrent swaps (requirement R.1). + +Three distinct vault accounting models exist in production: + +### Cached reserves with sync/skim (Uniswap V2) + +The pool caches token reserves in storage (`reserve0`, `reserve1`) and +updates them after each operation. Actual ERC-20 balances can drift +from cached reserves through direct transfers, rebasing tokens, or +fee-on-transfer mechanics. Two functions handle reconciliation: + +- **`sync()`** updates cached reserves to match live balances (absorbs + a deficit from a negative rebase; LPs bear the loss). +- **`skim(to)`** transfers the surplus (balance minus reserve) to a + caller (extracts orphaned tokens from a positive rebase; anyone can + call it). + +This model is simple and self-healing but leaks positive rebase value +to MEV bots (the `skim()` surplus is publicly extractable). + +### Live balance reads with fee isolation (Curve StableSwapNG) + +The pool reads live token balances via `_balance()` on every operation +and stores admin fees in a separate `admin_balances[]` array. There is +no cached reserve to drift. Positive rebases accrue to LPs (not +skimmable by bots); negative rebases reduce LP value automatically. +Admin fees are isolated from LP balances, so neither rebases nor direct +transfers corrupt fee accounting. + +### Flash accounting / transient accounting (Uniswap V4, Balancer V3) + +A singleton contract holds all pool tokens. Operations accumulate +credits and debits in EIP-1153 transient storage (TSTORE/TLOAD). Only +the net token amounts are transferred at the end of the session. There +is no cached reserve separate from the live balance. This model +eliminates the reserve-reconciliation pattern: TSTORE costs 100 gas vs +SSTORE's 20,000 gas for a cold write, so per-operation state tracking +is roughly two orders of magnitude cheaper. + +### Solana/SVM account model (Raydium, Orca) + +Token balances reside in SPL Token Accounts whose authority is a pool +PDA. The pool program can only move tokens *out* of the vault by +signing with the PDA via `invoke_signed`. However, any external account +can transfer tokens *into* a vault account: the SPL Token `transfer` +instruction only requires the sender's signature, not the recipient's. +This means surplus can accumulate in vault accounts through unsolicited +transfers, just as on Ethereum. + +Neither Raydium nor Orca implements a `sync()` or `skim()` equivalent. +Both protocols update pool state atomically within each swap or +liquidity instruction and do not read live vault balances for reserve +reconciliation. In practice, unsolicited transfers to pool vaults are +rare on Solana (there are no rebasing tokens, no fee-on-transfer +mechanics, and no interest accrual), so the surplus problem has not +been operationally significant. The surplus simply sits in the vault, +unacknowledged by the pool's reserve accounting. + +### Summary + +| Model | Surplus possible | Recovery mechanism | Rebase handling | +|-------|-----------------|-------------------|-----------------| +| Cached reserves (V2) | Yes (direct transfer, rebase) | `sync()` / `skim()` | Partial | +| Live balance reads (Curve) | No (reads live balance) | Not needed | Native | +| Flash accounting (V4, Balancer V3) | No (singleton, transient deltas) | Not needed | Via hooks | +| SVM accounts (Raydium, Orca) | Yes (unsolicited transfer) | None implemented | Not applicable (no rebasing tokens on SVM) | + +## 10. Reserve reconciliation: `sync()` and `skim()` + +In a cached-reserve AMM, the pool stores token reserves in its own +state and updates them after each operation. The live token balance +(actual tokens held by the pool's vault) can diverge from the cached +reserves when value enters or leaves the vault outside the normal +swap or liquidity flow. Different protocols handle this divergence +differently. + +### How divergence arises + +On Ethereum, several mechanisms can cause vault balances to drift +from cached reserves: + +- **Direct transfers:** anyone can `transfer()` ERC-20 tokens to a + pool's address without calling the pool contract. +- **Rebasing tokens:** elastic-supply tokens (e.g. AMPL, stETH before + wrapper) periodically adjust holder balances; positive rebases + create surplus, negative rebases create deficits. +- **Fee-on-transfer tokens:** the amount received differs from the + amount sent, so post-transfer balances do not match the swap + arithmetic. +- **Interest-bearing tokens:** balances grow over time without explicit + transfers. + +On SVM, the surface is narrower. There are no rebasing tokens, no +fee-on-transfer mechanics, and no interest-bearing balance growth. +The only divergence channel is an explicit SPL `transfer` instruction +sending tokens to the pool's vault account outside the normal flow, +because the SPL Token program permits transfers without the recipient's +signature. + +### `sync()` and `skim()` in Uniswap V2 + +Uniswap V2 exposes two permissionless functions to reconcile cached +reserves with vault balances: + +- **`sync()`** writes the cached reserves to match the live balances. + Surplus is absorbed into the pool and accrues to LPs proportionally. + Deficit (from negative rebase) is written down so swaps continue to + function rather than reverting. +- **`skim(to)`** transfers the difference between the live balance and + the cached reserve to a caller-specified address, leaving the cached + reserves unchanged. This extracts surplus without folding it into LP + value. + +Both functions are permissionless. Their interaction creates a race: +any pending `skim()` can be neutralised by a `sync()` call, since +`sync()` consumes the surplus into the pool. On Ethereum, MEV bots +monitor pools for skimmable amounts and routinely capture rebase +surplus before LPs benefit. + +### Ecosystem precedent + +| Protocol | sync() | skim() / recoverSurplus() | Notes | +|----------|--------|--------------------------|-------| +| Uniswap V2 | Yes (permissionless reserve sync) | Yes (permissionless) | Handles rebasing tokens, direct transfers, and negative-rebase deficits | +| Uniswap V4 | `PoolManager.sync()` exists but checkpoints balances for flash accounting, not reserve reconciliation | No | Flash accounting eliminates cached reserve drift | +| Balancer V3 | No | No | Transient accounting; no cached reserves | +| Curve StableSwapNG | No (reads live balances) | No | Admin fees isolated in a separate array | +| Raydium | No | No | Surplus silently accumulates in vault accounts | +| Orca Whirlpools | No | No | Same as Raydium | + +Uniswap V2 is the only surveyed protocol that implements both +functions for reserve reconciliation. Later AMMs eliminated the need +for them by switching to live balance reads (Curve) or transient +accounting (Uniswap V4, Balancer V3). Solana DEXes implement neither: +surplus tokens in Raydium and Orca vaults remain unacknowledged by +pool accounting and are not recoverable through any program-exposed +instruction. + +## References + +Full research notes with per-claim source URLs are available in +the [research-dex](https://github.com/marclawclaw/research-dex) +Obsidian vault. Key sources by section: + +1. Uniswap V2 whitepaper: https://app.uniswap.org/whitepaper.pdf +2. Uniswap V4 whitepaper: https://app.uniswap.org/whitepaper-v4.pdf +3. Balancer V3 launch: https://medium.com/balancer-protocol/balancer-v3-is-live-2e5e8462aa4c +4. Curve StableSwapNG docs: https://docs.curve.finance/stableswap-exchange/stableswap-ng/pools/overview/ +5. Raydium CLMM source: https://github.com/raydium-io/raydium-clmm +6. Orca Whirlpools source: https://github.com/orca-so/whirlpools +7. Orca developer docs: https://dev.orca.so +8. DeFiLlama: https://defillama.com