Skip to main content

Command Palette

Search for a command to run...

ERC 6909 - Minimal Multi-Token Interface

Update to ERC1155

Updated
5 min read
ERC 6909 - Minimal Multi-Token Interface
J

Hi, I'm Jay Nalam, a seasoned Web3 Engineer committed to advancing decentralized technologies. Specializing in EVM-based blockchains, smart contracts, and web3 protocols, I've developed NFTs, DeFi protocols, and more, pushing boundaries in the crypto realm.

Hello Everyone..

Welcome to another day of exploring Web3 Engineering. With blockchain taking new steps towards the decentralized finance (DeFi) and blooming with a new token standard or extension everyday, today we have come across a new one called Minimal Multi-token standard interface. With Uniswap V4 using it in their new contracts, the significance of understanding it has increased. So, without any further ado, let’s get started.

What is ERC 6909 ?

ERC 6909, in simple terms, is an improvement to the already popular ERC1155: Multi-token standard. By decreasing the overhead of ERC1155 implementation and some modifications to the features, the applications of using Multi-token standard have increased. First let us recall the original ERC1155 token standard and then let us compare the differences between.

ERC1155: Multi-token standard

Here we have the interface for the ERC1155 token standard.

interface ERC1155 /* is ERC165 */ {
    event TransferSingle(address indexed _operator, address indexed _from, address indexed _to, uint256 _id, uint256 _value);
    event TransferBatch(address indexed _operator, address indexed _from, address indexed _to, uint256[] _ids, uint256[] _values);
    event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);
    event URI(string _value, uint256 indexed _id);
    function safeTransferFrom(address _from, address _to, uint256 _id, uint256 _value, bytes calldata _data) external;
    function safeBatchTransferFrom(address _from, address _to, uint256[] calldata _ids, uint256[] calldata _values, bytes calldata _data) external;
    function balanceOf(address _owner, uint256 _id) external view returns (uint256);
    function balanceOfBatch(address[] calldata _owners, uint256[] calldata _ids) external view returns (uint256[] memory);
    function setApprovalForAll(address _operator, bool _approved) external;
    function isApprovedForAll(address _owner, address _operator) external view returns (bool);
}

Here is the interface for the ERC1155Receiver contract.

interface ERC1155TokenReceiver {
    function onERC1155Received(address _operator, address _from, uint256 _id, uint256 _value, bytes calldata _data) external returns(bytes4);
    function onERC1155BatchReceived(address _operator, address _from, uint256[] calldata _ids, uint256[] calldata _values, bytes calldata _data) external returns(bytes4);       
}

Functionalities

ERC1155

  • safeTransferFrom - To transfer tokens from one user to another user of the given token id.

    • If the function is called by a user to transfer owned tokens, enough balance is expected

    • If the function is called by a third party account to transfer our tokens, the operator must be approved prior to the call.

    • If the receiver of the tokens is a contract, onERC1155Received will be called on the receiver contract

  • safeBatchTransfer - same as a safeTokenTransfer, but can transfer multiple tokens in a single transaction.

    • The caller needs to specify the array of token ids to transfer

    • Same as safeTransferFrom, if the function is called by an operator, the prior approval is necessary.

    • If the receiver of the tokens is a contract, onERC1155BatchReceived will be called on the receiver contract

  • balanceOf - to check the balance of the given user for the given token id.

  • balanceOfBatch - to check the balance of users for the array of given token Ids.

  • setApprovalForAll - to grant operator approval for a given user. It sets approval for unlimited balance of the user on all the token Ids.

ERC1155Receiver

  • onERC1155Received - A custom hook function that must be implemented by the receiver contract that gets triggered after transferring the tokens using safeTransfer on ERC1155 and it should return bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)")) signaling the successful transfer of the tokens.

  • onERC1155BatchReceived - A custom hook function that must be implemented by the receiver contract that gets triggered after transferring the tokens using safeBatchTransfer on ERC1155 and it should return bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)")) signaling the successful batch transfer of the tokens.

Issues with ERC 1155

  • If we look closely, there is no standard transfer and transferFrom functionalities in the standard. and there are only safe transfer functions. Even though they are safe, they add an unnecessary overhead on the token transfer increasing the gas fees.

  • There is only one global operator approval for all the tokens of a given user which is limiting in some situations where a user needs to specify either multiple operators for a given token or a separate operator for each token Id.

ERC6909 addresses these issues by removing the transfer checks (splitting them to extensions) on the transfer and introducing multiple operators for a given user. Have a look at the ERC6909 interface below.

interface ERC6909 {
    error InsufficientBalance(address owner, uint256 id);
    error InsufficientPermission(address spender, uint256 id);

    event Transfer(address caller, address indexed sender, address indexed receiver, uint256 indexed id, uint256 amount);
    event OperatorSet(address indexed owner, address indexed spender, bool approved);
    event Approval(address indexed owner, address indexed spender, uint256 indexed id, uint256 amount);

    mapping(address owner => mapping(uint256 id => uint256 amount)) public balanceOf;
    mapping(address owner => mapping(address spender => mapping(uint256 id => uint256 amount))) public allowance;
    mapping(address owner => mapping(address spender => bool)) public isOperator;

    function transfer(address receiver, uint256 id, uint256 amount) public returns (bool);
    function transferFrom(address sender, address receiver, uint256 id, uint256 amount) public returns (bool);
    function approve(address spender, uint256 id, uint256 amount) public returns (bool);
    function setOperator(address spender, bool approved) public returns (bool);
    function supportsInterface(bytes4 interfaceId) public pure returns (bool supported);

    // ============= internal ===================
    function _mint(address receiver, uint256 id, uint256 amount) internal;
    function _burn(address sender, uint256 id, uint256 amount) internal;
}

The ERC6909 introduces a new role called Operators that are specific to a user. Each user can set multiple operators for their tokens and the operators have approval to the unlimited balance of the user on all of the token Ids.

Functionalities

  • approve - To grant approval for a spender for a given amount of tokens of give token Id.

  • transfer - To transfer given amount of tokens of the given token id to the receiver. Should be called to transfer owned tokens.

  • transferFrom - To transfer given amount of tokens of the given token id from the sender to the receiver. Should be called to transfer third party (sender) tokens and also a prior approval is needed on the amount of the token Id tokens. If it is called by an operator, no approval call is needed, since they already have approval to all of the balance of them.

  • setOperator - To set or reset an operator for a the caller. Once the operator role is granted, operator would have access to all of their funds.

With these additions, ERC6909 have overcome the inefficiencies that ERC1155 is lacking. And with these new features, ERC6909 plays a crucial role in the Uniswap V4, which we will explore in our upcoming blogs.

That’s all for the day. Feel free to comment down your questions.