# ERC-7683 — The Standard for Cross-Chain Intents
Cross-chain bridging has always been clunky. Lock tokens on chain A, wait for finality, mint wrapped tokens on chain B. Users do not care about the mechanics — they just want USDC on Arbitrum.
Intents flip the model. Instead of specifying how to execute a transaction, users specify what they want. Fillers compete to settle the intent off-chain and post the result on-chain.
ERC-7683 is the new standard for cross-chain intents. Here is how it works.
Declarative vs Imperative
Traditional bridging is imperative:
bridge.lock(1000 USDC on Ethereum);
bridge.mint(1000 USDC on Arbitrum);
Intents are declarative:
// I want 1000 USDC on Arbitrum, I have 1000 USDC on Ethereum
CrossChainOrder({
originChainId: 1,
destinationChainId: 42161,
inputToken: USDC_ETH,
outputToken: USDC_ARB,
inputAmount: 1000e6,
minOutputAmount: 999e6,
recipient: msg.sender
});
Fillers watch for these orders, transfer 999 USDC to the user on Arbitrum, then claim the 1000 USDC on Ethereum as their payment.
ERC-7683 Spec
The core struct:
struct CrossChainOrder {
address originSettler;
address user;
uint256 nonce;
uint32 originChainId;
uint32 destinationChainId;
address inputToken;
address outputToken;
uint256 inputAmount;
uint256 minOutputAmount;
address recipient;
uint256 deadline;
bytes32 fillDeadline;
}
Users sign this struct off-chain. Fillers submit it to the origin settler (on Ethereum) and the destination settler (on Arbitrum).
Settler Contracts
Two contracts handle settlement:
Origin Settler
Locks the user's input tokens and emits an event for fillers:
function open(CrossChainOrder calldata order, bytes calldata signature) external {
require(ECDSA.recover(hash(order), signature) == order.user, "Invalid sig");
IERC20(order.inputToken).transferFrom(order.user, address(this), order.inputAmount);
emit OrderOpened(orderId, order);
}
Destination Settler
Verifies the fill and releases funds to the user:
function fill(bytes32 orderId, CrossChainOrder calldata order) external {
IERC20(order.outputToken).transferFrom(msg.sender, order.recipient, order.minOutputAmount);
emit OrderFilled(orderId, msg.sender);
}
Fillers must post a proof back to the origin chain to claim the locked input tokens.
Gasless Orders
ERC-7683 integrates with Permit2 for gasless approvals. Users sign the order + a Permit2 signature, and fillers pay gas to submit it.
Permit2.permitTransferFrom(
PermitTransferFrom({
token: order.inputToken,
amount: order.inputAmount,
nonce: order.nonce,
deadline: order.deadline
}),
transferDetails,
order.user,
signature
);
This removes the need for users to hold ETH on every chain.
Filler Role
Fillers are off-chain agents that:
OrderOpened events on origin chainsFillers compete on speed and price. The first filler to settle wins the order.
Examples in Production
- Across Protocol — uses ERC-7683 for gasless cross-chain swaps
- Uniswap X — Dutch auction intents for best execution
- 1inch Fusion — intent-based swaps with solver competition
Solidity Integration
Here is a minimal ERC-7683 settler:
contract SimpleSolver {
mapping(bytes32 => bool) public filled;
function fill(CrossChainOrder calldata order) external {
bytes32 orderId = keccak256(abi.encode(order));
require(!filled[orderId], "Already filled");
require(block.timestamp < order.deadline, "Expired");
IERC20(order.outputToken).transferFrom(msg.sender, order.recipient, order.minOutputAmount);
filled[orderId] = true;
emit OrderFilled(orderId, msg.sender);
}
}
The filler transfers output tokens and marks the order as filled.
Risks
- Filler griefing — malicious filler front-runs with invalid fill
- Sequencing — if two fillers submit at the same time, only one succeeds
- Oracle failure — if the origin chain cannot verify the fill, funds are stuck
Summary
ERC-7683 makes cross-chain UX as simple as signing a message. Users do not need to understand bridges or wrapped tokens. Fillers handle the complexity. This is the future of cross-chain.