Permit3 is a revolutionary protocol that enables cross-chain token approvals and transfers with a single signature. It unlocks a one-signature cross-chain future through innovative UnhingedProofs and non-sequential nonces, while maintaining Permit2 compatibility.
"Permit3 unlocks a one-click/signature cross-chain future."
- π Cross-Chain Operations: Authorize token operations across multiple blockchains with one signature
- π Direct Permit Execution: Execute permit operations without signatures when caller has authority
- π ERC-7702 Integration: Account Abstraction support for enhanced user experience
- π² Unhinged Merkle Trees: A novel two-part data structure that combines:
[H1] β [H2] β [H3] β ROOT β Sequential chain (top part) / \ \ \ [BR] [D5] [D6] [D7] β Additional chain data / \ [BH1] [BH2] β Balanced tree (bottom part) / \ / \ [D1] [D2] [D3] [D4] β Leaf data
- π½ Bottom part: Standard balanced tree for efficient membership proofs within a chain
- πΌ Top part: Sequential hash chain incorporating the balanced root and cross-chain data
- π― Benefits: Optimal gas usage by processing only what each chain needs
- π° Calldata Optimization: Chains should be ordered by calldata cost (cheapest first, most expensive last)
- β‘ Gas Efficiency: Expensive chains (like Ethereum) only need to verify a minimal preHash value
- π§© Witness Functionality: Attach arbitrary data to permits for enhanced verification and complex permission patterns
- π Flexible Allowance Management:
- β¬οΈ Increase/decrease allowances asynchronously
- β±οΈ Time-bound permissions with automatic expiration
- π Account locking for enhanced security
- β‘ Gas-Optimized Design:
- π’ Non-sequential nonces for concurrent operations
- ποΈ Bitmap-based nonce tracking for efficient gas usage
- π UnhingedProofs for efficient and secure cross-chain verification
- π‘οΈ Emergency Security Controls:
- π« Cross-chain revocation system
- π Account locking mechanism
- β³ Time-bound permissions
- π Full Permit2 Compatibility:
- π Implements all Permit2 interfaces
- π Drop-in replacement for existing integrations
Comprehensive documentation is available in the docs directory:
Section | Description | Quick Links |
---|---|---|
π Overview | Getting started with Permit3 | Introduction |
ποΈ Core Concepts | Understanding the fundamentals | Architecture Β· Witnesses Β· Cross-Chain Β· Merkle Trees Β· Nonces Β· Allowances |
π Guides | Step-by-step tutorials | Quick Start Β· ERC-7702 Β· Witness Integration Β· Cross-Chain Β· Signatures Β· Security |
π API Reference | Technical specifications | Full API Β· Data Structures Β· Interfaces Β· Events Β· Error Codes |
π» Examples | Code samples | ERC-7702 Β· Witness Β· Cross-Chain Β· Allowance Β· Security Β· Integration |
Permit3 implements IPermit for Permit2 transfer compatibility:
// Existing contracts using Permit2 can work without changes
IPermit2 permit2 = IPermit2(PERMIT3_ADDRESS);
permit2.transferFrom(msg.senΞ©zΞ©der, recipient, 1000e6, USDC);
// Standard approvals
function approve(address token, address spender, uint160 amount, uint48 expiration) external;
// Direct transfers
function transferFrom(address from, address to, uint160 amount, address token) external;
// Batched transfers
function transferFrom(AllowanceTransferDetails[] calldata transfers) external;
// Permission management
function allowance(address user, address token, address spender)
external view returns (uint160 amount, uint48 expiration, uint48 nonce);
function lockdown(TokenSpenderPair[] calldata approvals) external;
// Direct permit execution (no signatures required, caller is owner)
function permit(AllowanceOrTransfer[] memory permits) external;
// Single-chain permit operations with signatures
function permit(address owner, bytes32 salt, uint256 deadline, uint48 timestamp,
ChainPermits memory chain, bytes calldata signature) external;
// Cross-chain operations with UnhingedProofs and signatures
function permit(address owner, bytes32 salt, uint256 deadline, uint48 timestamp,
UnhingedPermitProof calldata proof, bytes calldata signature) external;
Direct Permit Usage:
// Execute permit operations directly (msg.sender becomes token owner)
AllowanceOrTransfer[] memory operations = [
AllowanceOrTransfer({
modeOrExpiration: 1735689600, // expiration timestamp
token: USDC_ADDRESS,
account: DEX_ADDRESS,
amountDelta: 1000e6 // 1000 USDC allowance
})
];
permit3.permit(operations); // No signature needed, no chainId needed!
The protocol centers around the AllowanceOrTransfer
structure:
struct AllowanceOrTransfer {
uint48 modeOrExpiration; // Operation mode/expiration
address token; // Token address
address account; // Approved spender/recipient
uint160 amountDelta; // Amount change/transfer amount
}
-
π€ Transfer Mode (
modeOrExpiration = 0
)- Executes immediate token transfer
account
is recipientamountDelta
is transfer amount
-
π Decrease Mode (
modeOrExpiration = 1
)- Reduces existing allowance
amountDelta
: regular decrease amount- Special:
type(uint160).max
resets to 0
-
π Lock Mode (
modeOrExpiration = 2
)- Enters special locked state
- Blocks increases/transfers
- Rejects all operations until unlocked
- Sets approval to 0 for that token/account pair
-
π Unlock Mode (
modeOrExpiration = 3
)- Cancels locked state
- Tracks unlock timestamp
- Sets allowance to provided amount
-
π Increase Mode (
modeOrExpiration > 3
)- Value acts as expiration timestamp
- Updates if timestamp is newer
amountDelta
: increase amount- Special cases:
0
: Updates expiration onlytype(uint160).max
: Unlimited approval
struct Allowance {
uint160 amount;
uint48 expiration;
uint48 timestamp;
}
- β° Timestamps order operations across chains
- π Most recent timestamp takes precedence in expiration updates
- π§ Prevents cross-chain race conditions
- π Critical for async allowance updates
Locked accounts have special restrictions:
- π« Cannot increase/decrease allowances
- π« Cannot execute transfers
- π Must submit unlock command with timestamp validation to disable
- π‘οΈ Provides emergency security control
// Access Permit2 compatibility
IPermit permit = IPermit(PERMIT3_ADDRESS);
permit.transferFrom(msg.senΞ©zΞ©der, recipient, 1000e6, USDC);
// Access Permit3 features
IPermit3 permit3 = IPermit3(PERMIT3_ADDRESS);
// 1. Increase Allowance
ChainPermits memory permitData = ChainPermits({
chainId: block.chainid,
permits: [AllowanceOrTransfer({
modeOrExpiration: block.timestamp + 1 days,
token: USDC,
account: DEX,
amountDelta: 1000e6
})]
});
// 2. Lock Account
permitData.permits.push(AllowanceOrTransfer({
modeOrExpiration: 2,
token: USDC,
account: address(0),
amountDelta: 0
}));
// 3. Execute Transfer
permitData.permits.push(AllowanceOrTransfer({
modeOrExpiration: 0,
token: USDC,
account: recipient,
amountDelta: 500e6
}));
// Create permits for each chain
const ethPermits = {
chainId: 1,
permits: [{
modeOrExpiration: futureTimestamp,
token: USDC_ETH,
account: DEX_ETH,
amountDelta: 1000e6
}]
};
const arbPermits = {
chainId: 42161,
permits: [{
modeOrExpiration: 1, // Decrease mode
token: USDC_ARB,
account: DEX_ARB,
amountDelta: 500e6
}]
};
// Generate subtree roots for each chain
const ethRoot = permit3.hashChainPermits(ethPermits);
const arbRoot = permit3.hashChainPermits(arbPermits);
// Create unhinged root and proof using UnhingedMerkleTree library
const unhingedRoot = UnhingedMerkleTree.hashLink(ethRoot, arbRoot);
const unhingedProof = UnhingedMerkleTree.createOptimizedProof(ethRoot, [], [arbRoot]);
// Note: Implementation uses calldata for optimal gas efficiency
// Create and sign with the unhinged root
const signature = signPermit3(owner, salt, deadline, timestamp, unhingedRoot);
-
π Allowance Management
- β±οΈ Set reasonable expiration times
- π Use lock mode for sensitive accounts
- π Monitor allowance changes across chains
-
β° Timestamp Validation
- π Validate operation ordering
- β³ Check for expired timestamps
- π Handle locked state properly
-
π Cross-Chain Security
- π Verify chain IDs match
- π’ Use unique nonces
- π Monitor pending operations
# Install
forge install
# Test
forge test
# Deploy
forge script script/DeployPermit3.s.sol:DeployPermit3 \
--rpc-url <RPC_URL> \
--private-key <KEY> \
--broadcast
MIT License - see LICENSE