forked from ethereum/ERCs
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add ERC: Privileged Non-Fungible Tokens Tied To RWA
Merged by EIP-Bot.
- Loading branch information
1 parent
62a7f8b
commit 034ed5b
Showing
4 changed files
with
338 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
--- | ||
eip: 7765 | ||
title: Privileged Non-Fungible Tokens Tied To RWA | ||
description: An interface extending ERC-721 representing real world assets that users can exercise privileges with NFTs. | ||
author: frank (@frankmint2024) <[email protected]> | ||
discussions-to: https://ethereum-magicians.org/t/erc7765-privileged-non-fungible-tokens-tied-to-rwa/21048 | ||
status: Draft | ||
type: Standards Track | ||
category: ERC | ||
created: 2024-08-20 | ||
requires: 165, 721 | ||
--- | ||
|
||
## Abstract | ||
|
||
This EIP defines an interface to carry a real world asset with some privileges that can be exercised by the holder of the corresponding NFT. The EIP standardizes the interface for non-fungible tokens representing real world assets with privileges to be exercised, such as products sold onchain which can be redeemed in the real world. | ||
|
||
And the privileges we describe here specifically refer to the rights and interests bound to the RWA NFT that can be executed by the holder in the real world. | ||
|
||
## Motivation | ||
|
||
NFTs bound to real-world assets sometimes need to carry certain privileges that can be exercised by the holder. Users can initiate transactions onchain to specify the exercise of a certain privilege, thereby achieving real-world privileges that directly map the onchain privilege through subsequent operations. For example, if a certain product such as a pair of shoes is sold onchain in the representation of NFT, the NFT holder can exercise the privilege of exchanging physical shoes offchain, to achieve the purpose of interoperability between the blockchain and the real world. | ||
|
||
Having a standard interface enables interoperability for services, clients, UI, and inter-contract functionalities on top of this use-case. | ||
|
||
## Specification | ||
|
||
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174. | ||
|
||
This standard inherits the [ERC-721](./eip-721.md) NFT token standard for all transfer and approval logic. All transfer and approval functions are inherited from this token standard without changes. Additionally, this standard also inherits the ERC-721 Metadata standards for name, symbol, and metadata URI lookup. | ||
|
||
Any compliant contract following this EIP **MUST** implement the following interface: | ||
|
||
``` | ||
pragma solidity >=0.7.0 <0.9.0; | ||
/// @title ERC-7765 Privileged Non-Fungible Tokens Tied To Real World Assets | ||
/// @dev See https://eips.ethereum.org/EIPS/eip-7765 | ||
interface IERC7765 /* is IERC721, IERC165 */ { | ||
/// @notice This event emitted when a specific privilege of a token is successfully exercised. | ||
/// @param _operator the address who exercised the privilege. | ||
/// @param _to the address to benefit from the privilege. | ||
/// @param _tokenId the NFT tokenID. | ||
/// @param _privilegeId the ID of the privileges. | ||
event PrivilegeExercised( | ||
address indexed _operator, | ||
address indexed _to, | ||
uint256 indexed _tokenId, | ||
uint256 _privilegeId | ||
); | ||
/// @notice This function exercise a specific privilege of a token. | ||
/// @dev Throws if `_privilegeId` is not a valid privilegeId. | ||
/// @param _to the address to benefit from the privilege. | ||
/// @param _tokenId the NFT tokenID. | ||
/// @param _privilegeId the ID of the privileges. | ||
/// @param _data extra data passed in for extra message or future extension. | ||
function exercisePrivilege( | ||
address _to, | ||
uint256 _tokenId, | ||
uint256 _privilegeId, | ||
bytes calldata _data | ||
) external; | ||
/// @notice This function is to check whether a specific privilege of a token can be exercised. | ||
/// @dev Throws if `_privilegeId` is not a valid privilegeId. | ||
/// @param _to the address to benefit from the privilege. | ||
/// @param _tokenId the NFT tokenID. | ||
/// @param _privilegeId the ID of the privileges. | ||
function isExercisable( | ||
address _to, | ||
uint256 _tokenId, | ||
uint256 _privilegeId | ||
) external view returns (bool _exercisable); | ||
/// @notice This function is to check whether a specific privilege of a token has been exercised. | ||
/// @dev Throws if `_privilegeId` is not a valid privilegeId. | ||
/// @param _to the address to benefit from the privilege. | ||
/// @param _tokenId the NFT tokenID. | ||
/// @param _privilegeId the ID of the privileges. | ||
function isExercised( | ||
address _to, | ||
uint256 _tokenId, | ||
uint256 _privilegeId | ||
) external view returns (bool _exercised); | ||
/// @notice This function is to list all privilegeIds of a token. | ||
/// @param _tokenId the NFT tokenID. | ||
function getPrivilegeIds( | ||
uint256 _tokenId | ||
) external view returns (uint256[] memory privilegeIds); | ||
} | ||
``` | ||
|
||
The function `exercisePrivilege` performs the exercise action to a specific privilege of a token. If succeeds, it is expected to emit a `PrivilegeExercised` event. | ||
|
||
The function `getPrivilegeIds` provides a way to manage the binding relationship between NFTs and privilegeIds. | ||
|
||
The **metadata extension** is OPTIONAL for [EIP-7765](./eip-7765.md) smart contracts. This allows your smart contract to be interrogated for its details about the privileges which your NFTs carry. | ||
|
||
``` | ||
pragma solidity >=0.7.0 <0.9.0; | ||
/// @title ERC-7765 Privileged Non-Fungible Tokens Tied To Real World Assets, optional metadata extension | ||
/// @dev See https://eips.ethereum.org/EIPS/eip-7765 | ||
interface IERC7765Metadata /* is IERC7765 */ { | ||
/// @notice A distinct Uniform Resource Identifier (URI) for a given privilegeId. | ||
/// @dev Throws if `_privilegeId` is not a valid privilegeId. URIs are defined in RFC | ||
/// 3986. The URI may point to a JSON file that conforms to the "ERC-7765 | ||
/// Metadata JSON Schema". | ||
function privilegeURI(uint256 _privilegeId) external view returns (string memory); | ||
} | ||
``` | ||
|
||
This is the “EIP-7765 Metadata JSON Schema” referenced above. | ||
|
||
``` | ||
{ | ||
"title": "Privilege Metadata", | ||
"type": "object", | ||
"properties": { | ||
"name": { | ||
"type": "string", | ||
"description": "Identifies the specific privilege." | ||
}, | ||
"description": { | ||
"type": "string", | ||
"description": "Describes the specific privilege." | ||
}, | ||
"resource": { | ||
"type": "string", | ||
"description": "A URI pointing to a resource representing the specific privilege." | ||
} | ||
} | ||
} | ||
``` | ||
|
||
`IERC7765Metadata` provides specifications for obtaining metadata information of privileges. A contract that implements `IERC7765Metadata` **SHALL** also implement `IERC7765`. | ||
|
||
## Rationale | ||
|
||
1. With the `PrivilegeExercised` event emitted onchain, we can determine that the user has confirmed the exercise of this privilege, so as to implement the privilege in the real world. | ||
|
||
2. We choose to include an address `_to` for functions `exercisePrivilege`, `isExercisable` and `isExercised` so that a specific privilege of an NFT MAY be exercised for someone who will benefit from it other than the NFT holder nor the transaction initiator. And This EIP doesn't assume who has the power to perform this action, it's totally decided by the developers who are using this standard. | ||
|
||
3. We choose to include an extra `_data` field to function `exercisePrivilege` for extra message or future extension. For example, developers can use `_data` to exercise a privilege that takes effect directly onchain such as direct distribution of cryptocurrency assets. | ||
|
||
4. The boolean view functions of `isExercisable` and `isExercised` can be used to check whether a specific privilege of an NFT can be exercisable or has been exercised to the `_to` address. | ||
|
||
## Backwards Compatibility | ||
|
||
This standard is an extension of ERC-721. It is fully compatible with both of the commonly used optional extensions (`IERC721Metadata` and `IERC721Enumerable`) mentioned in the ERC-721 standard. | ||
|
||
## Reference Implementation | ||
|
||
The reference implementation of Privileged NFTs can be found [Here](../assets/eip-7765/contracts/ERC7765Example.sol). | ||
|
||
## Security Considerations | ||
|
||
Compliant contracts should pay attention to the storage to the states of the privileges. The contract should properly handle the state transition of each privilege of each NFT, clearly showing that each privilege is exercisable or has been exercised. | ||
|
||
Compliant contracts should also carefully define access control, particularly whether any EOA or contract account may or may not call `exercisePrivilege` function in any use case. Security audits and tests should be used to verify that the access control to the `exercisePrivilege` function behaves as expected. | ||
|
||
## Copyright | ||
|
||
Copyright and related rights waived via [CC0](../LICENSE.md). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity >=0.7.0 <0.9.0; | ||
|
||
import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; | ||
import "@openzeppelin/contracts/utils/Strings.sol"; | ||
import "@openzeppelin/contracts/utils/Base64.sol"; | ||
import "./interfaces/IERC7765.sol"; | ||
import "./interfaces/IERC7765Metadata.sol"; | ||
|
||
contract ERC7765Example is ERC721, IERC7765, IERC7765Metadata { | ||
uint256[] private privilegeIdsArr = [1, 2]; | ||
mapping(uint256 privilegeId => bool) private privilegeIds; | ||
|
||
mapping(uint256 tokenId => mapping(uint256 privilegeId => address to)) privilegeStates; | ||
|
||
constructor(string memory name_, string memory symbol_) ERC721(name_, symbol_) { | ||
privilegeIds[1] = true; | ||
privilegeIds[2] = true; | ||
} | ||
|
||
/// @notice This function exercised a specific privilege of a token if succeeds. | ||
/// @dev Throws if `_privilegeId` is not a valid privilegeId. | ||
/// @param _to the address to benifit from the privilege. | ||
/// @param _tokenId the NFT tokenID. | ||
/// @param _privilegeId the ID of the privileges. | ||
/// @param _data extra data passed in for extra message or future extension. | ||
function exercisePrivilege(address _to, uint256 _tokenId, uint256 _privilegeId, bytes calldata _data) external { | ||
if (_to == address(0)) { | ||
_to = msg.sender; | ||
} | ||
|
||
require(ownerOf(_tokenId) == msg.sender, "Token not exist"); | ||
require(privilegeIds[_privilegeId], "Privilege not exist"); | ||
require(privilegeStates[_tokenId][_privilegeId] == address(0), "Privilege already exercised"); | ||
|
||
// Optional to deal with _data | ||
dealWithData(_data); | ||
|
||
privilegeStates[_tokenId][_privilegeId] = _to; | ||
emit PrivilegeExercised(msg.sender, _to, _tokenId, _privilegeId); | ||
} | ||
|
||
function dealWithData(bytes calldata _data) internal { | ||
// | ||
} | ||
|
||
/// @notice This function is to check whether a specific privilege of a token can be exercised. | ||
/// @dev Throws if `_privilegeId` is not a valid privilegeId. | ||
/// @param _to the address to benifit from the privilege. | ||
/// @param _tokenId the NFT tokenID. | ||
/// @param _privilegeId the ID of the privileges. | ||
function isExercisable(address _to, uint256 _tokenId, uint256 _privilegeId) | ||
external | ||
view | ||
returns (bool _exercisable) | ||
{ | ||
require(_to != address(0), "Illegal _to address"); | ||
require(ownerOf(_tokenId) != address(0), "Token not exist"); | ||
require(privilegeIds[_privilegeId], "Privilege not exist"); | ||
|
||
return privilegeStates[_tokenId][_privilegeId] == address(0); | ||
} | ||
|
||
/// @notice This function is to check whether a specific privilege of a token has been exercised. | ||
/// @dev Throws if `_privilegeId` is not a valid privilegeId. | ||
/// @param _to the address to benifit from the privilege. | ||
/// @param _tokenId the NFT tokenID. | ||
/// @param _privilegeId the ID of the privileges. | ||
function isExercised(address _to, uint256 _tokenId, uint256 _privilegeId) external view returns (bool _exercised) { | ||
require(_to != address(0), "Illegal _to address"); | ||
require(ownerOf(_tokenId) != address(0), "Token not exist"); | ||
require(privilegeIds[_privilegeId], "Privilege not exist"); | ||
|
||
return privilegeStates[_tokenId][_privilegeId] == _to; | ||
} | ||
|
||
/// @notice This function is to list all privilegeIds of a token. | ||
/// @param _tokenId the NFT tokenID. | ||
function getPrivilegeIds(uint256 _tokenId) external view returns (uint256[] memory) { | ||
require(ownerOf(_tokenId) != address(0), "Token not exist"); | ||
return privilegeIdsArr; | ||
} | ||
|
||
/// @notice A distinct Uniform Resource Identifier (URI) for a given privilegeId. | ||
/// @dev Throws if `_privilegeId` is not a valid privilegeId. URIs are defined in RFC | ||
/// 3986. The URI may point to a JSON file that conforms to the "ERC-7765 | ||
/// Metadata JSON Schema". | ||
function privilegeURI(uint256 _privilegeId) external view returns (string memory) { | ||
require(privilegeIds[_privilegeId], "Privilege not exist"); | ||
|
||
return string( | ||
abi.encodePacked("data:application/json;base64,", Base64.encode(bytes(privilegeURIJSON(_privilegeId)))) | ||
); | ||
} | ||
|
||
function privilegeURIJSON(uint256 _privilegeId) internal pure returns (string memory) { | ||
return string( | ||
abi.encodePacked( | ||
"{", | ||
'"name": "Privilege #', | ||
Strings.toString(_privilegeId), | ||
'",', | ||
'"description": "description -', | ||
Strings.toString(_privilegeId), | ||
'",', | ||
'"resource": "ipfs://abc/', | ||
Strings.toString(_privilegeId), | ||
'"}' | ||
) | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity >=0.7.0 <0.9.0; | ||
|
||
/// @title ERC-7765 Privileged Non-Fungible Tokens Tied To Real World Assets | ||
/// @dev See https://eips.ethereum.org/EIPS/eip-7765 | ||
interface IERC7765 /* is IERC721, IERC165 */ { | ||
/// @notice This event emitted when a specific privilege of a token is successfully exercised. | ||
/// @param _operator the address who exercised the privilege. | ||
/// @param _to the address to benefit from the privilege. | ||
/// @param _tokenId the NFT tokenID. | ||
/// @param _privilegeId the ID of the privileges. | ||
event PrivilegeExercised( | ||
address indexed _operator, address indexed _to, uint256 indexed _tokenId, uint256 _privilegeId | ||
); | ||
|
||
/// @notice This function exercise a specific privilege of a token. | ||
/// @dev Throws if `_privilegeId` is not a valid privilegeId. | ||
/// @param _to the address to benefit from the privilege. | ||
/// @param _tokenId the NFT tokenID. | ||
/// @param _privilegeId the ID of the privileges. | ||
/// @param _data extra data passed in for extra message or future extension. | ||
function exercisePrivilege(address _to, uint256 _tokenId, uint256 _privilegeId, bytes calldata _data) external; | ||
|
||
/// @notice This function is to check whether a specific privilege of a token can be exercised. | ||
/// @dev Throws if `_privilegeId` is not a valid privilegeId. | ||
/// @param _to the address to benefit from the privilege. | ||
/// @param _tokenId the NFT tokenID. | ||
/// @param _privilegeId the ID of the privileges. | ||
function isExercisable(address _to, uint256 _tokenId, uint256 _privilegeId) | ||
external | ||
view | ||
returns (bool _exercisable); | ||
|
||
/// @notice This function is to check whether a specific privilege of a token has been exercised. | ||
/// @dev Throws if `_privilegeId` is not a valid privilegeId. | ||
/// @param _to the address to benefit from the privilege. | ||
/// @param _tokenId the NFT tokenID. | ||
/// @param _privilegeId the ID of the privileges. | ||
function isExercised(address _to, uint256 _tokenId, uint256 _privilegeId) external view returns (bool _exercised); | ||
|
||
/// @notice This function is to list all privilegeIds of a token. | ||
/// @param _tokenId the NFT tokenID. | ||
function getPrivilegeIds(uint256 _tokenId) external view returns (uint256[] memory privilegeIds); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity >=0.7.0 <0.9.0; | ||
|
||
/// @title ERC-7765 Privileged Non-Fungible Tokens Tied To Real World Assets, optional metadata extension | ||
/// @dev See https://eips.ethereum.org/EIPS/eip-7765 | ||
interface IERC7765Metadata /* is IERC7765 */ { | ||
/// @notice A distinct Uniform Resource Identifier (URI) for a given privilegeId. | ||
/// @dev Throws if `_privilegeId` is not a valid privilegeId. URIs are defined in RFC | ||
/// 3986. The URI may point to a JSON file that conforms to the "ERC-7765 | ||
/// Metadata JSON Schema". | ||
function privilegeURI(uint256 _privilegeId) external view returns (string memory); | ||
} |