Toxic flow mitigation
AIMM prices toxicity rather than banning it. Each defense below is stated as a quantity: what it bounds, what it costs, and where the shipped parameters do not yet cover the bound.
1. The framing
An external-mark AMM leaks value through exactly three channels, each with a different clock and defense:
| Channel | Clock | Bound | Defense | Priced by |
|---|---|---|---|---|
| Push-latency LVR | between pushes | (the deviation trigger) | keeper cadence + minFee floor | |
| OEV | around the push tx | mempool visibility of batchPushSigned | minFee, private relay | |
| Keeper delinquency | past the grace | staleness surcharge |
Classical curve LVR (the reserve-ratio kind, where the pool’s own invariant is the stale price) does not apply: the quote center is a fresh external mark, never a reserve- or trade-derived accumulator. That does not remove all LVR.
A fourth channel is not oracle-timing at all: inventory-direction toxicity, flow that persistently moves coverage one way. It is priced by the mid skew and the coverage toll, not by the spread. See §5.
Coverage ratio definition: Inventory Management §2. Spread and fee mechanics: Spread & Fees.
2. The 2\theta fence: the one number that matters
2.1. The mechanism
The keeper pushes a new mark when either trigger fires (deviation trigger evaluated in the keeper’s push loop):
- deviation: bps
- heartbeat:
heartbeat_selapsed
Between pushes the mark is frozen and the true price wanders. The deviation trigger bounds that wander: while the keeper is live, the gap never exceeds before a push fires. This is the primary LVR defense, and the only one independent of volatility and elapsed time.
2.2. The derivation
An informed trader observing sees an edge of at most bps in whichever direction the mark is wrong. Extracting it requires a round trip: buy at the stale mark, sell back at truth. That crosses the quoted width twice and pays in total ( per swap, on the output, Spread & Fees §5.1). The pick-off is unprofitable when
The floor must satisfy it, not the runtime spread: , confidence and staleness all add to the quote, so a fence built on them would evaporate in exactly the calm conditions where the arb is cheapest to run.
2.3. The enforcement
The floor predicate is one comparison:
minfee_covers_theta(minfee_pbps, theta_bps) = minfee_pbps >= 200 * theta_bpsIt is checked at three points, all off chain - the keeper’s startup gate, the pre-submit coupling gate and parameter generation - enumerated in Parametrization §11.2.
exists only in the keeper: not an on-chain parameter, not in Asset, not in OracleConfig. The chain carries maxDeviation and ttl, the acceptance band: maxDeviation bounds what a signer may push, bounds when the keeper chooses to push.
2.4. Live values
The requirement, on the pre-Arc reference roster (per-parameter tables in Parametrization):
| Class | (bps) | (PBPS) | heartbeat |
|---|---|---|---|
| Stables (per-leg range) | 0.25 - 0.87 | 50 - 173 | 1800 s |
| Majors and EURC (WETH, WBTC, cbBTC, BNB, XAUT, PAXG, EURC) | 5.0 | 1000 | 300 s |
| FX (QCAD, AUDF, BRLA, JPYC, KRW1) | 5.0 | 1000 | 240 - 300 s |
The shipped minFeePbps column is deliberately not reproduced here: the floors are mid-change, a
tighter σ-derived floor is being applied leg by leg and is only partly on chain, so a number quoted
on this page would be stale within the week (Parametrization §11.3).
Read getAsset(token).minFeePbps. Every live leg clears the fence above either way; that
is what the startup gate checks. A cross needs more, hence the summed path floor (§3).
3. The path floor sums over legs, it does not reduce by max
The pick-off edge on a cross is the sum of the per-leg thetas. Both marks are independently allowed to sit from truth, and an adversary selects the joint worst realization, so a max would fund one leg and leave the other unfunded.
Over the reference roster’s stable crosses a max rule under-fences most pairs; the shipped sum rule under-fences none. The measurement, the worst pair and the per-row arithmetic are at Spread & Fees §9.
What matters here is the shape of the residual. Under a max rule the extraction is repeatable and size-unbounded: a per-swap rate, not a one-off, with nothing in the pool capping how often it runs. It is small against a live quote, but it is guaranteed, and it bites hardest exactly when the rest of the spread is at its thinnest: the surcharge terms that make up three quarters of a live stable quote all vanish when the keeper is healthy (Spread & Fees §8).
The shipped rule sums, and the three aggregation rules are one mechanism rather than three choices. Nothing caps the composed spread from above, so the summed floor cannot be clamped away on the tape where it is needed; trader protection is minAmountOut (Spread & Fees §3.1).
4. Staleness: what happens when the keeper misses
funds the pool only while the keeper honors its contract. Past the grace it has not, and the guarantee is void; the premium then ramps with the unobserved drift:
with the leg’s feed age in seconds and its ttl.
Pricing._staleTerm, _staleExcessOf, summed per leg in _walkLegs. Per leg, not one coupled term at : one keeper feeds several spokes, so an outage staleses them together and the coupled form charged two equally stale legs of what they each owe. Worked: , 100 s past grace 10 bp; 1800 s past grace 42 bp. The hard revert sits above it at age > ttl (FeedMathLib.gate).
Three properties, in order of importance:
- Zero inside the grace. A flat tape produces no pushes and owes none; penalizing an old-but-accurate mark quotes the pool out of the market for nothing.
_staleTermshort-circuits before thesqrtwhen , so a fresh leg beside a stale one pays nothing. - The grace is capped at 30 s regardless of
ttl(Pricing.STALE_GRACE_CAP_SECS). It was sized to V1’s per-instancemaxRelayLagSecs, which the live V4 does not have — V4’s only past bound is the constantMAX_RECON_AGE= 6 h — so the cap now stands on its own and nothing on chain couples the two. Without it attl = 600feed bought a 300 s premium-free window. - It is defense-in-depth, not the defense. Under a per-block keeper (Base ~2 s, BNB ~1 s) every is zero and the term never engages. It exists so a late or censored keeper degrades gracefully instead of being picked off at the frozen mark.
The trade: during a stale window the pool quotes wider and wins less flow. That is correct (better to sit out than be picked off), and not a substitute for a fresh keeper.
Operational requirement: the keeper heartbeat must be strictly below ttl/2 on every feed (heartbeat_le_half_ttl, reported per feed at GET /thresholds on the keeper’s status endpoint).
5. Inventory toxicity: skew and the coverage toll
5.1. Skew is a Glosten-Milgrom soft fee
Coverage imbalance shifts the mid, not the spread. The spread is direction-invariant by construction (Spread & Fees §4), so a coverage-worsening trade pays a worse price at the same width.
Inventory-moving (informed) flow walks up the skew and pays the displacement on top of the floor; that slippage stays in reserves. Round-trip (noise) flow returns coverage where it found it, the displacement nets to zero, and it pays only the floor. Because the mark is frozen between pushes, the quote moves only through realized inventory. The construction, its formula and the flash-loan guardrail are at Spread & Fees §7.2.
Which of the two dominates is a composition property rather than a pricing one. Inside a core of correlated legs, flow that drains one leg tends to arrive against another, so inventory mean-reverts and one-sided accumulation is the exception; an uncorrelated roster makes it the default and the skew degrades into a toll nobody ever pays back. It is a tendency, not a guarantee: correlated legs can move together and leave every leg in the core displaced the same way at once (Pool Composition).
Consequence: defensiveness is tuned with the shape, not with . A pool can quote a tight floor for the benign majority and still charge the toxic minority, because the charge is a slope on inventory rather than a level on volume.
The levers, exactly. The skew slope is not one of them. Pricing.computeInventorySkew is a fixed protocol law with no per-asset field behind it, and its two arms are asymmetric because the filling arm’s slope is the round-trip impact-conservation bound read from its other side; “tidying” them into one slope reopens a ping-pong extraction. Holding the law in constants is what makes it safe: no write can move it. Formula and derivation: Inventory Management §3.
Tunable per asset is the magnitude that fixed skew index buys: the dispersion (per-asset fitted floor minDispersionPbps and sensitivity vegaBps; σ scales κ 1:1 above the floor at the shipped , capped only by the protocol-wide MAX_DISPERSION_PBPS), the preset the asset points at, and for the coverage wall.
5.2. The coverage toll is a level shift, and it is asymmetric
charged once, on the terminal output asset, before the fee (Pricing._settleQuote, _covToll). Charge-only: , never rebated.
Its marginal rate at zero size is
Measured on a pre-Arc reference roster with RLUSD at and bps: 4.44 bp, and dust pays the same 4.44 bp. The live stable is 600, so the same coverage is 26.6 bp today (Parametrization §6.2). That is the correct anti-drain shape: it charges the first unit that worsens an already-deficient leg, not only the block trade.
_covToll is output-only. A sell into the hub pays hub κ when κ>0. Same-pool spoke→spoke terminals the destination spoke, so the toll asymmetry is still . Old P3 “base κ=0 ⇒ sell-into-base is free” is void. Proof: Spread & Fees §6.4.
Operator rule: every listed asset including the hub must have κ>0 (Invariants §I-9); live per-leg values belong to 2. Deployments.
6. Mark integrity
The mark is the single input everything above trusts. Four independent bounds sit on it:
| Bound | Mechanism | Constant |
|---|---|---|
| Per-push move | volatility-adaptive deviation band: , the term capped at maxDeviation (so any push is bounded at maxDeviation) | ExternalOracle.sol |
| Uncertainty | revert past a 1σ CI of 1000 bps | MAX_CONFIDENCE_HALT_BPS, PoolConstantsLib.sol |
| Age | revert past ttl; priced below it | FeedMathLib.gate |
| Depeg | feed-relative band only (refFeedId/refBandBps): the asset’s mark must sit within refBandBps of an independent reference feed’s price for the same pair, checked on both endpoints and every interior node. The base additionally halts past 500 bps from parity. An absolute per-asset price band would need a unit to be denominated in, which multi-anchor does not supply; that policy call is the guardian’s | BASE_DEPEG_HALT_BPS, PoolConstantsLib.sol; PoolIOLib.priceBandGuard / priceBandGuardPath |
Two details that matter for toxicity:
- The in the deviation band is the stored prior , never the incoming push’s own. Using the incoming value would let a signer inflate its own band (
ExternalOracle.sol). - The observation clock is (
FeedMathLib.observedAt), so a relay cannot stamp a withheld signed quote fresh and buy back the staleness surcharge.
Authority is a k-of-n signer quorum with immediate revokeSigner, not a single feed’s uptime. A mark landed this block is deliberately not age-gated: withholding it would hand any address a pool-wide outage switch, since batchPushSigned takes authority from signatures rather than msg.sender.
7. JIT liquidity
Attack: add liquidity immediately before a large swap, remove immediately after, capturing fee share without carrying inventory risk.
flowCooldownSecs enforces a mandatory delay between deposit-withdraw and stake-unstake on the same account, making the atomic version impossible. Default 15 s (DEFAULT_FLOW_COOLDOWN, PoolConstantsLib.sol), hard ceiling 300 s (MAX_FLOW_COOLDOWN, PoolConstantsLib.sol). The ceiling exists because the receipt is transferable and the setter is untimelocked: it bounds the worst-case unavailability one compromised admin key can impose. 0 remains permitted as an explicit disable.
Detail: Flow Guards, Layer 2.
Second reason JIT is unattractive here: the LP fee is credited as a liability via liquidityIndexWad (PoolLiquidity.accrueLpFee), pro rata across the whole leg. A JIT depositor’s share of one swap’s fee is its share of the leg, so capture is proportional to real capital committed, not to timing.
8. What is not defended
| Residual | Size | Why it is accepted |
|---|---|---|
| OEV at the push | deterministic once the push is pending | needs a private relay or a jump auction; neither is shipped. minFee is the only shipped lever |
| Push-latency LVR inside | per event, funded by | by design: the fence makes it unprofitable, it does not make it impossible |
| Cross-leg basis on an anchored pair | unmeasured | if a leg’s ratio is not its own push trigger, both legs can sit inside their own bands while the ratio drifts to the sum, and no push fires. Applies once multi-anchor ships; the mitigation is a pinned synthetic feed with its own and its own trigger |
| Interior hop clamped against a hub balance | 0 | Pricing._legScaleOut caps against destination reserves only on the delivering leg (if (!delivering) return), so an interior hop is never clamped against a balance the swap does not spend. The decimal rescale runs per leg regardless. Listed so a regression is recognizable |
Auction-shaped recapture of the push OEV is out of scope, by decision. Residual OEV measures $0.05 to $0.46 per push, about $80/day gross on a $10M pool at ~1,400 pushes/day, below the level at which an auction clears: Arbitrum Timeboost decayed to $5.65/round over 494,608 rounds, with 3 entities taking 99.74% of them. Ordering rent accrues to whoever owns the ordering rule, not to the venue whose flow creates it: Timeboost paid $2.44M to the DAO and $69,806 to searchers, nothing to the applications involved. A pool that does not control sequencing has no ordering right to sell. The lever that measurably works is the adverse-selection fee already in the curve: on BTC at bp, moving the fee floor from 10 bp to 40 bp takes LVR from 10.15% to 1.14% APR with organic fill rising.
The reopen threshold is chain-neutral, measuring LP harm rather than BTR’s ability to monetize it: a sustained residual above $5/push (~100x the measured residual above) or above 1% APR of pool TVL under a healthy oracle. Value leaks from LPs whether the taker is a searcher, a block assembler or a latency racer, and whether or not anyone auctions the right to be it. The push-concentrated share is 1.1% to 28% of LVR depending on ; the remaining 72% to 99% is between-push latency loss, which no mempool policy, auction or ordering rule touches.
Arc disposition. If the threshold trips on Arc, the response is not a capture mechanism. Block production is a permissioned validator set with Circle-gated membership and a single block assembler, the intra-block ordering rule is undocumented, maxPriorityFeePerGas = 0 is the documented recommendation so no priority-fee auction runs, no third-party ordering privilege exists, and the only announced MEV path routes proceeds to validator and staker rewards and to burn rather than to applications. That is a statement about BTR’s rights, not a claim that Arc has no MEV: an opaque assembler holds an unpriced ordering option and latency races persist. BTR has no ordering right to sell. Permitted responses on Arc: lower CADENCE_CAP_PER_H / MIN_BATCH_PUSH_GAP_S (keeper push-cadence config), raise the fee floor or the staleness premium, or escalate to Circle. Revisit only if Arc ships an application-addressable ordering primitive.
9. Why not the alternatives
| Approach | Portability | Centralization | Oracle dependency | Permissionless |
|---|---|---|---|---|
| McAMM (first-trade auction) | low (needs PBS) | medium (pro MEV shops) | none | yes |
| am-AMM (Harberger manager lease) | high | high (single manager) | practical (manager needs CEX) | yes |
| UAMM / DODO / Swaap (oracle-first) | high | very high (oracle control) | total | yes |
| AIMM (external mark + adverse-selection fee) | high | medium (k-of-n signer quorum) | external mark, hardened | yes |
- McAMM auctions the right to trade first in each block, sending the bid to LPs. Clean and strongly LVR-reducing, but requires PBS and builders who respect the auction, realistically Ethereum mainnet only. It also concentrates capture in builder-integrated shops, and “first trade of block” does not translate cleanly to a multi-asset pool.
- am-AMM auctions a manager lease; the winner pays continuous rent and captures internal arb. Economically elegant and chain-agnostic, but a single manager controls all fees and trading revenue at any time, and a competitive manager needs off-chain CEX feeds anyway.
- UAMM and the oracle-first family quote at oracle price and add slippage only for inventory. The strongest LVR answer if the oracle is perfect, which is precisely the failure mode: a wrong, manipulated or lagging oracle produces unbounded LP loss, and AMM safety collapses to oracle safety.
AIMM is external-mark-primary, so it inherits the oracle-first LVR result. The difference: the fragile parts are fenced individually.
- A volatility-adaptive per-push band bounds a compromised key.
- A confidence halt rejects an uncertain mark.
- A staleness surcharge degrades on keeper lag.
- Per-asset depeg bands gate tail risk.
- Trust rests on a k-of-n quorum with immediate revocation rather than one feed’s uptime.
Residual push-latency LVR and OEV remain (Oracles §8.4).
10. Monitoring
Three quarters of a live stable quote is oracle-health rather than pricing (Spread & Fees §8), so the spread decomposition is the monitoring dashboard:
| Metric | Source | Alarm |
|---|---|---|
confidence per feed | rising share means the source, not the market, is degrading | |
| mark age vs grace | non-zero at all means a push was missed | |
| vs | per cross | any negative value is a live pick-off exposure |
covToll / amountOut | SwapQuote.covToll | rising means a leg is draining |
(exec - mid)/mid vs (mid - mark)/mark | SwapQuote.midPrice, markPrice | splits extractable value from skew premium exactly |
That last row makes OEV measurable rather than inferred: the book is centered on the mid, not the mark, so the two components sum to the naive exec-vs-mark number and can be attributed separately (IPool.sol). Both are exact WAD, so the split carries no encoding error into the attribution.
Off-chain: LP-versus-arbitrageur PnL segregation, adverse-selection cost estimation, CEX-DEX price correlation.
11. Related documentation
- Spread & Fees: the complete fee model and both theorems
- Inventory Management: coverage and haircut
- Parametrization: every parameter, its fence, its live value
- Feed Oracle: mark, , confidence, push API
- Oracles: signer quorum, deviation band, LVR/OEV risk register
- Flow Guards: JIT cooldown
https://ethresear.ch/t/mev-capturing-amm-mcamm/13336
https://www.gate.com/learn/articles/-introduction-to-mev-mitigation/5121
https://www.pyth.network/blog/beyond-the-dark-forest-experiments-in-mitigating-mev
https://arxiv.org/html/2403.03367v1
https://fc25.ifca.ai/preproceedings/183.pdf
https://arxiv.org/abs/2403.03367
https://fenbushi.vc/2024/01/20/ending-lps-losing-game-exploring-the-loss-versus-rebalancing-lvr-problem-and-its-solutions/
https://arxiv.org/html/2308.06375v2
https://arxiv.org/abs/2308.06375