ERC-6909

ERC-6909 is a multi-token standard for managing multiple tokens by their ID within a single contract, allowing each user to hold balances of different tokens efficiently without deploying separate contracts for each token type.

ERC-6909 natively supports multi-token actions using its built-in mint and burn functions, while ERC-20 requires distinct approve and transfer calls for token interactions.

Additionally, ERC-6909 offers a simplified alternative to the ERC-1155 token standard. While ERC-1155 introduced a multi-token interface capable of managing both fungible and non-fungible assets (such as ERC-20 and ERC-721) within a single smart contract, ERC-6909 streamlines this approach for greater efficiency by overcoming the limitations of ERC-1155 by removing contract-level callbacks and batch transfers, and by replacing the single-operator permission model with a hybrid allowance–operator system that enables more fine-grained token management.

The OpenZeppelin Stylus Contracts provides a complete implementation of the ERC-6909 standard. On the API reference you’ll find detailed information on their properties and usage.

Constructing an ERC-6909 Token Contract

We can easily create our own ERC-6909 token contract, which can be used as a VaultManager that brings all token operations into one interface, removing the need for multiple approvals or tracking allowances across contracts. This lets you focus on your application’s core logic while it handles token management.

Here’s what our VaultManager token might look like.

use openzeppelin_stylus::{
    token::erc6909::{self, Erc6909, IErc6909},
    utils::introspection::erc165::IErc165,
};

use stylus_sdk::{
    alloy_primitives::{aliases::B32, Address, U256},
    prelude::*,
};

#[entrypoint]
#[storage]
struct VaultManager {
    erc6909: Erc6909,
}

#[public]
#[implements(IErc6909<Error = erc6909::Error>, IErc165)]
impl VaultManager {
    fn mint(
        &mut self,
        to: Address,
        id: U256,
        amount: U256,
    ) -> Result<(), erc6909::Error> {
        self.erc6909._mint(to, id, amount)
    }

    fn burn(
        &mut self,
        from: Address,
        id: U256,
        amount: U256,
    ) -> Result<(), erc6909::Error> {
        self.erc6909._burn(from, id, amount)
    }
}

#[public]
impl IErc6909 for VaultManager {
    type Error = erc6909::Error;

    fn balance_of(&self, owner: Address, id: U256) -> U256 {
        self.erc6909.balance_of(owner, id)
    }

    fn allowance(&self, owner: Address, spender: Address, id: U256) -> U256 {
        self.erc6909.allowance(owner, spender, id)
    }

    fn is_operator(&self, owner: Address, spender: Address) -> bool {
        self.erc6909.is_operator(owner, spender)
    }

    fn approve(
        &mut self,
        spender: Address,
        id: U256,
        amount: U256,
    ) -> Result<bool, Self::Error> {
        self.erc6909.approve(spender, id, amount)
    }

    fn set_operator(
        &mut self,
        spender: Address,
        approved: bool,
    ) -> Result<bool, Self::Error> {
        self.erc6909.set_operator(spender, approved)
    }

    fn transfer(
        &mut self,
        receiver: Address,
        id: U256,
        amount: U256,
    ) -> Result<bool, Self::Error> {
        self.erc6909.transfer(receiver, id, amount)
    }

    fn transfer_from(
        &mut self,
        sender: Address,
        receiver: Address,
        id: U256,
        amount: U256,
    ) -> Result<bool, Self::Error> {
        self.erc6909.transfer_from(sender, receiver, id, amount)
    }
}

#[public]
impl IErc165 for VaultManager {
    fn supports_interface(&self, interface_id: B32) -> bool {
        self.erc6909.supports_interface(interface_id)
    }
}

This set of interfaces and contracts are all related to ERC-6909 Minimal Multi-Token Interface.

Extensions

Additionally, there is one custom extension:

  • ERC-6909 Supply: Extension of the ERC-6909 standard that adds total supply functionality for each token id.