Skip to main content

Sell an NFT at a Fixed Price

Get started selling an NFT using the Asks v1.1 Module#

Key Benefits#

ZORA allows anyone to easily sell any ERC-721 NFT at a fixed price. Selling an NFT via the Asks Module comes with multiple benefits including:

  • Permissionless - No one can stop or censor sales
  • No Protocol Fee - ZORA doesn't take a cut of the final sale amount
  • Instant Royalty Payout - Royalties are paid out as soon as the sale confirms

Asks Module#

ZORA V3 is composed of a few core contracts and an ever-expanding set of modules. The Asks Module allows an ERC-721 NFT to be sold at a fixed price. This guide will walk you through how to interact with the Asks v1.1 module.

You can read more about the latest version of the Asks Module here.


Approving Tokens to ZORA#

note

Before getting started with the Asks Module, we strongly recommend that you check out our guide on how to approve tokens to ZORA V3.

TLDR, ZORA V3 requires that a user approves their assets to a Transfer Helper contract as well as approve the Asks v1.1 Module in the Module Manager contract. Note that token approvals to ZORA are a one-time approval per token contract per user address. In addition, each module only ever needs to be approved once per user address.


Creating an Ask#

Once a user has approved their ERC-721 NFT to the ERC-721 Transfer Helper and approved the Asks v1.1 Module in the Module Manager, they are ready to create an ask. The core components of an ask are as follows:

struct Ask {  address seller;  address sellerFundsRecipient;   address askCurrency;  uint16 findersFeeBps;  uint256 askPrice;}
  • seller: The owner or approved address for an NFT
  • sellerFundsRecipient: The address to send funds to once the NFT is sold
  • askCurrency: ERC-20 token to accept, or the 0 address for ETH
  • findersFeeBps: The bps of the sale amount to be sent to the referrer of the sale
  • askPrice: The sale price
Create Ask Parameters#
function createAsk(  address _tokenContract,  uint256 _tokenId,  uint256 _askPrice,  address _askCurrency,  address _sellerFundsRecipient,  uint16 _findersFeeBps)
Calling Create Ask#

Imagine that I would like to sell a Blitmap with tokenId 1. After approving the ERC-721 Transfer Helper to be able to move my Blitmaps and approving the Asks 1.1 Module in the Module Manager, I am ready to create an ask. In the example below, I set the sale price to 100 ETH. If I were pricing the NFT in an ERC-20, I would pass in the ERC-20 token address as the askCurrency. However, since the sale is priced in ETH I need to set the 0 address.

Lastly, I am adding an incentive by setting the Finders Fee to 2% (200 Basis Points) of the sale amount. Meaning whoever helps find the buyer for this NFT will receive a 2 ETH reward (100 ETH x 2%).

The ABIs for all ZORA V3 contracts can be found in the @zoralabs/v3 npm package. You can use the following 2 packages to import the ABIs and typechain types (if using typescript):

yarn add @zoralabs/v3 ethers or npm install @zoralabs/v3 ethers

import { ethers } from "ethers";import mainnetZoraAddresses from "@zoralabs/v3/dist/addresses/1.json"; // Mainnet addressesimport { AsksV11__factory } from "@zoralabs/v3/dist/typechain/factories/AsksV11__factory";
// This should be an ethers.js signer instance: // You can get the signer from a wallet using web3modal/rainbowkit/blocknative wallet etc.// See: https://docs.ethers.io/v5/api/signer/const provider = new ethers.providers.Web3Provider(web3.currentProvider, 1);const signer = await provider.getSigner();
// Initialize Asks v1.1 Module Contractconst askModuleContract = AsksV11__factory.connect(mainnetZoraAddresses.AsksV1_1, signer);
const blitmapAddress = "0x8d04a8c79cEB0889Bdd12acdF3Fa9D207eD3Ff63"; // Blitmap NFT Contractconst askPrice = ethers.utils.parseEther("100") // 100 ETH Sale Priceconst ownerAddress = "0xF296178d553C8Ec21A2fBD2c5dDa8CA9ac905A00"; // Owner of the assetsconst findersFeeBps = "200"; // 2% Finders Fee (in BPS)
// Calling Create Ask// Notice: Since this interaction submits a transaction to the blockchain it requires a signer.// A signer interfaces with a wallet. You can use walletconnect or injected web3.await askModuleContract.createAsk(  blitmapAddress,  "1", // Token Id  askPrice,  "0x0000000000000000000000000000000000000000", // 0 address for ETH sale  ownerAddress,  findersFeeBps);
Different Networks#

Note these examples use Mainnet, but if you would like to test on Geroli then you can update the networkId in the import to be 5.json to get the Geroli addresses.

@zoralabs/v3/dist/addresses/1.json change to @zoralabs/v3/dist/addresses/5.json

You can also get Geroli ETH at the faucet here or here.

Setting the Finders Fee#

The Finders Fee is an optional amount that can be included when an ask is created. This amount can be awarded to the party that helped matched the buyer to the NFT. The Finders Fee is set in basis points, so if you wanted 2% of the sale amount to go to the finder then you would set findersFeeBps to 200 (2 x 100 = 200).


Filling an Ask#

Once an ask has been created it can be filled by anyone. If the NFT is priced in an ERC-20 token then the user trying to fill the ask must approve their tokens to the ERC-20 Transfer Helper and then approve the Asks 1.1 Module in the Module Manager.

function fillAsk(    address _tokenContract,    uint256 _tokenId,    address _fillCurrency,    uint256 _fillAmount,    address _finder) 
const blitmapAddress = "0x8d04a8c79cEB0889Bdd12acdF3Fa9D207eD3Ff63"; // Blitmap NFT Contractconst fillAmount = ethers.utils.parseEther("100"); // 100 ETH Sale Priceconst finder = "0x17cd072cBd45031EFc21Da538c783E0ed3b25DCc"; // Address that helped find the buyer. Can be the 0 address if no address is specified

// *Note: Since the ask is priced in ETH the transaction must also send the same amount of ETH in the transaction. This can be done by specifying the value as an override in the transaction.const overrides = {  value: fillAmount}await askModuleContract.fillAsk(  blitmapAddress,  "1", // Token Id  "0x0000000000000000000000000000000000000000", // 0 address for ETH sale  fillAmount,  finder,  overrides // Only for asks priced in ETH);
The Finder Address#

The finder is an address of the party that helped matched the buyer to the NFT and is awarded the Finders Fee. The finder address can be set to the 0 address if no address is provided or no Finder's Fee is set. This will skip over the Finder's Fee logic in the contract and will send the total sale amount to the seller's address.


Updating an Ask#

An ask can be updated at anytime, but it can only be updated by the owner of the NFT.

function setAskPrice(  address _tokenContract,  uint256 _tokenId,  uint256 _askPrice,  address _askCurrency) 

In the example below I am updating the price from 100 ETH to 110 ETH.

const blitmapAddress = "0x8d04a8c79cEB0889Bdd12acdF3Fa9D207eD3Ff63"; // Blitmap NFT Contractconst askPrice = ethers.utils.parseEther("110"); // 110 ETH Sale Priceconst overrides = { value: askPrice } // Need to pass in an override in the function call since you are sending Ether in the tx
await askModuleContract.setAskPrice(  blitmapAddress,  "1", // Token Id  askPrice,  "0x0000000000000000000000000000000000000000", // 0 address for ETH sale  overrides);

Cancelling an Ask#

An ask can be canceled by either the token owner or an approved operator. Note that transferring an NFT from the address that originally created the ask will temporarily invalidate the ask. However, if the NFT is ever transferred back to the original address then the ask will become valid again. It is always best to directly cancel an ask rather than transfer the NFT to a different address.

function cancelAsk(address _tokenContract, uint256 _tokenId)
const blitmapAddress = "0x8d04a8c79cEB0889Bdd12acdF3Fa9D207eD3Ff63"; // Blitmap NFT Contract
await askModuleContract.cancelAsk(  blitmapAddress,  "1", // Token Id);