ERC-6909
ERC-6909¶
Overview¶
ERC-6909 (Minimal Multi-Token Interface) is Ethereum's minimal multi-token interface standard, a simplified and optimized version of ERC-1155. This standard retains the core multi-token management functionality while removing redundant features from ERC-1155, providing higher Gas efficiency and a more flexible permission system.
ERC-6909 is particularly well-suited for DeFi protocols; for example, Uniswap V4 uses it to manage liquidity positions.
Design Motivation¶
Limitations of ERC-1155 Although ERC-1155 is a multi-token standard, it has some issues: 1. Mandatory callbacks: Every transfer must call the receiver's callback function 2. Batch operation coupling: Batch transfers and individual transfers are coupled together 3. Gas inefficiency: Unnecessary checks and callbacks increase Gas costs 4. Coarse permissions: Only global operator authorization, lacking fine-grained control
ERC-6909 Improvements - Removed mandatory callbacks - Optional batch operations - Hybrid permission system (allowance + operator) - Lower Gas costs - Retains core multi-token functionality
Core Interface¶
Transfer Functions
// Single token transfer
function transfer(
address receiver,
uint256 id,
uint256 amount
) external returns (bool);
// Transfer from a third party (requires authorization)
function transferFrom(
address sender,
address receiver,
uint256 id,
uint256 amount
) external returns (bool);
Query Functions
// Query balance
function balanceOf(address owner, uint256 id) external view returns (uint256);
// Query allowance for a specific token
function allowance(
address owner,
address spender,
uint256 id
) external view returns (uint256);
// Query operator authorization
function isOperator(
address owner,
address spender
) external view returns (bool);
Authorization Functions
// Authorize a specific amount of a specific token
function approve(
address spender,
uint256 id,
uint256 amount
) external returns (bool);
// Set operator (global authorization)
function setOperator(address spender, bool approved) external returns (bool);
Hybrid Permission System¶
Two-Layer Authorization Mechanism
Layer 1: Allowance (Fine-Grained) Similar to ERC-20 approve/allowance: - Authorizes a specific amount of a specific token - Precise permission control - Amount decreases after use
// Authorize 100 of token ID=5 to spender
token.approve(spender, 5, 100);
// Spender uses the authorization
token.transferFrom(owner, recipient, 5, 50);
// Remaining allowance: 50
Layer 2: Operator (Global Authorization) Similar to ERC-721/1155's setApprovalForAll: - Authorizes all operations for all tokens - Suitable for trusted contracts (such as marketplaces) - One-time authorization, unlimited use
// Authorize operator to manage all tokens
token.setOperator(marketplace, true);
// Operator can transfer any token
token.transferFrom(owner, buyer, anyId, anyAmount);
Permission Check Logic
function transferFrom(address sender, address receiver, uint256 id, uint256 amount) public {
if (msg.sender != sender) {
// Check if operator
if (!isOperator[sender][msg.sender]) {
// Check allowance
require(allowance[sender][msg.sender][id] >= amount, "Insufficient allowance");
allowance[sender][msg.sender][id] -= amount;
}
// Operator does not consume allowance
}
_transfer(sender, receiver, id, amount);
}
Comparison with ERC-1155¶
| Feature | ERC-1155 | ERC-6909 |
|---|---|---|
| Callback functions | Mandatory | None |
| Batch operations | Built-in | Optional extension |
| Permission system | Operator only | Allowance + operator |
| Gas efficiency | Lower | High |
| Data field | Must be provided with each call | None |
| Complexity | High | Low |
| Contract size | Larger | Small |
Gas Cost Comparison
Uniswap V4 Usage¶
Why Uniswap V4 Chose ERC-6909
Uniswap V4 introduces a Singleton architecture: - All liquidity pools are in one contract - User token balances exist as ERC-6909 form - Avoids transferring ERC-20 tokens with every trade
Workflow
-
Deposit
-
Add Liquidity
-
Swap
-
Withdraw
Gas Advantage Traditional approach (Uniswap V2/V3):
Uniswap V4 + ERC-6909:
Implementation Example¶
Basic Implementation
contract ERC6909 {
// State variables
mapping(address => mapping(uint256 => uint256)) public balanceOf;
mapping(address => mapping(address => mapping(uint256 => uint256))) public allowance;
mapping(address => mapping(address => bool)) public isOperator;
// Events
event Transfer(address caller, address indexed from, address indexed to, uint256 indexed id, uint256 amount);
event Approval(address indexed owner, address indexed spender, uint256 indexed id, uint256 amount);
event OperatorSet(address indexed owner, address indexed spender, bool approved);
// Transfer
function transfer(address receiver, uint256 id, uint256 amount) public returns (bool) {
balanceOf[msg.sender][id] -= amount;
balanceOf[receiver][id] += amount;
emit Transfer(msg.sender, msg.sender, receiver, id, amount);
return true;
}
// Authorized transfer
function transferFrom(address sender, address receiver, uint256 id, uint256 amount) public returns (bool) {
if (msg.sender != sender && !isOperator[sender][msg.sender]) {
uint256 allowed = allowance[sender][msg.sender][id];
if (allowed != type(uint256).max) {
allowance[sender][msg.sender][id] = allowed - amount;
}
}
balanceOf[sender][id] -= amount;
balanceOf[receiver][id] += amount;
emit Transfer(msg.sender, sender, receiver, id, amount);
return true;
}
// Approve
function approve(address spender, uint256 id, uint256 amount) public returns (bool) {
allowance[msg.sender][spender][id] = amount;
emit Approval(msg.sender, spender, id, amount);
return true;
}
// Set operator
function setOperator(address spender, bool approved) public returns (bool) {
isOperator[msg.sender][spender] = approved;
emit OperatorSet(msg.sender, spender, approved);
return true;
}
}
Extension: Batch Operations
contract ERC6909Extensions is ERC6909 {
function batchTransfer(
address receiver,
uint256[] calldata ids,
uint256[] calldata amounts
) public {
require(ids.length == amounts.length, "Length mismatch");
for (uint256 i = 0; i < ids.length; i++) {
transfer(receiver, ids[i], amounts[i]);
}
}
function batchTransferFrom(
address sender,
address receiver,
uint256[] calldata ids,
uint256[] calldata amounts
) public {
require(ids.length == amounts.length, "Length mismatch");
for (uint256 i = 0; i < ids.length; i++) {
transferFrom(sender, receiver, ids[i], amounts[i]);
}
}
}
Use Cases¶
*DeFi* Protocols** - AMMs (such as Uniswap V4) - Lending protocol position tokens - Options and derivatives - Synthetic assets
Gaming - Multiple game items - Resources and currencies - Characters and NFTs
Membership Systems - Different tier membership tokens - Points and rewards - Access permission tokens
Advantages Summary¶
*Gas* Efficiency** - No mandatory callbacks: Saves ~20,000 gas - Simplified logic: Fewer computations - Optimized storage layout
Flexibility - Hybrid permission system: Precise control - Optional batch operations: Implement as needed - Extensible: Freely add functionality
Developer Friendly - Simple interface: Easy to understand - Minimal design: Fewer errors - Modular: Easy to compose
Adoption Status¶
*Uniswap* V4** The first protocol to adopt ERC-6909 at scale: - Manages internal balances - Represents liquidity positions - Optimizes Gas costs
Other Potential Adopters - Next-generation AMMs - Lending protocols - Derivatives platforms - Gaming and metaverse
Future Outlook¶
Ecosystem Development - More protocol adoption - Tools and library support - Wallet integration
Standard Evolution - Possible extension standards - Combination with other ERCs - Cross-chain bridge support
Competitive Landscape - ERC-1155 still has its advantages (callbacks, batching) - ERC-6909 is better suited for DeFi - Both will coexist long-term
Related Links¶
- ERC-6909 Official Specification
- ERC-6909 Explained (RareSkills)
- Uniswap V4 Documentation
- Ethereum Developer Forum Discussion