Skip to content

feat(hindsight): decode on-chain aggregator swaps (ENG-6122)#263

Open
kayibal wants to merge 4 commits into
feat/eng-6121-tools-commonfrom
feat/eng-6122-capture-swaps
Open

feat(hindsight): decode on-chain aggregator swaps (ENG-6122)#263
kayibal wants to merge 4 commits into
feat/eng-6121-tools-commonfrom
feat/eng-6122-capture-swaps

Conversation

@kayibal

@kayibal kayibal commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Decode settled aggregator swaps from chain data, with Allium verification. Includes decoder reliability: decline ambiguous multi-token nets, and decode CoW/batch settlements via the order maker rather than the solver sender. Stacked PR #2/6; base eng-6121.

) -> Option<(Address, (Address, U256, Address, U256))> {
// Prefer externally-owned accounts; pools and routers carry code.
let mut maker = None;
for (candidate, trade) in maker_candidates(logs, native, exclude, names) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Batch settlements decode only one maker. This loop returns the first candidate with a clean two-token net, so a CoW batch that settles multiple retail orders in one tx contributes a single decoded trade — the rest surface as 'Allium only' gaps and the decoder systematically under-counts batch trades. If decoding one maker per batch is intentional for v0, make it explicit (doc or warn!); the batch/CoW path is the focus of this PR.

/// Addresses with a clean two-token net swap, excluding the zero address, the
/// excluded addresses, and known registry contracts. Ordered by address for
/// deterministic selection.
fn maker_candidates(

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Maker selection has no settlement-tied tiebreak. When several non-excluded EOAs each net to a clean two-token swap (two independent makers, or a maker plus an EOA counterparty), the winner is just the first in maker_candidates' address-ordered iteration. The EOA-vs-contract filter disambiguates maker-vs-pool but not maker-vs-maker, so a correct-looking decode can attribute the wrong account's flow. Consider tying the choice to the actual settlement rather than address order.

Comment on lines +105 to +121
fn largest_external_call(
root: &CallFrame,
entry_point: Address,
sender: Address,
) -> Option<Address> {
let mut best: Option<(Address, U256)> = None;
for child in &root.calls {
if child.error.is_some() {
continue;
}
let Some(to) = child.to else { continue };
if to == entry_point || to == sender {
continue;
}
let value = child.value.unwrap_or_default();
if best.is_none_or(|(_, best_value)| value > best_value) {
best = Some((to, value));

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Aggregator fallback degenerates to 'first child' for ERC-20 routes. largest_external_call ranks the entry point's child calls by native ETH value, but a token→token route through an unknown router carries no native value, so every candidate ties at zero and the first child wins. That attributes the swap to an essentially arbitrary contract; the label is then diffed against Allium, so the mis-attribution reads as a spurious aggregator mismatch rather than a clean 'unknown'. Prefer returning unknown when all candidates tie at zero value.

@kayibal kayibal force-pushed the feat/eng-6121-tools-common branch from a3db0f2 to e6507a6 Compare June 30, 2026 12:08
@kayibal kayibal force-pushed the feat/eng-6122-capture-swaps branch from f1d1a72 to f1fcbd8 Compare June 30, 2026 12:09
tamaralipows and others added 4 commits June 30, 2026 13:27
Decode settled aggregator swaps for a block and report what each trade
put in and took out, with client and aggregator attribution.

- Fetch receipts per block with eth_getBlockReceipts and trace matched
  transactions concurrently (bounded), keeping each block within its
  block time
- Recover native ETH legs from the callTracer trace, since token->ETH
  and ETH->token swaps deliver ETH without emitting a log
- Attribute client vs aggregator: direct swaps settle at the entry
  point; client-routed swaps (e.g. Relay) resolve the aggregator from
  the trace, falling back to the contract address when unknown

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a `hindsight verify` subcommand that decodes a block locally and diffs
each transaction against Allium's aggregator_trades table as ground truth
(allium.rs async Explorer client, verify.rs comparison by token, amount in
bps, and aggregator). Split the CLI into `decode` and `verify` subcommands.

Extend the decoder to catch filler-initiated intent fills (UniswapX, 1inch
limit orders) by matching a known aggregator log signature and resolving the
order maker by net flow. Add 1inch v5, the UniswapX reactor, and the Tycho
router addresses.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
net_trade picked the largest-by-raw-amount leg when a tracked address netted
more than one token, but raw amounts are not comparable across decimals, so it
paired unrelated tokens. Decline unless exactly one token nets in and one nets
out, so only clean single swaps are decoded.
For CoW the tx sender is the solver settling a whole batch, so tracking it nets
unrelated orders. Mark batch settlers (registry::is_batch_settler) and decode
them by finding the order maker, like filler-initiated intent fills.
@kayibal kayibal force-pushed the feat/eng-6121-tools-common branch from e6507a6 to 8fe771d Compare June 30, 2026 12:29
@kayibal kayibal force-pushed the feat/eng-6122-capture-swaps branch from f1fcbd8 to 71e8727 Compare June 30, 2026 12:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants