4 min read

[EthersJs - 7] Saga of blocks, mempool and glimpse of the dark forest

[EthersJs - 7] Saga of blocks, mempool and glimpse of the dark forest
Photo by Shubham Dhage / Unsplash

Blocks in Ethereum are just batches of transactions that has been verified by the network. It has hash of previous block, which links blocks together in a chain. Hence, the word blockchain. Here's a really cool visualization tool inspired by South Park for transactions and blocks that I recommend you checkout:

Blockchain Transaction Visualizer - TxStreet.com
Cryptocurrency (Bitcoin, Ethereum etc) blockchain live transaction visualizer. Every tx is a person, and they fill up buses which represent blocks.

We will explore blocks with Ethers JS in this tutorial.

Assuming we have a provider, we can get the current block number with provider.getBlockNumber() Then, after we have the block number, we can look into a block with provider.getBlockWithTransactions(blockNumber) by passing it the block number as the parameter.

Let's put it together:  

const ethers = require("ethers");
const provider = new ethers.providers.InfuraProvider(
    "mainnet",        
    "your-infura-key"
)

const main = async () => {
    const blockNumber = await provider.getBlockNumber()
    const blockInfo = await provider.getBlock(blockNumber)
    console.log(blockInfo)

    const { txs } = await provider.getBlockWithTransactions(block)

    console.log("--- the first transaction -----")
    console.log(txs[0])
}

main()

Here we used the async/await format again for querying the ethereum mainnet blockchain.

First, we get the block number, Then, we pass in the block number to get block info. We then console.log out the the block (which is a collection of verified transactions). This looks like:

We then, also log the details of first transaction with console.log(tx[0]).  Which looks like:

Theoretically, we can go through each block and all the transaction to pull any data from the mainnet using this method. That would be time consuming and impractical. Thankfully, there are many options to deal with this like - The Graph, Moralis, Transpose Data, Dune and more. Going through these solutions is not within the scope of this discussion, but I encourage you to go look at what these solutions are doing if you need access to historical data.

Ethereum Blocks are mined approximately every 12 seconds. If a transaction is included in the current block, it might take 12 seconds for it to get verified. Therefore, we can conclude that there are a lot of transactions waiting in queue to be verified by the validator nodes. This queue of unverified transactions is called the mempool.

Let's see how we can access mempool with Ethers JS. We can do so by simply querying pending transactions with:

const ethers = require("ethers");
var provider = new ethers.providers.WebSocketProvider(
  "wss://mainnet.infura.io/ws/v3/[your-infutra-key-here]"
);


const main = () => {
    provider.on("pending", (tx) => {
    console.log(tx)
  })
}

main()

Running this, we get a streaming list of transaction hashes waiting to validated and included in the block:

Note: Since it is a streaming list of incoming transactions we need to use websocket provider instead of normal provider.

We can also get details of each transaction :

const ethers = require("ethers");
var provider = new ethers.providers.WebSocketProvider(
  "wss://mainnet.infura.io/ws/v3/f579b1382aa5446cadfccfd3643caf37"
);

const main = async () => {
    provider.on("pending", async (txhash) => {
    let tx = await provider.getTransaction(txhash)
    if(tx != null){
        let gasPrice = ethers.utils.formatEther(tx.gasPrice)
        console.log(`${txhash} -> ${gasPrice}`)
    }
  })
}

main()

Here, as an example we pull the gas price of a transcation -

First, we get details of a transaction with

let tx = await provider.getTransaction(txhash)

and then, once we have the transaction, we get the gas price with

let gasPrice = ethers.utils.formatEther(tx.gasPrice)

Then we just log it out:

Instead of parsing the transaction we can also just log the entire transaction with

const ethers = require("ethers");
var provider = new ethers.providers.WebSocketProvider(
  "wss://mainnet.infura.io/ws/v3/f579b1382aa5446cadfccfd3643caf37"
);

const main = async () => {
    provider.on("pending", async (txhash) => {
    let tx = await provider.getTransaction(txhash)
    if(tx != null){
        console.log(tx)
    }
  })
}

main()

The transaction details looks like this:

As you can see, you get all detailed information of pending transaction.  This forms the basics of mempool oggling.

Now the million dollar question is - what can we do with all the mempool information. The answer is - welcome to the world of MEV.

Instead of me explaining what it is all about, I will link two amazing resources everyone should look into if you are interested in the subject.

Ethereum is a Dark Forest - Paradigm
This is a horror story. The Challenge Like any normal person, I spend a lot of time lurking in the #support channel of the Uniswap Discord. (Disclosure: Uniswap is a portfolio company of Paradigm.) On