A tutorial on how to make a script for selling NFTs on caviar.
This tutorial assumes you already went through the buying tutorial here. If you haven't read through that tutorial yet I would highly recommend reading it first.
In the previous tutorial we already installed the required dependencies and purchased some BAYCs. Now, let's figure out how to sell those NFTs back into the same pool.
First, create a file called sell.js and then import the dependencies and setup the required interfaces:
constethers=require("ethers");constcaviarAbi=require("./caviar.abi.json");constpairAbi=require("./pair.abi.json");const { Alchemy,Network } =require("alchemy-sdk");constfetch=require("node-fetch");require("dotenv").config();constmain=async () => {// create the provider to connect to the networkconstprovider=newethers.providers.AlchemyProvider("goerli",process.env.ALCHEMY_API_KEY );// create the signer to sign transactions from a walletconstsigner=newethers.Wallet(process.env.PRIVATE_KEY, provider);// create the contract instance for caviarconstGOERLI_CAVIAR_ADDRESS="0x15B9D8ba57E67D6683f3E7Bec24A32b98a7cdb6b";constGOERLI_BAYC_ADDRESS="0xc1a308d95344716054d4c078831376fc78c4fd72";constCaviar=newethers.Contract(GOERLI_CAVIAR_ADDRESS, caviarAbi, provider );// fetch the address of the floor pair for BAYC:ETHconstbaycEthPairAddress=awaitCaviar.pairs(GOERLI_BAYC_ADDRESS,// address of the NFT tokenethers.constants.AddressZero,// address of the base token (address(0) for ETH)ethers.constants.HashZero // hash of the merkle root for valid token ids (hash(0) for no merkle root) );// create the contract instance for the floor BAYC:ETH pairconstBaycEthPair=newethers.Contract(baycEthPairAddress, pairAbi, signer);console.log("BAYC:ETH pair address:", baycEthPairAddress);// fetch the reserves for the BAYC:ETH pairconstbaseTokenReserves=awaitBaycEthPair.baseTokenReserves();constfractionalTokenReserves=awaitBaycEthPair.fractionalTokenReserves();};main();
Now using the xy=k formula and re-implementing the sellQuote logic in the pair contract, we can figure out how much ETH we expect to receive from selling the two BAYCs into the pool. We will use this value as the minimum expected output when submitting the transaction to the pair contract.
constmain=async () => {/// ... SNIP ... ///// calculate the amount of ETH received when selling 2 BAYCs using xy=k formula// 2 BAYC == 2 * 10^18constAMOUNT_TO_SELL=ethers.BigNumber.from("2").mul(ethers.utils.parseUnits("1",18));constethReceived=AMOUNT_TO_SELL.mul("990").mul(baseTokenReserves).div(fractionalTokenReserves.mul("1000").add(AMOUNT_TO_SELL.mul("990")));console.log("ETH received:",ethers.utils.formatEther(ethReceived),"ETH");};
Great, now we can fetch the token IDs of the BAYCs that we hold and use them as input to the sell transaction.
constmain=async () => {/// ... SNIP ... ///// create the alchemy instanceconstalchemy=newAlchemy({ apiKey:process.env.ALCHEMY_API_KEY, network:Network.ETH_GOERLI, });// fetch some BAYC NFTs that you currently holdconst { ownedNfts } =awaitalchemy.nft.getNftsForOwner(signer.address, { contractAddresses: [GOERLI_BAYC_ADDRESS], });consttokenIdsToSell= [ownedNfts[0].tokenId, ownedNfts[1].tokenId];console.log("Token IDs to sell:", tokenIdsToSell);};
To execute the sell, the pair contract must be first approved for spending the BAYCs from your wallet. So on your first sell two transactions must be submitted - one to approve, and one to execute the sell. After you have approved the contract once, it does not need to be done again and you can execute sells directly. We will need the ERC721 ABI to make the approval. Create a file erc721.abi.json and copy and paste the abi from here. And then import it at the top of the file:
consterc721Abi=require("./erc721.abi.json");
Now lets make the approval and swap:
constmain=async () => {/// ... SNIP ... ///constbayc=newethers.Contract(GOERLI_BAYC_ADDRESS, erc721Abi, signer);constapproveTx=awaitbayc.setApprovalForAll(baycEthPairAddress,true);console.log("Approve transaction:", approveTx);awaitapproveTx.wait();// deadline for the trade is 60 minutes from nowconstdeadline=parseInt((Date.now() +1000*60*60) /1000);console.log("Trade deadline unix timestamp:", deadline);// fetch the stolen NFT proofs from reservoirconstreservoirUrl=`https://api.reservoir.tools/oracle/tokens/status/v2?${tokenIdsToSell.map((tokenId) =>`tokens=${GOERLI_BAYC_ADDRESS}:${tokenId}`).join("&")}`;const { messages } =awaitfetch(reservoirUrl, { headers: { "x-api-key":"demo-api-key" },// you can use your own API key here or the default "demo-api-key" }).then((res) =>res.json());// order the stolen NFT proofs so that the match up with the tokenIdsToSell arrayconstorderedMessages=tokenIdsToSell.map( (tokenId) =>messages.find(({ token }) =>token.split(":")[1] ===tokenId.toString()) .message );consttx=awaitBaycEthPair.nftSell( tokenIdsToSell, ethReceived, deadline, [], orderedMessages );console.log("Sell transaction:", tx);};
Nice, you just sold two BAYCs. You should see a transaction very similar to this one here. The code for this tutorial is all available on github here. Thanks for reading and feel free to reach out on discord if you have any questions!