An order book funded by a Uniswap v4 position, unwound just in time inside the fill transaction.
Bebecita is an order book whose maker never holds its own inventory. The tokens it quotes stay inside a Uniswap v4 liquidity position, earning fees, and they are unwound one instruction before they leave, inside the same transaction that settles the fill.
It runs on 1inch Aqua. Aqua never custodies anything: shipping a strategy writes a number into a mapping, and Aqua.pull decrements that number and only then calls safeTransferFrom on the maker's wallet. The money therefore only has to exist for the width of two instructions, so it can work somewhere else until then. Bebecita puts a Uniswap position in that gap.
Two pieces make it work. A custom SwapVM instruction, opcode 0x92, reads the position's liquidity on chain and clamps the quotable output balance to what the position can genuinely release: balanceOut = min(balanceOut, free float + reachable collateral). It construction. Then the maker hooks: preTransferOut executes the /lp/decrease calldata the Uniswap API built for that fill, the position unwinds, Aqua pays the taker, and postTransferIn executes /lp/increase so the inventory goes straight back to work.
The withdrawal calldata comes from the taker, because the API builds it against live chain state and it cannot be baked into an immutable order. The vault does not trust it. It pins the callee, pins the selector, and judges the payload by its effects: the output balance must grow by at least what the VM computed, the other side of the book may not shrink, one fill may not unwind more than maxUnwindPct of the position, a redeposit may only grow it, and the value the unwind actually released, priced at the live pool price, has to land in this vault. That last guard exists because the first four were not enough: a real modifyLiquidities payload composes DECREASE_LIQUIDITY with TAKE naming any recipient, so a taker could unwind the whole cap, pay itself the remainder and pass every other check. The attack is in the test suite and is rejected on chain.
Take the Uniswap API out and there is no calldata for either hook slice, the vault has no float, and the first fill reverts on a transfer out of an empty wallet. Take the custom instruction out and Aqua prices against a virtual balance nothing backs, and the fill asks for more than the unwind can produce. That failure is asserted by a test, not described. Both legs break, neither is decoration.
Everything is live on Ethereum Sepolia: seventeen real fills settled through the canonical Aqua at 0x499943E74FB0cE105688beeE8Ef2ABec5D936d31, the vault reports TVL and immediately swappable liquidity under URC-3, and the dashboard runs a full fill from a judge's own connected wallet.
Contracts are Solidity 0.8.30 with Foundry. BebecitaRouter is a redeployed SwapVM pointing at the canonical Aqua, and not one line of 1inch source is modified: BebecitaOpcodes subclasses AquaOpcodes and overrides _runOpcode with a super fallthrough, the same style AquaOpcodesDebug uses. The new instruction takes the reserved _92 slot of the 0x90-0xaf balances tuning bank, which is where OpcodeList.sol says a balance adjusting instruction belongs, and a test asserts uint256(Opcode._92) equals the dispatched index instead of trusting the comment. Three Controls instructions that ship in swap-vm but are unreachable from the Aqua table, Stop, Revert and JumpIfDirection, are wired back in the same file, because a program that cannot halt early cannot express a conditional strategy.
The program prices on XYCConcentrateSwap, 0x51, and not XYCSwap, 0x50. 0x50 never clamps its own output, so once 0x92 has lowered balanceOut an exact-out taker asking for more gets an arithmetic panic out of the VM. 0x51 clamps on balanceOut and re-solves the other side, which turns that same moment into an exact partial fill: the taker is paid exactly the reachable collateral and charged exactly what it costs. Its two arguments, sqrtPriceMin and sqrtPriceMax, are filled from the position's own tick bounds and the live sqrtRatioX96 returned by POST /lp/pool_info, then compiled into the program bytes, so the book's curve is the range of the Uniswap position backing it.
The Uniswap API is the funding mechanism, not a data source. /lp/create opens the pool and the position so /lp/check_approval with generatePermitAsTransaction returns permits as executable transactions rather than EIP-712 typed data, which is the single thing that makes a contract owned position possible, because the vault cannot sign, only execute. /lp/decrease and /lp/increase are called once per fill and their calldata goes verbatim into the preTransferOutHookData and postTransferInHookData slices of TakerTraits. /lp/claim_fees reads back the fees the same capital earned while it was quoting.
There is no backend, and removing it cost nothing because there was no trust in it to remove. What kept the fill in a process was TakerTraitsLib.build being internal pure Solidity, so it was ported to TypeScript, and contracts/test/TakerTraits.t.sol builds twelve argument shapes with the sponsor's own library and asserts byte equality against the port before round tripping the live fill shape back through the library's slice readers. The fill plan itself reaches the chain and the API through two injected interfaces, so the CLI and the browser run the same sizing arithmetic with no second copy. Frontend is Vite, React 19, viem and wagmi, on Vercel, with two serverless proxies: one attaches x-api-key so the Uniswap key never reaches the bundle, the other forwards JSON-RPC.
The hacky detail worth naming: full range ticks do not survive the concentrate instruction's 1e18 fixed point. sqrt(1.0001^-887220) is 5.4e-20, which truncates to zero, and the args builder rejects a zero lower bound. The shipped bounds are therefore the range clamped to a factor of 1e9 on the sqrt price either side of spot. At that width the virtual reserves add about a billionth of the real ones, so the curve is constant product to nine significant digits and what changed is the clamp, not the price: 915.372435469721101398 out against 915.372435390345788254 on 0x50.

