strikeline
Technical docs

How Strikeline works under the hood

Strikeline is a replicating market maker (RMM-01) written as two custom 1inch SwapVM instructions. This page covers the program an offer ships, the maths, the solvency check, the fee and how to run it. Every claim links to the source.

On this page
  1. Architecture
  2. The program
  3. RmmSwap · 0x55
  4. Coverage · 0x93
  5. Protocol fee
  6. Reading offers
  7. Numbers and tests
  8. Running it
  9. Limits and risks

01Architecture#

A maker approves the official Aqua registry (0x1111113CCf1426A8E30e2bfF5E005d929bF6a90a) and ships a strategy whose app is our router. Aqua stores virtual balances and publishes the program bytes; it never holds tokens. When a taker trades, the router runs the program and Aqua moves tokens straight from the maker's wallet to the taker.

Maker walletholds WETH / USDCAqua registry0x1111113C…a90a · no tokensTakerquote() · swap()STRIKELINE ROUTER · REDEPLOYED SWAPVMFeeProtocolCoverageRmmSwapship0 tokenstradepull · pushCoverage readsbalanceOf ∧ allowance

The router is StrikelineRouter, a redeployed SwapVM that adds exactly two opcodes to _runOpcode. It keeps every official instruction except PeggedSwap, dropped to fit under EIP-170.

02The program#

Every offer is the same five-instruction program, built by buildLegProgram:

DeadlineFeeProtocolCoverageRmmSwapSalt
InstructionWhat it does
DeadlineStops the offer from trading after maturity plus a grace window.
FeeProtocolTakes 0.10% of the taker's input for the treasury and runs the rest on the net amount.
CoverageRuns the curve, then refuses any output the maker's wallet can't deliver.
RmmSwapPrices the trade on the RMM-01 curve at the current block time.
SaltA nonce, so two offers with the same terms get different hashes.
Why this order

The fee comes first so both the check and the curve see the net trade. Coverage wraps the curve and runs it with a nested ctx.runLoop(): clamping the balance before pricing would move the reserve point and change the price, not just the size.

03RmmSwap · opcode 0x55#

RmmSwap implements RMM-01, the covered-call curve from Angeris, Evans & Chitra (arXiv:2103.14769, arXiv:2111.13740):

Y = L·K·Φ( Φ⁻¹(1 − X/L) − σ√τ )

X and Y are the risky and stable reserves, K the strike, σ the implied volatility, L the liquidity, and τ the time left in years, read from block.timestamp with a one-hour floor. Holding reserves on this curve is long spot and short a call at K.

Arguments · 62 bytes

[uint8 flags][uint64 sigmaWad][uint40 maturity][uint128 strikeWad]
[uint128 liquidityWad][uint64 rateRisky][uint64 rateStable]

The spread that opens with time

Reserves are pinned to the curve when the offer ships. As τ shrinks the curve moves away from them, so a trade must first close that gap. A trade inside it reverts with RmmInsideSpread(shortfall), which carries the exact amount. That gap is the maker's premium, and the program has no fee inside the reserves because a fee there would push them off the curve and leak it.

Expiry and puts

At τ = 0 the Gaussian drops out and the curve becomes Y = K·(L − X), a constant-sum order at the strike, so assignment is an ordinary swap; the reverse direction reverts RmmSettlementOneWay. The same arguments are a cash-secured put when the reserves start in the stable token.

Precision

Φ and Φ⁻¹ are fixed-point (Solady), about 5e-12 relative error against a 50-digit reference. The guard band EPS = 2e-6 is sized from the measured 1.18e-6 round trip and always favours the maker.

04Coverage · opcode 0x93#

Aqua lets a maker over-allocate: ship() checks no balance and safeBalances() never looks at the wallet. Coverage closes the gap inside the call that prices the trade.

args:  [uint8 flags][uint16 haircutBps]      // 3 bytes
check: amountOut ≤ min(balanceOf(maker), allowance(maker, Aqua)) × (1 − haircut)
else:  revert NotCovered(needed, free)

Every offer reads the same wallet, so a fill on one immediately lowers what the others can deliver, in the same block, with no keeper or shared storage. It reverts rather than partially filling, and coverage() publishes the same bound for UIs and solvers.

05Protocol fee#

The fee is 1inch SwapVM's own FeeProtocol, charged on the taker's input: 10_000 in SwapVM units where 1e7 is 100%, so 0.10%. The receiver is protocolFeeReceiver from the deployment manifest, or the router owner.

Only the net input reaches the maker's reserves, and that net amount is exactly what RmmSwap priced, so the curve and the premium are unchanged. Coverage only adds output-side fees to its obligation, so the solvency check is unaffected. Details in PROTOCOL-FEE.md.

06Reading offers#

Aqua's Shipped event carries the full program, so an offer's strike, expiry, size and volatility are public. The app finds them with getLogs, decodes the program, then reads everything else in block-pinned multicalls so every figure on screen comes from the same block.

  • stableFor / riskyFor quote the curve at any reserve point.
  • bandFor returns the spread a trade must clear, which the premium chart draws.
  • SurfaceLens.book prices a whole book in one call; it is a separate contract so it costs the router nothing in size.

07Numbers and tests#

Router runtime size23,851 B, 725 B under EIP-170
Gas per fillabout 211k, around a cent on Base
Offline Foundry tests147 passing (make test)
Mainnet-fork tests11, real WETH/USDC through the official contracts (make test-fork)
TestProves
test_Theta_DecayOpensASpreadTime alone opens a two-sided spread.
test_Book_FillOnOneLegShrinksSiblingDepthOne fill lowers the other offers' depth.
test_Book_WithoutCoverageTheDepthIsPhantomWithout Coverage, quoted depth isn't real.
test_Expiry_SettlesAtStrikeOneWayAt expiry it settles at the strike, one way.
test_Roll_MovesNoTokensAndCanRepeatParametersRolling an offer moves zero tokens.
testFuzz_QuoteEqualsSwapQuotes always match swaps.
test_Fee_LeavesTheCurveAndPremiumUnchangedThe protocol fee doesn't touch the premium.

The suites live in contracts/test/strikeline.

08Running it#

Locally

make install        # once
make fork           # terminal 1: anvil fork of Base, chain id 31337
make story-setup    # deploy the router, fund demo wallets, freeze the state
make story-load     # rewind to that state (about a second)
make story-1        # ship four demo offers from one wallet
make web            # http://localhost:3000/app

Hosted demo

The live demo at strikeline-mu.vercel.app/app talks to a fork of Base mainnet (chain ID 31337) run by anvil on a Google Cloud VM. The site reaches it through /api/rpc, which forwards JSON-RPC and blocks the anvil_, evm_ and debug_ admin methods.

09Limits and risks#

  • Short volatility. The position loses when realised volatility exceeds the σ it was written at.
  • No up-front premium. The premium is only realised when a taker crosses the spread.
  • Reverts, not partial fills. A taker asking for more than the wallet covers gets nothing.
  • Withdrawable. dock is instant, so a buyer can't rely on an offer like a listed option.
  • Approximated Φ. Dust-sized trades hit the guard band rather than an unbounded error.
  • Demo chain. The hosted demo is a fork; nothing is deployed to a public network.
Heads up

This is hackathon code and unaudited. Don't point it at real funds.