50 KiB
eip | title | description | author | discussions-to | status | type | category | created | requires |
---|---|---|---|---|---|---|---|---|---|
5700 | Bindable Token Interface | Interface for binding fungible and non-fungible tokens to assets. | Leeren (@leeren) | https://ethereum-magicians.org/t/eip-5700-bindable-token-standard/11077 | Draft | Standards Track | ERC | 2022-09-22 | 165, 721, 1155 |
Abstract
The proposed standard defines an interface by which fungible and non-fungible tokens may be bound to arbitrary assets (typically represented as NFTs themselves), enabling token ownership and transfer attribution to be proxied through the assets they are bound to.
A bindable token ("bindable") is an EIP-721 or EIP-1155 token which, when bound to an asset, delegates ownership and tracking through its bound asset, remaining locked for direct transfers until it is unbound. When unbound, bindable tokens function normally according to their base token implementations.
A bound asset ("binder") has few restrictions on how it is represented, except that it be unique and expose an interface for ownership queries. A binder would most commonly be represented as an EIP-721 NFT. Binders and bindables form a one-to-many relationship.
Below are example use-cases that benefit from such a standard:
- NFT-bundled physical assets: microchipped streetwear bundles, digitized automobile collections, digitally-twinned real-estate property
- NFT-bundled digital assets: accessorizable virtual wardrobes, composable music tracks, customizable metaverse land
Motivation
A standard interface for token binding allows tokens to be bundled and transferred with other assets in a way that is easily integrable with wallets, marketplaces, and other NFT applications, and avoids the need for ad-hoc ownership attribution strategies that are neither flexible nor backwards-compatible.
Unlike other standards tackling delegated ownership attribution, which look at composability on the account level, this standard addresses composability on the asset level, with the goal of creating a universal interface for token modularity that is compatible with existing EIP-721 and EIP-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.
EIP-721 Bindable
Smart contracts implementing the EIP-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/EIPS/eip-5700
/// Note: the ERC-165 identifier for this interface is 0x82a34a7d.
interface IERC721Bindable /* is IERC721 */ {
/// @notice The `Bind` event MUST emit when NFT ownership is delegated
/// through an asset and when minting an NFT bound to an existing asset.
/// @dev When minting bound NFTs, `from` MUST be set to the zero address.
/// @param operator The address calling the bind.
/// @param from The unbound NFT owner address.
/// @param to The bound NFT owner delegate address.
/// @param tokenId The identifier of the NFT being bound.
/// @param bindId The identifier of the asset being bound to.
/// @param bindAddress The contract address handling asset ownership.
event Bind(
address indexed operator,
address indexed from,
address to,
uint256 tokenId,
uint256 bindId,
address indexed bindAddress
);
/// @notice The `Unbind` event MUST emit when asset-bound NFT ownership is
/// revoked, as well as when burning an NFT bound to an existing asset.
/// @dev When burning bound NFTs, `to` MUST be set to the zero address.
/// @param operator The address calling the unbind.
/// @param from The bound asset owner address.
/// @param to The unbound NFT owner address.
/// @param tokenId The identifier of the NFT being unbound.
/// @param bindId The identifier of the asset being unbound from.
/// @param bindAddress The contract address handling bound asset ownership.
event Unbind(
address indexed operator,
address indexed from,
address to,
uint256 tokenId,
uint256 bindId,
address indexed bindAddress
);
/// @notice Binds NFT `tokenId` owned by `from` to asset `bindId` at address
/// `bindAddress`, delegating NFT-bound ownership to `to`.
/// @dev The function MUST throw unless `msg.sender` is the current owner,
/// an authorized operator, or the approved address for the NFT. It also
/// MUST throw if the NFT is already bound, if `from` is not the NFT owner,
/// or if `to` is not `bindAddress` or its asset owner. After binding, the
/// function MUST check if `bindAddress` is a valid contract / (code size
/// > 0), and if so, call `onERC721Bind` on it, throwing if the wrong
/// identifier is returned (see "Binding Rules") or if the contract is
/// invalid. On bind completion, the function MUST emit `Bind` & `Transfer`
/// events to reflect delegated ownership change.
/// @param from The unbound NFT original owner address.
/// @param to The bound NFT delegate owner address (SHOULD be `bindAddress`).
/// @param tokenId The identifier of the NFT being bound.
/// @param bindId The identifier of the asset being bound to.
/// @param bindAddress The contract address handling asset ownership.
/// @param data Additional data sent with the `onERC721Bind` hook.
function bind(
address from,
address to,
uint256 tokenId,
uint256 amount,
uint256 bindId,
address bindAddress,
bytes calldata data
) external;
/// @notice Unbinds NFT `tokenId` from asset `bindId` owned by `from` at
/// address `bindAddress`, assigning unbound NFT ownership to `to`.
/// @dev The function MUST throw unless `msg.sender` is the asset owner or
/// an approved operator. It also MUST throw if NFT `tokenId` is not bound,
/// if `from` is not the asset owner, or if `to` is the zero address. After
/// unbinding, the function MUST check if `bindAddress` is a valid contract
/// (code size > 0), and if so, call `onERC721Unbind` on it, throwing if
/// the wrong identifier is returned (see "Binding Rules") or if the
/// contract is invalid. The function also MUST check if `to` is a valid
/// contract, and if so, call `onERC721Received`, throwing if the wrong
/// identifier is returned. On unbind completion, the function MUST emit
/// `Unbind` & `Transfer` events to reflect delegated ownership change.
/// @param from The bound asset owner address.
/// @param to The unbound NFT owner address.
/// @param tokenId The identifier of the NFT being unbound.
/// @param bindId The identifier of the asset being unbound from.
/// @param bindAddress The contract address handling bound asset ownership.
/// @param data Additional data sent with the `onERC721Unbind` hook.
function unbind(
address from,
address to,
uint256 tokenId,
uint256 bindId,
address bindAddress,
bytes calldata data
) external;
/// @notice Gets the asset identifier and address which an NFT is bound to.
/// @param tokenId The identifier of the NFT being queried.
/// @return The bound asset identifier and contract address.
function binderOf(uint256 tokenId) external returns (uint256, address);
/// @notice Counts NFTs bound to asset `bindId` at address `bindAddress`.
/// @param bindAddress The contract address handling bound asset ownership.
/// @param bindId The identifier of the bound asset.
/// @return The total number of NFTs bound to the asset.
function boundBalanceOf(address bindAddress, uint256 bindId) external returns (uint256);
Smart contracts managing assets MUST implement the IERC721Binder
interface if they are to accept binds from EIP-721 bindables.
Implementers of the IERC721Binder
interface MUST return true
if 0x2ac2d2bc
is passed as the identifier to the supportsInterface
function.
/// @dev Note: the ERC-165 identifier for this interface is 0x2ac2d2bc.
interface IERC721Binder /* is IERC165 */ {
/// @notice Handles the binding of an IERC721Bindable-compliant NFT.
/// @dev An IERC721Bindable-compliant smart contract MUST call this function
/// at the end of a `bind` after ownership is delegated through an asset.
/// The function MUST revert if `to` is not the asset owner or the binder
/// address. The function MUST revert if it rejects the bind. If accepting
/// the bind, the function MUST return `bytes4(keccak256("onERC721Bind(address,address,address,uint256,uint256,bytes)"))`
/// Caller MUST revert the transaction if the above value is not returned.
/// Note: The contract address of the binding NFT is `msg.sender`.
/// @param operator The address initiating the bind.
/// @param from The unbound NFT owner address.
/// @param to The bound NFT owner delegate address.
/// @param tokenId The identifier of the NFT being bound.
/// @param bindId The identifier of the asset being bound to.
/// @param data Additional data sent along with no specified format.
/// @return `bytes4(keccak256("onERC721Bind(address,address,address,uint256,uint256,bytes)"))`
function onERC721Bind(
address operator,
address from,
address to,
uint256 tokenId,
uint256 bindId,
bytes calldata data
) external returns (bytes4);
/// @notice Handles the unbinding of an IERC721Bindable-compliant NFT.
/// @dev An IERC721Bindable-compliant smart contract MUST call this function
/// at the end of an `unbind` after revoking asset-delegated ownership.
/// The function MUST revert if `from` is not the asset owner of `bindId`.
/// The function MUST revert if it rejects the unbind. If accepting the
/// unbind, the function MUST return `bytes4(keccak256("onERC721Unbind(address,address,address,uint256,uint256,bytes)"))`
/// Caller MUST revert the transaction if the above value is not returned.
/// Note: The contract address of the unbinding NFT is `msg.sender`.
/// @param from The bound asset owner address.
/// @param to The unbound NFT owner address.
/// @param tokenId The identifier of the NFT being unbound.
/// @param bindId The identifier of the asset being unbound from.
/// @param data Additional data with no specified format.
/// @return `bytes4(keccak256("onERC721Unbind(address,address,address,uint256,uint256,bytes)"))`
function onERC721Unbind(
address operator,
address from,
address to,
uint256 tokenId,
uint256 bindId,
bytes calldata data
) external returns (bytes4);
/// @notice Gets the owner address of the asset identified by `bindId`.
/// @dev This function MUST throw for assets assigned to the zero address.
/// @param bindId The identifier of the asset whose owner is being queried.
/// @return The address of the owner of the asset.
function ownerOf(uint256 bindId) external view returns (address);
/// @notice Checks if an operator can act on behalf of an asset owner.
/// @param owner The address that owns an asset.
/// @param operator The address that can act on behalf of the asset owner.
/// @return True if `operator` can act on behalf of `owner`, else False.
function isApprovedForAll(address owner, address operator) external view returns (bool);
}
EIP-1155 Bindable
Smart contracts implementing the EIP-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/EIPS/eip-5700
/// Note: the ERC-165 identifier for this interface is 0xd0d555c6.
interface IERC1155Bindable /* is IERC1155 */ {
/// @notice The `Bind` event MUST emit when token ownership is delegated
/// through an asset and when minting tokens bound to an existing asset.
/// @dev When minting bound tokens, `from` MUST be set to the zero address.
/// @param operator The address calling the bind.
/// @param from The owner address of the unbound tokens.
/// @param to The delegate owner address of the bound tokens.
/// @param tokenId The identifier of the token type being bound.
/// @param amount The number of tokens of type `tokenId` being bound.
/// @param bindId The identifier of the asset being bound to.
/// @param bindAddress The contract address handling asset ownership.
event Bind(
address indexed operator,
address indexed from,
address to,
uint256 tokenId,
uint256 amount,
uint256 bindId,
address indexed bindAddress
);
/// @notice The `BindBatch` event MUST emit when token ownership of
/// different token types are delegated through multiple assets and when
/// minting different token types bound to multiple existing assets.
/// @dev When minting bound tokens, `from` MUST be set to the zero address.
/// @param operator The address calling the bind.
/// @param from The owner address of the unbound tokens.
/// @param to The delegate owner address of the bound tokens.
/// @param tokenIds The identifiers of the token types being bound.
/// @param amounts The number of tokens for each token type being bound.
/// @param bindIds The identifiers of the assets being bound to.
/// @param bindAddress The contract address handling asset ownership.
event BindBatch(
address indexed operator,
address indexed from,
address to,
uint256[] tokenIds,
uint256[] amounts,
uint256[] bindIds,
address indexed bindAddress
);
/// @notice The `Unbind` event MUST emit when asset-delegated token
/// ownership is revoked and when burning tokens bound to existing assets.
/// @dev When burning bound tokens, `to` MUST be set to the zero address.
/// @param operator The address calling the unbind.
/// @param from The owner address of the bound asset.
/// @param to The owner address of the unbound tokens.
/// @param tokenId The identifier of the token type being unbound.
/// @param amount The number of tokens of type `tokenId` being unbound.
/// @param bindId The identifier of the asset being unbound from.
/// @param bindAddress The contract address handling bound asset ownership.
event Unbind(
address indexed operator,
address indexed from,
address to,
uint256 tokenId,
uint256 amount,
uint256 bindId,
address indexed bindAddress
);
/// @notice The `UnbindBatch` event MUST emit when asset-delegated token
/// ownership is revoked for different token types and when burning
/// different token types bound to multiple existing assets.
/// @dev When burning bound tokens, `to` MUST be set to the zero address.
/// @param operator The address calling the unbind.
/// @param from The owner address of the bound asset.
/// @param to The owner address of the unbound tokens.
/// @param tokenIds The identifiers of the token types being unbound.
/// @param amounts The number of tokens for each token type being unbound.
/// @param bindIds The identifiers of the assets being unbound from.
/// @param bindAddress The contract address handling bound asset ownership.
event UnbindBatch(
address indexed operator,
address indexed from,
address to,
uint256[] tokenIds,
uint256[] amounts,
uint256[] bindIds,
address indexed bindAddress
);
/// @notice Binds `amount` tokens of type `tokenId` owned by `from` to asset
/// `bindId` at `bindAddress`, delegating token-bound ownership to `to`.
/// @dev The function MUST throw unless `msg.sender` is an approved operator
/// for `from`. The function also MUST throw if `from` owns fewer than
/// `amount` tokens, or if `to` is not `bindAddress` or its asset owner.
/// After binding, the function MUST check if `bindAddress` is a valid
/// contract (code size > 0), and if so, call `onERC1155Bind` on it,
/// throwing if the wrong identifier is returned (see "Binding Rules") or
/// if the contract is invalid. On bind completion, the function MUST emit
/// `Bind` & `TransferSingle` events to reflect delegated ownership change.
/// @param from The owner address of the unbound tokens.
/// @param to The delegate owner address of the bound tokens (SHOULD be `bindAddress`).
/// @param tokenId The identifier of the token type being bound.
/// @param amount The number of tokens of type `tokenId` being bound.
/// @param bindId The identifier of the asset being bound to.
/// @param bindAddress The contract address handling asset ownership.
/// @param data Additional data sent with the `onERC1155Bind` hook.
function bind(
address from,
address to,
uint256 tokenId,
uint256 amount,
uint256 bindId,
address bindAddress,
bytes calldata data
) external;
/// @notice Binds `amounts` tokens of types `tokenIds` owned by `from` to
/// assets `bindIds` at `bindAddress`, delegating bound ownership to `to`.
/// @dev The function MUST throw unless `msg.sender` is an approved operator
/// for `from`. The function also MUST throw if length of `amounts` is not
/// the same as `tokenIds` or `bindIds`, if any balances of `tokenIds` for
/// `from` is less than that of `amounts`, or if `to` is not `bindAddress`
/// or the asset owner. After delegating ownership, the function MUST check
/// if `bindAddress` is a valid contract (code size > 0), and if so, call
/// `onERC1155BatchBind` on it, throwing if the wrong identifier is
/// returned (see "Binding Rules") or if the contract is invalid. On bind
/// completion, the function MUST emit `BindBatch` & `TransferBatch` events
/// to reflect delegated ownership changes.
/// @param from The owner address of the unbound tokens.
/// @param to The delegate owner address of the bound tokens (SHOULD be `bindAddress`).
/// @param tokenIds The identifiers of the token types being bound.
/// @param amounts The number of tokens for each token type being bound.
/// @param bindIds The identifiers of the assets being bound to.
/// @param bindAddress The contract address handling asset ownership.
/// @param data Additional data sent with the `onERC1155BatchBind` hook.
function batchBind(
address from,
address to,
uint256[] calldata tokenIds,
uint256[] calldata amounts,
uint256[] calldata bindIds,
address bindAddress,
bytes calldata data
) external;
/// @notice Revokes delegated ownership of `amount` tokens of type `tokenId`
/// owned by `from` bound to `bindId`, switching ownership to `to`.
/// @dev The function MUST throw unless `msg.sender` is the asset owner or
/// an approved operator. It also MUST throw if `from` is not the asset
/// owner, if fewer than `amount` tokens are bound to the asset, or if `to`
/// is the zero address. Once delegated ownership is revoked, the function
/// MUST check if `bindAddress` is a valid contract (code size > 0), and if
/// so, call `onERC1155Unbind` on it, throwing if the wrong identifier is
/// returned (see "Binding Rules") or if the contract is invalid. The
/// function also MUST check if `to` is a contract, and if so, call on it
/// `onERC1155Received`, throwing if the wrong identifier is returned. On
/// unbind completion, the function MUST emit `Unbind` & `TransferSingle`
/// events to reflect delegated ownership change.
/// @param from The owner address of the bound asset.
/// @param to The owner address of the unbound tokens.
/// @param tokenId The identifier of the token type being unbound.
/// @param amount The number of tokens of type `tokenId` being unbound.
/// @param bindId The identifier of the asset being unbound from.
/// @param bindAddress The contract address handling bound asset ownership.
/// @param data Additional data sent with the `onERC1155Unbind` hook.
function unbind(
address from,
address to,
uint256 tokenId,
uint256 amount,
uint256 bindId,
address bindAddress,
bytes calldata data
) external;
/// @notice Revokes delegated ownership of `amounts` tokens of `tokenIds`
/// owned by `from` bound to assets `bindIds`, switching ownership to `to`.
/// @dev The function MUST throw unless `msg.sender` is the assets' owner or
/// approved operator. It also MUST throw if the length of `amounts` is not
/// the same as `tokenIds` or `bindIds`, if `from` is not the owner of all
/// assets, if any count in `amounts` is fewer than the number of tokens
/// bound for the corresponding token-asset pair given by `tokenIds` and
/// `bindIds`, or if `to` is the zero address. Once delegated ownership is
/// revoked for all tokens, the function MUST check if `bindAddress` is a
/// valid contract (code size > 0), and if so, call `onERC1155BatchUnbind`
/// on it, throwing if a wrong identifier is returned (see "Binding Rules")
/// or if the contract is invalid. The function also MUST check if `to` is
/// valid contract, and if so, call `onERC1155BatchReceived` on it,
/// throwing if the wrong identifier is returned. On unbind completion, the
/// function MUST emit `BatchUnbind` and `TransferBatch` events to reflect
/// delegated ownership change.
/// @param from The owner address of the bound asset.
/// @param to The owner address of the unbound tokens.
/// @param tokenIds The identifiers of the token types being unbound.
/// @param amounts The number of tokens for each token type being unbound.
/// @param bindIds The identifier of the assets being unbound from.
/// @param bindAddress The contract address handling bound asset ownership.
/// @param data Additional data sent with the `onERC1155BatchUnbind` hook.
function batchUnbind(
address from,
address to,
uint256[] calldata tokenIds,
uint256[] calldata amounts,
uint256[] calldata bindIds,
address bindAddress,
bytes calldata data
) external;
/// @notice Gets the balance of bound tokens of type `tokenId` bound to the
/// asset `bindId` at address `bindAddress`.
/// @param bindAddress The contract address handling bound asset ownership.
/// @param bindId The identifier of the bound asset.
/// @param tokenId The identifier of the counted bound token type.
/// @return The total number of tokens of type `tokenId` bound to the asset.
function boundBalanceOf(
address bindAddress,
uint256 bindId,
uint256 tokenId
) external returns (uint256);
/// @notice Gets the balance of bound tokens for multiple token types given
/// by `tokenIds` bound to assets `bindIds` at address `bindAddress`.
/// @param bindAddress The contract address handling bound asset ownership.
/// @param bindIds List of bound asset identifiers.
/// @param tokenIds The identifiers of the counted bound token types.
/// @return balances The bound balances for each asset / token type pair.
function boundBalanceOfBatch(
address bindAddress,
uint256[] calldata bindIds,
uint256[] calldata tokenIds
) external returns (uint256[] memory balances);
}
Smart contracts managing assets MUST implement the IERC1155Binder
interface if they are to accept binds from EIP-1155 bindables.
Implementers of the IERC1155Binder
interface MUST return true
if 0x6fc97e78
is passed as the identifier to the supportsInterface
function.
pragma solidity ^0.8.16;
/// @dev Note: the ERC-165 identifier for this interface is 0x6fc97e78.
interface IERC1155Binder /* is IERC165 */ {
/// @notice Handles binding of an IERC1155Bindable-compliant token type.
/// @dev An IERC1155Bindable-compliant smart contract MUST call this
/// function at the end of a `bind` after ownership is delegated through an
/// asset. The function MUST revert if `to` is not the asset owner or
/// binder address. The function MUST revert if it rejects the bind. If
/// accepting the bind, the function MUST return `bytes4(keccak256("onERC1155Bind(address,address,address,uint256,uint256,uint256,bytes)"))`
/// Caller MUST revert the transaction if the above value is not returned.
/// Note: The contract address of the binding token is `msg.sender`.
/// @param operator The address responsible for binding.
/// @param from The owner address of the unbound tokens.
/// @param to The delegate owner address of the bound tokens.
/// @param tokenId The identifier of the token type being bound.
/// @param bindId The identifier of the asset being bound to.
/// @param data Additional data sent along with no specified format.
/// @return `bytes4(keccak256("onERC1155Bind(address,address,address,uint256,uint256,uint256,bytes)"))`
function onERC1155Bind(
address operator,
address from,
address to,
uint256 tokenId,
uint256 amount,
uint256 bindId,
bytes calldata data
) external returns (bytes4);
/// @notice Handles binding of multiple IERC1155Bindable-compliant tokens
/// `tokenIds` to multiple assets `bindIds`.
/// @dev An IERC1155Bindable-compliant smart contract MUST call this
/// function at the end of a `batchBind` after delegating ownership of
/// multiple token types to the asset owner. The function MUST revert if
/// `to` is not the asset owner or binder address. The function MUST revert
/// if it rejects the bind. If accepting the bind, the function MUST return
/// `bytes4(keccak256("onERC1155BatchBind(address,address,address,uint256[],uint256[],uint256[],bytes)"))`
/// Caller MUST revert the transaction if the above value is not returned.
/// Note: The contract address of the binding token is `msg.sender`.
/// @param operator The address responsible for performing the binds.
/// @param from The unbound tokens' original owner address.
/// @param to The bound tokens' delegate owner address (SHOULD be `bindAddress`).
/// @param tokenIds The list of token types being bound.
/// @param amounts The number of tokens for each token type being bound.
/// @param bindIds The identifiers of the assets being bound to.
/// @param data Additional data sent along with no specified format.
/// @return `bytes4(keccak256("onERC1155Bind(address,address,address,uint256[],uint256[],uint256[],bytes)"))`
function onERC1155BatchBind(
address operator,
address from,
address to,
uint256[] calldata tokenIds,
uint256[] calldata amounts,
uint256[] calldata bindIds,
bytes calldata data
) external returns (bytes4);
/// @notice Handles unbinding of an IERC1155Bindable-compliant token type.
/// @dev An IERC1155Bindable-compliant contract MUST call this function at
/// the end of an `unbind` after revoking delegated asset ownership. The
/// function MUST revert if `from` is not the asset owner. The function
/// MUST revert if it rejects the unbind. If accepting the unbind, the
/// function MUST return `bytes4(keccak256("onERC1155Unbind(address,address,address,uint256,uint256,uint256,bytes)"))`
/// Caller MUST revert the transaction if the above value is not returned.
/// Note: The contract address of the unbinding token is `msg.sender`.
/// @param operator The address responsible for performing the unbind.
/// @param from The owner address of the bound asset.
/// @param to The owner address of the unbound tokens.
/// @param tokenId The token type being unbound.
/// @param amount The number of tokens of type `tokenId` being unbound.
/// @param bindId The identifier of the asset being unbound from.
/// @param data Additional data sent along with no specified format.
/// @return `bytes4(keccak256("onERC1155Unbind(address,address,address,uint256,uint256,uint256,bytes)"))`
function onERC1155Unbind(
address operator,
address from,
address to,
uint256 tokenId,
uint256 amount,
uint256 bindId,
bytes calldata data
) external returns (bytes4);
/// @notice Handles unbinding of multiple IERC1155Bindable-compliant token types.
/// @dev An IERC1155Bindable-compliant contract MUST call this function at
/// the end of a `batchUnbind` after revoking asset-delegated ownership.
/// The function MUST revert if `from` is not the asset owner. The function
/// MUST revert if it rejects the unbinds. If accepting the unbinds, the
/// function MUST return `bytes4(keccak256("onERC1155Unbind(address,address,address,uint256[],uint256[],uint256[],bytes)"))`
/// Caller MUST revert the transaction if the above value is not returned.
/// Note: The contract address of the unbinding token is `msg.sender`.
/// @param operator The address responsible for performing the unbinds.
/// @param from The owner address of the bound asset.
/// @param to The owner address of the unbound tokens.
/// @param tokenIds The list of token types being unbound.
/// @param amounts The number of tokens for each token type being unbound.
/// @param bindIds The identifiers of the assets being unbound from.
/// @param data Additional data sent along with no specified format.
/// @return `bytes4(keccak256("onERC1155Unbind(address,address,address,uint256[],uint256[],uint256[],bytes)"))`
function onERC1155BatchUnbind(
address operator,
address from,
address to,
uint256[] calldata tokenIds,
uint256[] calldata amounts,
uint256[] calldata bindIds,
bytes calldata data
) external returns (bytes4);
/// @notice Gets the owner address of the asset represented by id `bindId`.
/// @param bindId The identifier of the asset whose owner is being queried.
/// @return The address of the owner of the asset.
function ownerOf(uint256 bindId) external view returns (address);
/// @notice Checks if an operator can act on behalf of an asset owner.
/// @param owner The owner address of an asset.
/// @param operator The address operating on behalf of the asset owner.
/// @return True if `operator` can act on behalf of `owner`, else False.
function isApprovedForAll(address owner, address operator) external view returns (bool);
}
Rules
This standard supports two modes of binding, depending on whether ownership is delegated to the asset owner or binder address.
- Delegated (RECOMMENDED):
- Bindable ownership is delegated to the binder address (
to
isbindAddress
in a bind). - Bindable ownership queries return the binder address.
- Bindable transfers MUST always throw.
- Bindable ownership is delegated to the binder address (
- Legacy (NOT RECOMMENDED):
- Bindable ownership is delegated to the asset owner address (
to
is the asset owner address in a bind). - Bindable ownership queries return the asset owner address.
- Bindable transfers MUST always throw, except when invoked as a result of bound assets being transferred.
- Transferrable bound assets MUST keep track of bound tokens following this binding mode.
- On transfer, bound assets MUST invoke ownership transfers for bound tokens following this binding mode.
- Bindable ownership is delegated to the asset owner address (
Binders SHOULD choose to only support the "delegated" binding mode by throwing if to
is not bindAddress
, otherwise both modes MAY be supported.
bind
rules:
- When binding an EIP-721 bindable to an asset:
- MUST throw if caller is not the current NFT owner, the approved address for the NFT, or an approved operator for
from
. - MUST throw if NFT
tokenId
is already bound. - MUST throw if
from
is not the NFT owner. - MUST throw if
to
is notbindAddress
or the asset owner. - After above conditions are met, MUST check if
bindAddress
is a smart contract (code size > 0). If so, it MUST callonERC721Bind
onbindAddress
withdata
passed unaltered and act appropriately (see "Hook Rules"). - MUST emit the
Bind
event to reflect asset-bound ownership delegation. - MUST emit the
Transfer
event iffrom
is different thanto
to reflect delegated ownership change.
- MUST throw if caller is not the current NFT owner, the approved address for the NFT, or an approved operator for
- When binding an EIP-1155 bindable to an asset:
- MUST throw if caller is not an approved operator for
from
. - MUST throw if
from
owns fewer thanamount
unbound tokens of typetokenId
. - MUST throw if
to
is notbindAddress
or the asset owner. - After above conditions are met, MUST check if
bindAddress
is a smart contract (code size > 0). If so, it MUST callonERC1155Bind
onbindAddress
withdata
passed unaltered and act appropriately (see "Hook Rules"). - MUST emit the
Bind
event to reflect asset-bound ownership delegation. - MUST emit the
TransferSingle
event iffrom
is different thanto
to reflect delegated ownership change.
- MUST throw if caller is not an approved operator for
unbind
rules:
- When unbinding an EIP-721 bindable from an asset:
- MUST throw if caller is not the owner of the asset or an approved asset operator for
from
. - MUST throw if NFT
tokenId
is not bound. - MUST throw if
from
is not the asset owner. - MUST throw if
to
is the zero address. - After above conditions are met, MUST check if
bindAddress
is a smart contract (code size > 0). If so, it MUST callonERC721Unbind
onbindAddress
withdata
passed unaltered and act appropriately (see "Hook Rules"). - In addition, it MUST check if
to
is a smart contract (code size > 0), and callonERC721Received
onto
withdata
passed unaltered and act appropriately (see "Hook Rules"). - MUST emit the
Unbind
event to reflect asset-bound ownership revocation. - MUST emit the
Transfer
event iffrom
is different thanto
to reflect delegated ownership change.
- MUST throw if caller is not the owner of the asset or an approved asset operator for
- When unbinding a an EIP-1155 bindable from an asset:
- MUST throw if caller is not the owner of the asset or an approved asset operator for
from
. - MUST throw if
from
is not the asset owner. - MUST throw if fewer than
amount
tokens of typetokenId
are bound tobindId
. - MUST throw if
to
is the zero address. - After above conditions are met, MUST check if
bindAddress
is a smart contract (code size > 0). If so, it MUST callonERC1155Unbind
onbindAddress
withdata
passed unaltered and act appropriately (see "Hook Rules"). - In addition, it MUST check if
to
is a smart contract (code size > 0), and callonERC1155Received
onto
withdata
passed unaltered and act appropriately (see "Hook Rules"). - MUST emit the
Unbind
event to reflect asset-bound ownership revocation. - MUST emit the
TransferSingle
event iffrom
is different thanto
to reflect delegated ownership change.
- MUST throw if caller is not the owner of the asset or an approved asset operator for
batchBind
& batchUnbind
rules:
- When performing a
batchBind
on EIP-1155 bindables:- MUST throw if caller is not an approved operator for
from
. - MUST throw if length of
tokenIds
is not the same as that ofamounts
orbindIds
. - MUST throw if any unbound token balances of
tokenIds
forfrom
are less than that ofamounts
. - MUST throw if
to
is notbindAddress
or the asset owner. - After above conditions are met, MUST check if
bindAddress
is a smart contract (code size > 0). If so, it MUST callonERC1155BatchBind
onbindAddress
withdata
passed unaltered and act appropriately (see "Hook Rules"). - MUST emit either
Bind
orBindBatch
events to properly reflect asset-delegated ownership attribution for all bound tokens. - MUST emit either
TransferSingle
orTransferBatch
events iffrom
is different thanto
to reflect delegated ownership changes for all tokens.
- MUST throw if caller is not an approved operator for
- When performing a
batchUnbind
on EIP-1155 bindables:- MUST throw if caller is not the owner of all assets or an approved asset operator for
from
. - MUST throw if length of
tokenIds
is not the same as that ofamounts
orbindIds
. - MUST throw if
from
is not the owner of all assets.
- MUST throw if caller is not the owner of all assets or an approved asset operator for
- MUST throw if any count in
amounts
is fewer than the number of tokens bound for the corresponding token-asset pair given bytokenIds
andbindIds
.- MUST throw if
to
is the zero address. - After above conditions are met, MUST check if
bindAddress
is a smart contract (code size > 0). If so, it MUST callonERC1155Unbind
onbindAddress
withdata
passed unaltered and act appropriately (see "Hook Rules"). - In addition, it MUST check if
to
is a smart contract (code size > 0), and callonERC1155Received
onto
withdata
passed unaltered and act appropriately (see "Hook Rules"). - MUST emit
Bind
event to reflect asset-bound ownership revocation. - MUST emit the
TransferSingle
event iffrom
is different thanto
to reflect delegated ownership change.
- MUST throw if
Bind
event rules:
- When emitting an EIP-721 bindable
Bind
event:- SHOULD be emitted to indicate a single bind has occurred between a
tokenId
andbindId
pair. - MAY be emitted multiple times to indicate multiple binds have occurred in a single transaction.
- The
operator
argument MUST be the owner of the NFTtokenId
, the approved address for the NFT, or the authorized operator offrom
. - The
from
argument MUST be the owner of the NFTtokenId
. - The
to
argument MUST bebinderAddress
(indicates "delegated" bind) or the owner of the bound asset (indicates "legacy" bind). - The
tokenId
argument MUST be the NFT being bound. - The
bindId
argument MUST be the identifier of the asset being bound to. - The
bindAddress
argument MUST be the contract address of the asset being bound to. - When minting NFTs bound to an asset, the
Bind
event must be emitted with thefrom
argument set to0x0
. Bind
events MUST be emitted to reflect asset-bound ownership delegation before calls toonERC721Bind
.
- SHOULD be emitted to indicate a single bind has occurred between a
- When emitting an EIP-1155 bindable
Bind
event:- SHOULD be emitted to indicate a bind has occurred between a single
tokenId
type andbinderId
pair. - MAY be emitted multiple times to indicate multiple binds have occurred in a single transaction, but
BindBatch
should be preferred in this case to reduce gas consumption. - The
operator
argument MUST be an authorized operator forfrom
. - The
from
argument MUST be the owner of the unbound tokens. - The
to
argument MUST bebinderAddress
(indicates "delegated" bind) or the owner of the bound assetbindId
(indicates "legacy" bind). - The
tokenId
argument MUST be the token type being bound. - The
amount
argument MUST be the number of tokens of typetokenId
being bound. - The
bindId
argument MUST be the identifier of the asset being bound to. - The
bindAddress
argument MUST be the contract address of the asset being bound to. - When minting NFTs bound to an asset, the
Bind
event must be emitted with thefrom
argument set to0x0
. Bind
events MUST be emitted to reflect asset-bound ownership delegation before calls toonERC1155Bind
oronERC1155BindBatch
.
- SHOULD be emitted to indicate a bind has occurred between a single
Unbind
event rules:
- When emitting an EIP-721 bindable
Unbind
event:- SHOULD be emitted to indicate a single unbind has occurred between a
tokenId
andbindId
pair. - MAY be emitted multiple times to indicate multiple unbinds have occurred in a single transaction.
- The
operator
argument MUST be the owner of the asset or an approved asset operator forfrom
. - The
from
argument MUST be the owner of the asset. - The
to
argument MUST be the recipient address of the unbound NFT. - The
tokenId
argument MUST be the NFT being unbound. - The
bindId
argument MUST be the identifier of the asset being unbound from. - The
bindAddress
argument MUST be the contract address of the asset being unbound from. - When burning NFTs bound to an asset, the
Bind
event must be emitted with theto
argument set to0x0
. Bind
events MUST be emitted to reflect delegated ownership revocation changes before calls toonERC721Unbind
.
- SHOULD be emitted to indicate a single unbind has occurred between a
- When emitting an EIP-1155 bindable
Unbind
event:- SHOULD be emitted to indicate an unbind has occurred between a single
tokenId
type andbinderId
pair. - MAY be emitted multiple times to indicate multiple unbinds have occurred in a single transaction, but
UnbindBatch
should be preferred in this case to reduce gas consumption. - The
operator
argument MUST be the owner of the asset or an approved asset operator forfrom
. - The
from
argument MUST be the asset owner. - The
to
argument MUST be the recipient address of the unbound tokens. - The
tokenId
argument MUST be the token type being unbound. - The
amount
argument MUST be the number of tokens of typetokenId
being unbound. - The
bindId
argument MUST be the identifier of the asset being unbound from. - The
bindAddress
argument MUST be the contract address of the asset being unbound from. - When burning NFTs bound to an asset, the
Bind
event must be emitted with theto
argument set to0x0
. Bind
events MUST be emitted to reflect delegated ownership revocation changes before calls toonERC1155Unbind
oronERC1155UnbindBatch
- SHOULD be emitted to indicate an unbind has occurred between a single
BindBatch
& UnbindBatch
event rules:
- When emitting a
BindBatch
event:- SHOULD be emitted to indicate a bind has occurred between multiple
tokenId
andbinderId
pairs. - The
operator
argument MUST be an authorized operator forfrom
. - The
from
argument MUST be the owner of the unbound tokens. - The
to
argument MUST bebinderAddress
(indicates "delegated" bind) or the owner of the bound asset (indicates "legacy" bind). - The
tokenIds
argument MUST be the identifiers of the token types being bound. - The
amounts
argument MUST be the number of tokens for each type intokenIds
being bound. - The
bindIds
argument MUST be the identifiers for all assets being bound to. - The
bindAddress
argument MUST be the contract address of the assets being bound to. - When batch minting NFTs bound to an asset, the
BindBatch
event must be emitted with thefrom
argument set to0x0
. BindBatch
events MUST be emitted to reflect asset-bound ownership delegation before calls toonERC1155BindBatch
- SHOULD be emitted to indicate a bind has occurred between multiple
- When emitting a
batchUnbind
event:- SHOULD be emitted to indicate an unbind has occurred between multiple
tokenId
andbinderId
pairs. - The
operator
argument MUST be an authorized operator or owner of the asset. - The
from
argument MUST be the owner of all assets. - The
to
argument MUST be the recipient address of the unbound tokens. - The
tokenIds
argument MUST be the identifiers of the token types being unbound. - The
amounts
argument MUST be the number of tokens for each typetokenId
being unbound. - The
bindIds
argument MUST be the identifiers for the assets being unbound from. - The
bindAddress
argument MUST be the contract address of the assets being unbound from. - When burning tokens bound to an asset, the
UnbindBatch
event must be emitted with theto
argument set to0x0
. UnbindBatch
events MUST be emitted to reflect asset-delegated ownership changes before calls toonERC1155UnbindBatch
- SHOULD be emitted to indicate an unbind has occurred between multiple
bind
hook rules:
- The
operator
argument MUST be the address calling the bind hook. - The
from
argument MUST be the owner of the NFT or token type being bound.- FROM must be
0x0
for a mint.
- FROM must be
- The
to
argument MUST bebinderAddress
(indicates "delegated" bind) or the owner of the bound asset (indicates "legacy" bind).- The binder contract MAY choose to reject legacy binds.
- For
onERC721Bind
/onERC1155Bind
, thetokenId
argument MUST be the NFT / token type being bound. - For
onERC1155BatchBind
,tokenIds
MUST be the list of token types being bound. - For
onERC1155Bind
, theamount
argument MUST be the number of tokens of typetokenId
being bound. - For
onERC1155BatchBind
, theamounts
argument MUST be a list of the number of tokens of each token type being bound. - For
onERC721Bind
/onERC1155Bind
, thebindId
argument MUST be the identifier for the asset being bound to. - For
onERC1155BatchBind
,bindIds
MUST be the list of assets being bound to. - The
data
argument MUST contain data provided by the caller for the bind with contents unaltered. - The binder contract MAY accept the bind by returning the binder call's designated magic value, in which case the bind MUST complete or revert if any other conditions for success are not met:
onERC721Bind
:bytes4(keccak256("onERC721Bind(address,address,address,uint256,uint256,bytes)"))
onERC1155Bind
:bytes4(keccak256("onERC1155Bind(address,address,address,uint256,uint256,uint256,bytes)"))
onERC1155BindBatch
:bytes4(keccak256("onERC1155BindBatch(address,address,address,uint256[],uint256[],uint256[],bytes)"))
- The binder contract MAY reject the bind by calling revert.
- A return of any other value than the designated magic value MUST result in the transaction being reverted by the caller.
unbind
hook rules:
- The
operator
argument MUST be the address calling the unbind hook. - The
from
argument MUST be the asset owner. - The
to
argument MUST the the recipient address of the unbound NFT or token type.- TO must be
0x0
for a burn.
- TO must be
- For
onERC721Unbind
/onERC1155Unbind
, thetokenId
argument MUST be the NFT / token type being unbound. - For
onERC1155BatchUnbind
,tokenIds
MUST be the list of token types being unbound. - For
onERC1155Unbind
, theamount
argument MUST be the number of tokens of typetokenId
being unbound. - For
onERC1155BatchUnbind
, theamounts
argument MUST be a list of the number of tokens of each token type being unbound. - For
onERC721Bind
/onERC1155Bind
, thebindId
argument MUST be the identifier for the asset being unbound from. - For
onERC1155BatchBind
,bindIds
MUST be the list of assets being unbound from. - The
data
argument MUST contain data provided by the caller for the bind with contents unaltered. - The binder contract MAY accept the unbind by returning the binder call's designated magic value, in which case the unbind MUST complete or MUST revert if any other conditions for success are not met:
onERC721Unbind
:bytes4(keccak256("onERC721Unbind(address,address,address,uint256,uint256,bytes)"))
onERC1155Unbind
:bytes4(keccak256("onERC1155Unbind(address,address,address,uint256,uint256,uint256,bytes)"))
onERC1155UnbindBatch
:bytes4(keccak256("onERC1155UnbindBatch(address,address,address,uint256[],uint256[],uint256[],bytes)"))
- The binder contract MAY reject the bind by calling revert.
- A return of any other value than the designated magic value MUST result in the transaction being reverted by the caller.
Rationale
A backwards-compatible standard for token binding unlocks a new layer of composability for allowing wallets, applications, and protocols to interact with, trade and display bundled assets. One example use-case of this is at Dopamine, where microchipped streetwear garments may be bundled with NFTs such as music, avatars, or digital-twins of the garments themselves, by linking chips to binder smart contracts capable of accepting token binds.
Binding Mechanism
In the “delegated” mode, because token ownership is attributed to the contract address of the asset it is bound to, asset ownership modifications are completely decoupled from bound tokens, making bundled transfers efficient as no state management overhead is imposed. This is the recommended binding mode.
The “legacy” binding mode was included purely for backwards-compatibility purposes, so that existing applications that have yet to integrate the standard can still display bundled tokens out-of-the-box. Here, since token ownership is attributed to the owner of the bound asset, asset ownership modifications are coupled to that of its bound tokens, making bundled transfers inefficient as binder contracts are required to track all bound tokens.
Binder and bindable implementations MAY choose to support both modes of binding.
Transfer Mechanism
One important consideration was whether binds should support transfers or not. Indeed, it would be much simpler for binds and unbinds to be processed only by addresses who owns both the bindable tokens and assets being bound to. Going this route, binds would not require any dependence on transfers, as asset-delegated ownership would not change, and applications could simply transfer the assets themselves following prescribed asset transfer rules. However, this was ruled out due to the lack of flexibility offered, especially around friction added for consumers wishing to bind their tokens to unowned assets.
Backwards Compatibility
The bindable interface is designed to be compatible with existing EIP-721 and EIP-1155 standards.
Reference Implementation
For reference EIP-721 implementations supporting "delegated" and "legacy" binding modes:
For reference EIP-1155 implementations supporting only the "delegated" binding mode:
Security Considerations
Bindable contracts supporting the "legacy" binding mode should be cautious with authorizing transfers once their tokens are bound. These should only be authorized as a result of their bound assets being transferred, and careful consideration must be taken when ensuring account balances are properly processed.
Binder contracts supporting the "legacy" binding mode must ensure that any accepted binds are tracked, and that asset transfers result in proper changing of bound token ownership.
Copyright
Copyright and related rights waived via CC0.