This standard defines an interface for ERC-721 or ERC-1155 tokens, known as “bindables”, to “bind” to ERC-721 NFTs.
When bindable tokens “bind” to an NFT, even though their ownership is transferred to the NFT, the NFT owner may “unbind” the tokens and claim their ownership. This enables bindable tokens to transfer with their bound NFTs without extra cost, offering a more effective way to create and transfer N:1 token-to-NFT bundles. Until an NFT owner decides to unbind them, bound tokens stay locked and resume their base token functionalities after unbinding.
This standard supports various use-cases such as:
NFT-bundled physical assets like microchipped streetwear, digitized car collections, and digitally twinned real estate.
NFT-bundled digital assets such as accessorizable virtual wardrobes, composable music tracks, and customizable metaverse land.
Motivation
A standard interface for NFT binding offers a seamless and efficient way to bundle and transfer tokens with NFTs, ensuring compatibility with wallets, marketplaces, and other NFT applications. It eliminates the need for rigid, implementation-specific strategies for token ownership.
In contrast with other standards that deal with token ownership at the account level, this standard aims to address token ownership at the NFT level. Its objective is to build a universal interface for token bundling, compatible with existing ERC-721 and ERC-1155 standards.
Specification
The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119.
ERC-721 Bindable
Smart contracts implementing the ERC-721 bindable standard MUST implement the IERC721Bindable interface.
Implementers of the IER721Bindable interface MUST return true if 0x82a34a7d is passed as the identifier to the supportsInterface function.
/// @title ERC-721 Bindable Token Standard
/// @dev See https://eips.ethereum.org/ERCS/eip-5700
/// Note: the ERC-165 identifier for this interface is 0x82a34a7d.
interfaceIERC721Bindable/* is IERC721 */{/// @notice This event emits when an unbound token is bound to an NFT.
/// @param operator The address approved to perform the binding.
/// @param from The address of the unbound token owner.
/// @param bindAddress The contract address of the NFT being bound to.
/// @param bindId The identifier of the NFT being bound to.
/// @param tokenId The identifier of binding token.
eventBind(addressindexedoperator,addressindexedfrom,addressindexedbindAddress,uint256bindId,uint256tokenId);/// @notice This event emits when an NFT-bound token is unbound.
/// @param operator The address approved to perform the unbinding.
/// @param from The owner of the NFT the token is bound to.
/// @param to The address of the new unbound token owner.
/// @param bindAddress The contract address of the NFT being unbound from.
/// @param bindId The identifier of the NFT being unbound from.
/// @param tokenId The identifier of the unbinding token.
eventUnbind(addressindexedoperator,addressindexedfrom,addressto,addressindexedbindAddress,uint256bindId,uint256tokenId);/// @notice Binds token `tokenId` to NFT `bindId` at address `bindAddress`.
/// @dev The function MUST throw unless `msg.sender` is the current owner,
/// an authorized operator, or the approved address for the token. It also
/// MUST throw if the token is already bound or if `from` is not the token
/// owner. Finally, it MUST throw if the NFT contract does not support the
/// ERC-721 interface or if the NFT being bound to does not exist. Before
/// binding, token ownership MUST be transferred to the contract address of
/// the NFT. On bind completion, the function MUST emit `Transfer` & `Bind`
/// events to reflect the implicit token transfer and subsequent bind.
/// @param from The address of the unbound token owner.
/// @param bindAddress The contract address of the NFT being bound to.
/// @param bindId The identifier of the NFT being bound to.
/// @param tokenId The identifier of the binding token.
functionbind(addressfrom,addressbindAddress,uint256bindId,uint256tokenId)external;/// @notice Unbinds token `tokenId` from NFT `bindId` at address `bindAddress`.
/// @dev The function MUST throw unless `msg.sender` is the current owner,
/// an authorized operator, or the approved address for the NFT the token
/// is bound to. It also MUST throw if the token is unbound, if `from` is
/// not the owner of the bound NFT, or if `to` is the zero address. After
/// unbinding, token ownership MUST be transferred to `to`, during which
/// the function MUST check if `to` is a valid contract (code size > 0),
/// and if so, call `onERC721Received`, throwing if the wrong identifier is
/// returned. On unbind completion, the function MUST emit `Unbind` &
/// `Transfer` events to reflect the unbind and subsequent transfer.
/// @param from The address of the owner of the NFT the token is bound to.
/// @param to The address of the unbound token new owner.
/// @param bindAddress The contract address of the NFT being unbound from.
/// @param bindId The identifier of the NFT being unbound from.
/// @param tokenId The identifier of the unbinding token.
functionunbind(addressfrom,addressto,addressbindAddress,uint256bindId,uint256tokenId)external;/// @notice Gets the NFT address and identifier token `tokenId` is bound to.
/// @dev When the token is unbound, this function MUST return the zero
/// address for the address portion to indicate no binding exists.
/// @param tokenId The identifier of the token being queried.
/// @return The token-bound NFT contract address and numerical identifier.
functionbinderOf(uint256tokenId)externalviewreturns(address,uint256);/// @notice Gets total tokens bound to NFT `bindId` at address `bindAddress`.
/// @param bindAddress The contract address of the NFT being queried.
/// @param bindId The identifier of the NFT being queried.
/// @return The total number of tokens bound to the queried NFT.
functionboundBalanceOf(addressbindAddress,uint256bindId)externalviewreturns(uint256);
ERC-1155 Bindable
Smart contracts implementing the ERC-1155 Bindable standard MUST implement the IERC1155Bindable interface.
Implementers of the IER1155Bindable interface MUST return true if 0xd0d55c6 is passed as the identifier to the supportsInterface function.
/// @title ERC-1155 Bindable Token Standard
/// @dev See https://eips.ethereum.org/ERCS/eip-5700
/// Note: the ERC-165 identifier for this interface is 0xd0d555c6.
interfaceIERC1155Bindable/* is IERC1155 */{/// @notice This event emits when token(s) are bound to an NFT.
/// @param operator The address approved to perform the binding.
/// @param from The owner address of the unbound tokens.
/// @param bindAddress The contract address of the NFT being bound to.
/// @param bindId The identifier of the NFT being bound to.
/// @param tokenId The identifier of the binding token type.
/// @param amount The number of tokens binding to the NFT.
eventBind(addressindexedoperator,addressindexedfrom,addressindexedbindAddress,uint256bindId,uint256tokenId,uint256amount);/// @notice This event emits when token(s) of different types are bound to an NFT.
/// @param operator The address approved to perform the batch binding.
/// @param from The owner address of the unbound tokens.
/// @param bindAddress The contract address of the NFTs being bound to.
/// @param bindId The identifier of the NFT being bound to.
/// @param tokenIds The identifiers of the binding token types.
/// @param amounts The number of tokens per type binding to the NFTs.
eventBindBatch(addressindexedoperator,addressindexedfrom,addressindexedbindAddress,uint256bindId,uint256[]tokenIds,uint256[]amounts);/// @notice This event emits when token(s) are unbound from an NFT.
/// @param operator The address approved to perform the unbinding.
/// @param from The owner address of the NFT the tokens are bound to.
/// @param to The address of the unbound tokens' new owner.
/// @param bindAddress The contract address of the NFT being unbound from.
/// @param bindId The identifier of the NFT being unbound from.
/// @param tokenId The identifier of the unbinding token type.
/// @param amount The number of tokens unbinding from the NFT.
eventUnbind(addressindexedoperator,addressindexedfrom,addressto,addressindexedbindAddress,uint256bindId,uint256tokenId,uint256amount);/// @notice This event emits when token(s) of different types are unbound from an NFT.
/// @param operator The address approved to perform the batch binding.
/// @param from The owner address of the unbound tokens.
/// @param to The address of the unbound tokens' new owner.
/// @param bindAddress The contract address of the NFTs being unbound from.
/// @param bindId The identifier of the NFT being unbound from.
/// @param tokenIds The identifiers of the unbinding token types.
/// @param amounts The number of tokens per type unbinding from the NFTs.
eventUnbindBatch(addressindexedoperator,addressindexedfrom,addressto,addressindexedbindAddress,uint256bindId,uint256[]tokenIds,uint256[]amounts);/// @notice Binds `amount` tokens of `tokenId` to NFT `bindId` at address `bindAddress`.
/// @dev The function MUST throw unless `msg.sender` is an approved operator
/// for `from`. It also MUST throw if the `from` owns fewer than `amount`
/// tokens. Finally, it MUST throw if the NFT contract does not support the
/// ERC-721 interface or if the NFT being bound to does not exist. Before
/// binding, tokens MUST be transferred to the contract address of the NFT.
/// On bind completion, the function MUST emit `Transfer` & `Bind` events
/// to reflect the implicit token transfers and subsequent bind.
/// @param from The owner address of the unbound tokens.
/// @param bindAddress The contract address of the NFT being bound to.
/// @param bindId The identifier of the NFT being bound to.
/// @param tokenId The identifier of the binding token type.
/// @param amount The number of tokens binding to the NFT.
functionbind(addressfrom,addressbindAddress,uint256bindId,uint256tokenId,uint256amount)external;/// @notice Binds `amounts` tokens of `tokenIds` to NFT `bindId` at address `bindAddress`.
/// @dev The function MUST throw unless `msg.sender` is an approved operator
/// for `from`. It also MUST throw if the length of `amounts` is not the
/// same as `tokenIds`, or if any balances of `tokenIds` for `from` is less
/// than that of `amounts`. Finally, it MUST throw if the NFT contract does
/// not support the ERC-721 interface or if the bound NFT does not exist.
/// Before binding, tokens MUST be transferred to the contract address of
/// the NFT. On bind completion, the function MUST emit `TransferBatch` and
/// `BindBatch` events to reflect the batch token transfers and bind.
/// @param from The owner address of the unbound tokens.
/// @param bindAddress The contract address of the NFTs being bound to.
/// @param bindId The identifier of the NFT being bound to.
/// @param tokenIds The identifiers of the binding token types.
/// @param amounts The number of tokens per type binding to the NFTs.
functionbatchBind(addressfrom,addressbindAddress,uint256bindId,uint256[]calldatatokenIds,uint256[]calldataamounts)external;/// @notice Unbinds `amount` tokens of `tokenId` from NFT `bindId` at address `bindAddress`.
/// @dev The function MUST throw unless `msg.sender` is an approved operator
/// for `from`. It also MUST throw if `from` is not the owner of the bound
/// NFT, if the NFT's token balance is fewer than `amount`, or if `to` is
/// the zero address. After unbinding, tokens MUST be transferred to `to`,
/// during which the function MUST check if `to` is a valid contract (code
/// size > 0), and if so, call `onERC1155Received`, throwing if the wrong \
/// identifier is returned. On unbind completion, the function MUST emit
/// `Unbind` & `Transfer` events to reflect the unbind and transfers.
/// @param from The owner address of the NFT the tokens are bound to.
/// @param to The address of the unbound tokens' new owner.
/// @param bindAddress The contract address of the NFT being unbound from.
/// @param bindId The identifier of the NFT being unbound from.
/// @param tokenId The identifier of the unbinding token type.
/// @param amount The number of tokens unbinding from the NFT.
functionunbind(addressfrom,addressto,addressbindAddress,uint256bindId,uint256tokenId,uint256amount)external;/// @notice Unbinds `amount` tokens of `tokenId` from NFT `bindId` at address `bindAddress`.
/// @dev The function MUST throw unless `msg.sender` is an approved operator
/// for `from`. It also MUST throw if the length of `amounts` is not the
/// same as `tokenIds`, if any balances of `tokenIds` for the NFT is less
/// than that of `amounts`, or if `to` is the zero addresss. After
/// unbinding, tokens MUST be transferred to `to`, during which the
/// function MUST check if `to` is a valid contract (code size > 0), and if
/// so, call `onERC1155BatchReceived`, throwing if the wrong identifier is
/// returned. On unbind completion, the function MUST emit `UnbindBatch` &
/// `TransferBatch` events to reflect the batch unbind and transfers.
/// @param from The owner address of the unbound tokens.
/// @param to The address of the unbound tokens' new owner.
/// @param bindAddress The contract address of the NFTs being unbound from.
/// @param bindId The identifier of the NFT being unbound from.
/// @param tokenIds The identifiers of the unbinding token types.
/// @param amounts The number of tokens per type unbinding from the NFTs.
functionbatchUnbind(addressfrom,addressto,addressbindAddress,uint256bindId,uint256[]calldatatokenIds,uint256[]calldataamounts)external;/// @notice Gets the number of tokens of type `tokenId` bound to NFT `bindId` at address `bindAddress`.
/// @param bindAddress The contract address of the bound NFT.
/// @param bindId The identifier of the bound NFT.
/// @param tokenId The identifier of the token type bound to the NFT.
/// @return The number of tokens of type `tokenId` bound to the NFT.
functionboundBalanceOf(addressbindAddress,uint256bindId,uint256tokenId)externalviewreturns(uint256);/// @notice Gets the number of tokens of types `bindIds` bound to NFTs `bindIds` at address `bindAddress`.
/// @param bindAddress The contract address of the bound NFTs.
/// @param bindIds The identifiers of the bound NFTs.
/// @param tokenIds The identifiers of the token types bound to the NFTs.
/// @return balances The bound balances for each token type / NFT pair.
functionboundBalanceOfBatch(addressbindAddress,uint256[]calldatabindIds,uint256[]calldatatokenIds)externalviewreturns(uint256[]memorybalances);}
Rationale
A standard for token binding unlocks a new layer of composability for allowing wallets, applications, and protocols to interact with, trade, and display bundled NFTs. One example use-case of this is at Dopamine, where streetwear garments may be bundled with digital assets such as music, avatars, or digital-twins of the garments, by representing these assets as bindable tokens and binding them to microchips represented as NFTs.
Binding Mechanism
During binding, a bindable token’s technical ownership is conferred to its bound NFT, while allowing the NFT owner to unbind at any time. A caveat of this lightweight design is that applications that have yet to adopt this standard will not show the bundled tokens as owned by the NFT owner.
Backwards Compatibility
The bindable token interface is designed to be compatible with existing ERC-721 and ERC-1155 standards.
During binding, because ownership is conferred to the bound NFT contract, implementations should take caution in ensuring unbinding may only be performed by the designated NFT owner.