Search projects, hackathons, sponsors and tech

    ← All projects

    Solvent

    Solvency-aware market making for 1inch Aqua: quotes widen as wallets thin and refuse past a floor

    Demo

    About

    1inch Aqua lets a market maker commit liquidity to a strategy without depositing it. Tokens stay in the maker's wallet and Aqua pulls them only when a taker fills, so one balance can back many strategies at once. The catch is that no strategy knows about the others. SwapVM prices each one as if it owned the whole wallet, Aqua's ship() never checks the total against the balance, and the whitepaper says it plainly: the AMM keeps quoting on virtual balances without checking real balances or allowances. The documented fix is for makers to watch their wallets and dock strategies by hand. We measured how often this happens. We rebuilt every maker's balance sheet across Aqua's entire history on Base from primary data, reading each wallet's balance and allowance from an archive node. 115 makers shipped 443 strategies. 78 of the 88 books that ever held more than $100 were under-backed at some point, and 89% of all material observations were under-backed. One book advertised about $51,000 of USDC while holding nothing, for weeks, and every taker who tried it paid gas for a revert. The same pipeline, pointed at the identical Aqua contracts on Arbitrum and Optimism, finds 123 of 138 material books under-backed across the three chains. Solvent makes an Aqua position aware of its own balance sheet, inside the strategy bytecode: - SolvencySkew, a new SwapVM instruction. The maker's utilisation (total committed across all strategies, divided by what the wallet holds and has approved) feeds into price. Below a threshold the quote is unchanged; above it the spread widens, because the last unit of a shared balance is worth more than the first. - SolvencyFloor, a new SwapVM instruction. Above a maker-chosen utilisation the strategy declines at quote time, with a reason, instead of reverting at settlement after the taker has spent gas. It lives in the program bytes, so any taker can read the covenant before trading. It is the automatic version of the whitepaper's manual docking. - The balance-sheet index. Both instructions need a maker's aggregate commitment, and Aqua cannot compute it on-chain. A subgraph maintains every maker's live book on Base, Arbitrum, Optimism and Base Sepolia, published on The Graph Network. A keeper publishes each maker's numbers to an on-chain oracle, SolventBook, where the instructions read them during the quote. Anyone can recompute the same numbers from the public index. The live demo has five views. Mainnet shows every real Aqua maker's book on Base, Arbitrum and Optimism right now, each capsule filled green only as far as the wallet can settle. How it works shows the deployed system, every component linked to its verified contract or logs. Quote desk runs live quotes from the deployed router on Base Sepolia: step through a healthy book, a wallet that drains (the spread widens from 3,106.67 to 3,202.86 at 87% utilisation) and a book past its floor (every quote declines with SolvencyFloor's reason), or connect a wallet and take a quote for real. Balance sheets and Settlement ledger show the numbers every quote reads and what actually settled, failed fills included. Makers get automated risk management instead of watching a wallet. Takers and agents get a pre-trade answer to whether a quote will fill. Aggregators can rank quotes by reserve ratio with one query and stop routing to phantom liquidity. The SDK, @aqua-solvent/core on npm, gives any app the same balance sheets on all four chains.

    How it's made

    Contracts (Solidity 0.8.30, Foundry). SolvencyFloor and SolvencySkew are custom SwapVM instructions. We followed upstream's own extension pattern: each takes a reserved placeholder slot in the opcode table (Floor at 0x22 in the guards bank, Skew at 0xb5 in the rate-tuning bank), so no official opcode moves and stock Aqua programs run unchanged on our router. SolventRouter is the official AquaSwapVMRouter shape carrying the extended set, running against a pinned, unmodified deployment of the official Aqua contract on Base Sepolia. Floor is validation-only (no register writes, safe under staticcall), so a decline happens inside a free view call. Skew wraps the rest of the program and mirrors upstream's FeeFlatIn arithmetic branch for branch, so a skewed quote equals a stock program carrying the equivalent flat fee, and below its threshold it is a provable no-op. Both read SolventBook, a small oracle holding each maker's committed and backing per token, with a timestamp so consumers can judge staleness. Two more instructions (ReputationGate and ReputationPriceAdjuster) read SolventScore, a counterparty score built from what actually settled. Tests include differential suites for the book and score math, invariant tests, a regression suite from upstream 1inch running on our router, and a test that reproduces the core failure against the real contracts: one wallet holding 10 WETH ships three strategies advertising 30, each passes a backing check on its own, and the second taker reverts. The Graph. This is the piece that makes the rest possible. Aqua stores balances per (maker, app, strategyHash, token) with no enumeration and no per-maker total, so no contract call can tell you a maker's aggregate commitment. The subgraph handles Shipped, Docked, Pulled and Pushed. Instead of replaying deltas, every touch re-reads Aqua.rawBalances for that strategy and token at the indexed block and folds the difference into the maker's book, which is exact by construction and immune to event ordering and wrapper edge cases. Backing is min(balanceOf, allowance to Aqua), read in the same handler, because a revoked allowance fails a fill just like an empty wallet. Ship calldata is decoded only to learn a strategy's token list; strategies shipped through wrappers are picked up on their first Pulled or Pushed, which covers 442 of 443 real strategies. One schema ships as four manifests (Base Sepolia, Base, Arbitrum, Optimism), all published on The Graph Network. A parity check rebuilds the largest books on each chain from an archive node and requires agreement to the wei. The keeper. Wallets drain through plain transfers that Aqua never sees, so the index alone goes stale. The attestor reads every book from the index, re-reads each wallet on-chain, diffs against SolventBook and writes only what changed, so a quiet market costs no gas. It runs on GitHub Actions about once a minute: each run does eight passes and triggers the next run only on success, with a 15-minute cron as a backstop. Its key holds ATTESTOR_ROLE and nothing else. The console is Vite, React, viem, wagmi and RainbowKit on GitHub Pages. The quote desk simulates every fill before sending and decodes the revert, so a floored book shows SolvencyFloor's reason before any gas is spent. It reads the published subgraphs through The Graph's gateway with a per-request fallback to Studio, pauses polling on hidden or idle tabs, and caps each browser's daily gateway use to protect the query budget. The SDK (@aqua-solvent/core on npm) exposes the same books on all four chains and encodes Solvent strategy programs. Notably hacky: - A reverted fill erases its own logs, so no subgraph can index a broken promise. SwapVM's transfer functions are private and swap() is not virtual, so a router cannot catch and re-emit the failure either. SolventRecorder records it from outside: a taker contract can try/catch its own swap and report the failure, or the attestor finds failed receipts aimed at the router and records them, citing the reverted transaction hash so anyone can check it on the explorer. - The mainnet numbers came from a one-off analyzer that decoded every ship() transaction, applied every Pulled and Pushed as a delta, and read backing from an archive node at 34 heights plus each book's peak. Partner technologies: - 1inch Aqua and SwapVM. Aqua's shared liquidity is what creates the problem, and SwapVM's instruction set is where the fix lives. Adding two opcodes put solvency inside the quote itself, with no change to Aqua. - The Graph. The aggregate balance sheet cannot be computed on-chain, so an index is the only place it can exist. The Graph let us maintain it on four chains from one schema, publish it on the decentralized network for anyone to query, and feed it back on-chain through the keeper.