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

Add EIP-6105: Marketplace extension for EIP-721 #6105

Merged
merged 17 commits into from
Feb 12, 2023
Merged
Changes from 7 commits
Commits
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
346 changes: 346 additions & 0 deletions EIPS/eip-6105.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,346 @@
---
eip: 6105
title: Marketplace extension for EIP-721
Pandapip1 marked this conversation as resolved.
Show resolved Hide resolved
description: Add a basic marketplace functionality to the EIP-721.
Pandapip1 marked this conversation as resolved.
Show resolved Hide resolved
author: Silvere Heraudeau (@lambdalf-dev), Martin McConnell (@offgridgecko), 5660-eth (@5660-eth), Abu <team10kuni@gmail.com>, Wizard Wang
discussions-to: https://ethereum-magicians.org/t/idea-a-marketplace-extension-to-erc721-standard/11975
status: Draft
type: Standards Track
category: ERC
created: 2022-12-02
requires: 165, 721
---

"Not your marketplace, not your royalties"
Pandapip1 marked this conversation as resolved.
Show resolved Hide resolved

## Abstract

Add a basic marketplace functionality to the [EIP-721](./eip-721.md)
Pandapip1 marked this conversation as resolved.
Show resolved Hide resolved
to to realize non-fungible tokens (NFTs) trading without relying on an NFTs trading intermediary platform.
Pandapip1 marked this conversation as resolved.
Show resolved Hide resolved

It includes:
- a method to list an item for sale or update an existing listing, whether private sale (only to a specific address) or public (to anyone),
- a method to delist an item that has previously been listed,
- a method to purchase a listed item,
- a method to view all items listed for sale, and
- a method to view a specific listing.
Pandapip1 marked this conversation as resolved.
Show resolved Hide resolved

## Motivation

Most of the current NFTs trading rely on the NFTs trading platform acting as an intermediary, which has the following problems.
Pandapip1 marked this conversation as resolved.
Show resolved Hide resolved

1.Security issues. For example, security issues caused by authorization through `setApprovalForAll`. The permission obtained by the NFT trading platform has carried out unnecessary risk exposure. Once there is a problem with the trading platform contract, it will bring huge losses to the entire industry.If the user has authorized the operation right of his/her NFTs to the NFT trading platform, the latest phishing website fraud method is to trick the user into signing, and let the victim place an order at a very low price on the NFTs trading platform, and designate the recipient. The scammer then accepts the deal, thereby reaping illicit benefits. Ordinary users are hard-pressed to guard against this.

2.High trading costs. On the one hand, with the increase in the number of trading platforms, the liquidity of NFTs is scattered. If a user needs to make a deal as quickly as possible, he needs to authorize and place orders on multiple platforms, which further increases the risk exposure, and each authorization needs to spend gas. Taking BAYC as an example, the total number of BAYC is 10,000, and the number of current holders exceeds 6,000. The average number of BAYC held by each holder is less than 2. Although `setApprovalForAll` saves the subsequent pending order gas expenditure of a single trading platform, due to the need to authorize to Multi-platform, in essence, leads to an increase in gas expenditures for users. On the other hand, more importantly, the trading service fee charged by the trading platform must also be listed as the cost of user trading, which is much higher than the required gas spending for authorization.

3.The aggregator provides a solution to aggregate liquidity, but whether the aggregator aggregates the liquidity of a certain trading platform, its decision is centralized. And because the order information of the trading platform is off-chain, the efficiency of the aggregator to obtain order data is affected by the frequency of the trading platform API, and sometimes the NFT trading platform will suspend the distribution of APIs and limit the frequency of APIs.

4.Whether the NFT project party’s copyright tax income is obtained depends on the centralized decision-making of the NFT trading platform. Some trading platforms ignore the interests of the project party and implement 0 copyright tax, which infringes the interests of the project party.

5.Not resistant to censorship. Some platforms have delisted many NFTs, and the formulation and implementation of delisting rules are centralized and not transparent enough. Previously, some NFT trading platforms have also failed and wrongly delisted some NFTs, which caused market panic.
Pandapip1 marked this conversation as resolved.
Show resolved Hide resolved

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

Implementers of this standard MUST have all of the following functions:
Pandapip1 marked this conversation as resolved.
Show resolved Hide resolved

```solidity

interface IERC6105 {
///
/// @dev A structure representing a listed token
///
/// @param tokenId - the NFT asset being listed
/// @param tokenPrice - the price the token is being sold for, regardless of currency
/// @param to - address of who this listing is for,
/// can be address zero for a public listing,
/// or non zero address for a private listing
///
struct Listing {
uint256 tokenId;
uint256 tokenPrice;
address to;
}
///
/// @dev Emitted when a token is listed for sale.
///
/// @param tokenId - the NFT asset being listed
/// @param from - address of who is selling the token
/// @param to - address of who this listing is for,
/// can be address zero for a public listing,
/// or non zero address for a private listing
/// @param price - the price the token is being sold for, regardless of currency
///
event Listed( uint256 indexed tokenId, address indexed from, address to, uint256 indexed price );
///
/// @dev Emitted when a token that was listed for sale is being delisted
///
/// @param tokenId - the NFT asset being delisted
///
event Delisted( uint256 indexed tokenId );
///
/// @dev Emitted when a token that was listed for sale is being purchased.
///
/// @param tokenId - the NFT asset being purchased
/// @param from - address of who is selling the token
/// @param to - address of who is buying the token
/// @param price - the price the token is being sold for, regardless of currency
///
event Purchased( uint256 indexed tokenId, address indexed from, address indexed to, uint256 price );

///
/// @dev Lists token `tokenId` for sale.
///
/// @param tokenId - the NFT asset being listed
/// @param to - address of who this listing is for,
/// can be address zero for a public listing,
/// or non zero address for a private listing
/// @param price - the price the token is being sold for, regardless of currency
///
/// Requirements:
/// - `tokenId` must exist
/// - Caller must own `tokenId`
/// - Must emit a {Listed} event.
///
function listItem( uint256 tokenId, uint256 price, address to ) external;
///
/// @dev Delists token `tokenId` that was listed for sale
///
/// @param tokenId - the NFT asset being delisted
///
/// Requirements:
/// - `tokenId` must exist and be listed for sale
/// - Caller must own `tokenId`
/// - Must emit a {Delisted} event.
///
function delistItem( uint256 tokenId ) external;
///
/// @dev Buys a token and transfers it to the caller.
///
/// @param tokenId - the NFT asset being purchased
///
/// Requirements:
/// - `tokenId` must exist and be listed for sale
/// - Caller must be able to pay the listed price for `tokenId`
/// - Must emit a {Purchased} event.
///
function buyItem( uint256 tokenId ) external payable;
///
/// @dev Returns a list of all current listings.
///
/// @return the list of all currently listed tokens,
/// along with their price and intended recipient
///
function getAllListings() external view returns ( Listing[] memory );
///
/// @dev Returns the listing for `tokenId`
///
/// @return the specified listing (tokenId, price, intended recipient)
///
function getListing( uint256 tokenId ) external view returns ( Listing memory );
}
```

The `listItem( uint256 tokenId, uint256 price, address to )` function MAY be implemented as `public` or `external`.

The `delistItem( uint256 tokenId )` function MAY be implemented as `public` or `external`.

The `buyItem( uint256 tokenId )` function MUST be implemented as `payable` and MAY be implemented as `public` or `external`.

The `getListing( uint256 tokenId )` function MAY be implemented as `pure` or `view`.

The `Listed` event MUST be emitted when an NFT is listed.

The `Delisted` event MUST be emitted when an NFT is delisted.

The `Purchased` event MUST be emitted when an NFT is traded.

The `supportsInterface` method MUST return `true` when called with `...`.

## Rationale

## Backwards Compatibility

This standard is compatible with current [EIP-721](./eip-721.md) and [EIP-2981](./eip-2981.md) standards.
Pandapip1 marked this conversation as resolved.
Show resolved Hide resolved

## Reference Implementation

```solidity
pragma solidity ^0.8.0;
import './ERC721.sol';
import './ERC2981.sol';

contract Example is IERC6105, ERC721, ERC2981 {
// List of all items for sale
Listing[] private _listings;

// Mapping from token ID to sale status
mapping( uint256 => bool ) private _isForSale;

// Mapping from token ID to listing index
mapping( uint256 => uint256 ) private _listingIndex;

// INTERNAL
/**
* @dev Create or update a listing for `tokenId_`.
*
* Note: Setting `buyer_` to the NULL address will create a public listing.
*
* @param tokenId_ : identifier of the token being listed
* @param price_ : the sale price of the token being listed
* @param tokenOwner_ : current owner of the token
* @param buyer_ : optional address the token is being sold too
*/
function _listItem( uint256 tokenId_, uint256 price_, address tokenOwner_, address buyer_ ) internal {
// Update existing listing or create a new listing
if ( _isForSale[ tokenId_ ] ) {
_listings[ _listingIndex[ tokenId_ ] ].tokenprice = price_;
_listings[ _listingIndex[ tokenId_ ] ].to = buyer_;
}
else {
Listing memory _listing_ = Listing( tokenId_, price_, buyer_ );
_listings.push( _listing_ );
}
emit Listed( tokenId_, expiry_, tokenOwner_, price_ );
}

/**
* @dev Removes the listing for `tokenId_`.
*
* @param tokenId_ : identifier of the token being listed
*/
function _removeListing( uint256 tokenId_ ) internal {
uint256 _len_ = _listings.length;
uint256 _index_ = _listingIndex[ tokenId_ ];
if ( _index_ + 1 != _len_ ) {
_listings[ _index_ ] = _listings[ _len_ - 1 ];
}
_listings.pop();
}

/**
* @dev Processes an ether of `amount_` payment to `recipient_`.
*
* @param amount_ : the amount to send
* @param recipient_ : the payment recipient
*/
function _processEthPayment( uint256 amount_, address recipient_ ) internal {
( boold _success_, ) = payable( recipient_ ).call{ value: amount_ }( "" );
require( success, "Ether Transfer Fail" );
}

/**
* @dev Transfers `tokenId_` from `fromAddress_` to `toAddress_`.
*
* This internal function can be used to implement alternative mechanisms to perform
* token transfer, such as signature-based, or token burning.
*
* @param fromAddress_ : previous owner of the token
* @param toAddress_ : new owner of the token
* @param tokenId_ : identifier of the token being transferred
*
* Emits a {Transfer} event.
*/
function _transfer( address fromAddress_, address toAddress_, uint256 tokenId_ ) internal override {
if ( _isForSale[ tokenId_ ] ) {
_removeListing( tokenId_ );
}
super._transfer( fromAddress_, toAddress_, tokenId_ );
}

// PUBLIC
function listItem( uint256 tokenId, uint256 price, address to ) external {
/**
* @notice Create or update a listing for `tokenId_`.
*
* Note: Setting `buyer_` to the NULL address will create a public listing.
*
* @param tokenId_ : identifier of the token being listed
* @param price_ : the sale price of the token being listed
* @param buyer_ : optional address the token is being sold too
*/
function listItem( uint256 tokenId_, uint256 price_, address buyer_ ) external {
address _tokenOwner_ = ownerOf( tokenId_ );
require( _tokenOwner_ == ownerOf( tokenId_ ), "Not token owner" );

_createListing( tokenId_, price_, _tokenOwner_, buyer_ );
}

/**
* @notice Removes the listing for `tokenId_`.
*
* @param tokenId_ : identifier of the token being listed
*/
function delistItem( uint256 tokenId_ ) external {
address _tokenOwner_ = ownerOf( tokenId_ );
require( _tokenOwner_ == ownerOf( tokenId_ ), "Not token owner" );

require( _isForSale[ tokenId_ ], "Invalid listing" );

_removeListing( _index_ );
emit Delisted( tokenId_ );
}

/**
* @notice Purchases the listed token `tokenId_`.
*
* @param tokenId_ : identifier of the token being purchased
*/
function buyItem( uint256 tokenId_ ) external payable {
require( _isForSale[ tokenId_ ], "Invalid listing" );
require(
msg.sender == _listings[ _listingIndex[ tokenId_ ] ].to ||
_listings[ _listingIndex[ tokenId_ ] ].to == address( 0 ),
"Invalid sale address"
)
require( msg.value == _listings[ _listingIndex[ tokenId_ ] ].price, "Incorrect price" );

address _tokenOwner_ = ownerOf( tokenId_ );
_tranfer( _tokenOwner_, msg.sender, tokenId_ );
emit Purchased( tokenId_, _tokenOwner_, msg.sender, _listing_.price );

// Handle royalties
( address _royaltyRecipient_, uint256 _royalties_ ) = royaltyInfo( tokenId_, msg.value );
_processEthPayment( _royalties_, _royaltyRecipient_ );

uint256 _payment_ = msg.value - _royalties_;
_processEthPayment( _payment_, _tokenOwner_ );
}

// VIEW
/**
* @notice Returns the list of all existing listings.
*
* @return the list of all existing listings
*/
function getAllListings() external view returns ( Listing[] memory ) {
return _listings;
}

/**
* @notice returns the existing listing for `tokenId_`.
*
* @return the existing listing for the requested token.
*/
function getListing( uint256 tokenId ) external view returns ( Listing memory ) {
return _listings[ _listingIndex[ tokenId_ ] ];
}
}

/// @dev See {IERC165-supportsInterface}.
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IERC6105).interfaceId || super.supportsInterface(interfaceId);

```

## Security Considerations

There are no security considerations related directly to the implementation of this standard.
Pandapip1 marked this conversation as resolved.
Show resolved Hide resolved

## Copyright

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