# Permit2 — Uniswap's Token Approval Revolution
Every DeFi protocol requires token approvals. Users approve Uniswap, Aave, Curve, and dozens of other contracts. Each approval is a potential security risk.
Permit2 solves this. Users approve one contract (Permit2), then grant sub-permissions to dapps via signatures. No more infinite approvals to unknown contracts.
The Approval UX Problem
Here is the traditional flow:
USDC.approve(uniswapRouter, type(uint256).max); // Approve Uniswap
USDC.approve(aavePool, type(uint256).max); // Approve Aave
USDC.approve(curvePool, type(uint256).max); // Approve Curve
Every approval is:
- An on-chain transaction (costs gas)
- A permanent trust assumption (contract can drain your tokens anytime)
- A new attack surface (if Uniswap is hacked, your USDC is gone)
Permit2 changes this:
USDC.approve(permit2, type(uint256).max); // One-time approval
// From now on, sign permits instead of approvals
Permit2 Architecture
Permit2 has two modes:
AllowanceTransfer
struct PermitDetails {
address token;
uint160 amount;
uint48 expiration;
uint48 nonce;
}
struct PermitSingle {
PermitDetails details;
address spender;
uint256 sigDeadline;
}
Users sign a PermitSingle off-chain. The dapp submits it to Permit2:
permit2.permit(owner, permitSingle, signature);
Permit2 then allows the dapp to transfer tokens on behalf of the user.
SignatureTransfer
For one-off transfers (like swaps), use SignatureTransfer:
struct TokenPermissions {
address token;
uint256 amount;
}
struct PermitTransferFrom {
TokenPermissions permitted;
uint256 nonce;
uint256 deadline;
}
permit2.permitTransferFrom(
permitTransferFrom,
transferDetails,
owner,
signature
);
This skips the allowance step entirely. The signature authorizes one transfer.
Integration Code
Here is a minimal DEX router using Permit2:
import {IPermit2} from "permit2/interfaces/IPermit2.sol";
contract SimpleSwap {
IPermit2 public immutable permit2;
constructor(address _permit2) {
permit2 = IPermit2(_permit2);
}
function swap(
address tokenIn,
address tokenOut,
uint256 amountIn,
uint256 minAmountOut,
uint256 deadline,
bytes calldata signature
) external {
// Transfer tokens from user via Permit2
permit2.permitTransferFrom(
IPermit2.PermitTransferFrom({
permitted: IPermit2.TokenPermissions({
token: tokenIn,
amount: amountIn
}),
nonce: 0, // Simple nonce strategy
deadline: deadline
}),
IPermit2.SignatureTransferDetails({
to: address(this),
requestedAmount: amountIn
}),
msg.sender,
signature
);
// Execute swap (simplified)
uint256 amountOut = _executeSwap(tokenIn, tokenOut, amountIn);
require(amountOut >= minAmountOut, "Slippage");
// Transfer output tokens to user
IERC20(tokenOut).transfer(msg.sender, amountOut);
}
function _executeSwap(address tokenIn, address tokenOut, uint256 amountIn)
internal
returns (uint256)
{
// Swap logic here
return amountIn; // Placeholder
}
}
Users sign the permit off-chain and submit it with their swap transaction. No separate approval tx needed.
Security Properties
Expiration
Every permit has an expiration timestamp. After expiration, the signature is invalid.
require(block.timestamp <= deadline, "Permit expired");
Nonces
Permits use nonces to prevent replay attacks. Once a nonce is used, it cannot be reused.
require(!nonceUsed[owner][nonce], "Nonce already used");
nonceUsed[owner][nonce] = true;
Witness Data
Permit2 supports witness data — extra fields signed by the user. This prevents signature reuse across different contexts.
struct PermitWitnessTransferFrom {
TokenPermissions permitted;
uint256 nonce;
uint256 deadline;
bytes32 witness; // Extra data signed by user
}
For example, a DEX can include the swap parameters in the witness field. If an attacker changes the swap parameters, the signature is invalid.
Gotchas
1. Permit2 Itself Needs Approval
Users still need to approve Permit2 once:
USDC.approve(permit2, type(uint256).max);
After that, all dapps use Permit2 and no more approvals are needed.
2. Not All Tokens Support Permit
ERC20 tokens without permit() (like USDT) still require a direct approval to Permit2. But that is a one-time cost.
3. Signature Format
Permit2 uses EIP-712 typed signatures. You need a signing library (ethers, viem) to generate them.
const domain = {
name: 'Permit2',
chainId: 1,
verifyingContract: permit2Address
};
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 signature = await signer._signTypedData(domain, types, permit);
Adoption State in 2026
As of 2026, Permit2 is used by:
- Uniswap (v3, v4, X)
- 1inch
- CoW Swap
- Across Protocol
It is becoming the standard for gasless approvals.
Full Integration Example
Here is a complete example with witness data:
contract SwapWithWitness {
IPermit2 public immutable permit2;
bytes32 public constant WITNESS_TYPE_HASH = keccak256(
"SwapWitness(address tokenOut,uint256 minAmountOut)"
);
struct SwapWitness {
address tokenOut;
uint256 minAmountOut;
}
function swap(
address tokenIn,
address tokenOut,
uint256 amountIn,
uint256 minAmountOut,
uint256 deadline,
bytes calldata signature
) external {
SwapWitness memory witness = SwapWitness({
tokenOut: tokenOut,
minAmountOut: minAmountOut
});
bytes32 witnessHash = keccak256(abi.encode(WITNESS_TYPE_HASH, witness));
permit2.permitWitnessTransferFrom(
IPermit2.PermitTransferFrom({
permitted: IPermit2.TokenPermissions({
token: tokenIn,
amount: amountIn
}),
nonce: 0,
deadline: deadline
}),
IPermit2.SignatureTransferDetails({
to: address(this),
requestedAmount: amountIn
}),
msg.sender,
witnessHash,
"SwapWitness(address tokenOut,uint256 minAmountOut)",
signature
);
// Execute swap
}
}
The witness ensures the signature cannot be replayed with different swap parameters.
Summary
Permit2 eliminates the need for per-protocol approvals. Users approve Permit2 once, then grant sub-permissions via gasless signatures. It is more secure (time-limited, nonces, witness data) and better UX (no approval txs). If you are building DeFi in 2026, integrate Permit2.