Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EIP-5058:Lockable ERC-721 Standard #5058

Merged
merged 48 commits into from
Aug 25, 2022
Merged
Show file tree
Hide file tree
Changes from 46 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
72cafb2
EIP-5058:Lockable ERC-721 Standard
radiocaca Apr 29, 2022
4696368
update eip
radiocaca May 1, 2022
49c31ff
Update eip-5058.md
radiocaca May 4, 2022
6b21dd1
Update eip-5058.md proposal language
sfumato00 May 10, 2022
1e4c9a0
Update eip-5058.md proposal language
sfumato00 May 10, 2022
47a04f3
Merge pull request #1 from sfumato00/master
radiocaca May 10, 2022
1e5f664
Update eip-5058.md
radiocaca May 10, 2022
fa18ed6
Update eip-5058.md
radiocaca May 10, 2022
a49ef35
upload example
radiocaca May 11, 2022
07f7fe6
Update assets/eip-5058/factory/IERC721Bound.sol
radiocaca May 11, 2022
456c60e
Update assets/eip-5058/ERC721Lockable.sol
radiocaca May 11, 2022
cdbe439
Update assets/eip-5058/IERC721Lockable.sol
radiocaca May 11, 2022
2a7e737
Update assets/eip-5058/factory/IEIP5058Factory.sol
radiocaca May 11, 2022
fcd52e2
Update assets/eip-5058/factory/ERC721Bound.sol
radiocaca May 11, 2022
5415e9c
Update assets/eip-5058/factory/EIP5058Factory.sol
radiocaca May 11, 2022
2c29f8c
Update assets/eip-5058/extensions/EIP5058Bound.sol
radiocaca May 11, 2022
82b6584
rename to ERC5058
radiocaca May 11, 2022
9676777
update revert domain
radiocaca May 11, 2022
9002b06
Update EIPS/eip-5058.md
radiocaca May 13, 2022
06d1955
Update EIPS/eip-5058.md
radiocaca May 17, 2022
38c75b8
Update EIPS/eip-5058.md
radiocaca May 17, 2022
135a48b
remove all external links and add lockExpiredTime function
radiocaca Jul 31, 2022
df1c064
add tests
radiocaca Jul 31, 2022
9999772
rename from to owner
radiocaca Jul 31, 2022
9610e08
Update EIPS/eip-5058.md
radiocaca Aug 3, 2022
ba9ebea
Update EIPS/eip-5058.md
radiocaca Aug 3, 2022
552e313
Update EIPS/eip-5058.md
radiocaca Aug 3, 2022
e5f1160
Update EIPS/eip-5058.md
radiocaca Aug 3, 2022
3f3d037
Update EIPS/eip-5058.md
radiocaca Aug 3, 2022
9d3980a
Update assets/eip-5058/IERC5058.sol
radiocaca Aug 3, 2022
286289b
Update EIPS/eip-5058.md
radiocaca Aug 3, 2022
eb1fc00
Update EIPS/eip-5058.md
radiocaca Aug 3, 2022
724e277
Update EIPS/eip-5058.md
radiocaca Aug 3, 2022
4810780
Apply suggestions from code review
radiocaca Aug 3, 2022
051e456
Apply suggestions from code review
radiocaca Aug 3, 2022
9a95d53
fix lockFrom
radiocaca Aug 3, 2022
21731b2
update md
radiocaca Aug 3, 2022
adaad9e
fix lockFrom tests
radiocaca Aug 3, 2022
0c6ee82
remove oz deps
radiocaca Aug 14, 2022
2a29457
modify md for eip change
radiocaca Aug 15, 2022
18afdec
refine tests
radiocaca Aug 17, 2022
ade97bf
Merge branch 'master' into master
radiocaca Aug 18, 2022
c0dce9b
Merge branch 'master' into master
radiocaca Aug 18, 2022
d68547a
Merge branch 'master' into master
radiocaca Aug 18, 2022
08db345
Merge branch 'master' into master
radiocaca Aug 19, 2022
2cec9fa
Merge branch 'master' into master
radiocaca Aug 22, 2022
e822eeb
Merge branch 'master' into master
radiocaca Aug 25, 2022
78f8908
Merge branch 'master' into master
Pandapip1 Aug 25, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
206 changes: 206 additions & 0 deletions EIPS/eip-5058.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
---
eip: 5058
title: Lockable Non-Fungible Tokens
description: Lockable EIP-721 tokens
author: Tyler (@radiocaca), Alex (@gojazdev), John (@sfumato00)
discussions-to: https://ethereum-magicians.org/t/eip-5058-erc-721-lockable-standard/9201
status: Draft
type: Standards Track
category: ERC
created: 2022-04-30
requires: 20, 165, 721
---

## Abstract

We propose to extend the [EIP-721](./eip-721.md) standard with a secure locking mechanism. The NFT owners approve the operator to lock the NFT through `setLockApprovalForAll()` or `lockApprove()`. The approved operator locks the NFT through `lock()`. The locked NFTs cannot be transferred until the end of the locking period. An immediate use case is to allow NFTs to participate in smart contracts without leaving the wallets of their owners.

## Motivation

NFTs, enabled by [EIP-721](./eip-721.md), have exploded in demand. The total market value and the ecosystem continue to grow with more and more blue chip NFTs, which are approximately equivalent to popular intellectual properties in a conventional sense. Despite the vast success, something is left to be desired. Liquidity has always been one of the biggest challenges for NFTs. Several attempts have been made to tackle the liquidity challenge: NFTFi and BendDAO, to name a few. Utilizing the currently prevalent EIP-721 standard, these projects require participating NFTs to be transferred to the projects' contracts, which poses inconveniences and risks to the owners:

1. Smart contract risks: NFTs can be lost or stolen due to bugs or vulnerabilities in the contracts.
2. Loss of utility: NFTs have utility values, such as profile pictures and bragging rights, which are lost when the NFTs are no longer seen under the owners' custody.
3. Missing Airdrops: The owners can no longer directly receive airdrops entitled to the NFTs. Considering the values and price fluctuation of some of the airdrops, either missing or not getting the airdrop on time can financially impact the owners.

All of the above are bad UX, and we believe the EIP-721 standard can be improved by adopting a native locking mechanism:

1. Instead of being transferred to a smart contract, an NFT remains in self-custody but locked.
2. While an NFT is locked, its transfer is prohibited. Other properties remain unaffected.
3. The owners can receive or claim airdrops themselves.

The value of an NFT can be reflected in two aspects: collection value and utility value. Collection value needs to ensure that the holder's wallet retains ownership of the NFT forever. Utility value requires ensuring that the holder can verify their NFT ownership in other projects. Both of these aspects require that the NFT remain in its owner's wallet.

The proposed standard allows the underlying NFT assets to be managed securely and conveniently by extending the EIP-721 standard to natively support common NFTFi use cases including locking, staking, lending, and crowdfunding. We believe the proposed standard will encourage NFT owners to participate more actively in NFTFi projects and, hence, improve the livelihood of the whole NFT ecosystem.

## 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.

Lockable EIP-721 **MUST** implement the `IERC5058` interfaces:

```solidity
// SPDX-License-Identifier: CC0-1.0

pragma solidity ^0.8.8;

/**
* @dev EIP-721 Non-Fungible Token Standard, optional lockable extension
* ERC721 Token that can be locked for a certain period and cannot be transferred.
* This is designed for a non-escrow staking contract that comes later to lock a user's NFT
* while still letting them keep it in their wallet.
* This extension can ensure the security of user tokens during the staking period.
* If the nft lending protocol is compatible with this extension, the trouble caused by the NFT
* airdrop can be avoided, because the airdrop is still in the user's wallet
*/
interface IERC5058 {
/**
* @dev Emitted when `tokenId` token is locked by `operator` from `from`.
*/
event Locked(address indexed operator, address indexed from, uint256 indexed tokenId, uint256 expired);

/**
* @dev Emitted when `tokenId` token is unlocked by `operator` from `from`.
*/
event Unlocked(address indexed operator, address indexed from, uint256 indexed tokenId);

/**
* @dev Emitted when `owner` enables `approved` to lock the `tokenId` token.
*/
event LockApproval(address indexed owner, address indexed approved, uint256 indexed tokenId);

/**
* @dev Emitted when `owner` enables or disables (`approved`) `operator` to lock all of its tokens.
*/
event LockApprovalForAll(address indexed owner, address indexed operator, bool approved);

/**
* @dev Returns the locker who is locking the `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function lockerOf(uint256 tokenId) external view returns (address locker);

/**
* @dev Lock `tokenId` token until the block number is greater than `expired` to be unlocked.
*
* Requirements:
*
* - `tokenId` token must be owned by `owner`.
* - `expired` must be greater than block.number
* - If the caller is not `owner`, it must be approved to lock this token
* by either {lockApprove} or {setLockApprovalForAll}.
*
* Emits a {Locked} event.
*/
function lock(uint256 tokenId, uint256 expired) external;

/**
* @dev Unlock `tokenId` token.
*
* Requirements:
*
* - `tokenId` token must be owned by `owner`.
* - the caller must be the operator who locks the token by {lock}
*
* Emits a {Unlocked} event.
*/
function unlock(uint256 tokenId) external;

/**
* @dev Gives permission to `to` to lock `tokenId` token.
*
* Requirements:
*
* - The caller must own the token or be an approved lock operator.
* - `tokenId` must exist.
*
* Emits an {LockApproval} event.
*/
function lockApprove(address to, uint256 tokenId) external;
radiocaca marked this conversation as resolved.
Show resolved Hide resolved

/**
* @dev Approve or remove `operator` as an lock operator for the caller.
* Operators can call {lock} for any token owned by the caller.
*
* Requirements:
*
* - The `operator` cannot be the caller.
*
* Emits an {LockApprovalForAll} event.
*/
function setLockApprovalForAll(address operator, bool approved) external;

/**
* @dev Returns the account lock approved for `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function getLockApproved(uint256 tokenId) external view returns (address operator);

/**
* @dev Returns if the `operator` is allowed to lock all of the assets of `owner`.
*
* See {setLockApprovalForAll}
*/
function isLockApprovedForAll(address owner, address operator) external view returns (bool);

/**
* @dev Returns if the `tokenId` token is locked.
*/
function isLocked(uint256 tokenId) external view returns (bool);
radiocaca marked this conversation as resolved.
Show resolved Hide resolved

/**
* @dev Returns the `tokenId` token lock expired time.
*/
function lockExpiredTime(uint256 tokenId) external view returns (uint256);
}
```

## Rationale

### NFT lock approvals

An NFT owner can give another trusted operator the right to lock his NFT through the approve functions. The `lockApprove()` function only approves for the specified NFT, whereas `setLockApprovalForAll()` approves for all NFTs of the collection under the wallet. When a user participates in an NFTFi project, the project contract calls `lock()` to lock the user's NFT. Locked NFTs cannot be transferred, but the NFTFi project contract can use the unlock function `unlock()` to unlock the NFT.

### NFT lock/unlock
radiocaca marked this conversation as resolved.
Show resolved Hide resolved

Authorized project contracts have permission to lock NFT with the `lock` method. Locked NFTs cannot be transferred until the lock time expires. The project contract also has permission to unlock NFT in advance through the `unlock` function. Note that only the address of the locked NFT has permission to unlock that NFT.

### NFT lock period

When locking an NFT, one must specify the lock expiration block number, which must be greater than the current block number. When the current block number exceeds the expiration block number, the NFT is automatically released and can be transferred.

### Bound NFT
radiocaca marked this conversation as resolved.
Show resolved Hide resolved

Bound NFT is an extension of this EIP, which implements the ability to mint a boundNFT during the NFT locking period. The boundNFT is identical to the locked NFT metadata and can be transferred. However, a boundNFT only exists during the NFT locking period and will be destroyed after the NFT is unlocked.
BoundNFT can be used to lend, as a staking credential for the contract. The credential can be locked in the contract, but also to the user. In NFT leasing, boundNFT can be rented to users because boundNFT is essentially equivalent to NFT. This consensus, if accepted by all projects, boundNFT will bring more creativity to NFT.

### Bound NFT Factory
radiocaca marked this conversation as resolved.
Show resolved Hide resolved

Bound NFT Factory is a common boundNFT factory, similar to Uniswap's [EIP-20](./eip-20.md) pairs factory. It uses the create2 method to create a boundNFT contract address for any NFT deterministic. BoundNFT contract that has been created can only be controlled by the original NFT contract.


## Backwards Compatibility

This standard is compatible with EIP-721.

## Test Cases
radiocaca marked this conversation as resolved.
Show resolved Hide resolved

Test cases written using hardhat can be found [here](../assets/eip-5058/test/test.ts)

## Reference Implementation

You can find an implementation of this standard in the [assets](../assets/eip-5058/ERC5058.sol) folder.

## Security Considerations

After being locked, the NFT can not be transferred, so before authorizing locking rights to other project contracts, you must confirm that the project contract can unlock NFT. Otherwise there is a risk of NFT being permanently locked. It is recommended to give a reasonable locking period in use for projects. NFT can be automatically unlocked, which can reduce the risk to a certain extent.

## Copyright

Copyright and related rights waived via [CC0](../LICENSE.md).
Loading