Locally reading from a live smart contract

Make sure you have the contract's ABI. Either via a blockchain explorer where it's published as verified, or compiled directly from source code in something like Remix.

In this example, we'll find the cheapest NFTs for sale in the first collection on Blvckmarket. Find the ABI here.

The function that gives us the price of items is tokenIdToSellingItem where sellingItem is a struct containing values of an "order". The output looks like this when an item is not for sale:

... and like this when it is:

So we need to check if price > 0, register the NFT ID and the price value, and then order by price.

Now that we know what we need to do, let's fire up a small local project.

Starting

yarn init #complete the setup by hitting enter
yarn add ethers

Then we make a simple JS file.

const { ethers } = require("ethers");
const provider = new ethers.providers.JsonRpcProvider(
  "https://pub.elara.patract.io/moonriver"
);

async function start() {
  let b = await provider.getBlockNumber();
  console.log(b);
}

start();

We run it by running node index.js.

This initiates a connection to Moonriver over Patract's node and reports back the current block number.

Note that I'm using Patract here for a node but that's super slow, you'd do better with another provider if you don't want to wait for hours for the loop to finish.

ABI

To check that we can interact with the contract, we first download its ABI JSON payload, and include it in our app - you can include it in a separate file, or you can include it directly into this main entry point of the app, doesn't matter in a throwaway application like this. I'll do the latter.

const { ethers } = require("ethers");
const provider = new ethers.providers.JsonRpcProvider(
  "https://pub.elara.patract.io/moonriver"
);

const address = "0xfed9e29b276c333b2f11cb1427142701d0d9f7bf";
const abi = [ ... ];

const blvckContract = new ethers.Contract(address, abi, provider);

async function start() {
  let b = await blvckContract.totalSupply();
  console.log(b);
}

start();

You'll notice the result gives us a hex version of the data:

[email protected]:~/repos/blvck$ node index.js
BigNumber { _hex: '0x0535', _isBigNumber: true }

We need to convert this to int so it's readable to us. Each BigNumber has a toString utility, so if we just do

console.log(b.toNumber());

... we should get 1333, which is the total supply of this NFT.

Loop

To find out the price of all items we need to loop through all available ones and fetch the price of those that are up for sale.

Let's try this with a smaller loop first:

async function start() {
  let b = await blvckContract.totalSupply();
  if (b.toNumber() > 0) {
    let returnPayload;
    for (let i = 0; i < 10; i++) {
      returnPayload = await blvckContract.tokenIdToSellingItem(i);
      console.log(returnPayload);
    }
  }
}

We should get results not unlike this:

Now let's make an array collecting all the price-item pairs.

async function start() {
  let b = await blvckContract.totalSupply();
  let pairs = [];
  if (b.toNumber() > 0) {
    let returnPayload;
    for (let i = 0; i < 15; i++) {
      returnPayload = await blvckContract.tokenIdToSellingItem(i);
      if (returnPayload.price.toBigInt() != BigInt(0)) {
        pairs.push([i, returnPayload.price.toBigInt()]);
      }
    }
  }

  console.log(pairs);
}

This adds to an array one array per NFT where the 0th element is the NFT's ID and the 1st element is the price in BigInt format.

The output is now a full list of the currently for-sale NFTs.

Let's sort it by price too:

  pairs.sort(function (a, b) {
    if (a > b) {
      return 1;
    } else if (a < b) {
      return -1;
    } else {
      return 0;
    }
  });

Finally, let's unlock the full loop, sprinkle in some file output magic in here to make a CSV, and our final code looks like this:

const { ethers } = require("ethers");
const fs = require("fs");

const provider = new ethers.providers.JsonRpcProvider(
  "https://moonriver-api.bwarelabs.com/ccec8d3c-ed1a-46c0-905b-115e16945323"
);

const address = "0xfed9e29b276c333b2f11cb1427142701d0d9f7bf";
const abi = [];

const blvckContract = new ethers.Contract(address, abi, provider);

async function start() {
  let b = await blvckContract.totalSupply();
  let pairs = [];
  if (b.toNumber() > 0) {
    let returnPayload;
    for (let i = 0; i < b.toNumber(); i++) {
      returnPayload = await blvckContract.tokenIdToSellingItem(i);
      if (returnPayload.price.toBigInt() != BigInt(0)) {
        pairs.push([i, returnPayload.price.toBigInt()]);
      }
    }
  }

  pairs.sort(function (a, b) {
    if (a[1] > b[1]) {
      return 1;
    } else if (a[1] < b[1]) {
      return -1;
    } else {
      return 0;
    }
  });

  let str = "";
  for (let pair of pairs) {
    str += `${pair[0]},${pair[1].toString()}\n`;
  }

  fs.writeFileSync("output.csv", str);
}

start();

This will take a while to run with a remote node, so it's always recommended to run a local one instead so you can hook directly into it but running a local copy of an EVM node is outside of the scope of this post.

Happy hunting!

0 comments