Skip to main content

Simulation Failures and Settlement Decisions

When filling Priority orders on Unichain or Base, simulation reverts are an expected part of normal operation — not a sign of a bug in your filler. Because Priority orders are keyed to a specific block (cosignerData.auctionTargetBlock), any simulation that runs slightly before or after that block will revert. This guide explains why this happens and how to decide whether to attempt on-chain settlement after a failed simulation.

Why Simulation Reverts Are Expected

Every Priority order includes a cosignerData.auctionTargetBlock field that specifies the exact block at which the order becomes executable. The PriorityOrderReactor checks this field on-chain and reverts if the current block does not match.

Because block times on Unichain and Base are very short (roughly 1 second), the window between when a simulation runs and when a transaction actually lands is enough for the target block to shift. As a result:

  • Simulating before auctionTargetBlockInvalidTargetBlock revert
  • Simulating at auctionTargetBlock → likely success (subject to other checks)
  • Simulating after auctionTargetBlockInvalidTargetBlock revert

Treat InvalidTargetBlock reverts as transient, not terminal. They do not indicate a problem with your fill logic or the order itself.

note

The UniswapXOrderQuoter requires a JsonRpcProvider that supports block overrides to correctly simulate Priority orders. Without block overrides, the quoter cannot advance the simulated block to auctionTargetBlock and will revert even for valid orders.

Decision Tree: Attempt vs Skip Settlement After Failed Simulation

Use the revert reason to decide whether to retry, attempt settlement anyway, or skip entirely:

Failed simulation

├── Revert reason: InvalidTargetBlock
│ └── Block-timing mismatch — RETRY at next block

├── Gas estimation fails entirely (no revert reason)
│ └── Node-level failure — SKIP this attempt; poll again next block

├── Revert reason: InvalidCosignature
│ └── Cosignature is missing or malformed — SKIP and ALERT
│ (order may have been tampered with or cosigner is unavailable)

├── Revert reason: DeadlinePassed
│ └── Order deadline has elapsed — SKIP; do not attempt settlement

├── Revert reason: InsufficientOutput / OutputsExceedInputs
│ └── Price moved against you — SKIP; recalculate profit before retrying

└── Any other revert reason
└── Treat as terminal for this attempt — SKIP and log for investigation

If your simulation succeeds, proceed to submit the fill transaction with your chosen priority fee. Remember that only the transaction with the highest priority fee wins; all others will revert on-chain (by design).

Error Code Reference

Revert ReasonCauseRecommended Action
InvalidTargetBlockCurrent block ≠ auctionTargetBlockRetry at next block
InvalidCosignatureCosignature missing or invalidSkip and alert your team
DeadlinePassedOrder deadline has elapsedSkip
InsufficientOutputFiller output below required minimumSkip; re-evaluate pricing
OutputsExceedInputsCallback returned less than requiredSkip; fix callback logic
OrderAlreadyFilledAnother filler won the orderSkip (normal PGA loss)

For a more complete list of revert reasons, see the KNOWN_ERRORS map in the UniswapX SDK.

Base L1 Data Fee Warning

On Base, every transaction incurs an additional L1 data fee (the cost of posting calldata to Ethereum mainnet) on top of the standard L2 execution gas. This fee is not reflected in a plain eth_estimateGas call against the Base RPC.

If your gas estimation does not account for the L1 data fee:

  • Your profitability calculation will be incorrect — the actual transaction cost will be higher than estimated.
  • A simulation that appears profitable may result in a loss on-chain.

Recommendation: Use the Base fee oracle (GasPriceOracle precompile at 0x420000000000000000000000000000000000000F) to query getL1Fee(bytes calldata _data) with your encoded transaction calldata before submitting. Add the returned L1 fee to your L2 gas estimate to get the true total cost.

// Pseudocode — adapt to your filler stack
const l2GasCost = await provider.estimateGas(fillTx);
const l1Fee = await gasPriceOracle.getL1Fee(encodedCalldata);
const totalCost = l2GasCost * gasPrice + l1Fee;

Fillers that skip this step frequently underestimate costs on Base, especially for orders with large calldata (e.g. complex callback paths).