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.
"Bro have you ever listened the mempool?
— Krakovia (@krakovia_evm) May 15, 2024
What sound does it make?" pic.twitter.com/iLt8cppi26
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 thiscd /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... β
βββββββββββββ΄ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ