Fix: ERC-7683 Cross-Chain Intent Replay & Settler Contract Revert (0x54656c6c)
The ERC-7683 Cross-Chain Intents Standard (co-developed by Across, Uniswap Labs, and Biconomy) establishes a unified interface for cross-chain liquidity and filler networks. Under ERC-7683, users sign an off-chain intent specifying desired outputs on a destination chain, and competitive filler relayers advance funds on the destination chain in exchange for reimbursement through settlement contracts.
However, production filler nodes, automated market-making relayers, and cross-chain dApps face severe transaction execution failures in settler contracts:
Transaction Reverted: Settler execution failed
Custom Error: InvalidOrderHash() (0x54656c6c)
or: NonceAlreadyFilled(bytes32 orderId)
If your relay architecture also interacts with off-chain token authorizations, consult our companion forensic analysis on Uniswap Permit2 InvalidNonce and SignatureExpired Reverts.
Technical Architecture of ERC-7683 ISettler
The core contract on both the origin chain and destination chain implements the ISettler interface:
// ERC-7683 Core Interface
struct GaslessCrossChainOrder {
address originSettler;
address user;
uint256 nonce;
uint256 originChainId;
uint32 openDeadline;
uint32 fillDeadline;
bytes32 orderDataType;
bytes orderData;
}
interface ISettler {
function fill(
bytes32 orderId,
bytes calldata originData,
bytes calldata fillerData
) external returns (bytes memory);
}
When a user initiates an intent:
- The user signs an EIP-712 payload representing
GaslessCrossChainOrder. - The user’s tokens on the origin chain are escrowed in
originSettler. - A filler network observes the intent and submits
fill()todestinationSettler. destinationSettlerverifies the order hash, checks nonces, transfers output tokens to the user, and triggers cross-chain settlement verification back to the origin chain.
Root Cause 1: orderDataType Hash Discrepancy Between Chains
In ERC-7683, orderData is an opaque bytes field whose internal ABI decoding is governed by orderDataType:
$$\text{resolvedOrderDataType} = \text{keccak256}(\text{“MandatoryOutputOrder(bytes32 recipient,address outputToken,uint256 amount,uint256 destinationChainId)”})$$
A widespread engineering defect occurs when origin client SDKs and destination settler implementations use different field orderings or variable bitwidths (e.g., uint32 destinationChainId vs uint256 destinationChainId) when computing orderDataType:
[Origin Chain]: Computes EIP-712 hash with orderDataType = 0x8a1b...
[Destination Node]: Expects schema with orderDataType = 0x9f4c...
[Settler.fill()]: keccak256(orderData) mismatch -> REVERTS with InvalidOrderHash()
The Fix: Standardized Typed Data Definition
Ensure strict canonical alignment of the internal orderData struct across both origin and destination clients:
// erc7683-order-builder.ts
import { keccak256, toHex, encodeAbiParameters, parseAbiParameters, type Hex } from 'viem';
export const MANDATORY_OUTPUT_ORDER_TYPE =
'MandatoryOutputOrder(bytes32 recipient,address outputToken,uint256 amount,uint256 destinationChainId)';
export const MANDATORY_OUTPUT_ORDER_TYPE_HASH: Hex = keccak256(
toHex(MANDATORY_OUTPUT_ORDER_TYPE)
);
export function encodeOrderData(
recipient: Hex,
outputToken: Hex,
amount: bigint,
destinationChainId: bigint
): Hex {
return encodeAbiParameters(
parseAbiParameters('bytes32 recipient, address outputToken, uint256 amount, uint256 destinationChainId'),
[recipient, outputToken, amount, destinationChainId]
);
}
Root Cause 2: Cross-Rollup Intent Nonce Replay
Because users sign intents that authorize fillers to take funds, what happens if an adversarial filler observes a valid intent intended for Base and attempts to fill it on Arbitrum?
If destinationChainId is not validated within the primary EIP-712 domain separator or explicitly asserted inside the destination settler’s fill() method:
// VULNERABLE SETTLER IMPLEMENTATION
function fill(bytes32 orderId, bytes calldata originData, bytes calldata fillerData) external {
GaslessCrossChainOrder memory order = abi.decode(originData, (GaslessCrossChainOrder));
// MISSING: require(order.destinationChainId == block.chainid);
require(!filledOrders[orderId], "NonceAlreadyFilled");
filledOrders[orderId] = true;
// Executes payout on the wrong chain!
}
An attacker can exploit chain-specific token price imbalances or arbitrage liquidity pools by executing the trade on an unintended chain, stranding user funds.
The Fix: Explicit Chain Binding & Domain Validation
The destination settler contract must strictly assert destination chain identity and verify that the origin settler address matches the authorized bridge registry:
// SECURE SETTLER VERIFICATION PATTERN
contract SecureDestinationSettler is ISettler {
mapping(bytes32 => bool) public filledOrders;
mapping(uint256 => address) public authorizedOriginSettlers;
error InvalidDestinationChain(uint256 expected, uint256 actual);
error UnauthorizedOriginSettler(address settler);
error OrderAlreadyFilled(bytes32 orderId);
function fill(
bytes32 orderId,
bytes calldata originData,
bytes calldata fillerData
) external override returns (bytes memory) {
GaslessCrossChainOrder memory order = abi.decode(originData, (GaslessCrossChainOrder));
// 1. Verify Origin Settler Trust Anchor
if (order.originSettler != authorizedOriginSettlers[order.originChainId]) {
revert UnauthorizedOriginSettler(order.originSettler);
}
// 2. Decode and Assert Destination Chain ID
(,,, uint256 destChainId) = abi.decode(order.orderData, (bytes32, address, uint256, uint256));
if (destChainId != block.chainid) {
revert InvalidDestinationChain(block.chainid, destChainId);
}
// 3. Mark Order as Filled
if (filledOrders[orderId]) {
revert OrderAlreadyFilled(orderId);
}
filledOrders[orderId] = true;
// Proceed with token disbursement to recipient
}
}
Root Cause 3: Asynchronous fillDeadline Expiration in Relayer Queues
Cross-chain message relays often encounter variable propagation latency:
- Ethereum Mainnet to Arbitrum: $\approx 10-15$ minutes (L1 finality + sequencer ingestion)
- Base to Optimism: $\approx 2-5$ seconds (soft settlement), but up to 20 minutes if proving gas spikes
If a user signs an intent with fillDeadline = block.timestamp + 120 (2 minutes), by the time the filler’s destination transaction gets included by the destination sequencer, block.timestamp on the destination rollup has already crossed fillDeadline.
The destination settler reverts:
require(block.timestamp <= order.fillDeadline, "OrderExpired");
The filler loses transaction gas fees, and the user’s origin deposit remains locked until openDeadline refund mechanics activate.
Defensive Relayer Logic: Dynamic Pre-Fill Deadline Gate
Filler bots must verify destination blocktime and discard expired orders before broadcasting transactions:
// filler-safety-guard.ts
import { type PublicClient } from 'viem';
export async function canSafelyFillOrder(
destClient: PublicClient,
orderFillDeadline: number,
safetyBufferSeconds: number = 180 // 3-minute execution buffer
): Promise<{ canFill: boolean; reason?: string }> {
const latestBlock = await destClient.getBlock({ blockTag: 'latest' });
const destTimestamp = Number(latestBlock.timestamp);
const remainingWindow = orderFillDeadline - destTimestamp;
if (remainingWindow <= 0) {
return { canFill: false, reason: `Order already expired on destination: ${remainingWindow}s` };
}
if (remainingWindow < safetyBufferSeconds) {
return {
canFill: false,
reason: `Remaining window too narrow (${remainingWindow}s < ${safetyBufferSeconds}s buffer)`,
};
}
return { canFill: true };
}
Production Diagnostic Checklist
When debugging ERC-7683 settler reverts:
[Diagnostic Routine]
├── 1. Compute Expected orderId:
│ └── keccak256(abi.encode(originChainId, originSettler, user, nonce, orderData))
├── 2. Verify Schema TypeHash:
│ └── Confirm keccak256(orderDataTypeString) matches destination settler constant
├── 3. Compare Chain IDs:
│ └── Verify destinationChainId in orderData equals block.chainid of execution network
└── 4. Inspect Block Timestamps:
└── Assert destination latestBlock.timestamp <= fillDeadline
Adopting strict typed data verification, cryptographically binding destination chain IDs, and setting realistic cross-chain deadline margins prevents settler execution failures and preserves high fill rates across cross-chain intent networks.