Search projects, hackathons, sponsors and tech

    ← All projects

    BONE DRY

    A promise that can't bounce: cross-chain solvency for 1inch Aqua via Opcode 35 & Uniswap v4

    Demo

    About

    Bone Dry: a promise that can't bounce Bone Dry makes sure a market maker on 1inch Aqua can't sell the same money twice. If a maker is promising more than their wallet can pay, their quote gets worse, and past a limit the trade is refused, on-chain, before anything breaks. 1. The problem On 1inch Aqua, market makers don't lock their money in a pool. It stays in their own wallet, and they just publish a promise: "I'll trade up to this much." That's efficient, but nothing stops a maker from promising the same balance again and again. When several traders try to use those promises, the first trade goes through and the rest fail, because the money isn't there. We measured it. 7 of the 12 largest maker positions on Base were promising money they didn't hold. On Ethereum, about 102,000 WETH was advertised, but only around 4 WETH could actually be delivered. 2. The fix: opcode 35 1inch Aqua strategies run on a small program called SwapVM. We added a new instruction to it, opcode 35. A maker can add it to any strategy, and every time someone trades against it, it: Checks what the wallet already owes to the maker's other strategies. Widens the price as the wallet gets stretched. The more of the wallet is already promised, the less generous the quote. Refuses the trade past a limit the maker chooses, for example 80%, so a promise the wallet can't cover never gets filled. Makers can still withdraw a promise at any time, in one transaction. 3. Swapping through Uniswap v4 We built a Uniswap v4 hook called Tap. Its pool holds zero money. When someone swaps, the hook fills the trade straight from Aqua makers' wallets in the same transaction. If a maker refuses, the hook doesn't fail the swap. It records the refusal on-chain and fills the trade from the next maker that can pay. 4. One wallet, two chains Bone Dry is live on Base and Ethereum, and the same wallet can make promises on both. For traders: before you sign, the app quotes your trade on both chains. It shows the price you'd get and how much of your trade each chain can actually fill, so you can choose. For makers: the app shows your whole book across both chains, promised against what your wallet holds, and warns you before you publish a strategy that would be refused. 5. The Graph Our Aquifer subgraph on Base reads every strategy as it's published. It spots the ones using opcode 35 and checks whether each one's list of the maker's other strategies is complete. It also records every opcode-35 fill and every refusal the hook catches, so the app can show what happened to each maker's strategies, with links to the transactions. 6. Proof it works Live on Base mainnet and Ethereum mainnet (router and Uniswap v4 hooks on both). First real trade through opcode 35 on Base: the quote was cut by 2.98% because the wallet was 60% committed (tx 0x95aab656…). First real refusal on Base: a strategy at 90% committed was turned away against its 80% limit, and the swap still completed from another maker (tx 0xe29c843d…). 56 contract tests and 15 subgraph tests, all passing.

    How it's made

    Built with: Solidity and Foundry · 1inch Aqua and SwapVM · Uniswap v4 (hooks and PoolManager) · The Graph (AssemblyScript subgraph) · Next.js 16, React 19, viem, wagmi and RainbowKit · Postgres for an Ethereum strategy index · deployed on Vercel. 1. Opcode 35, added to 1inch SwapVM without forking it Extending, not copying: 1inch's opcode table is overridable, so our BoneDryRouter extends it and adds one new instruction at slot 35. The original opcodes 0–34 are untouched, so every existing 1inch strategy still runs on our router. What a strategy carries: its settings are packed into its program as [declared total][sibling count][sibling hashes][max utilisation][widen bps]. On every trade the instruction: reads the maker's other strategies on-chain (up to 6); rejects a declared total lower than what it finds; reads the wallet's real balance and allowance; then widens the price or refuses. Safe maths: it uses mulDiv so huge numbers can't overflow. The whole router is 19,793 bytes, under Ethereum's 24,576-byte contract limit, with nothing removed. Events only on real trades: it emits EncumbranceApplied only on a real trade, never during a price quote, so the index only records trades that happened. 2. A Uniswap v4 hook with an empty pool Mining the address: a v4 hook's permissions live in the last bits of its address. Our Foundry script searches salts until the address carries exactly "before swap" and "return a delta". Filling the swap: the hook takes each swap in beforeSwap and fills it maker by maker. It pulls tokens from each maker's wallet through Aqua and settles with the PoolManager in the same transaction, so the pool never holds a balance. Catching refusals: each maker's quote and fill is wrapped in try/catch. A refusing maker becomes a MakerSkipped(maker, strategy, token, amount, reason) event, and the trade moves on to the next maker. Normally a revert leaves no trace; this turns it into a record. Two bugs we hit and fixed: Rounding remainder: splitting a trade across makers left a few units unfilled, which made multi-maker swaps fail. The remainder now goes to the first maker. Partial fills: in an empty pool, a partial fill would push the price to its limit and break the pool. Swaps are now all-or-nothing. 3. The Graph: reading strategies as they're published No extra events needed: when a strategy is published, the subgraph walks its program bytes the same way the VM does. It finds opcode 35 and decodes its settings from the bytes alone. Completeness check: it compares each strategy's list of siblings with the maker's actual live strategies and flags lists that are incomplete. Fills and refusals: it indexes our router's EncumbranceApplied and both hooks' MakerSkipped, and collapses a refusal the hook retried within one swap into a single record. Mappings are covered by 15 Matchstick tests. 4. The app Two books, never mixed: Aqua keeps balances per router, so 1inch's makers and our opcode-35 makers are separate books. The route API plans each one, re-checks every maker's real balance live, drops makers whose quote would fail, and picks the better book. Both chains compared fairly: the same trade is quoted on Base and Ethereum and compared by price per unit actually filled, plus how much of the trade each chain can take. Always a current quote: browsing uses a 20-second cache, so repeat quotes come back in about 36 ms. Pressing Swap always re-quotes fresh and stops if the price moved. Your book on every chain: the maker book comes from the subgraph on Base, and from our Postgres index plus on-chain logs on Ethereum, with every amount re-read live. Honest declarations: the strategy builder works out the declared total on the server from live balances, so a maker can't under-declare by mistake. 5. Things we're proud of Refusals you can see: the hook turns a failed quote into an on-chain event, and the subgraph makes it searchable. The first real refusal on Base is indexed and shown in the app. Cross-chain without a bridge: opcode 35 only rejects a declaration that's too low. So a maker can include what they owe on another chain, and the strategy will refuse when their whole book is overstretched. Rehearsed before sending: every mainnet script ran on a fork first, with safety checks that abort rather than over-promise. The full Ethereum deployment cost about 0.0004 ETH, and our tests swap against the live Ethereum contracts.