How to make a buy programatically

A tutorial on how to make a script for buying NFTs on caviar

For this tutorial we will use the ethers.js library and alchemy to programatically buy a few BAYC NFTs on the goerli testnet. To get goerli ETH you can claim some from paradigm's faucet.

The first step is to create an empty directory and install the dependencies:

mkdir caviar-swap-example
cd caviar-swap-example
npm init -y
npm install ethers dotenv alchemy-sdk node-fetch@2

Now before we start writing code we also need the required ABI files (caviar.abi.json and pair.abi.json) to interact with the contracts. Those can be found on the caviar github repository here. Copy and paste caviar.abi.json and pair.abi.json into the root of caviar-swap-example/.

Great, now we can start actually writing the script. Create a file buy.js and then open it and import the required dependencies along with making an empty entrypoint function called main().

const ethers = require("ethers");
const caviarAbi = require("./caviar.abi.json");
const pairAbi = require("./pair.abi.json");
const { Alchemy, Network } = require("alchemy-sdk");
require("dotenv").config();

const main = async () => {}

main();

Now we need to setup an ethers.js signer so that we can interact with the chain and send transactions. Inside of the main() function:

const main = async () => {
  // create the provider to connect to the network
  const provider = new ethers.providers.AlchemyProvider(
    "goerli",
    process.env.ALCHEMY_API_KEY
  );

  // create the signer to sign transactions from a wallet
  const signer = new ethers.Wallet(process.env.PRIVATE_KEY, provider);
};

And then create a .env file with the following content:

ALCHEMY_API_KEY=<your alchemy api key>
PRIVATE_KEY=<your private key>

Now let's fetch the pair contract address for floor BAYC:ETH pair and create a contract instance that we can interact with. We can do this by querying the Caviar factory contract:

const main = async () => {
  /// ... SNIP ... ///

  // create the contract instance for caviar
  const GOERLI_CAVIAR_ADDRESS = "0x15B9D8ba57E67D6683f3E7Bec24A32b98a7cdb6b";
  const GOERLI_BAYC_ADDRESS = "0xc1a308d95344716054d4c078831376fc78c4fd72";
  const Caviar = new ethers.Contract(
    GOERLI_CAVIAR_ADDRESS,
    caviarAbi,
    provider
  );

  // fetch the address of the floor pair for BAYC:ETH
  const baycEthPairAddress = await Caviar.pairs(
    GOERLI_BAYC_ADDRESS, // address of the NFT token
    ethers.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 pair
  const BaycEthPair = new ethers.Contract(baycEthPairAddress, pairAbi, signer);
  console.log("BAYC:ETH pair address:", baycEthPairAddress);
};

Cool, that's most of the setup out of the way. Now let's query the contract and figure out how much ETH we will have to spend to buy two BAYCs from the pool. To do this we need to query the reserves and implement some xy=k logic which is the same as buyQuote in the pair contract:

const main = async () => {
  /// ... SNIP ... ///

  // fetch the reserves for the BAYC:ETH pair
  const baseTokenReserves = await BaycEthPair.baseTokenReserves();
  const fractionalTokenReserves = await BaycEthPair.fractionalTokenReserves();

  // calculate the amount of ETH to buy 2 BAYCs using xy=k formula
  // 2 BAYCs == 2 * 1e18 fractional tokens
  const AMOUNT_TO_BUY = ethers.BigNumber.from("2").mul(ethers.utils.parseUnits("1", 18));
  const ethCost = AMOUNT_TO_BUY
    .mul(baseTokenReserves)
    .mul("1000")
    .div(fractionalTokenReserves.sub(AMOUNT_TO_BUY).mul("997"))
    .add("1");

  console.log("ETHER cost to buy 2 BAYCs:", ethers.utils.formatEther(ethCost), "ETH");
};

So we know how much ETH we will need to spend. All we need to do now is pick some NFTs that we want to buy from the pair. In order to do this we need to find out what NFTs the pair actually holds. This can be done via the alchemy NFT API:

const main = async () => {
  /// ... SNIP ... ///

  // create the alchemy instance
  const alchemy = new Alchemy({
    apiKey: process.env.ALCHEMY_API_KEY,
    network: Network.ETH_GOERLI,
  });

  // fetch some BAYC NFTs that are currently in the pair contract
  const { ownedNfts } = await alchemy.nft.getNftsForOwner(baycEthPairAddress, {
    contractAddresses: [GOERLI_BAYC_ADDRESS],
  });

  const tokenIdsToBuy = [ownedNfts[0].tokenId, ownedNfts[1].tokenId];
  console.log("Token Ids to buy:", tokenIdsToBuy);

  const deadline = parseInt((Date.now() + 1000 * 60 * 60) / 1000);
  console.log("Trade deadline unix timestamp:", deadline);
};

Although you can choose any two NFTs, we will just choose the first two that were returned from the response. Now for the final moment! let's actually submit a transaction to buy:

const main = async () => {
  /// ... SNIP ... //

  // submit the transaction to buy the NFTs
  const tx = await BaycEthPair.nftBuy(tokenIdsToBuy, ethCost, deadline, { value: ethCost });
  console.log("Transaction:", tx);
};

If all goes well you should have just bought two BAYCs. Here is a link to a transaction I submitted using this script on etherscan. Yours should look quite similar except with different token IDs. All of the code for this tutorial is available on github here. The next step is now to make a sell transaction. The sell tutorial is available here.

Last updated