Skip to content

[indexer]: track fill userop hashes, actual delivered amounts, and placeOrder fee token - #1112

Draft
seunlanlege wants to merge 2 commits into
mainfrom
indexer-fill-userop-amounts-fee-token
Draft

[indexer]: track fill userop hashes, actual delivered amounts, and placeOrder fee token#1112
seunlanlege wants to merge 2 commits into
mainfrom
indexer-fill-userop-amounts-fee-token

Conversation

@seunlanlege

@seunlanlege seunlanlege commented Aug 8, 2026

Copy link
Copy Markdown
Member

Closes #1104.

Adds the three missing pieces of IntentGatewayV3 data to the indexer: the userop hash behind each fill, the amount the beneficiary actually received, and the token placeOrder fees are denominated in.

Schema

Entity Field Notes
IOrderV3Fill, IOrderV3PartialFill userOpHash: String nullable — null for EOA fills
IOrderV3FillOutputAsset, IOrderV3PartialFillOutputAsset amountReceived: BigInt nullable — null for native outputs
IOrderV3 feeToken: String!, feeTokenDecimals: Int! derived from the host contract

All additions are nullable or derivable, so no reindex-incompatible change.

Userop hash

Solvers fill through the ERC-4337 SolverAccount, so the stored transactionHash is the bundler's EntryPoint transaction, not the userop the solver submitted and tracks. The fill handlers now read the fill transaction's receipt and take the userOpHash from the first UserOperationEvent after the fill log whose sender is the filler. The EntryPoint emits that event once an op finishes executing, so within a bundle containing several solvers' ops the sender match plus the log-index ordering picks the right one. Plain EOA fills emit no such event and leave the field null.

Amount received

ExtrinsicIntents.fill() emits outputFills[i].amount = totalRequired — the order's promised amount (ExtrinsicIntents.sol:134). On an overfill the beneficiary receives totalRequired + beneficiaryShare from the surplus split, and the solver's actual solverAmount never appears in the event. Since the solver transfers output assets directly to the user, amountReceived comes from the Transfer logs (filler → beneficiary) in the fill receipt.

Two cases the matching handles:

  • Batched fills. A solver can fill several orders in one transaction. Transfers are only considered between the preceding OrderFilled/PartialFill log and this one, so each delivery is attributed to the fill it belongs to. Repeated tokens within one fill consume their transfers in log order.
  • Unindexed orders. Matching needs the beneficiary, normally read from the indexed order. Cross-chain fills are indexed on the destination chain independently of the source chain that placed the order, so the order may not exist yet. In that case the order is recovered from the fill transaction's calldata (direct or nested, via the call tracer) and accepted only if its recomputed commitment matches the event's — which also disambiguates when one transaction carries several fillOrder calls.

Native-token outputs emit no Transfer log, so they stay null rather than being guessed.

Fee token

Fees are paid in the host's fee token (IDispatcher(host()).feeToken()), which differs per chain, so IOrderV3.fees couldn't be displayed or priced without hardcoded per-chain knowledge. orderPlacedV3 now reads it from the host contract resolved through CHAINS_BY_ISMP_HOST, along with the token's decimals so fees renders without a second lookup. Both are static per chain and cached together per chain rather than fetched per order.

Tests

13 unit tests in src/utils/__tests__/fill.helpers.test.ts cover userop-hash selection (sender mismatch, events before the fill, EOA fills), transfer matching (overfill, repeated tokens, native outputs, wrong-direction and protocol-share transfers, batched fills), and fillOrder calldata decoding.

The indexer build (ENV=mainnet npm run build:release, including schema codegen) passes. The three failing suites in the wider run — state-commitment, phantom-decode, bridgeTokenSupply — fail identically on a clean tree; they hit live networks.

…r fee token

Three pieces of IntentGatewayV3 fill/order data that consumers need were
absent from the indexed entities.

Solvers fill through the ERC-4337 SolverAccount, so the stored
transactionHash is the bundler's EntryPoint transaction rather than the
userop the solver tracks. Fills now carry a nullable userOpHash, resolved
from the fill receipt's UserOperationEvent whose sender matches the filler;
it stays null for plain EOA fills.

ExtrinsicIntents.fill() emits outputFills[i].amount = totalRequired, the
promised amount, so an overfilled order's event understates what the
beneficiary got. Fill output assets now carry amountReceived alongside the
promised amount, read from the filler -> beneficiary Transfer logs in the
fill receipt. Native outputs emit no Transfer log and stay null.

Fees are denominated in the host's fee token, which differs per chain, so
IOrderV3.fees was unpriceable without hardcoded per-chain knowledge. Orders
now record feeToken, read once per chain from the host contract and cached.

Transfer matching is scoped to the log range between the preceding fill
event and this one, so a solver batching several fills in one transaction
attributes each delivery to the right fill. Beneficiaries come from the
indexed order, falling back to recovering the order from the fill calldata
and verifying it against the commitment when the order has not been indexed
yet, as happens for cross-chain fills.
Knowing which token the fees are denominated in is not enough to render
them — consumers still had to resolve the token's decimals themselves.
The host fee token is static per chain, so its decimals are fetched once
and cached in the same entry as the address.
@seunlanlege
seunlanlege force-pushed the indexer-fill-userop-amounts-fee-token branch from fac0279 to 671ed97 Compare August 8, 2026 10:25
@seunlanlege
seunlanlege marked this pull request as draft August 8, 2026 11:49
@Wizdave97

Copy link
Copy Markdown
Member

@royvardhan for this PR we'll need to modify subquery to allow us add new fields to schema entities, please look into it.

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.

[indexer]: track fill userop hashes, actual delivered amounts, and placeOrder fee token

3 participants