Skip to content

Commit

Permalink
EIP5007: TimeNFT, ERC-721 Time Extension (ethereum#5007)
Browse files Browse the repository at this point in the history
* 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
2 people authored and nachomazzara committed Jan 13, 2023
1 parent a12ffed commit e3b369c
Show file tree
Hide file tree
Showing 10 changed files with 369 additions and 0 deletions.
73 changes: 73 additions & 0 deletions EIPS/eip-5007.md
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).
2 changes: 2 additions & 0 deletions assets/eip-5007/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
package-lock.json
15 changes: 15 additions & 0 deletions assets/eip-5007/README.md
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
```
63 changes: 63 additions & 0 deletions assets/eip-5007/contracts/ERC5007.sol
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);
}
}
23 changes: 23 additions & 0 deletions assets/eip-5007/contracts/ERC5007Demo.sol
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 ;
}
}
17 changes: 17 additions & 0 deletions assets/eip-5007/contracts/IERC5007.sol
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);
}
6 changes: 6 additions & 0 deletions assets/eip-5007/migrations/1_initial_migration.js
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');
};

20 changes: 20 additions & 0 deletions assets/eip-5007/package.json
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"
}
}
33 changes: 33 additions & 0 deletions assets/eip-5007/test/test.js
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");

});
});
117 changes: 117 additions & 0 deletions assets/eip-5007/truffle-config.js
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"
// }
// }
}
};

0 comments on commit e3b369c

Please sign in to comment.