forked from ethereum/EIPs
-
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.
EIP5007: TimeNFT, ERC-721 Time Extension (ethereum#5007)
* add eip-TimeNFT.md * add Reference Implementation * update code * update Implementation code * add test code * update test code * update code * update test code * rename file name * remove spaces * update Reference Implementation * update title * update by SamWilsn's suggestion * update Backwards Compatibility * update code * simplify EIP5007 * remove empty lines * udpate readme * update readme * update Test Cases * fix link in readme.md * fix link in readme.md * remove function isValidNow * update Copyright * update copyright * change EIP5007 to EIP-5007 Co-authored-by: anders <> Co-authored-by: Anders <anders@emojidao.org>
- Loading branch information
1 parent
a12ffed
commit e3b369c
Showing
10 changed files
with
369 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,73 @@ | ||
--- | ||
eip: 5007 | ||
title: TimeNFT, ERC-721 Time Extension | ||
description: Add start time and end time to ERC-721 tokens. | ||
author: Anders (@0xanders), Lance (@LanceSnow), Shrug <shrug@emojidao.org> | ||
discussions-to: https://ethereum-magicians.org/t/eip5007-erc721-time-extension/8924 | ||
status: Draft | ||
type: Standards Track | ||
category: ERC | ||
created: 2022-04-13 | ||
requires: 165, 721 | ||
--- | ||
|
||
## Abstract | ||
|
||
This standard is an extension of [ERC-721](./eip-721.md). It proposes some additional functions (`startTime`, `endTime`) to help with on-chain time management. | ||
|
||
## Motivation | ||
|
||
Some NFTs have a defined usage period and cannot be used outside of that period. With traditional NFTs that do not include time information, if you want to mark a token as invalid or enable it at a specific time, you need to actively submit a transaction—a process both cumbersome and expensive. | ||
|
||
Some existing NFTs contain time functions, but their interfaces are not consistent, so it is difficult to develop third-party platforms for them. | ||
|
||
By introducing these functions (`startTime`, `endTime`), it is possible to enable and disable NFT automatically on chain. | ||
|
||
## Specification | ||
|
||
The keywords "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. | ||
|
||
```solidity | ||
interface IERC5007 /* is IERC721 */ { | ||
/// @notice Get the start time of the NFT | ||
/// @dev Throws if `tokenId` is not valid NFT | ||
/// @param tokenId The tokenId of the NFT | ||
/// @return The start time of the NFT | ||
function startTime(uint256 tokenId) external view returns (uint64); | ||
/// @notice Get the end time of the NFT | ||
/// @dev Throws if `tokenId` is not valid NFT | ||
/// @param tokenId The tokenId of the NFT | ||
/// @return The end time of the NFT | ||
function endTime(uint256 tokenId) external view returns (uint64); | ||
} | ||
``` | ||
|
||
The `supportsInterface` method MUST return `true` when called with `0x7a0cdf92`. | ||
|
||
## Rationale | ||
|
||
### Time Data Type | ||
|
||
The max value of `uint64` is 18446744073709551615, timestamp 18446744073709551615 is about year 584942419325. `uint256` is too big for some software such as MySQL, Elastic Search, and `uint64` is natively supported on mainstream programming languages. | ||
|
||
## Backwards Compatibility | ||
|
||
As mentioned in the specifications section, this standard can be fully ERC-721 compatible by adding an extension function set. | ||
|
||
## Test Cases | ||
|
||
Test cases are included in [test.js](../assets/eip-5007/test/test.js). | ||
|
||
See [README.md](../assets/eip-5007/README.md) for how to run the test cases. | ||
|
||
## Reference Implementation | ||
See [ERC5007.sol](../assets/eip-5007/contracts/ERC5007.sol). | ||
|
||
## Security Considerations | ||
|
||
No security issues found. | ||
|
||
## 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,2 @@ | ||
node_modules | ||
package-lock.json |
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,15 @@ | ||
# EIP-5007 | ||
This standard is an extension of [ERC-721](../../EIPS/eip-721.md). It proposes some additional functions (`startTime`, `endTime`) to help with on-chain time management. | ||
|
||
## Tools | ||
* [Truffle](https://truffleframework.com/) - a development framework for Ethereum | ||
|
||
## Install | ||
``` | ||
npm install | ||
``` | ||
|
||
## Test | ||
``` | ||
truffle test | ||
``` |
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,63 @@ | ||
// SPDX-License-Identifier: CC0 | ||
pragma solidity ^0.8.0; | ||
|
||
import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; | ||
import "./IERC5007.sol"; | ||
|
||
contract ERC5007 is ERC721, IERC5007 { | ||
|
||
struct TimeNftInfo { | ||
uint64 startTime; | ||
uint64 endTime; | ||
} | ||
|
||
mapping(uint256 /* tokenId */ => TimeNftInfo) internal _timeNftMapping; | ||
|
||
constructor(string memory name_, string memory symbol_) ERC721(name_, symbol_){ | ||
} | ||
|
||
/// @notice Get the start time of the token | ||
/// @dev Throws if `tokenId` is not valid token | ||
/// @param tokenId The tokenId of the token | ||
/// @return The start time of the token | ||
function startTime(uint256 tokenId) public view virtual override returns (uint64) { | ||
require(_exists(tokenId),"invalid tokenId"); | ||
return _timeNftMapping[tokenId].startTime; | ||
} | ||
|
||
/// @notice Get the end time of the token | ||
/// @dev Throws if `tokenId` is not valid token | ||
/// @param tokenId The tokenId of the token | ||
/// @return The end time of the token | ||
function endTime(uint256 tokenId) public view virtual override returns (uint64) { | ||
require(_exists(tokenId),"invalid tokenId"); | ||
return _timeNftMapping[tokenId].endTime; | ||
} | ||
|
||
|
||
/// @notice mint a new time NFT | ||
/// @param to_ The owner of the new token | ||
/// @param id_ The id of the new token | ||
/// @param startTime_ The start time of the new token | ||
/// @param endTime_ The end time of the new token | ||
function _mintTimeNft(address to_, uint256 id_, uint64 startTime_, uint64 endTime_) internal virtual { | ||
_mint(to_, id_); | ||
|
||
TimeNftInfo storage info = _timeNftMapping[id_]; | ||
info.startTime = startTime_; | ||
info.endTime = endTime_; | ||
} | ||
|
||
|
||
/// @notice burn a time NFT | ||
/// @param tokenId The id of the token | ||
function _burn(uint256 tokenId) internal virtual override{ | ||
super._burn(tokenId); | ||
delete _timeNftMapping[tokenId]; | ||
} | ||
|
||
/// @dev See {IERC165-supportsInterface}. | ||
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { | ||
return interfaceId == type(IERC5007).interfaceId || super.supportsInterface(interfaceId); | ||
} | ||
} |
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,23 @@ | ||
// SPDX-License-Identifier: CC0 | ||
pragma solidity ^0.8.0; | ||
|
||
import "./ERC5007.sol"; | ||
|
||
contract ERC5007Demo is ERC5007{ | ||
|
||
constructor(string memory name_, string memory symbol_) ERC5007(name_, symbol_){ | ||
} | ||
|
||
/// @notice mint a new original time NFT | ||
/// @param to_ The owner of the new token | ||
/// @param id_ The id of the new token | ||
/// @param startTime_ The start time of the new token | ||
/// @param endTime_ The end time of the new token | ||
function mint(address to_, uint256 id_, uint64 startTime_, uint64 endTime_) public { | ||
_mintTimeNft(to_, id_, startTime_, endTime_); | ||
} | ||
|
||
function getInterfaceId() public pure returns (bytes4) { | ||
return type(IERC5007).interfaceId ; | ||
} | ||
} |
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,17 @@ | ||
// SPDX-License-Identifier: CC0 | ||
|
||
pragma solidity ^0.8.0; | ||
|
||
interface IERC5007 /* is IERC721 */ { | ||
/// @notice Get the start time of the NFT | ||
/// @dev Throws if `tokenId` is not valid NFT | ||
/// @param tokenId The tokenId of the NFT | ||
/// @return The start time of the NFT | ||
function startTime(uint256 tokenId) external view returns (uint64); | ||
|
||
/// @notice Get the end time of the NFT | ||
/// @dev Throws if `tokenId` is not valid NFT | ||
/// @param tokenId The tokenId of the NFT | ||
/// @return The end time of the NFT | ||
function endTime(uint256 tokenId) external view returns (uint64); | ||
} |
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,6 @@ | ||
const ERC5007Demo = artifacts.require("ERC5007Demo"); | ||
|
||
module.exports = function (deployer) { | ||
deployer.deploy(ERC5007Demo,'ERC5007Demo','ERC5007Demo'); | ||
}; | ||
|
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,20 @@ | ||
{ | ||
"name": "ERC5007", | ||
"version": "1.0.0", | ||
"description": "", | ||
"main": "truffle-config.js", | ||
"directories": { | ||
"test": "test" | ||
}, | ||
"scripts": {}, | ||
"keywords": [], | ||
"author": "", | ||
"license": "ISC", | ||
"dependencies": { | ||
"@openzeppelin/contracts": "^4.3.3", | ||
"@types/chai": "^4.3.0", | ||
"@types/mocha": "^9.1.0", | ||
"bignumber.js": "^9.0.1", | ||
"chai": "^4.3.6" | ||
} | ||
} |
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,33 @@ | ||
const { assert } = require("chai"); | ||
|
||
const { BigNumber } = require("bignumber.js") | ||
|
||
const ERC5007Demo = artifacts.require("ERC5007Demo"); | ||
|
||
contract("test ERC5007", async accounts => { | ||
|
||
it("test TimeNFT", async () => { | ||
const Alice = accounts[0]; | ||
|
||
const instance = await ERC5007Demo.deployed("ERC5007Demo", "ERC5007Demo"); | ||
const demo = instance; | ||
|
||
let now = Math.floor(new Date().getTime()/1000); | ||
let inputStartTime1 = new BigNumber(now - 10000); | ||
let inputEndTime1 = new BigNumber(now + 10000); | ||
let id1 = 1; | ||
|
||
await demo.mint(Alice, id1, inputStartTime1.toFixed(0), inputEndTime1.toFixed(0)); | ||
|
||
|
||
let outputStartTime1 = await demo.startTime(id1); | ||
let outputEndTime1 = await demo.endTime(id1); | ||
assert.equal(inputStartTime1.comparedTo(outputStartTime1) == 0 && inputEndTime1.comparedTo(outputEndTime1) == 0, true, "wrong data"); | ||
|
||
|
||
console.log("InterfaceId:", await demo.getInterfaceId()) | ||
let isSupport = await demo.supportsInterface('0x7a0cdf92'); | ||
assert.equal(isSupport, true , "supportsInterface error"); | ||
|
||
}); | ||
}); |
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,117 @@ | ||
/** | ||
* Use this file to configure your truffle project. It's seeded with some | ||
* common settings for different networks and features like migrations, | ||
* compilation and testing. Uncomment the ones you need or modify | ||
* them to suit your project as necessary. | ||
* | ||
* More information about configuration can be found at: | ||
* | ||
* trufflesuite.com/docs/advanced/configuration | ||
* | ||
* To deploy via Infura you'll need a wallet provider (like @truffle/hdwallet-provider) | ||
* to sign your transactions before they're sent to a remote public node. Infura accounts | ||
* are available for free at: infura.io/register. | ||
* | ||
* You'll also need a mnemonic - the twelve word phrase the wallet uses to generate | ||
* public/private key pairs. If you're publishing your code to GitHub make sure you load this | ||
* phrase from a file you've .gitignored so it doesn't accidentally become public. | ||
* | ||
*/ | ||
|
||
// const HDWalletProvider = require('@truffle/hdwallet-provider'); | ||
// | ||
// const fs = require('fs'); | ||
// const mnemonic = fs.readFileSync(".secret").toString().trim(); | ||
|
||
module.exports = { | ||
/** | ||
* Networks define how you connect to your ethereum client and let you set the | ||
* defaults web3 uses to send transactions. If you don't specify one truffle | ||
* will spin up a development blockchain for you on port 9545 when you | ||
* run `develop` or `test`. You can ask a truffle command to use a specific | ||
* network from the command line, e.g | ||
* | ||
* $ truffle test --network <network-name> | ||
*/ | ||
|
||
networks: { | ||
// Useful for testing. The `development` name is special - truffle uses it by default | ||
// if it's defined here and no other network is specified at the command line. | ||
// You should run a client (like ganache-cli, geth or parity) in a separate terminal | ||
// tab if you use this network and you must also set the `host`, `port` and `network_id` | ||
// options below to some value. | ||
// | ||
// development: { | ||
// host: "127.0.0.1", // Localhost (default: none) | ||
// port: 8545, // Standard Ethereum port (default: none) | ||
// network_id: "*", // Any network (default: none) | ||
// }, | ||
// Another network with more advanced options... | ||
// advanced: { | ||
// port: 8777, // Custom port | ||
// network_id: 1342, // Custom network | ||
// gas: 8500000, // Gas sent with each transaction (default: ~6700000) | ||
// gasPrice: 20000000000, // 20 gwei (in wei) (default: 100 gwei) | ||
// from: <address>, // Account to send txs from (default: accounts[0]) | ||
// websocket: true // Enable EventEmitter interface for web3 (default: false) | ||
// }, | ||
// Useful for deploying to a public network. | ||
// NB: It's important to wrap the provider as a function. | ||
// ropsten: { | ||
// provider: () => new HDWalletProvider(mnemonic, `https://ropsten.infura.io/v3/YOUR-PROJECT-ID`), | ||
// network_id: 3, // Ropsten's id | ||
// gas: 5500000, // Ropsten has a lower block limit than mainnet | ||
// confirmations: 2, // # of confs to wait between deployments. (default: 0) | ||
// timeoutBlocks: 200, // # of blocks before a deployment times out (minimum/default: 50) | ||
// skipDryRun: true // Skip dry run before migrations? (default: false for public nets ) | ||
// }, | ||
// Useful for private networks | ||
// private: { | ||
// provider: () => new HDWalletProvider(mnemonic, `https://network.io`), | ||
// network_id: 2111, // This network is yours, in the cloud. | ||
// production: true // Treats this network as if it was a public net. (default: false) | ||
// } | ||
}, | ||
|
||
// Set default mocha options here, use special reporters etc. | ||
mocha: { | ||
// timeout: 100000 | ||
}, | ||
|
||
// Configure your compilers | ||
compilers: { | ||
solc: { | ||
version: "0.8.10", // Fetch exact version from solc-bin (default: truffle's version) | ||
// docker: true, // Use "0.5.1" you've installed locally with docker (default: false) | ||
settings: { // See the solidity docs for advice about optimization and evmVersion | ||
optimizer: { | ||
enabled: false, | ||
runs: 200 | ||
} | ||
// , | ||
// evmVersion: "byzantium" | ||
// } | ||
} | ||
}, | ||
|
||
// Truffle DB is currently disabled by default; to enable it, change enabled: | ||
// false to enabled: true. The default storage location can also be | ||
// overridden by specifying the adapter settings, as shown in the commented code below. | ||
// | ||
// NOTE: It is not possible to migrate your contracts to truffle DB and you should | ||
// make a backup of your artifacts to a safe location before enabling this feature. | ||
// | ||
// After you backed up your artifacts you can utilize db by running migrate as follows: | ||
// $ truffle migrate --reset --compile-all | ||
// | ||
// db: { | ||
// enabled: false, | ||
// host: "127.0.0.1", | ||
// adapter: { | ||
// name: "sqlite", | ||
// settings: { | ||
// directory: ".db" | ||
// } | ||
// } | ||
} | ||
}; |