How to access the mempool

The mempool is where all the transactions are stored before they are included in a block.
When you send a transaction, it goes to the mempool first.
Validators pick transactions from the mempool and include them in a block.

Imagine the mempool as a big pool of screaming transactions, waiting to be picked up by a validator.

Here you can see a streaming example of the ETH's mempool.

If you download Metamask, charge some ETH and send a transaction, you can see the transaction in the mempool.
That's an important part of many blockchain systems, BTC included.

Mempool.space is a mempool explorer for BTC, for example.

But we're on EVM, our space is a little different.

etherscan.io has a mempool explorer for Ethereum.

On ethereum there's a block every ~12 seconds, but if you send your transactions in the last seconds, it can still be mined in the next block.
This will make your transaction appear in the mempool for a very short time.

Where is the mempool in EVM?

Every full or validator node has a mempool.
It's constantly shared between all the nodes in the network.
You can access the mempool of a node by using the JSON-RPC API, but most of the public one have that subscription disabled.

What data is in the mempool?

Well.. your transaction is there.
So

  • from
  • to
  • value
  • gas
  • gasPrice
  • nonce
  • data

until it gets dropped (because of low gas price or replaced)
or it gets included in a block.

LET ME IN!

Let's get into the mempool of a node.
You will create this

Choose your hero:

Python

In the python example, the node must support newPendingTransactions with full tx body.
This will avoid the need to fetch the transaction data from the node (getTransaction).

cd /py
python -m venv venv
.\venv\Scripts\activate (or 'source venv/bin/activate' on linux)
pip install web3
pip install rich (for the tables)

create a new file called main.py

from web3 import AsyncWeb3
from web3.providers import WebsocketProviderV2
import asyncio
from rich.console import Console
from rich.table import Table


async def listen_mempool():
    tx_count = 0
    ws_url = "ws://127.0.0.1:8549"
    async with AsyncWeb3.persistent_websocket(
        WebsocketProviderV2(ws_url)
    ) as w3:
        print("Connected to websocket")
        pending_subscription = await w3.eth.subscribe("newPendingTransactions", True)
        print("Subscribed to pending transactions")
        async for response in w3.ws.process_subscriptions():
            if response["subscription"] == pending_subscription:
                tx           = response["result"]
                tx_from      = f'{tx["from"]}'
                tx_to        = f'{tx["to"]}'
                tx_value     = f'{tx["value"]}'
                tx_gas_price = f'{tx["gasPrice"]}'
                tx_gas_limit = f'{tx["gas"]}'
                tx_nonce     = f'{tx["nonce"]}'
                tx_data      = f'{tx["input"].hex()}'
                tx_data = tx_data[:50] + '...' if len(tx_data) > 50 else tx_data

                # print a table with the transaction details
                table = Table(title=f"New Pending Transaction ({tx_count} tx so far)")
                table.add_column("Field")
                table.add_column("Value")
                table.add_row("From", tx_from)
                table.add_row("To", tx_to)
                table.add_row("Value", tx_value)
                table.add_row("Gas Price", tx_gas_price)
                table.add_row("Gas Limit", tx_gas_limit)
                table.add_row("Nonce", tx_nonce)
                table.add_row("Data", tx_data)
                console = Console()
                console.print(table)
                tx_count += 1

asyncio.run(listen_mempool())

the output will be like this

               New Pending Transaction (15 tx so far)
┏━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Field     ┃ Value                                                 ┃
┑━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
β”‚ From      β”‚ 0x3dd375Ff03968851B5e52c9202F902E680f3dF85            β”‚
β”‚ To        β”‚ 0xdAC17F958D2ee523a2206206994597C13D831ec7            β”‚
β”‚ Value     β”‚ 0                                                     β”‚
β”‚ Gas Price β”‚ 4100000000                                            β”‚
β”‚ Gas Limit β”‚ 64043                                                 β”‚
β”‚ Nonce     β”‚ 27                                                    β”‚
β”‚ Data      β”‚ 0xa9059cbb00000000000000000000000016f99d01c1d8ada2... β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
Javascript i'll use ethers.js version 5 for this
cd /js
pnpm init
pnpm i ethers@5

create a new file called main.js

const { ethers } = require('ethers');
const provider   = new ethers.providers.WebSocketProvider("http://127.0.0.1:8549");
var tx_count     = 0;

provider.on('error', (error) => {
    console.error('Provider error:', error);
});
console.log("Listening for pending transactions...");

// listen for mempool transactions
provider.on('pending', async (txHash) => {
    try {
        const tx = await provider.getTransaction(txHash);
        console.warn("--------------------------------------");
        console.log(`Transaction ${tx.hash} received (${tx_count} transactions received so far)`);

        // extract data
        const tx_from     = tx.from;
        const tx_to       = tx.to;
        const tx_value    = ethers.utils.formatEther(tx.value);
        const tx_gasPrice = ethers.utils.formatUnits(tx.gasPrice, 'gwei');
        const tx_gasLimit = BigInt(tx.gasLimit).toString();
        const tx_nonce    = tx.nonce;
        const tx_data     = tx.data.length > 50 ? tx.data.substring(0, 50) + '...' : tx.data;

        // console table all data
        console.table({
            'From'     : tx_from,
            'To'       : tx_to,
            'Value'    : tx_value,
            'Gas Price': tx_gasPrice,
            'Gas Limit': tx_gasLimit,
            'Nonce'    : tx_nonce,
            'Data'     : tx_data
        });
        tx_count++;
    } catch (error) {
        console.error('Error:', error);
    }
});

the output will be like this

Transaction 0xa846f78e6825af5f263984bc98a749cf2e09e8a12c3af3ad7d223c1bae2a5750 received (8 transactions received so far)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  (index)  β”‚                    Values                    β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚   From    β”‚ '0x3C9B6EcEF221cEb3409d5E118955CEC4082A11d3' β”‚
β”‚    To     β”‚ '0xF13CeBaA72510aEE5c5feB603b052fE1bDB69AA8' β”‚
β”‚   Value   β”‚                   '0.001'                    β”‚
β”‚ Gas Price β”‚                '3.733313056'                 β”‚
β”‚ Gas Limit β”‚                   '21000'                    β”‚
β”‚   Nonce   β”‚                     273                      β”‚
β”‚   Data    β”‚                     '0x'                     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
Rust
cd /rs
cargo init

add this to your Cargo.toml


[dependencies]
comfy-table = "7.1.1"
ethers = { version= "2.0.14", features = ["ws"] }
tokio = { version = "1", features = ["full"] }

open file src/main.rs

use ethers::providers::{Middleware, Provider, ProviderError, StreamExt, Ws};
use std::sync::Arc;
use std::vec;
use comfy_table::Table;
use comfy_table::presets::UTF8_FULL;
use comfy_table::modifiers::UTF8_SOLID_INNER_BORDERS;
use comfy_table::modifiers::UTF8_ROUND_CORNERS;

const RPC_URL: &str = "ws://127.0.0.1:8549";

#[tokio::main]
async fn main() {
    let mut tx_count = 0;
    // connect to the network
    let provider = match Provider::<Ws>::connect(RPC_URL).await.map_err(|e| eprintln!("Failed to connect to the network: {}", e)) {
        Ok(provider) => Arc::new(provider),
        Err(_) => {
            return;
        }
    };
    let mut stream = match provider.subscribe_pending_txs().await {
        Ok(stream) => stream,
        Err(e) => match e {
            ProviderError::JsonRpcClientError(error) => {
                println!("(mempool_tracker)sub Error: {} retrying...", error);
                return;
            },
            // other errors
            _ => {
                println!("(mempool_tracker)sub Error (unknown): {} retrying...", e);
                return;
            }
        }
    };
    while let Some(tx) = stream.next().await {
        // fetch tx from rpc
        let tx = match provider.get_transaction(tx).await {
            Ok(tx) => tx,
            Err(e) => {
                println!("(mempool_tracker)tx Error: {}", e);
                continue;
            }
        };
        // check if tx is None
        let tx = match tx {
            Some(tx) => tx,
            None => continue,
        };
        let tx_hash = format!("{:?}", tx.hash);
        let tx_from = format!("{:?}", tx.from);
        let tx_to = format!("{:?}", tx.to.unwrap());
        let tx_value = tx.value;
        let tx_gas = tx.gas;
        let tx_gas_price = tx.gas_price.unwrap();
        let tx_nonce = tx.nonce;
        let tx_data = if tx.input.len() > 50 {
            format!("{}...", tx.input.to_string().chars().take(50).collect::<String>())
        } else {
            tx.input.to_string()
        };
        println!("--------------------------------------");
        println!("Transaction {} received ({} transactions received so far)", tx_hash, tx_count);

        let mut table = Table::new();
        table
        .load_preset(UTF8_FULL)
        .apply_modifier(UTF8_SOLID_INNER_BORDERS)
        .set_header(vec!["Key", "Value"])
        .add_row(vec!["From",&tx_from.to_string()])
        .add_row(vec!["To",&tx_to.to_string()])
        .add_row(vec!["Value",&tx_value.to_string()])
        .add_row(vec!["Gas",&tx_gas.to_string()])
        .add_row(vec!["Gas Price",&tx_gas_price.to_string()])
        .add_row(vec!["Nonce",&tx_nonce.to_string()])
        .add_row(vec!["Data",&tx_data]);
        println!("{}", table);
        tx_count += 1;

    }

}

the output will be like this

Transaction 0x7cb6a00c841076e19243dd2c5344442d9a8895acb6b6b59aad3f9bd36a600e1b received (4 transactions received so far)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Key       β”‚ Value                                                 β”‚
β•žβ•β•β•β•β•β•β•β•β•β•β•β•ͺ═══════════════════════════════════════════════════════║
β”‚ From      β”‚ 0x1d5f6122773b667bb9eef088df824f989a99444c            β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ To        β”‚ 0x89d584a1edb3a70b3b07963f9a3ea5399e38b136            β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Value     β”‚ 0                                                     β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Gas       β”‚ 70213                                                 β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Gas Price β”‚ 9000000000                                            β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Nonce     β”‚ 1                                                     β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Data      β”‚ 0xa9059cbb00000000000000000000000075e89d5979e4f6fb... β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜