Actualités·8 min de lecture·Par Solingo

ERC-7683 — Le Standard pour les Intents Cross-Chain

Les intents sont la nouvelle UX cross-chain. ERC-7683 standardise comment les fillers les découvrent et les settle.

# 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é.

Prêt à mettre en pratique ?

Applique ces concepts avec des exercices interactifs sur Solingo.

Commencer gratuitement