How Uniswap Labs and Across Protocol are standardizing the plumbing for a multi-chain world
In a recent post, I discussed the potential of intent bridges and how they differ from the classical paradigm 👇
Cross-Chain Is Now Instant. What Changed Under the Hood?
If you’ve ever tried to move assets from Arbitrum to Optimism, or triggered a contract call across chains, you’ve encountered the fragmentation that defines today’s multi-chain landscape. Every bridge, every solver network, every intent protocol speaks a different dialect. A filler network optimized for Across can’t fill Uniswap X orders.
The result is siloed liquidity, duplicated infrastructure, and users who pay higher fees because solvers can’t aggregate demand across systems. As of 2026, there are dozens of cross-chain bridging and intent systems, each reinventing the wheel on order formats, settlement logic, and filler coordination.
ERC-7683 is an attempt to fix some of this variability. It doesn’t prescribe how you settle, how you price, or how you route. It standardizes only what must be standardized: the shape of a cross-chain order and the minimal interface that contracts must expose, allowing the same solver to fulfill intents on multiple protocols at the same time (i.e., unifying solver networks into one).
ERC7683 main data structures and entitiesERC-7683 is a cross-chain intents standard co-authored by Uniswap Labs and Across Protocol. It defines:
The EIP was submitted to the Ethereum Magicians forum and has gained rapid adoption from major bridging and intent infrastructure teams.
Traditional cross-chain interactions require users to specify how an action happens. You call a bridge contract, specify a route, pick a relayer, and hope the liquidity is there. The user bears the complexity.
Intents invert this. A user declares what they want (“I want at least 1,000 USDC on Optimism in exchange for 1,050 USDC on Arbitrum, settled within 10 minutes”) and lets a competitive market of fillers figure out how to deliver it. Fillers compete on price and speed, and the user gets a better outcome without needing to understand routing.
ERC-7683 standardizes the format of these intents so that any filler — regardless of which protocol originally created the intent — can parse, evaluate, and fill it.
There are four roles in the ERC-7683 system:
User (EOA / Wallet): Creates and signs cross-chain orders. In the gasless flow, they never broadcast a transaction to the origin chain at all — they just sign a message.
Filler (Solver / Relayer): An off-chain actor (often a bot or market-maker) that monitors for open orders, evaluates profitability, and executes fills on destination chains in exchange for a fee embedded in the order.
IOriginSettler: A smart contract on the origin chain. It accepts order submissions, validates them, locks the user’s tokens, and emits a standardized Open event that fillers monitor.
IDestinationSettler: A smart contract on the destination chain. It receives the filler’s fill() call and executes the user's desired action — transferring tokens, executing calldata, or both.
This is where things get interesting. ERC-7683 defines a careful hierarchy of structs.
The standard supports two ways for a user to submit an order.
GaslessCrossChainOrder is for situations where the user doesn't want to pay gas on the origin chain. They sign this struct off-chain and hand it to a filler, who submits it to the origin settler on their behalf.
struct GaslessCrossChainOrder {
address originSettler; // which settler contract handles this
address user; // the signing user
uint256 nonce; // replay protection
uint256 originChainId; // ensures the order is chain-specific
uint32 openDeadline; // filler must open before this timestamp
uint32 fillDeadline; // filler must fill before this timestamp
bytes32 orderDataType; // keccak256 hash of the sub-type name
bytes orderData; // ABI-encoded sub-type-specific data
}
OnchainCrossChainOrder is for users who are already broadcasting a transaction to the origin chain (e.g., they're approving tokens anyway). It's a leaner struct because the user's address and chain context are implicit from msg.sender and block.chainid.
struct OnchainCrossChainOrder {
uint32 fillDeadline; // filler must fill before this timestamp
bytes32 orderDataType; // keccak256 hash of the sub-type name
bytes orderData; // ABI-encoded sub-type-specific data
}
Note what’s absent from OnchainCrossChainOrder: originSettler (it's the contract receiving the call), user (it's msg.sender), nonce (managed by the settler), and originChainId (it's block.chainid). The standard stays minimal.
Both order types resolve into a single canonical struct that every filler can interpret:
struct ResolvedCrossChainOrder {
address user;
uint256 originChainId;
uint32 openDeadline;
uint32 fillDeadline;
bytes32 orderId; // unique identifier for this order
Output[] maxSpent; // max tokens the settler will take from the user
Output[] minReceived; // min tokens the user must receive
FillInstruction[] fillInstructions; // instructions for each destination leg
}
maxSpent and minReceived express the economic bounds of the order as a set of token flows. The filler knows exactly what they're committing to without needing to decode any protocol-specific logic.
orderId is a bytes32 unique identifier that links origin and destination activity together. It's emitted in the Open event and passed to fill(), providing the correlation handle across chains.
struct Output {
bytes32 token; // token address (padded to bytes32 for cross-chain compat)
uint256 amount;
bytes32 recipient; // recipient address (padded to bytes32)
uint256 chainId;
}
Output appears in both maxSpent and minReceived. Using bytes32 instead of address for both token and recipient is a deliberate design choice — it makes the struct compatible with non-EVM chains where addresses aren't 20 bytes. The standard is already thinking beyond Ethereum.
struct FillInstruction {
uint64 destinationChainId;
bytes32 destinationSettler; // bytes32 for cross-chain address compat
bytes originData; // opaque blob passed to fill()
}
FillInstruction is the most architecturally interesting struct in the spec. The array in ResolvedCrossChainOrder supports multi-leg orders — you could have one order that requires fills on three different destination chains.
The originData field is completely opaque. As the spec notes:
This is a key insight. The filler’s job is to deliver originData to the destination settler, not to understand it. The settler on the destination side decodes it. This separation means you can pack arbitrary proof data, routing hints, or execution calldata into originData without the filler network needing a software upgrade to support it.
The bridge contracts need to follow some interfaces depending on whether they serve as a “source-chain contract” or a “destination-chain contract”.
The interface that a contract must implement to create new intents.
interface IOriginSettler {
// Gasless path: filler submits a signed order on behalf of the user
function openFor(
GaslessCrossChainOrder calldata order,
bytes calldata signature,
bytes calldata originFillerData
) external;
// Onchain path: user submits directly
function open(OnchainCrossChainOrder calldata order) external;
// Resolve a gasless order into canonical form (view function)
function resolveFor(
GaslessCrossChainOrder calldata order,
bytes calldata fillerData
) external view returns (ResolvedCrossChainOrder memory);
// Resolve an onchain order into canonical form (view function)
function resolve(
OnchainCrossChainOrder calldata order
) external view returns (ResolvedCrossChainOrder memory);
// Emitted when an order is opened
event Open(
bytes32 indexed orderId,
ResolvedCrossChainOrder resolvedOrder
);
}
A few things worth noting:
resolveFor and resolve are view functions. This means fillers can simulate resolution off-chain or via eth_call without spending gas. A filler can call resolveFor on any order they encounter to get the canonical form and assess profitability before committing to a fill.
originFillerData in openFor is a filler-specific parameter. It allows fillers to pass protocol-specific hints (e.g., preferred settlement route, fee tier, proof format) to the settler when opening a gasless order.
The Open event is the signal every filler is watching. Once emitted, an order is "live" and any filler can race to fill it on the destination chain.
The interface that a contract must implement to fulfill intents created on other blockchains.
interface IDestinationSettler {
function fill(
bytes32 orderId,
bytes calldata originData,
bytes calldata fillerData
) external;
}
Minimal by design. The destination settler receives the orderId (for cross-chain correlation), the originData blob from FillInstruction (decoded internally), and fillerData (filler-specific instructions, also extensible via the sub-type system).
The destination settler is responsible for verifying that the fill is legitimate, executing the user’s desired action, and emitting whatever events your cross-chain messaging layer needs to trigger settlement back on the origin chain.
Let’s put ourselves in the shoes of a user that wants to create an intent. There are two possible ways to accomplish this goal.
This is the primary UX target for ERC-7683. The user pays zero gas on the origin chain.
1. User signs GaslessCrossChainOrder off-chain (no gas)
2. User passes signed order + signature to a Filler
3. Filler calls IOriginSettler.openFor(order, signature, fillerData)
4. IOriginSettler validates signature, locks user tokens, emits Open event
5. Filler monitors Open event, sees their fill instructions
6. Filler calls IDestinationSettler.fill(orderId, originData, fillerData) on dest chain
7. Destination settler executes the user's desired action
8. Cross-chain messaging triggers settlement: filler gets repaid
This path is optimal when the user is already spending gas, for example, if they’re doing a token approval anyway.
1. User calls IOriginSettler.open(OnchainCrossChainOrder)
2. IOriginSettler locks user tokens, emits Open event
3. (Steps 5–8 identical to gasless path)
orderDataType is a bytes32 field. The keccak256 hash of the sub-type's full EIP-712 type string, including all field names and types (e.g., https://github.com/across-protocol/contracts/blob/5225521544a19ca7bfbb8bd793dc7303ad6f842e/contracts/erc7683/ERC7683Permit2Lib.sol#L46). orderData is the ABI-encoded payload of that sub-type.
The spec defines an example subtype called Message that allows intents to carry executable call
struct Message {
Call[] calls;
}
struct Call {
address target;
bytes callData;
uint256 value;
}
With the Message sub-type, a user can express intents like “swap 1 ETH for USDC on Optimism and then call stake() on this contract with the USDC." The destination settler decodes the Message, executes the calls inside the filler's transaction, and the whole thing lands atomically.
Sub-types are registered in an off-chain repository (the “subtypes repo” referenced in the spec). This is intentionally off-chain (the standard doesn’t try to govern a registry on-chain), which would add governance overhead. Teams publish sub-types, and the community adopts those that prove useful.
fillerData is passed by the filler to fill() and carries the filler's repayment instructions, for example, the address on the origin chain where settlement should be sent after the fill is confirmed.
Understanding the boundaries is as important as understanding the spec itself.
Settlement proofs. How does the origin chain know a fill happened on the destination chain? ERC-7683 doesn’t care. Across uses an optimistic oracle. Uniswap X uses off-chain attestations. You could use ZK proofs. The standard is agnostic — this is settled at the protocol layer, not the standard layer.
Filler discovery and order routing. There’s no on-chain order book in ERC-7683. Fillers discover orders by listening to Open events. How they find gasless orders before they're opened (to decide whether to submit openFor) is left to off-chain coordination, RFQ systems, or gossip networks.
Pricing and competition. The standard doesn’t define how fillers compete. That’s a market mechanism built on top of the standard, not part of it.
Settlement token mechanics. How the filler gets paid back on the origin chain after filling on the destination chain is entirely up to the settler implementation. The standard just ensures the orderId is the common reference.
This restraint is intentional and correct. A standard that tries to specify everything becomes impossible to adopt. ERC-7683 specifies the minimum viable interface and lets protocols compete on the rest.
Here are two examples of real-world implementations of the standard. For each protocol, there is a link for the GitHub repo and documentation:
Out of curiosity, and for comparison, below are two intent-based bridges that do not follow the standard:
The value of standards compounds. ERC-20 didn’t just make tokens interoperable, it made DEXes, lending protocols, and yield aggregators possible because they could all assume a common interface. ERC-7683 has the same compounding potential for the cross-chain layer.
Consider what becomes possible when every intent protocol speaks ERC-7683:
The standard is still young, but the architecture is sound: minimal required interface, rich optional extensibility, and deliberately agnostic about the layers above and below.
ERC-7683 is infrastructure, not a product.
The core insight of the design is elegant: separate the expression of user intent (the order structs) from the resolution of that intent (the settler implementations), with a canonical intermediate form (ResolvedCrossChainOrder) that any filler can interpret. Layer an extensibility system on top via orderData sub-types, and you get a standard that's both immediately deployable and infinitely customizable.
Interested in building on ERC-7683? The spec lives at erc7683.org.
Sources:
ERC-7683: A Technical Deep Dive into the Cross-Chain Intents Standard was originally published in Coinmonks on Medium, where people are continuing the conversation by highlighting and responding to this story.


