A standard interface, reference implementation and caveats on implementation of Asset-Bound NFTs. This was originally developed for the use with physical assets and goods (e.g. physical collectables, machine parts, …) and has been extended to be suitable for any digital (off-chain!) assets. Asset-Bound NFTs can even be used with abstract assets, such as e.g. club memberships as well.
TLDR
Asset-bound Non-Fungible Tokens anchor a token 1:1 to a (physical or digital) asset and token transfers are authorized through attestation of control over the asset by an oracle
Specification
TLDR, the proposed standard interface:
// SPDX-License-Identifier: MIT OR CC0-1.0
pragma solidity ^0.8.18;
/**
* @title IERC6956 Asset-Bound Non-Fungible Tokens
* @notice Asset-bound Non-Fungible Tokens anchor a token 1:1 to a (physical or digital) asset and token transfers are authorized through attestation of control over the asset
* @dev See https://eips.ethereum.org/EIPS/eip-6956
* Note: The ERC-165 identifier for this interface is 0xa9cf7635
*/
interface IERC6956 {
/** @dev Authorization, typically mapped to authorizationMaps, where each bit indicates whether a particular ERC6956Role is authorized
* Typically used in constructor (hardcoded or params) to set burnAuthorization and approveAuthorization
* Also used in optional updateBurnAuthorization, updateApproveAuthorization, I
*/
enum Authorization {
NONE, // = 0, // None of the above
OWNER, // = (1<<OWNER), // The owner of the token, i.e. the digital representation
ISSUER, // = (1<<ISSUER), // The issuer of the tokens, i.e. this smart contract
ASSET, // = (1<<ASSET), // The asset, i.e. via attestation
OWNER_AND_ISSUER, // = (1<<OWNER) | (1<<ISSUER),
OWNER_AND_ASSET, // = (1<<OWNER) | (1<<ASSET),
ASSET_AND_ISSUER, // = (1<<ASSET) | (1<<ISSUER),
ALL // = (1<<OWNER) | (1<<ISSUER) | (1<<ASSET) // Owner + Issuer + Asset
}
/**
* @notice This emits when approved address for an anchored tokenId is changed or reaffirmed via attestation
* @dev This emits when approveAnchor() is called and corresponds to ERC-721 behavior
* @param owner The owner of the anchored tokenId
* @param approved The approved address, address(0) indicates there is no approved address
* @param anchor The anchor, for which approval has been chagned
* @param tokenId ID (>0) of the anchored token
*/
event AnchorApproval(address indexed owner, address approved, bytes32 indexed anchor, uint256 tokenId);
/**
* @notice This emits when the ownership of any anchored NFT changes by any mechanism
* @dev This emits together with tokenId-based ERC-721.Transfer and provides an anchor-perspective on transfers
* @param from The previous owner, address(0) indicate there was none.
* @param to The new owner, address(0) indicates the token is burned
* @param anchor The anchor which is bound to tokenId
* @param tokenId ID (>0) of the anchored token
*/
event AnchorTransfer(address indexed from, address indexed to, bytes32 indexed anchor, uint256 tokenId);
/**
* @notice This emits when an attestation has been used indicating no second attestation with the same attestationHash will be accepted
* @param to The to address specified in the attestation
* @param anchor The anchor specificed in the attestation
* @param attestationHash The hash of the attestation, see ERC-6956 for details
* @param totalUsedAttestationsForAnchor The total number of attestations already used for the particular anchor
*/
event AttestationUse(address indexed to, bytes32 indexed anchor, bytes32 indexed attestationHash, uint256 totalUsedAttestationsForAnchor);
/**
* @notice This emits when the trust-status of an oracle changes.
* @dev Trusted oracles must explicitely be specified.
* If the last event for a particular oracle-address indicates it's trusted, attestations from this oracle are valid.
* @param oracle Address of the oracle signing attestations
* @param trusted indicating whether this address is trusted (true). Use (false) to no longer trust from an oracle.
*/
event OracleUpdate(address indexed oracle, bool indexed trusted);
/**
* @notice Returns the 1:1 mapped anchor for a tokenId
* @param tokenId ID (>0) of the anchored token
* @return anchor The anchor bound to tokenId, 0x0 if tokenId does not represent an anchor
*/
function anchorByToken(uint256 tokenId) external view returns (bytes32 anchor);
/**
* @notice Returns the ID of the 1:1 mapped token of an anchor.
* @param anchor The anchor (>0x0)
* @return tokenId ID of the anchored token, 0 if no anchored token exists
*/
function tokenByAnchor(bytes32 anchor) external view returns (uint256 tokenId);
/**
* @notice The number of attestations already used to modify the state of an anchor or its bound tokens
* @param anchor The anchor(>0)
* @return attestationUses The number of attestation uses for a particular anchor, 0 if anchor is invalid.
*/
function attestationsUsedByAnchor(bytes32 anchor) view external returns (uint256 attestationUses);
/**
* @notice Decodes and returns to-address, anchor and the attestation hash, if the attestation is valid
* @dev MUST throw when
* - Attestation has already been used (an AttestationUse-Event with matching attestationHash was emitted)
* - Attestation is not signed by trusted oracle (the last OracleUpdate-Event for the signer-address does not indicate trust)
* - Attestation is not valid yet or expired
* - [if IERC6956AttestationLimited is implemented] attestationUsagesLeft(attestation.anchor) <= 0
* - [if IERC6956ValidAnchors is implemented] validAnchors(data) does not return true.
* @param attestation The attestation subject to the format specified in ERC-6956
* @param data Optional additional data, may contain proof as the first abi-encoded argument when IERC6956ValidAnchors is implemented
* @return to Address where the ownership of an anchored token or approval shall be changed to
* @return anchor The anchor (>0)
* @return attestationHash The attestation hash computed on-chain as `keccak256(attestation)`
*/
function decodeAttestationIfValid(bytes memory attestation, bytes memory data) external view returns (address to, bytes32 anchor, bytes32 attestationHash);
/**
* @notice Indicates whether any of ASSET, OWNER, ISSUER is authorized to burn
*/
function burnAuthorization() external view returns(Authorization burnAuth);
/**
* @notice Indicates whether any of ASSET, OWNER, ISSUER is authorized to approve
*/
function approveAuthorization() external view returns(Authorization approveAuth);
/**
* @notice Corresponds to transferAnchor(bytes,bytes) without additional data
* @param attestation Attestation, refer ERC-6956 for details
*/
function transferAnchor(bytes memory attestation) external;
/**
* @notice Changes the ownership of an NFT mapped to attestation.anchor to attestation.to address.
* @dev Permissionless, i.e. anybody invoke and sign a transaction. The transfer is authorized through the oracle-signed attestation.
* - Uses decodeAttestationIfValid()
* - When using a centralized "gas-payer" recommended to implement IERC6956AttestationLimited.
* - Matches the behavior of ERC-721.safeTransferFrom(ownerOf[tokenByAnchor(attestation.anchor)], attestation.to, tokenByAnchor(attestation.anchor), ..) and mint an NFT if `tokenByAnchor(anchor)==0`.
* - Throws when attestation.to == ownerOf(tokenByAnchor(attestation.anchor))
* - Emits AnchorTransfer
*
* @param attestation Attestation, refer EIP-6956 for details
* @param data Additional data, may be used for additional transfer-conditions, may be sent partly or in full in a call to safeTransferFrom
*
*/
function transferAnchor(bytes memory attestation, bytes memory data) external;
/**
* @notice Corresponds to approveAnchor(bytes,bytes) without additional data
* @param attestation Attestation, refer ERC-6956 for details
*/
function approveAnchor(bytes memory attestation) external;
/**
* @notice Approves attestation.to the token bound to attestation.anchor. .
* @dev Permissionless, i.e. anybody invoke and sign a transaction. The transfer is authorized through the oracle-signed attestation.
* - Uses decodeAttestationIfValid()
* - When using a centralized "gas-payer" recommended to implement IERC6956AttestationLimited.
* - Matches the behavior of ERC-721.approve(attestation.to, tokenByAnchor(attestation.anchor)).
* - Throws when ASSET is not authorized to approve.
*
* @param attestation Attestation, refer EIP-6956 for details
*/
function approveAnchor(bytes memory attestation, bytes memory data) external;
/**
* @notice Corresponds to burnAnchor(bytes,bytes) without additional data
* @param attestation Attestation, refer ERC-6956 for details
*/
function burnAnchor(bytes memory attestation) external;
/**
* @notice Burns the token mapped to attestation.anchor. Uses ERC-721._burn.
* @dev Permissionless, i.e. anybody invoke and sign a transaction. The transfer is authorized through the oracle-signed attestation.
* - Uses decodeAttestationIfValid()
* - When using a centralized "gas-payer" recommended to implement IERC6956AttestationLimited.
* - Throws when ASSET is not authorized to burn
*
* @param attestation Attestation, refer EIP-6956 for details
*/
function burnAnchor(bytes memory attestation, bytes memory data) external;
}
Abstract
This standard allows to integrate physical and digital ASSETS without signing capabilities into dApps/web3 by extending ERC-721.
An ASSET, for example a physical object, is marked with a uniquely identifiable ANCHOR. The ANCHOR is bound in a secure and inseperable manner 1:1 to an NFT on-chain - over the complete life cylce of the ASSET.
Through an ATTESTATION, an ORACLE testifies that a particular ASSET associated with an ANCHOR has been CONTROLLED when defining the to
-address for certain operations (mint, transfer, burn, approve, …). The ORACLE signs the ATTESTATION off-chain. The operations are authorized through verifying on-chain that ATTESTATION has been signed by a trusted ORACLE. Note that authorization is solely provided through the ATTESTATION, or in other words, through PROOF-OF-CONTROL over the ASSET. The controller of the ASSET is guaranteed to be the controller of the Asset-Bound NFT.
The proposed ATTESTATION-authorized operations such as transferAnchor(attestation)
are permissionless, meaning neither the current owner (from
-address) nor the receiver (to
-address) need to sign.
Figure 1 shows the data flow of an ASSET-BOUND NFT transfer through a simplified example system employing the proposed standard. The system is utilizing a smartphone as user-device to interact with a physical ASSET and specify the to
-address.
Illustrative use-case
This corresponds to the Posession based digital twin use case outlined in the EIP.
For illustration and as shown in this video, I decided to use a some artwork from my baby-girl, equip it with anchor-technology and hang that artwork in an NFT-Frame in the alpha-version of HELIX Metaverse
Left picture on the wall: Artwork from my daughter represented through Asset-Bound NFT
Right picture on the wall: Lighter from Lukas, the co-author, represented through Asset-Bound NFT
Video of the complete system in action: https://youtu.be/bz-sgusrcVA
Rationale (Shortened!)
ERC-721 outlines that “NFTs can represent ownership over digital or physical assets”. ERC-721 excels in this task when used to represent ownership over digital, on-chain assets, i.e. when the asset is “holding a token of a specific contract” or the asset is an NFT’s metadata. However, we do see the inherent problem of non-enforcability, when ERC-721 is used without further adaptions to represent off-chain ASSETs, in particular physical objects or goods such as physical collectibles, cars, rental agreements involving physical goods etc.
When an off-chain ASSET’s ownership or posession changes, this shall be refleced on-chain through the corresponding NFT. Over an ASSET’s lifecycle, the ASSET’s ownership and posession state changes multiple, sometimes thousands, of times. Each of those state changes may result in shifting obligations and privileges for the involved parties. Therefore tokenization of an ASSET without enforcably anchoring the ASSET’s associated obligation and properties to the token is not complete. Nowadays, off-chain ASSETs are often “anchored” through adding an ASSET-identifier to a NFT’s metadata. Metadata is off-chain. The majority of implementations completely neglect that metadata can be changed off-chain. More serious implementations strive to preserve integrity by e.g. hashing metadata and storing the hash mapped to the tokenId on-chain. However, this approach does not allow for use-case, where metadata besides the asset-identifier, e.g. traits, “hours played”, … shall be mutable or evolvable.
In the proposed EIP we suggest to map an ASSET identifier (ANCHOR
) on-chain to tokenId
s.
Even if a (physical) ASSET is mass produced with fungible characteristics, each ASSET has an individual property graph and thus shall be represented in a non-fungible way. Hence this EIP follows the design decision that ASSET (represented via a unique asset identifier called ANCHOR) and token are always mapped 1-1 and not 1-N, so that a token represents the individual property graph of the ASSET.
In this EIP we propose a standard and two optional extensions that cover tokenization in ownership- and posession-based use cases. We will denote the standard and it’s extensions through their interface names, i.e. the standard is IERCxxxx, while the extensions are IERCxxxxAttestationLimited and IERCxxxFloatable.
Current status
We have gathered a vast amount of use-case feedback for use with physical assets. Main interested industries (with some already implementations exist)
- Phygital Collectables [References still under NDA unfortunately]
- Representing physical or phygital objects in Metaverses and Gaming (aka Digital Twin)
- Wine & Spirits, e.g. some of our early customers Crypto Gin or especially nice use case with Dreissigacker wine, where you can digitally trade the NFT representing a bottle of wine, that’s still aging in the winery’s cellar for years.
- Logistics, especially for parts or spare-parts in automotive (warranty, liability, … )
- Players from DeFi have expressed great interested as it enables or improves use-cases where physical goods are used as collateral. We ensured the proposed EIP can be extended with Lock-/Lien-Mechanisms on assets, such as ERC-5058: Lockable Non-Fungible Tokens or ERC-5753: Lockable Extension for EIP-721. Also, ERC-5604: NFT Lien.
An earlier version of the Reference Implementation (using a transfer mechnism listed as “Alternatives considered” has already been security-reviewed successfully, the present EIP Draft will be reviewed later as well)
We are very much looking forward to your technical and use-case input. We are in particular looking for comments and discussion on
- Use-cases with digital off-chain assets, such as [Insert creative use-case here]
- Use-cases with abstract off-chain assets, such as memberships, …
- Implementation aspects
- Security concerns