Sell an NFT at a Fixed Price
#
Get started selling an NFT using the Asks v1.1 Module#
Key BenefitsZORA 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 ModuleZORA 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 ZORAnote
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 AskOnce 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 Parametersfunction createAsk( address _tokenContract, uint256 _tokenId, uint256 _askPrice, address _askCurrency, address _sellerFundsRecipient, uint16 _findersFeeBps)
#
Calling Create AskImagine 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 NetworksNote 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 FeeThe 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 AskOnce 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 AddressThe 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 AskAn 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 AskAn 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);