Skip to content

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

ERC-1155 transfer: ~50,000 gas
ERC-6909 transfer: ~30,000 gas

Savings: ~40%

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

  1. Deposit

    // User deposits USDC into PoolManager
    poolManager.deposit(USDC, 1000e6);
    
    // PoolManager mints ERC-6909 tokens to the user
    // ID: Address of USDC
    // Amount: 1000e6
    

  2. Add Liquidity

    // Use ERC-6909 balance to add liquidity
    // No actual ERC-20 token transfer needed
    poolManager.addLiquidity(poolKey, liquidity);
    
    // Mints LP tokens (also ERC-6909)
    // ID: Unique identifier for the pool
    

  3. Swap

    // Use internal ERC-6909 balance for swapping
    // Ultimate Gas optimization
    poolManager.swap(poolKey, swapParams);
    

  4. Withdraw

    // Burn ERC-6909 tokens
    // Transfer actual ERC-20 tokens to the user
    poolManager.withdraw(USDC, 1000e6);
    

Gas Advantage Traditional approach (Uniswap V2/V3):

Per trade:
1. User -> Pool (ERC-20 transfer)
2. Pool -> User (ERC-20 transfer)
Gas: ~100,000+

Uniswap V4 + ERC-6909:

After initial deposit:
1. Internal balance update (storage write)
Gas: ~30,000

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