6 min read

[EthersJs - 2] It is all about connections!

Learn how to connect to the blockchain and extract information
[EthersJs - 2] It is all about connections!
Photo by Toa Heftiba / Unsplash

Now that we have the playground setup, let's do something fun with it instead of a simple console log statement. To do anything with blockchain, we must connect with the blockchain. Therefore, that is where our adventure must begin!

Connecting to Blockchain and reading ETH balances


For fun,  let's connect to the ethereum mainnet and find out how much ETH Vitalik is holding in his wallet at the moment.

Therfore, Goal of this first adventure:

  • We want to connect to the Ethereum mainnnet
  • We want to know how much ETH is in Vitalik Buterin's wallet at this moment.

For this, we will need Vitalik's address. How do we know the address of Vitalik's account?

Because of this tweet:

To connect to the ethereum mainnet, we will be using - you have guessed it...Ethers JS.

Before we go any further, let's learn about the most common concepts of Ethers Js

  • Provider - a class that provides an abstraction for connection to the Ethereum or EVM compatible network
  • Signer - a class that has access to private key of an address with which we can sign messages and send transactions.

In this lesson, we do not need to send any transactions or sign a message, so we will not be needing a signer. We will however be using a provider.

Let's replace our console.log statement with the following code in play.js


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

const provider = new ethers.providers.getDefaultProvider();
const address = '0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B'

const main = async () => {
	const balance = await provider.getBalance(address)
	console.log(`Vitalik owns ${ethers.utils.formatEther(balance)} ETH`)
   }

main()

Then, lets run the script with the command node play

We should get something like:

Disregarding the notice ( we will come back to it later), we see that we have successfully pulled the amount of ETH Vitalik has in this particular account at this moment.  

Now, let's break down the code line by line and understand what it really did:

In line 2, we import the ethers package

In line 4, we define our Provider. This is the most important part of the script. This line initiates a provider instance through which, we connect to the Ethereum mainnet.

line 5 is pretty self explanatory, we just define Vitalik's address. We can replace his address with any other address or ENS (e.g. ethers.eth) to get balance of that address/ENS.

Underneath the hood, the default provider connects to a number of nodes provided by Infura, Alchemy, Cloudflare, Pocket and others. When a request is made, it forwards that request to multiple backends simultaneously. As responses from each backend are returned, they are checked that they agree. Once a quorum has been reached, it gives us the result.

This ensures that if a backend has become out-of-sync, or if it has been compromised that its responses are dropped in favor of responses that match the majority. – Ethers Docs

However, you can imagine this all takes a while, which is why whenever we use the provider to get some data, we use await keyword (line 8) , and since we want to use await, we wrap the data fetching logic inside a async function. [lines 7-10 ]

Once we get the balance, we log the balance out in  line 9.  The balance of ethers returned in line 8 is in wei (the smallest denomination of ether).

 1 ETH = 10^18 wei

With that many 0s, this is not easily readable. So, we format this value with the utility function ethers.utils.formatEther(balance)

Finally, we call the function main() in line 12,  so that whenever we run the script with node play , it executes the function main.

And that is it, there's not much to it.

Now, how do we get rid of the annoying request rate exceeded notice?

We do it by using a private node instead of the shared connection that every ethers user is using. We will also replace the default provider with an Infura provider. While the default provider provides redundancy, we really don't need it while learning.

We do this in two steps

Step 1

Sign up in Infura.io and get an API key. Once you login to the Dashboard, you can create new key

Once you have an api key, based on it, you will have access to an https endpoint which will look like  - https://mainnet.infura.io/v3/[your-api-key-here]. This is your gateway to the Ethereum blockchain which the ethers provider will generate under the hood based on the api key.

Note: Do not share this private key with anyone

Step 2

Replace line 4 of the play.js script with:

const provider = new ethers.providers.InfuraProvider(
	"mainnet",        
    [your-api-key]
);

Note: replace [your-api-key] with your api key that you got from Infura. Your script should look like this now:

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

const provider = new ethers.providers.InfuraProvider(
	"mainnet",        
    "b579aaa1382aa5446cccfd3645caf37"
);
const address = '0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B'

const main = async () => {
    const balance = await provider.getBalance(address)
    console.log(`Vitalik owns ${ethers.utils.formatEther(balance)} ETH`)
}

main()

Now you can run the script without any annoying notice!

You can read more about the Infura provider in the documentation

As an optional exercise, you can try to replace the Infura provider with Alchemy or EtherScan Provider to achieve similar results by consulting the documentation linked above.

You can also connect to different chains that Infura supports other than "mainnet", by replacing that keyword with the names of other chains like "kovan" or "polygon".

Connecting to a Smart Contract


Okay great, so now we know how to get Vitalik's ETH balance, but how would we know how much SHIB (Shiba Inu) he still holds?

For that,

  • We have to connect to a smart contract (Shiba Inu's ERC20 contract in this case).
  • Then, we can call the  ERC20 contract's balanceOf function to access the data we need.  (For ERC20 specification, check https://eips.ethereum.org/EIPS/eip-20)

To connect to a smart contract, we will need its Application Binary Interface (ABI) and the address where it is deployed.  An ABI  provides a standard way to interact with contracts both from outside the blockchain and for contract-to-contract interaction.  Another way of thinking about it is that - ABI is mapping of function inputs, parameters and outputs.

So, to connect to the Shiba INU contract ( which is just a modified implementation of standard ERC) We do this:

Note: We do not need the entire ABI, we can just pass in the mapping of functions we intend to call.

const ERC20_ABI = [
    "function totalSupply() view returns (uint256)",
    "function balanceOf(address) view returns (uint)",
];

const address = '0x95ad61b0a150d79219dcf64e1e6cc01f0b64c4ce' // SHIBA INU
const contract = new ethers.Contract(address, ERC20_ABI, provider)

Now that we have the contract instance, we can call balanceOf(address) and totalSupply() functions on it.  And since we know that it will take some time to hear back from the blockchain through our provider, we will have to use the await keyword and wrap the promises in async function.

We do it like this -

const vitalik = '0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B'

const main = async () => {
    const totalSupply = await contract.totalSupply()
	const balance = await contract.balanceOf(vitalik)
    console.log(`Balance: ${ethers.utils.formatEther(balance)}`)
    percent_vitalik_holds = balance/totalSupply * 100
    console.log(`Vitalik hold ${percent_vitalik_holds}% of the total shiba supply`)
}

main()

Here, we are calling totalSupply() to get the total supply of Shiba, and then we are getting how many Shiba tokens Vitalik holds with balanceOf() function. Then we can calculate the the percentage of supply he holds.

Putting the two parts together, our complete script should looks like:

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

const provider = new ethers.providers.InfuraProvider(
	"mainnet",        
    "f579b1382aa5446cadfccfd3643caf37"
);

const ERC20_ABI = [
    "function totalSupply() view returns (uint256)",
    "function balanceOf(address) view returns (uint)",
];

const address = '0x95ad61b0a150d79219dcf64e1e6cc01f0b64c4ce' // SHIBA INU
const contract = new ethers.Contract(address, ERC20_ABI, provider)
const vitalik = '0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B'

const main = async () => {
    const totalSupply = await contract.totalSupply()
	const balance = await contract.balanceOf(vitalik)
    const percent_vitalik_holds = (balance/totalSupply) * 100
    console.log(`Vitalik hold ${percent_vitalik_holds}% of the total shiba supply`)
    console.log(`Balance: ${ethers.utils.formatEther(balance)}`)
}

main();

Running this, we get

As you can see, Vitalik doesn't really hold that much percentage of the total supply, and at the time of writing this tutorial, he holds 5718351 tokens.

In Summary, we learned:

  • The difference between a provider and a signer
  • How to instantiate a provider with default provider option of Ethers
  • How to read Ether balances of addresses/ENS
  • How to use other provider options like Infura Provider, and use private node.
  • How to instantiate a smart contract
  • How to read data from smart contracts
  • How to read ERC20 token balances