feat(decoder): Universal Router execute() + V3/V2 opcodes + V4_SWAP named-skip + real-mainnet fixtures - #171
Merged
0xfandom merged 4 commits intoMay 27, 2026
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
0xfandom
force-pushed
the
feat/decoder-universal-router-v3-swap-exact-in
branch
from
May 27, 2026 18:17
13094bf to
5574e46
Compare
Uniswap Universal Router is the dominant post-2024 entry point for aggregator traffic but our pipeline ignored it on two fronts: the Alchemy pending-tx filter never subscribed to the UR address, and the decoder layer had no handler for execute(bytes, bytes[]) / execute(bytes, bytes[], uint256). UR is a command-byte VM, not a multicall — each byte in commands is an opcode that consumes one entry from the parallel inputs[] array. Three changes ship together so the wire-up is end-to-end usable: 1. Add Universal Router V2 (0x66a9893c...) and V1.2 (0x3fc91a3a...) to the default Alchemy router filter. 2. Declare IUniversalRouter with both execute overloads — sol! emits execute_0Call (selector 0x24856bc3) and execute_1Call (selector 0x3593564c) faithful to mainnet. 3. Add try_universal_router dispatcher and decode_ur_opcode handler. This first cut covers V3_SWAP_EXACT_IN (opcode 0x00) only; permit / wrap / sweep opcodes are silently skipped and unknown opcodes drop without erroring so the dispatcher stays robust against future on-chain VM additions. Additional swap opcodes (V3_OUT, V2_IN, V2_OUT) land as follow-up PRs.
Extends the Universal Router opcode handler with the remaining three swap variants so wallet-issued UR calls covering exact-output trades and V2-only paths are not silently dropped. - V3_SWAP_EXACT_OUT (0x01): reverses the path (token_out → token_in in calldata) before surfacing the canonical (token_in, token_out) pair; same exact-out amount-surfacing convention as the V3 router's standalone exactOutput single / multi-hop family. - V2_SWAP_EXACT_IN (0x08): decodes address[] path, first hop is path[0] → path[1], remaining tokens carry into path_extra. - V2_SWAP_EXACT_OUT (0x09): same path orientation as V2_IN (NOT reversed, unlike V3 exact-out); exact-out amount mapping mirrors the V3 branch. All three reject empty / single-element paths with EmptyPath. A new mixed V2+V3 chain test exercises multi-opcode dispatch order.
Synthetic sol!-encoded tests can mask encoder-vs-decoder drift because both sides use the same Rust macro. Pin the decoder against three real mainnet pending-tx payloads to catch that class of bug. Each fixture is the raw eth_getTransactionByHash → input bytes of a real Universal Router call, embedded as a hex constant: - 0xfbb854...8996 (UR V2, ops=[V2_SWAP_EXACT_IN, SWEEP]): WETH → arbitrary token; asserts V2 surfacing with canonical WETH as token_in and zero fee_bps. - 0x87a841...4d4e33 (UR V1.2, ops=[WRAP_ETH, V3_SWAP_EXACT_OUT, UNWRAP_WETH]): asserts the V3 exact-out path flip lands WETH on the token_in side and surfaces the 1% (10000 bps) fee tier. - 0x637397...0b1f (UR V2, ops=[V2_SWAP_EXACT_IN, UNWRAP_WETH]): token → WETH; asserts WRAP/UNWRAP opcodes are dropped silently and only the swap survives. All three fixtures use the production decode_pending_many entry point, so any future drift in the dispatcher, opcode parser, or sol! ABI shape regresses these tests.
The E5 fixture sweep showed that Universal Router V2 traffic is now dominated by opcode 0x10 (V4_SWAP), which wraps a Uniswap V4 PoolManager action stream. Roughly four out of every five UR V2 pending txs the captures touched had V4_SWAP at the top level. The prior catch-all skip dropped them indistinguishably from other unknown opcodes, hiding their volume in the dashboards. V4 PoolManager decode is a substantial separate workstream — new PoolKey shape, hooks-aware pricing, per-action ABI tables — and is out of scope for the current V2/V3 backrun target. This PR is deliberately minimal: - Add a named V4_SWAP = 0x10 constant in ur_commands with a doc comment that explains why it is currently un-decoded and what full support would entail. - Route the opcode through an explicit match arm that emits a debug-level trace event labelled 'ur_v4_swap_unsupported' so operators can size the missed-volume from logs ahead of any formal metric migration. - Unit test that confirms a mixed [V4_SWAP, V3_SWAP_EXACT_IN] call silently drops the V4 record and surfaces the V3 swap intact.
0xfandom
force-pushed
the
feat/decoder-universal-router-v3-swap-exact-in
branch
from
May 27, 2026 18:21
5574e46 to
b8f1737
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
toAddressfilter never subscribed to its address, and the decoder layer had no handler forexecute(bytes commands, bytes[] inputs[, uint256 deadline]).commandsis an opcode (after masking withCOMMAND_TYPE_MASK = 0x3f) that consumes one entry from the parallelinputs[]array.V3_SWAP_EXACT_IN,V3_SWAP_EXACT_OUT,V2_SWAP_EXACT_IN,V2_SWAP_EXACT_OUT). Permit / wrap / sweep / unknown opcodes are silently skipped so the dispatcher stays robust against future on-chain VM additions.0x10(V4_SWAP) which wraps a Uniswap V4 PoolManager action stream. Full V4 decode is out of scope for the current V2/V3 backrun target, but the opcode is now named explicitly and routed to a logged skip so operators can size the missed-volume from production.eth_getTransactionByHashpayloads to catch encoder-vs-decoder drift (syntheticsol!-encoded tests can hide that class of bug because both sides use the same Rust macro).Files Changed
crates/ingestion/src/mempool.rs0x66a9...) and V1.2 (0x3fc9...) todefault_router_addresses(). New unit test pins both.crates/pools/src/router_decoder.rsIUniversalRoutersol! interface,ur_commandsopcode constants (V3_IN, V3_OUT, V2_IN, V2_OUT, V4_SWAP),try_universal_routerdispatcher,decode_ur_opcodehandler covering all four swap variants plus the V4_SWAP named-skip. Fourteen tests (synthetic + 3 real mainnet fixtures + V4_SWAP skip).Acceptance Criteria
execute_0Call::SELECTOR == 0x24856bc3.execute_1Call::SELECTOR == 0x3593564c.default_router_addresses().V3_SWAP_EXACT_INcalldata decodes into oneProtocol::UniswapV3swap with the correct(token_in, token_out, fee, amount_in, amount_out_min, recipient, router).V3_SWAP_EXACT_OUTflips the path so(token_in, token_out)are in canonical pool-pair order.V2_SWAP_EXACT_INdecodesaddress[]paths; intermediate tokens carry intopath_extra.V2_SWAP_EXACT_OUTfollows V2 path orientation (NOT reversed).V4_SWAPopcode0x10is named, logged atdebugwith targetur_v4_swap_unsupported, and does not error a UR call that mixes it with recognised opcodes.commandsandinputsreturnsDecodeError::AbiDecode.DecodeError::EmptyPath.cargo clippy -p aether-pools -p aether-ingestion -- -D warningsclean.Test plan
cargo test -p aether-pools --lib router_decoder— 47 pass (14 UR tests including 3 mainnet fixtures + V4_SWAP skip), 0 new failures (1 pre-existing unrelated overflow indecode_bancor_trade_by_source_amount).cargo test -p aether-ingestion --lib mempool— all pass.cargo build --workspaceclean.ur_v4_swap_unsupportedlog line additionally lets operators size the V4 follow-up against production volume.Follow-up scope (NOT in this PR)
Full V4 PoolManager decode is a separate workstream — adds a
V4Pooltrait implementation, parses(bytes actions, bytes[] params)tuples, models V4 hooks for post-state prediction. Tracked as a downstream task once shadow data quantifies the missed volume.