Permit3 is an approval system that enables cross-chain token approvals and transfers with a single signature. It unlocks a one-signature cross-chain future through Unbalanced Merkle Trees and non-sequential nonces, while maintaining Permit2 compatibility.
The contracts in this repository are deployed on-chain and custody user funds. A vulnerability that becomes public before it is fixed can be exploited immediately and irreversibly — public disclosure of an unpatched bug is itself the attack.
If you find a security vulnerability, report it privately. Do not open a public pull request, push a branch, or open a public issue. Report it through the Security tab → "Report a vulnerability", which opens a private advisory visible only to you and the maintainers. Permit3 is deployed at a deterministic address on all supported chains — treat all contracts as live unless a maintainer explicitly confirms otherwise.
See SECURITY.md for the full policy, including specific instructions
for AI coding agents. This applies to humans and automated tools alike.
Permit3 is deployed using ERC-2470 Singleton Factory for deterministic addresses across all chains:
- Deployment Address:
0xEc00030C0000245E27d1521Cc2EE88F071c2Ae34
This ensures the same contract address on all supported networks, enabling seamless cross-chain operations.
- Cross-Chain Operations: Authorize token operations across multiple blockchains with one signature
- Multi-Token Support: Unified interface for ERC20, ERC721 NFTs, and ERC1155 semi-fungible tokens
- Direct Permit Execution: Execute permit operations without signatures when caller has authority
- ERC-7702 Integration: Account Abstraction support for enhanced user experience
- Witness Functionality: Attach arbitrary data to permits for enhanced verification and complex permission patterns
- NFT & Semi-Fungible Token Features:
- Dual-allowance system (per-token and collection-wide)
- TokenId encoding for signed permits
- Batch operations for multiple token types
- ERC1155 gaming asset support
- 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
- Standard merkle proofs using OpenZeppelin's MerkleProof library
- Emergency Security Controls:
- Cross-chain revocation system
- Account locking mechanism
- Time-bound permissions
- Full Permit2 Compatibility:
- Implements basic transfer Permit2 interfaces
- Drop-in replacement for existing integrations
- Unbalanced Merkle Trees: Hybrid two-part structure for cross-chain proofs:
[H1] → [H2] → [H3] → ROOT ← Unbalanced upper structure / \ \ \ [BR] [D5] [D6] [D7] ← Additional chain data / \ [BH1] [BH2] ← Balanced tree (bottom part) / \ / \ [D1] [D2] [D3] [D4] ← Leaf data- Unbalanced Design: Combines balanced subtrees with unbalanced upper structure for efficiency
- Bottom Part: Efficient membership proofs with O(log n) complexity
- Top Part: Unbalanced structure minimizes proof size for expensive chains
- Gas Optimization: Chain ordering (cheapest chains first, expensive last)
- "Unbalanced": Deliberate deviation from balanced trees at top level
- Security: Uses merkle tree verification for compatibility
Comprehensive documentation is available in the docs directory:
| Section | Description | Quick Links |
|---|---|---|
| Overview | Getting started with Permit3 | Introduction |
| Core Concepts | Understanding the fundamentals | Architecture · Multi-Token · Witnesses · Cross-Chain · Merkle Trees · Nonces · Allowances · Permit2 Compatibility |
| Guides | Step-by-step tutorials | Quick Start · Multi-Token · NFT Permits · ERC-7702 · Witness · Cross-Chain · Signatures · Security |
| API Reference | Technical specifications | Full API · Data Structures · Interfaces · Events · Error Codes |
| Examples | Code samples | Multi-Token · ERC-7702 · Witness · Cross-Chain · Allowance · Security · Integration |
The protocol centers around the AllowanceOrTransfer structure:
struct AllowanceOrTransfer {
uint48 modeOrExpiration; // Operation mode/expirationaddress token; // Token addressaddress account; // Approved spender/recipientuint160 amountDelta; // Amount change/transfer amount
}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.sender, recipient, 1000e6, USDC);
// Access Permit3 features
IPermit3 permit3 =IPermit3(PERMIT3_ADDRESS);For direct transfers without signatures, use specialized functions:
// NFT transfer
permit3.transferFromERC721(from, to, nftContract, tokenId);
// ERC1155 transfer
permit3.transferFromERC1155(from, to, erc1155Contract, tokenId, amount);
// Batch mixed tokens
TokenTypeTransfer[] memory transfers = [...];
permit3.batchTransferMultiToken(transfers);// 1. Create permits array directly
AllowanceOrTransfer[] memory permits =newAllowanceOrTransfer[](3);
// 2. Increase Allowance
permits[0] =AllowanceOrTransfer({
modeOrExpiration: uint48(block.timestamp+1 days),
token: USDC,
account: DEX,
amountDelta: 1000e6
});
// 3. Lock Account
permits[1] =AllowanceOrTransfer({
modeOrExpiration: 2,
token: USDC,
account: address(0),
amountDelta: 0
});
// 4. Execute Transfer
permits[2] =AllowanceOrTransfer({
modeOrExpiration: 0,
token: USDC,
account: recipient,
amountDelta: 500e6
});
// Execute the permits
permit3.permit(owner, salt, deadline, timestamp, permits, signature);// Create permits for each chainconstethPermits={chainId: 1,permits: [{modeOrExpiration: futureTimestamp,token: USDC_ETH,account: DEX_ETH,amountDelta: 1000e6}]};constarbPermits={chainId: 42161,permits: [{modeOrExpiration: 1,// Decrease modetoken: USDC_ARB,account: DEX_ARB,amountDelta: 500e6}]};// Hash each chain's permits to create leaf nodesconstethLeaf=permit3.hashChainPermits(ethPermits);constarbLeaf=permit3.hashChainPermits(arbPermits);// Build merkle tree and get root (typically done off-chain)constleaves=[ethLeaf,arbLeaf];constmerkleRoot=buildMerkleRoot(leaves);// Generate merkle proof for specific chainconstarbProof=generateMerkleProof(leaves,1);// Index 1 for Arbitrumconstproof={nodes: arbProof};// Create and sign with the unbalanced rootconstsignature=signPermit3(owner,salt,deadline,timestamp,merkleRoot);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> \
--broadcastSee Audits
MIT License - see LICENSE
