# ERC-7683 — Le Standard pour les Intents Cross-Chain
Les bridges, c'est de la plomberie. Les intents, c'est l'UX que les users veulent vraiment.
Le Problème : Bridges Sont Trop Low-Level
// User veut : "Swap mes USDC sur Arbitrum pour ETH sur Base"
// Mais doit faire :
// 1. Bridge USDC Arbitrum → Ethereum (attendre 7j)
// 2. Bridge USDC Ethereum → Base (attendre 20min)
// 3. Swap USDC → ETH sur Base
// 3 txs, 2 bridges, 7 jours. Horrible.
La Solution : Intents
Intent = déclaration de ce que vous voulez, pas comment l'obtenir.
struct Intent {
address user;
uint256 inputAmount; // 1000 USDC on Arbitrum
address inputToken;
uint256 inputChainId;
uint256 outputAmount; // 0.5 ETH on Base
address outputToken;
uint256 outputChainId;
uint256 deadline;
}
// Un "filler" trouve le chemin optimal, exécute, prend un fee.
// User ne gère rien, juste signe l'intent.
ERC-7683 : La Spec
ERC-7683 standardise les intents cross-chain pour que tous les fillers puissent les lire.
Structure CrossChainOrder
struct CrossChainOrder {
address settlementContract; // Où settle la destination
address swapper; // User qui signe
uint256 nonce;
uint32 originChainId;
uint32 destinationChainId;
address originSettler; // Contract sur chain d'origine
address destinationSettler; // Contract sur chain de destination
bytes orderData; // Order-specific data
}
struct ResolvedCrossChainOrder {
address user;
uint256 originChainId;
uint256 destinationChainId;
Input[] inputs; // Quoi le user donne
Output[] outputs; // Quoi le user reçoit
bytes32 fillDeadline;
}
struct Input {
address token;
uint256 amount;
}
struct Output {
address token;
uint256 amount;
address recipient;
uint256 chainId;
}
Interface IOriginSettler
interface IOriginSettler {
// User ouvre un order
function openFor(
bytes calldata order,
bytes calldata signature,
bytes calldata originData
) external;
// Résout l'order en données concrètes
function resolveFor(
bytes calldata order,
bytes calldata originData
) external view returns (ResolvedCrossChainOrder memory);
}
Interface IDestinationSettler
interface IDestinationSettler {
// Filler remplit l'order sur la destination
function fill(
bytes32 orderId,
bytes calldata originData,
bytes calldata fillerData
) external;
}
Implémentation Complète
1. Origin Settler
import {IOriginSettler} from "erc7683/IOriginSettler.sol";
import {Permit2} from "permit2/Permit2.sol";
contract SwapSettler is IOriginSettler {
Permit2 public immutable permit2;
mapping(bytes32 => bool) public filledOrders;
event OrderOpened(bytes32 indexed orderId, ResolvedCrossChainOrder order);
function openFor(
bytes calldata order,
bytes calldata signature,
bytes calldata originData
) external override {
ResolvedCrossChainOrder memory resolved = resolveFor(order, originData);
bytes32 orderId = keccak256(order);
require(block.timestamp <= resolved.fillDeadline, "Expired");
require(!filledOrders[orderId], "Already filled");
// Transfer input tokens via Permit2 (gasless pour user)
for (uint i = 0; i < resolved.inputs.length; i++) {
permit2.transferFrom(
resolved.user,
address(this),
resolved.inputs[i].amount,
resolved.inputs[i].token
);
}
emit OrderOpened(orderId, resolved);
}
function resolveFor(
bytes calldata order,
bytes calldata /* originData */
) public pure override returns (ResolvedCrossChainOrder memory) {
// Decode order bytes
CrossChainOrder memory cco = abi.decode(order, (CrossChainOrder));
(Input[] memory inputs, Output[] memory outputs) = abi.decode(
cco.orderData,
(Input[], Output[])
);
return ResolvedCrossChainOrder({
user: cco.swapper,
originChainId: cco.originChainId,
destinationChainId: cco.destinationChainId,
inputs: inputs,
outputs: outputs,
fillDeadline: /* decode from orderData */
});
}
}
2. Destination Settler
import {IDestinationSettler} from "erc7683/IDestinationSettler.sol";
contract DestinationSwapSettler is IDestinationSettler {
mapping(bytes32 => bool) public filled;
event OrderFilled(bytes32 indexed orderId, address indexed filler);
function fill(
bytes32 orderId,
bytes calldata originData,
bytes calldata fillerData
) external override {
require(!filled[orderId], "Already filled");
filled[orderId] = true;
// Decode origin data
ResolvedCrossChainOrder memory order = abi.decode(originData, (ResolvedCrossChainOrder));
// Filler doit fournir les outputs
for (uint i = 0; i < order.outputs.length; i++) {
Output memory output = order.outputs[i];
IERC20(output.token).transferFrom(
msg.sender, // filler
output.recipient,
output.amount
);
}
emit OrderFilled(orderId, msg.sender);
// Filler sera remboursé via proof cross-chain (hors scope ici)
}
}
Intégration Permit2
Gasless signing pour users.
// User signe un Permit2 message (off-chain, 0 gas)
struct PermitTransferFrom {
TokenPermissions permitted;
address spender; // SwapSettler
uint256 nonce;
uint256 deadline;
}
// SwapSettler utilise la signature dans openFor
permit2.permitTransferFrom(
permitMsg,
signature,
SwapSettler.address
);
User paye 0 gas pour créer l'intent. Filler paye le gas sur origin + destination.
Exemples Réels
Across Protocol
// Across = bridging via intents
// User demande USDC sur Optimism
// Filler donne USDC immédiatement
// Filler est remboursé via canonical bridge (lent mais sûr)
contract AcrossOriginSettler {
function openFor(...) {
// Lock funds
// Emit RelayRequested event
// Fillers off-chain voient l'event, fill sur destination
}
}
Uniswap X
// UniswapX = swaps via intents (Dutch auction)
// User signe : "Je veux 100 USDC pour max 0.05 ETH, deadline 1min"
// Prix decay over time (Dutch auction)
// Fillers compete pour donner le meilleur prix
struct DutchOrder {
address inputToken;
address outputToken;
uint256 inputAmount;
uint256 outputStartAmount; // Prix initial
uint256 outputEndAmount; // Prix final (decay)
uint256 decayStartTime;
uint256 decayEndTime;
}
Risques
1. Filler Griefing
// Filler malicious peut :
// 1. Voir l'intent
// 2. Front-run et fill partiellement
// 3. User stuck avec partial fill
// Mitigation : all-or-nothing fills
require(filledAmount == order.outputAmount, "Partial fill not allowed");
2. Sequencing
// Si 2 fillers submit en même temps :
// - Le premier on-chain gagne
// - Le deuxième revert (already filled)
// → Filler #2 a payé gas pour rien
// Mitigation : off-chain coordination (filler pools, auctions)
3. Cross-Chain Proof Latency
// Filler fill sur destination, attend proof de l'origin
// Si proof tarde (bridge congestion), filler est exposé à :
// - Price volatility (ETH pumpe entre fill et remboursement)
// - Opportunity cost (capital locked)
// Mitigation : filler pricing intègre le risque
Adoption 2026
- Across : production depuis 2023, volume $5B+
- UniswapX : live sur Ethereum + Arbitrum + Polygon
- Cowswap : gasless intents via solvers
- 1inch Fusion : intent-based swaps
ERC-7683 unifie tous ces systèmes. Un filler peut maintenant remplir des orders de tous les protocoles compatibles.
Verdict
Les intents = meilleure UX que les bridges directs. ERC-7683 = le railway standard qui permet l'interopérabilité.
Si vous construisez du cross-chain en 2026, supportez ERC-7683. Sinon vous serez isolé.