Marketplace V3 design document.

This is a live document that explains what the thirdweb Marketplace V3 smart contract is, how it works and can be used, and why it is written the way it is.

The document is written for technical and non-technical readers. To ask further questions about Marketplace V3, please visit our support site or create a github issue.

Background

The thirdweb Marketplace V3 is a marketplace where people can sell NFTs — ERC 721 or ERC 1155 tokens — at a fixed price ( what we'll refer to as a "Direct listing"), or auction them (what we'll refer to as an "Auction listing"). It also allows users to make "Offers" on unlisted NFTs.

Marketplace V3 offers improvements over previous version in terms of design and features, which are discussed in this document. You can refer to previous (v2) Marketplace design document here.

Context behind this update

We have given Marketplace an update that was long overdue. The marketplace product is still made up of three core ways of exchanging NFTs for money:

  • Selling NFTs via a ‘direct listing'.
  • Auctioning off NFTs.
  • Making offers for NFTs not on sale at all, or at favorable prices.

The core improvement of the Marketplace V3 smart contract is better developer experience of working with the contract.

Previous version had some limitations, arising due to (1) the smart contract size limit of ~24.576 kb on Ethereum mainnet (and other thirdweb supported chains), and (2) the way the smart contract code is organized (single, large smart contract that inherits other contracts). The previous Marketplace smart contract has functions that have multiple jobs, behave in many different ways under different circumstances, and a lack of convenient view functions to read data easily.

Moreover, over time, we received feature requests for Marketplace, some of which have been incorporated in Marketplace V3, for e.g.:

  • Ability to accept multiple currencies for direct listings
  • Ability to explicitly cancel listings
  • Explicit getter functions for fetching high level states e.g. “has an auction ended”, “who is the winning bidder”, etc.
  • Simplify start time and expiration time for listings

For all these reasons and feature additions, the Marketplace contract is getting an update, and being rolled out as Marketplace V3. In this update:

  • the contract has been broken down into independent extensions (later offered in Solidity SDK).
  • the contract provides explicit functions for each important action (something that is missing from the contract, today).
  • the contract provides convenient view functions for all relevant state of the contract, without expecting users to rely on events to read critical information.

Finally, to accomplish all these things without the constraint of the smart contract size limit, the Marketplace V3 contract is written in the following new code pattern, which we call Plugin Pattern. It was influenced by EIP-2535. You can read more about Plugin Pattern here.

Extensions that make up Marketplace V3

The Marketplace V3 smart contract is now written as the sum of three main extension smart contracts:

  • DirectListings: List NFTs for sale at a fixed price. Buy NFTs from listings.
  • EnglishAuctions: Put NFTs up for auction. Bid for NFTs up on auction. The highest bid within an auction's duration wins.
  • Offers: Make offers of ERC20 or native token currency for NFTs. Accept a favorable offer if you own the NFTs wanted.

Each of these extension smart contracts is independent, and does not care about the state of the other extension contracts.

What the Marketplace will look like to users

There are two groups of users — (1) thirdweb's customers who'll set up the marketplace, and (2) the end users of thirdweb customers' marketplaces.

To thirdweb customers, the marketplace can be set up like any of the other thirdweb contract (e.g. 'NFT Collection') through the thirdweb dashboard, the thirdweb SDK, or by directly consuming the open sourced marketplace smart contract.

To the end users of thirdweb customers, the experience of using the marketplace will feel familiar to popular marketplace platforms like OpenSea, Zora, etc. The biggest difference in user experience will be that performing any action on the marketplace requires gas fees.

  • Thirdweb's customers
    • Deploy the marketplace contract like any other thirdweb contract.
    • Can set a % 'platform fee'. This % is collected on every sale — when a buyer buys tokens from a direct listing, and when a seller collects the highest bid on auction closing. This platform fee is distributed to the platform fee recipient (set by a contract admin).
    • Can list NFTs for sale at a fixed price.
    • Can edit an existing listing's parameters, e.g. the currency accepted. An auction's parameters cannot be edited once it has started.
    • Can make offers to NFTs listed/unlisted for a fixed price.
    • Can auction NFTs.
    • Can make bids to auctions.
    • Must pay gas fees to perform any actions, including the actions just listed.

EIPs implemented / supported

To be able to escrow NFTs in the case of auctions, Marketplace implements the receiver interfaces for ERC1155 and ERC721 tokens.

To enable meta-transactions (gasless), Marketplace implements ERC2771.

Marketplace also honors ERC2981 for the distribution of royalties on direct and auction listings.

Events emitted

All events emitted by the contract, as well as when they're emitted, can be found in the interface of the contract, here. In general, events are emitted whenever there is a state change in the contract.

Currency transfers

The contract supports both ERC20 currencies and a chain's native token (e.g. ether for Ethereum mainnet). This means that any action that involves transferring currency (e.g. buying a token from a direct listing) can be performed with either an ERC20 token or the chain's native token.

💡 Note: The exception is offers — these can only be made with ERC20 tokens, since Marketplace needs to transfer the offer amount from the buyer to the seller, in case the latter accepts the offer. This cannot be done with native tokens without escrowing the requisite amount of currency.

The contract wraps all native tokens deposited into it as the canonical ERC20 wrapped version of the native token (e.g. WETH for ether). The contract unwraps the wrapped native token when transferring native tokens to a given address.

If the contract fails to transfer out native tokens, it wraps them back to wrapped native tokens, and transfers the wrapped native tokens to the concerned address. The contract may fail to transfer out native tokens to an address, if the address represents a smart contract that cannot accept native tokens transferred to it directly.

API Reference for Extensions

Direct listings

The DirectListings extension smart contract lets you buy and sell NFTs (ERC-721 or ERC-1155) for a fixed price.

createListing

What: List NFTs (ERC721 or ERC1155) for sale at a fixed price.

  • Interface

    struct ListingParameters {
    address assetContract;
    uint256 tokenId;
    uint256 quantity;
    address currency;
    uint256 pricePerToken;
    uint128 startTimestamp;
    uint128 endTimestamp;
    bool reserved;
    }
    function createListing(ListingParameters memory params) external returns (uint256 listingId);
  • Parameters

    ParameterDescription
    assetContractThe address of the smart contract of the NFTs being listed.
    tokenIdThe tokenId of the NFTs being listed.
    quantityThe quantity of NFTs being listed. This must be non-zero, and is expected to be 1 for ERC-721 NFTs.
    currencyThe currency in which the price must be paid when buying the listed NFTs. The address considered for native tokens of the chain is 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE
    pricePerTokenThe price to pay per unit of NFTs listed.
    startTimestampThe UNIX timestamp at and after which NFTs can be bought from the listing.
    expirationTimestampThe UNIX timestamp at and after which NFTs cannot be bought from the listing.
    reservedWhether the listing is reserved to be bought from a specific set of buyers.
  • Criteria that must be satisfied

    • The listing creator must own the NFTs being listed.
    • The listing creator must have already approved Marketplace to transfer the NFTs being listed (since the creator is not required to escrow NFTs in the Marketplace).
    • The listing creator must list a non-zero quantity of tokens. If listing ERC-721 tokens, the listing creator must list only quantity 1.
    • The listing start time must not be less than 1+ hour before the block timestamp of the transaction. The listing end time must be after the listing start time.
    • Only ERC-721 or ERC-1155 tokens must be listed.
    • The listing creator must have LISTER_ROLE if role restrictions are active.
    • The asset being listed must have ASSET_ROLE if role restrictions are active.

updateListing

What: Update information (e.g. price) for one of your listings on the marketplace.

  • Interface

    struct ListingParameters {
    address assetContract;
    uint256 tokenId;
    uint256 quantity;
    address currency;
    uint256 pricePerToken;
    uint128 startTimestamp;
    uint128 endTimestamp;
    bool reserved;
    }
    function updateListing(uint256 listingId, ListingParameters memory params) external
  • Parameters

    ParameterDescription
    listingIdThe unique ID of the listing being updated.
    assetContractThe address of the smart contract of the NFTs being listed.
    tokenIdThe tokenId of the NFTs being listed.
    quantityThe quantity of NFTs being listed. This must be non-zero, and is expected to be 1 for ERC-721 NFTs.
    currencyThe currency in which the price must be paid when buying the listed NFTs. The address considered for native tokens of the chain is 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE
    pricePerTokenThe price to pay per unit of NFTs listed.
    startTimestampThe UNIX timestamp at and after which NFTs can be bought from the listing.
    expirationTimestampThe UNIX timestamp at and after which NFTs cannot be bought from the listing.
    reservedWhether the listing is reserved to be bought from a specific set of buyers.
  • Criteria that must be satisfied

    • The caller of the function must be the creator of the listing being updated.
    • The listing creator must own the NFTs being listed.
    • The listing creator must have already approved Marketplace to transfer the NFTs being listed (since the creator is not required to escrow NFTs in the Marketplace).
    • The listing creator must list a non-zero quantity of tokens. If listing ERC-721 tokens, the listing creator must list only quantity 1.
    • Only ERC-721 or ERC-1155 tokens must be listed.
    • The listing start time must be greater than or equal to the incumbent start timestamp. The listing end time must be after the listing start time.
    • The asset being listed must have ASSET_ROLE if role restrictions are active.

cancelListing

What: Cancel (i.e. delete) one of your listings on the marketplace.

  • Interface

    function cancelListing(uint256 listingId) external;
  • Parameters

    ParameterDescription
    listingIdThe unique ID of the listing to cancel i.e. delete.
  • Criteria that must be satisfied

    • The caller of the function must be the creator of the listing being cancelled.
    • The listing must exist.

approveBuyerForListing

What: Approve a buyer to buy from a reserved listing.

  • Interface

    function approveBuyerForListing(
    uint256 listingId,
    address buyer,
    bool toApprove
    ) external;
  • Parameters

    ParameterDescription
    listingIdThe unique ID of the listing.
    buyerThe address of the buyer to approve to buy from the listing.
    toApproveWhether to approve the buyer to buy from the listing.
  • Criteria that must be satisfied

    • The caller of the function must be the creator of the listing in question.
    • The listing must be reserved.

approveCurrencyForListing

What: Approve a currency as a form of payment for the listing.

  • Interface

    function approveCurrencyForListing(
    uint256 listingId,
    address currency,
    uint256 pricePerTokenInCurrency,
    ) external;
  • Parameters

    ParameterDescription
    listingIdThe unique ID of the listing.
    currencyThe address of the currency to approve as a form of payment for the listing.
    pricePerTokenInCurrencyThe price per token for the currency to approve. A value of 0 here disapprove a currency.
  • Criteria that must be satisfied

    • The caller of the function must be the creator of the listing in question.
    • The currency being approved must not be the main currency accepted by the listing.

buyFromListing

What: Buy NFTs from a listing.

  • Interface

    function buyFromListing(
    uint256 listingId,
    address buyFor,
    uint256 quantity,
    address currency,
    uint256 expectedTotalPrice
    ) external payable;
  • Parameters

    ParameterDescription
    listingIdThe unique ID of the listing to buy NFTs from.
    buyForThe recipient of the NFTs being bought.
    quantityThe quantity of NFTs to buy from the listing.
    currencyThe currency to use to pay for NFTs.
    expectedTotalPriceThe expected total price to pay for the NFTs being bought.
  • Criteria that must be satisfied

    • The buyer must own the total price amount to pay for the NFTs being bought.
    • The buyer must approve the Marketplace to transfer the total price amount to pay for the NFTs being bought.
    • If paying in native tokens, the buyer must send exactly the expected total price amount of native tokens along with the transaction.
    • The buyer's expected total price must match the actual total price for the NFTs being bought.
    • The buyer must buy a non-zero quantity of NFTs.
    • The buyer must not attempt to buy more NFTs than are listed at the time.
    • The buyer must pay in a currency approved by the listing creator.

totalListings

What: Returns the total number of listings created so far.

  • Interface

    function totalListings() external view returns (uint256);

getAllListings

What: Returns all listings between the start and end Id (both inclusive) provided.

  • Interface

    enum TokenType {
    ERC721,
    ERC1155
    }
    enum Status {
    UNSET,
    CREATED,
    COMPLETED,
    CANCELLED
    }
    struct Listing {
    uint256 listingId;
    address listingCreator;
    address assetContract;
    uint256 tokenId;
    uint256 quantity;
    address currency;
    uint256 pricePerToken;
    uint128 startTimestamp;
    uint128 endTimestamp;
    bool reserved;
    TokenType tokenType;
    Status status;
    }
    function getAllListings(uint256 startId, uint256 endId) external view returns (Listing[] memory listings);
  • Parameters

    ParameterDescription
    startIdInclusive start listing Id
    endIdInclusive end listing Id

getAllValidListings

What: Returns all valid listings between the start and end Id (both inclusive) provided. A valid listing is where the listing is active, as well as the creator still owns and has approved Marketplace to transfer the listed NFTs.

  • Interface

    function getAllValidListings(uint256 startId, uint256 endId) external view returns (Listing[] memory listings);
  • Parameters

    ParameterDescription
    startIdInclusive start listing Id
    endIdInclusive end listing Id

getListing

What: Returns a listing at the provided listing ID.

  • Interface

    function getListing(uint256 listingId) external view returns (Listing memory listing);
  • Parameters

    ParameterDescription
    listingIdThe ID of the listing to fetch.

English auctions

The EnglishAuctions extension smart contract lets you sell NFTs (ERC-721 or ERC-1155) in an english auction.

createAuction

What: Put up NFTs (ERC721 or ERC1155) for an english auction.

  • What is an English auction?

    • Alice deposits her NFTs in the Marketplace contract and specifies: [1] a minimum bid amount, and [2] a duration for the auction.
    • Bob is the first person to make a bid.
      • Before the auction duration ends, Bob makes a bid in the auction (≥ minimum bid).
      • Bob's bid is now deposited and locked in the Marketplace.
    • Tom also wants the auctioned NFTs. Tom's bid must be greater than Bob's bid.
      • Before the auction duration ends, Tom makes a bid in the auction (≥ Bob's bid).
      • Tom's bid is now deposited and locked in the Marketplace. Bob's is automatically refunded his bid.
    • After the auction duration ends:
      • Alice collects the highest bid that has been deposited in Marketplace.
      • The “highest bidder” e.g. Tom collects the auctioned NFTs.
  • Interface

    struct AuctionParameters {
    address assetContract;
    uint256 tokenId;
    uint256 quantity;
    address currency;
    uint256 minimumBidAmount;
    uint256 buyoutBidAmount;
    uint64 timeBufferInSeconds;
    uint64 bidBufferBps;
    uint64 startTimestamp;
    uint64 endTimestamp;
    }
    function createAuction(AuctionParameters memory params) external returns (uint256 auctionId);
  • Parameters

    ParameterDescription
    assetContractThe address of the smart contract of the NFTs being auctioned.
    tokenIdThe tokenId of the NFTs being auctioned.
    quantityThe quantity of NFTs being auctioned. This must be non-zero, and is expected to be 1 for ERC-721 NFTs.
    currencyThe currency in which the bid must be made when bidding for the auctioned NFTs.
    minimumBidAmountThe minimum bid amount for the auction.
    buyoutBidAmountThe total bid amount for which the bidder can directly purchase the auctioned items and close the auction as a result.
    timeBufferInSecondsThis is a buffer e.g. x seconds. If a new winning bid is made less than x seconds before expirationTimestamp, the expirationTimestamp is increased by x seconds.
    bidBufferBpsThis is a buffer in basis points e.g. x%. To be considered as a new winning bid, a bid must be at least x% greater than the current winning bid.
    startTimestampThe timestamp at and after which bids can be made to the auction
    expirationTimestampThe timestamp at and after which bids cannot be made to the auction.
  • Criteria that must be satisfied

    • The auction creator must own and approve Marketplace to transfer the auctioned tokens to itself.
    • The auction creator must auction a non-zero quantity of tokens. If the auctioned token is ERC721, the quantity must be 1.
    • The auction creator must specify a non-zero time and bid buffers.
    • The minimum bid amount must be less than the buyout bid amount.
    • The auction start time must not be less than 1+ hour before the block timestamp of the transaction. The auction end time must be after the auction start time.
    • The auctioned token must be ERC-721 or ERC-1155.
    • The auction creator must have LISTER_ROLE if role restrictions are active.
    • The asset being auctioned must have ASSET_ROLE if role restrictions are active.

cancelAuction

What: Cancel an auction.

  • Interface

    function cancelAuction(uint256 auctionId) external;
  • Parameters

    ParameterDescription
    auctionIdThe unique ID of the auction to cancel.
  • Criteria that must be satisfied

    • The caller of the function must be the auction creator.
    • There must be no bids placed in the ongoing auction. (Default true for all auctions that haven't started)

collectAuctionPayout

What: Once the auction ends, collect the highest bid made for your auctioned NFTs.

  • Interface

    function collectAuctionPayout(uint256 auctionId) external;
  • Parameters

    ParameterDescription
    auctionIdThe unique ID of the auction to collect the payout for.
  • Criteria that must be satisfied

    • The auction must be expired.
    • The auction must have received at least one valid bid.

collectAuctionTokens

What: Once the auction ends, collect the auctioned NFTs for which you were the highest bidder.

  • Interface

    function collectAuctionTokens(uint256 auctionId) external;
  • Parameters

    ParameterDescription
    auctionIdThe unique ID of the auction to collect the payout for.
  • Criteria that must be satisfied

    • The auction must be expired.
    • The caller must be the winning bidder.

bidInAuction

What: Make a bid in an auction.

  • Interface

    function bidInAuction(uint256 auctionId, uint256 bidAmount) external payable;
  • Parameters

    ParameterDescription
    auctionIdThe unique ID of the auction to bid in.
    bidAmountThe total bid amount.
  • Criteria that must be satisfied

    • Auction must not be expired.
    • The caller must own and approve Marketplace to transfer the requisite bid amount to itself.
    • The bid amount must be a winning bid amount. (For convenience, this can be verified by calling isNewWinningBid)

isNewWinningBid

What: Check whether a given bid amount would make for a new winning bid.

  • Interface

    function isNewWinningBid(uint256 auctionId, uint256 bidAmount) external view returns (bool);
  • Parameters

    ParameterDescription
    auctionIdThe unique ID of the auction to bid in.
    bidAmountThe total bid amount.
  • Criteria that must be satisfied

    • The auction must not have been cancelled or expired.

totalAuctions

What: Returns the total number of auctions created so far.

  • Interface

    function totalAuctions() external view returns (uint256);

getAuction

What: Fetch the auction info at a particular auction ID.

  • Interface

    struct Auction {
    uint256 auctionId;
    address auctionCreator;
    address assetContract;
    uint256 tokenId;
    uint256 quantity;
    address currency;
    uint256 minimumBidAmount;
    uint256 buyoutBidAmount;
    uint64 timeBufferInSeconds;
    uint64 bidBufferBps;
    uint64 startTimestamp;
    uint64 endTimestamp;
    TokenType tokenType;
    Status status;
    }
    function getAuction(uint256 auctionId) external view returns (Auction memory auction);
  • Parameters

    ParameterDescription
    auctionIdThe unique ID of the auction.

getAllAuctions

What: Returns all auctions between the start and end Id (both inclusive) provided.

  • Interface

    function getAllAuctions(uint256 startId, uint256 endId) external view returns (Auction[] memory auctions);
  • Parameters

    ParameterDescription
    startIdInclusive start auction Id
    endIdInclusive end auction Id

getAllValidAuctions

What: Returns all valid auctions between the start and end Id (both inclusive) provided. A valid auction is where the auction is active, as well as the creator still owns and has approved Marketplace to transfer the auctioned NFTs.

  • Interface

    function getAllValidAuctions(uint256 startId, uint256 endId) external view returns (Auction[] memory auctions);
  • Parameters

    ParameterDescription
    startIdInclusive start auction Id
    endIdInclusive end auction Id

getWinningBid

What: Get the winning bid of an auction.

  • Interface

    function getWinningBid(uint256 auctionId)
    external
    view
    returns (
    address bidder,
    address currency,
    uint256 bidAmount
    );
  • Parameters

    ParameterDescription
    auctionIdThe unique ID of an auction.

isAuctionExpired

What: Returns whether an auction is expired or not.

  • Interface

    function isAuctionExpired(uint256 auctionId) external view returns (bool);
  • Parameters

    ParameterDescription
    auctionIdThe unique ID of an auction.

Offers

makeOffer

What: Make an offer for any ERC721 or ERC1155 NFTs (unless ASSET_ROLE restrictions apply)

  • Interface

    struct OfferParams {
    address assetContract;
    uint256 tokenId;
    uint256 quantity;
    address currency;
    uint256 totalPrice;
    uint256 expirationTimestamp;
    }
    function makeOffer(OfferParams memory params) external returns (uint256 offerId);
  • Parameters

    ParameterDescription
    assetContractThe contract address of the NFTs wanted.
    tokenIdThe tokenId of the NFTs wanted.
    quantityThe quantity of NFTs wanted.
    currencyThe currency offered for the NFT wanted.
    totalPriceThe price offered for the NFTs wanted.
    expirationTimestampThe timestamp at which the offer expires.
  • Criteria that must be satisfied

    • The offeror must own and approve Marketplace to transfer the requisite amount currency offered for the NFTs wanted.
    • The offeror must make an offer for non-zero quantity of NFTs. If offering for ERC721 tokens, the quantity wanted must be 1.
    • Expiration timestamp must be greater than block timestamp, or within 1 hour of block timestamp.

cancelOffer

What: Cancel an existing offer.

  • Interface

    function cancelOffer(uint256 offerId) external;
  • Parameters

    ParameterDescription
    offerIdThe unique ID of the offer
  • Criteria that must be satisfied

    • The caller of the function must be the offeror.

acceptOffer

What: Accept an offer made for your NFTs.

  • Interface

    function acceptOffer(uint256 offerId) external;
  • Parameters

    ParameterDescription
    offerIdThe unique ID of the offer.
  • Criteria that must be satisfied

    • The caller of the function must own and approve Marketplace to transfer the tokens for which the offer is made.
    • The offeror must still own and have approved Marketplace to transfer the requisite amount currency offered for the NFTs wanted.

totalOffers

What: Returns the total number of offers created so far.

  • Interface

    function totalOffers() external view returns (uint256);

getOffer

What: Returns the offer at a particular offer Id.

  • Interface

    struct Offer {
    uint256 offerId;
    address offeror;
    address assetContract;
    uint256 tokenId;
    uint256 quantity;
    address currency;
    uint256 totalPrice;
    uint256 expirationTimestamp;
    TokenType tokenType;
    Status status;
    }
    function getOffer(uint256 offerId) external view returns (Offer memory offer);
  • Parameters

    ParameterDescription
    offerIdThe unique ID of an offer.

getAllOffers

What: Returns all offers between the start and end Id (both inclusive) provided.

  • Interface

    function getAllOffers(uint256 startId, uint256 endId) external view returns (Offer[] memory offers);
  • Parameters

    ParameterDescription
    startIdInclusive start offer Id
    endIdInclusive end offer Id

getAllValidOffers

What: Returns all valid offers between the start and end Id (both inclusive) provided. A valid offer is where the offer is active, as well as the offeror still owns and has approved Marketplace to transfer the currency tokens.

  • Interface

    function getAllValidOffer(uint256 startId, uint256 endId) external view returns (Offer[] memory offers);
  • Parameters

    ParameterDescription
    startIdInclusive start offer Id
    endIdInclusive end offer Id