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