Tutoriel·8 min de lecture·Par Solingo

Permit2 — La Révolution Uniswap des Token Approvals

Une approbation pour chaque app. Permit2 consolide les approvals derrière un seul contrat battle-tested.

# Permit2 — La Révolution Uniswap des Token Approvals

Les approvals infinies partout = risque. Permit2 = solution.

Le Problème : Infinite Approvals

// UX classique :

// 1. User veut swap sur Uniswap

USDC.approve(UniswapRouter, type(uint256).max); // Infinite approval

// 2. User veut stake sur Aave

USDC.approve(AavePool, type(uint256).max);

// 3. User veut lend sur Compound

USDC.approve(CompoundCToken, type(uint256).max);

// Résultat :

// - 3 contracts ont infinite approval

// - Si 1 seul est hacké → tous les USDC perdus

// - Revoke = gas cost × N protocols

Problème : chaque protocol = nouvelle attack surface pour vos approvals.

La Solution : Permit2

Permit2 = contrat singleton qui gère TOUS les approvals.

// 1 seule approval (one-time)

USDC.approve(Permit2, type(uint256).max);

// Ensuite, tous les protocols utilisent Permit2 (pas d'approval directe)

// Uniswap, Aave, Compound → tous passent par Permit2

// → 1 seul contrat à trust (battle-tested par Uniswap)

Architecture

AllowanceTransfer

// Approvals standards avec expiration

struct PermitDetails {

address token;

uint160 amount; // Max amount

uint48 expiration; // Unix timestamp

uint48 nonce; // Pour invalider

}

// User signe off-chain

struct PermitSingle {

PermitDetails details;

address spender; // Le protocol (ex: UniswapRouter)

uint256 sigDeadline;

}

// Protocol appelle Permit2

interface IPermit2 {

function permit(

address owner,

PermitSingle memory permitSingle,

bytes calldata signature

) external;

function transferFrom(

address from,

address to,

uint160 amount,

address token

) external;

}

SignatureTransfer

// Approval one-time (pas de state on-chain)

struct PermitTransferFrom {

TokenPermissions permitted;

uint256 nonce;

uint256 deadline;

}

struct TokenPermissions {

address token;

uint256 amount;

}

// Usage : signature uniquement, pas d'approval on-chain

interface IPermit2 {

function permitTransferFrom(

PermitTransferFrom memory permit,

SignatureTransferDetails calldata transferDetails,

address owner,

bytes calldata signature

) external;

}

Différence :

  • AllowanceTransfer = approval persistante (comme approve normal, mais avec expiration)
  • SignatureTransfer = approval one-shot (signature = approval, pas de state)

Intégration : DEX Router

import {IPermit2} from "permit2/interfaces/IPermit2.sol";

contract MyDEXRouter {

IPermit2 public immutable permit2;

constructor(IPermit2 _permit2) {

permit2 = _permit2;

}

function swapWithPermit(

address tokenIn,

address tokenOut,

uint256 amountIn,

uint256 amountOutMin,

uint256 deadline,

bytes calldata signature

) external {

// 1. Transfer via Permit2 (utilise la signature)

permit2.permitTransferFrom(

IPermit2.PermitTransferFrom({

permitted: IPermit2.TokenPermissions({

token: tokenIn,

amount: amountIn

}),

nonce: 0, // Nonce tracking off-chain

deadline: deadline

}),

IPermit2.SignatureTransferDetails({

to: address(this),

requestedAmount: amountIn

}),

msg.sender,

signature

);

// 2. Exécuter le swap

uint256 amountOut = _swap(tokenIn, tokenOut, amountIn);

require(amountOut >= amountOutMin, "Slippage");

// 3. Send output

IERC20(tokenOut).transfer(msg.sender, amountOut);

}

}

Propriétés de Sécurité

1. Expiration

// Approvals expirent automatiquement

PermitDetails memory details = PermitDetails({

token: address(USDC),

amount: 1000e6,

expiration: uint48(block.timestamp + 1 hours), // Expire dans 1h

nonce: 0

});

// Après 1h, approval est invalide

// → Réduit la window d'attaque

2. Nonces

// User peut invalider une approval à tout moment

function invalidateNonces(uint256 wordPos, uint256 mask) external {

// Invalidate plusieurs nonces en 1 tx

}

// Utile si :

// - Signature leaked

// - User change d'avis

// - Emergency revoke

3. Witness Data

// Inclure des données custom dans la signature

struct PermitWitnessTransferFrom {

TokenPermissions permitted;

uint256 nonce;

uint256 deadline;

address witness; // Extra data

}

// Exemple : swap avec slippage protection dans la signature

struct SwapWitness {

uint256 amountOutMin;

address recipient;

}

// Signature couvre aussi le witness → protection contre tampering

Piège : Permit2 Nécessite une Approval

// ⚠️ Permit2 n'est PAS magique

// User doit quand même approve Permit2 (une fois)

// Première fois :

USDC.approve(Permit2Address, type(uint256).max); // On-chain tx, coûte du gas

// Ensuite :

// - Tous les swaps = signature only (0 gas pour approval)

// - Mais approval initiale = toujours nécessaire

UX : pour un nouveau user, il faut 2 txs (approve Permit2 + swap). Pas 1.

Adoption 2026

Protocoles supportant Permit2 :
  • Uniswap (v3, v4, UniversalRouter)
  • 1inch
  • Cowswap
  • Paraswap
  • Across
  • Pancakeswap

Tokens supportant ERC-2612 (permit natif) :

  • DAI, USDC (Circle), USDT (new version), etc.
  • → Peuvent skip Permit2 et utiliser permit() natif

Exemple Complet : Gasless Swap

// Frontend : user signe (off-chain, 0 gas)

const domain = {

name: "Permit2",

chainId: 1,

verifyingContract: PERMIT2_ADDRESS

};

const types = {

PermitTransferFrom: [

{ name: "permitted", type: "TokenPermissions" },

{ name: "spender", type: "address" },

{ name: "nonce", type: "uint256" },

{ name: "deadline", type: "uint256" }

],

TokenPermissions: [

{ name: "token", type: "address" },

{ name: "amount", type: "uint256" }

]

};

const message = {

permitted: {

token: USDC_ADDRESS,

amount: ethers.parseUnits("100", 6)

},

spender: ROUTER_ADDRESS,

nonce: 0,

deadline: Math.floor(Date.now() / 1000) + 3600

};

const signature = await signer.signTypedData(domain, types, message);

// Backend/Relayer : submit la tx (paye le gas)

await router.swapWithPermit(

USDC_ADDRESS,

WETH_ADDRESS,

ethers.parseUnits("100", 6),

ethers.parseUnits("0.03", 18), // Min output

message.deadline,

signature

);

// User a swappé sans payer de gas (relayer payé)

Comparaison avec ERC-2612 Permit

// ERC-2612 : permit natif dans le token

interface IERC20Permit {

function permit(

address owner,

address spender,

uint256 value,

uint256 deadline,

uint8 v, bytes32 r, bytes32 s

) external;

}

// Avantage : pas besoin de Permit2 (natif au token)

// Inconvénient : tous les tokens ne le supportent pas

// Permit2 : universel

// - Fonctionne avec TOUS les ERC20 (même vieux tokens)

// - Standard unique pour protocols

// - Expiration + nonces + witness data

Verdict : Permit2 > ERC-2612 pour les protocols (plus flexible, universel).

Conclusion

Permit2 = grosse amélioration de sécu et UX.

1 approval (Permit2) > N approvals (chaque protocol).

Si vous construisez un protocol en 2026, supportez Permit2. Sinon vous êtes en retard.

Prêt à mettre en pratique ?

Applique ces concepts avec des exercices interactifs sur Solingo.

Commencer gratuitement