This document explains Permit3's comprehensive support for multiple token standards including ERC20, ERC721 (NFTs), and ERC1155 (semi-fungible tokens).
Permit3's MultiToken functionality extends the protocol beyond ERC20 tokens to provide unified permission management for:
- ERC721 NFTs: Unique, non-fungible tokens with individual token IDs
- ERC1155 Semi-Fungible Tokens: Tokens that combine fungible and non-fungible properties
- ERC20 Tokens: Standard fungible tokens (already supported in base Permit3)
This unified approach enables developers to manage permissions for any token type through a single interface, dramatically simplifying multi-token applications.
- Characteristics: Divisible, interchangeable tokens
- Use Case: Cryptocurrencies, governance tokens, utility tokens
- Permit3 Handling: Standard allowance and transfer mechanisms
- Characteristics: Unique, indivisible tokens with individual IDs
- Use Case: Digital art, collectibles, gaming assets, real estate
- Permit3 Handling: Per-token and collection-wide allowances
- Characteristics: Hybrid tokens supporting both fungible and non-fungible properties
- Use Case: Gaming items (e.g., 100 swords of type #5), editions, batch minting
- Permit3 Handling: Combined tokenId and amount management
enum TokenStandard {
ERC20, // Standard fungible tokens
ERC721, // Non-fungible tokens (NFTs)
ERC1155 // Semi-fungible tokens
}A key innovation in MultiTokenPermit is the dual-allowance system that provides maximum flexibility for NFT and semi-fungible token permissions.
The system checks two levels of allowances in order:
- Per-Token Allowance: Specific permission for an individual token ID
- Collection-Wide Allowance: Blanket permission for all tokens in a collection
// Example flow for ERC721 transfer
1. Check if spender has allowance for specific tokenId #42
2. If not, check if spender has collection-wide allowance for the NFT contract
3. If either check passes, allow the transfer- Granular Control: Approve specific NFTs while keeping others restricted
- Bulk Operations: Approve entire collections with a single signature
- Gas Efficiency: Collection-wide approvals eliminate per-token transactions
- Backwards Compatible: Works with existing NFT marketplaces and protocols
// Approve a specific NFT (token ID 42)
permit3.approve(nftContract, spender, 42, 1, expiration);
// Approve entire NFT collection (use 4-parameter approve)
permit3.approve(nftContract, spender, type(uint160).max, expiration);
// Approve specific ERC1155 token with amount
permit3.approve(erc1155Contract, spender, tokenId, amount, expiration);To efficiently store and manage permissions for millions of potential token IDs, Permit3 uses a deterministic encoding scheme.
// Convert token + tokenId into a unique address identifier
address encodedId = address(uint160(uint256(
keccak256(abi.encodePacked(token, tokenId))
)));- Storage Efficiency: Reuses existing allowance mapping structure
- Deterministic: Same token+ID always produces same encoded address
- Collision Resistant: Keccak256 ensures virtually no collisions
- Gas Optimized: Single storage slot per token ID permission
The encoding process:
- Concatenates the token contract address with the token ID
- Hashes the result using Keccak256 (256-bit output)
- Truncates to 160 bits (Ethereum address size)
- Casts to address type for storage in allowance mappings
This creates a unique "virtual address" for each token ID that can be used in the standard allowance storage system:
mapping(address owner =>
mapping(address tokenOrEncodedId =>
mapping(address spender => Allowance))) allowances;MultiTokenPermit supports efficient batch operations for handling multiple tokens in a single transaction.
ERC721Transfer[] memory transfers = new ERC721Transfer[](3);
transfers[0] = ERC721Transfer(owner, recipient, tokenId1, nftContract);
transfers[1] = ERC721Transfer(owner, recipient, tokenId2, nftContract);
transfers[2] = ERC721Transfer(owner, recipient, tokenId3, nftContract);
permit3.batchTransferERC721(transfers);uint256[] memory tokenIds = [1, 2, 3];
uint256[] memory amounts = [100, 50, 25];
ERC1155BatchTransfer memory batchTransfer =
ERC1155BatchTransfer(
owner,
recipient,
tokenIds,
amounts,
erc1155Contract
);
permit3.batchTransferERC1155(batchTransfer);TokenTypeTransfer[] memory mixedTransfers = new TokenTypeTransfer[](3);
// Add an ERC20 transfer
mixedTransfers[0] = TokenTypeTransfer(
TokenStandard.ERC20,
TokenTransfer(owner, recipient, usdcContract, 0, amount)
);
// Add an ERC721 transfer
mixedTransfers[1] = TokenTypeTransfer(
TokenStandard.ERC721,
TokenTransfer(owner, recipient, nftContract, tokenId, 1)
);
// Add an ERC1155 transfer
mixedTransfers[2] = TokenTypeTransfer(
TokenStandard.ERC1155,
TokenTransfer(owner, recipient, sftContract, tokenId, amount)
);
permit3.batchTransferMultiToken(mixedTransfers);- Single Transaction: All transfers in one TX saves base gas costs
- Shared Validation: Signature verification happens once
- Batched Events: Reduced event emission overhead
- Optimized Loops: Internal optimizations for batch processing
- List multiple NFTs with one signature
- Bulk purchases across collections
- Collection offers and floor sweeping
- Royalty distribution across multiple NFTs
- Transfer game assets (ERC1155 items)
- Batch crafting operations
- Guild treasury management
- Cross-game asset bridges
- NFT collateralized lending
- Fractionalized NFT pools
- NFT staking and farming
- Liquidity provision with NFT/token pairs
- Batch transfers for wallet migration
- Multi-asset rebalancing
- Estate planning and inheritance
- DAO treasury operations
- Unified permissions across chains
- Batch bridging operations
- Collection migrations
- Multi-chain marketplace listings
// Unified transfer structure for any token type
struct TokenTransfer {
address from; // Token owner
address to; // Recipient
address token; // Token contract
uint256 tokenId; // Token ID for NFT/ERC1155
uint160 amount; // Amount (1 for ERC721)
}
// ERC721-specific transfer
struct ERC721Transfer {
address from;
address to;
uint256 tokenId;
address token;
}
// ERC1155 batch transfer
struct ERC1155BatchTransfer {
address from;
address to;
uint256[] tokenIds;
uint256[] amounts;
address token;
}- Safe Transfer Methods: Always uses
safeTransferFromfor NFTs - Reentrancy Protection: Allowance updates before external calls
- Overflow Protection: Amount validation for ERC1155
- Permission Verification: Dual-check system prevents unauthorized transfers
To integrate multi-token support in your application:
- Deploy or connect to Permit3 with MultiTokenPermit
- Implement token type detection in your UI
- Use appropriate approval methods based on token standard
- Leverage batch operations for gas efficiency
For detailed implementation guidance, see the Multi-Token Integration Guide.
| ⬅️ Previous | 🏠 Section | ➡️ Next |
|---|---|---|
| Allowance System | Concepts | Architecture |