The problem: one-shot arbitrage
MEV-protection hooks that analyze continuation flow miss the most dangerous case: an arbitrageur that executes exactly one trade to snap the AMM to the global price and leaves. There is no continuation to analyze. So Markout doesn't look at what the trader did — it looks at what the pool's price did after them.
The premium, payable through any router
Every swap fills immediately at the pool's 3 bps fee. On top, the hook charges a reversion-insurance premium — a live-quoted bps rate of the realized input taken from the post-swap balanceDelta, never a slot0 estimate, for exact-in and exact-out alike. Dust swaps whose bond would round to zero revert with SwapTooSmall.
The charge rides v4's hook-delta mechanism: for exact-in swaps the hook returns it on the specified delta in beforeSwap; for exact-out swaps on the unspecified delta in afterSwap. Either way it lands in the swap caller's own PoolManager delta — so any router that can settle a normal v4 swap (Universal Router, your own contract, v4's test routers) pays it with zero Markout-specific code. No allowlist, no settleFor, no initializeRouter.
The rate is live-quoted from this pool's own settle history: every donate verdict raises it 3 bps, every refund verdict lowers it 1 bp, and it clamps at 5 bps (dust swaps can never bypass) and 60 bps (no runaway). The only way to pump the rate is to actually donate real premia to LPs, so the grief is self-funding for the pool — poke spam and token gifts cannot move it. premiumQuoteFor is the exact rate the next swap charges.
The 24-second fixed window
Each trade records its own immutable window [bondTime, bondTime + 24 s]. The hook maintains a previous-tick accumulator (Uniswap-V2-style attribution: elapsed time accrues to the tick held before each update), stored as an append-only history that nothing ever prunes. Settlement computes the window's average tick by binary-searching that history — so settling at window close, an hour late, or after heavy churn produces the identical verdict, and permissionless pokes or later swaps can never freeze escrow.
24 s ≈ two 12 s blocks: a full 1:1 reversion landing one block after the trade sits exactly on the 50% frontier and refunds — no overshoot needed.
The 50% reversion frontier
The classifier is normalized: it compares the reversion to the trade's own impact, measured in tick space, not to an absolute bps band. A 2-tick trade and a 2000-tick trade face the same frontier.
Zero-impact trades refund. Overshoot past pre refunds. Movement further away donates. In plain English: if at least half of your own price impact came back, you were organic and the bond returns — in the settlement transaction itself.
Outcomes 1 / 2 / 3
Settlement is permissionless — anyone calls settle(tradeId) after the window — and terminal: the verdict is recorded before any value moves, and settle credits in-range LPs in the same transaction whenever liquidity exists. Permissionless does not mean automatic: someone still has to send the settlement transaction.
The oracle said refund and the token delivered. The bond is paid to the trader inside the settlement transaction. No claim transaction exists on this path.
The oracle said refund but delivery failed (blacklist-style tokens). The verdict survives; claimRefund retries it, marking claimed before the transfer so reentrancy and replay are impossible. A failed retry resets and stays retryable — settlement can never brick.
The price sustained. The premium is forfeited — and whenever the pool has active liquidity, in-range LPs are credited inside the settlement transaction itself through v4's donate(). Only at zero liquidity does the value wait in a per-pool pending bucket for the permissionless flushDonation(poolId); the settle itself still succeeds and can never brick. Credit goes to whoever is in range at settlement or flush time, not specifically to the LPs who carried inventory through the original move.
The opt-in batch lane
The SAME 24-second clock runs a second, opt-in lane. A trader enqueues one side of a 24 s epoch by calling placeBatchOrder directly — no router needed — and the full deposit moves into explicit custody in the hook, cancellable any time before the epoch clears. When the epoch ends, anyone calls clearBatch: opposing orders net at the epoch's accumulator TWAP, the dust-bounded residual executes as one normal bonded spot swap (through an immutable hook-owned child router — v4-core skips hook callbacks on self-calls, so the hook cannot dodge its own premium lane), and every order on a side fills at the same uniform price: the TWAP clamped by realized execution, so the hook never subsidizes a fill.
Why any router works
Because the premium is part of the swap caller's own delta, a router settles it the way it settles every other wei it owes: pay your own delta, done. The provided MarkoutRouter is convenience, not a gate — it adds a deadline, exact-in minimum output, exact-out maximum input (bond included), strict transfer checks, and native support, and declares the human beneficiary in hookData so refunds route to the end user.
The beneficiary rule is exact: only a 32-byte hookData holding a nonzero address declares a beneficiary. Empty payloads, arbitrary-length payloads, and a zeroed 32-byte word all fall back to the direct swap caller — no revert mid-swap, and no refund can ever be sent to address(0). Universal Router integrators: pass exactly abi.encode(endUser) as hookData; a router that declares nothing receives its own refunds.
Try it
Hosted at markout-nine.vercel.app — best in Chrome desktop with MetaMask (or any injected wallet) on Sepolia. The app runs the whole loop against the live deployment, with one-click deterministic demos for both verdicts; the landing page streams the live tape with no wallet at all. The terminal runbook in demo.md is copy-pasteable on the current ABI — including the pre-signed back-to-back swap pair that lands the reversion one block after the buy. Demo video: coming, human-recorded.