Skip to content

eco/permit3

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ” Permit3: One-Click Cross-Chain Token Permissions

License: MIT

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 Unbalanced Merkle Trees and non-sequential nonces, while maintaining Permit2 compatibility.

"Permit3 unlocks a one-click/signature cross-chain future."

✨ Key Features

  • πŸŒ‰ 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
  • 🌲 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
  • 🧩 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
    • πŸ” 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

πŸ“š Documentation

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

πŸ”„ Permit2 Compatibility

Permit3 implements IPermit for Permit2 transfer compatibility:

// Existing contracts using Permit2 can work without changes
IPermit2 permit2 = IPermit2(PERMIT3_ADDRESS);
permit2.transferFrom(msg.sender, recipient, 1000e6, USDC);

βœ… Supported Permit2 Functions

// 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;

πŸ”— Enhanced Functions (Permit3 Exclusive)

// 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, uint48 deadline, uint48 timestamp, 
                AllowanceOrTransfer[] calldata permits, bytes calldata signature) external;

// Cross-chain operations with Merkle proofs and signatures
function permit(address owner, bytes32 salt, uint48 deadline, uint48 timestamp,
                ChainPermits calldata permits, bytes32[] 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!

πŸ’‘ Core Concepts

πŸ”„ Allowance Operations

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
}

βš™οΈ Operation Modes

  1. πŸ“€ Transfer Mode (modeOrExpiration = 0)

    • Executes immediate token transfer
    • account is recipient
    • amountDelta is transfer amount
  2. πŸ“‰ Decrease Mode (modeOrExpiration = 1)

    • Reduces existing allowance
    • amountDelta: regular decrease amount
    • Special: type(uint160).max resets to 0
  3. πŸ”’ 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
  4. πŸ”“ Unlock Mode (modeOrExpiration = 3)

    • Cancels locked state
    • Tracks unlock timestamp
    • Sets allowance to provided amount
  5. πŸ“ˆ Increase Mode (modeOrExpiration > 3)

    • Value acts as expiration timestamp
    • Updates if timestamp is newer
    • amountDelta: increase amount
    • Special cases:
      • 0: Updates expiration only
      • type(uint160).max: Unlimited approval

⏱️ Timestamp Management

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

πŸ” Account Locking

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

πŸ”Œ Integration

πŸ› οΈ Basic Setup

// Access Permit2 compatibility
IPermit permit = IPermit(PERMIT3_ADDRESS);
permit.transferFrom(msg.sender, recipient, 1000e6, USDC);

// Access Permit3 features
IPermit3 permit3 = IPermit3(PERMIT3_ADDRESS);

πŸ“ Example Operations

// 1. Create permits array directly
AllowanceOrTransfer[] memory permits = new AllowanceOrTransfer[](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);

πŸŒ‰ Cross-Chain Usage with Merkle Proofs

// 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
    }]
};

// Hash each chain's permits to create leaf nodes
const ethLeaf = permit3.hashChainPermits(ethPermits);
const arbLeaf = permit3.hashChainPermits(arbPermits);

// Build merkle tree and get root (typically done off-chain)
const leaves = [ethLeaf, arbLeaf];
const merkleRoot = buildMerkleRoot(leaves);

// Generate merkle proof for specific chain
const arbProof = generateMerkleProof(leaves, 1); // Index 1 for Arbitrum
const proof = { nodes: arbProof };

// Create and sign with the unbalanced root
const signature = signPermit3(owner, salt, deadline, timestamp, merkleRoot);

πŸ›‘οΈ Security Guidelines

  1. πŸ”‘ Allowance Management

    • ⏱️ Set reasonable expiration times
    • πŸ”’ Use lock mode for sensitive accounts
    • πŸ“Š Monitor allowance changes across chains
  2. ⏰ Timestamp Validation

    • πŸ“‹ Validate operation ordering
    • ⏳ Check for expired timestamps
    • πŸ” Handle locked state properly
  3. 🌐 Cross-Chain Security

    • πŸ” Verify chain IDs match
    • πŸ”’ Use unique nonces
    • πŸ‘€ Monitor pending operations

πŸ‘¨β€πŸ’» Development

# Install
forge install

# Test
forge test

# Deploy
forge script script/DeployPermit3.s.sol:DeployPermit3 \
    --rpc-url <RPC_URL> \
    --private-key <KEY> \
    --broadcast

πŸ“„ License

MIT License - see LICENSE

About

πŸ” Permit3: One-Click Cross-Chain Token Permissions

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 4

  •  
  •  
  •  
  •