Skip to main content
Base has no public mempool — pending transactions are held privately by the sequencer, so eth_newPendingTransactionFilter and eth_subscribe("newPendingTransactions") return empty. Since the Denim upgrade, Base seals a full canonical block every 200 ms, so the standard newHeads and logs subscriptions are the real-time stream: newHeads notifies about five times per second, and logs delivers the matching events of each block. This guide streams transactions block by block and filtered event logs, with tested Python and JavaScript.

Prerequisites

  • A Base Mainnet WebSocket endpoint. On Chainstack, copy the WSS endpoint from your Base node’s Access and credentials section — it looks like wss://base-mainnet.core.chainstack.com/<key>. The examples use YOUR_CHAINSTACK_WSS_ENDPOINT as a placeholder.
  • Python 3.8+ with the websockets library (pip install websockets), or Node.js 22+ (the WebSocket client is built in, so there are no dependencies to install).

Why pending-transaction filters return empty on Base

Base is an OP Stack chain with a single sequencer. Transactions go to the sequencer’s private mempool and are never gossiped to RPC nodes, so a node has no public pending transactions to report. eth_newPendingTransactionFilter and eth_subscribe("newPendingTransactions") are accepted but always return empty — this is by design, not a node error, and it is the same on every Base RPC provider. See Mempool configurations for the per-protocol breakdown. To see transactions as early as possible on Base, stream the sealed blocks instead. A new block arrives every 200 ms.

Stream transactions block by block

eth_subscribe("newHeads") pushes the header of each new block. The header has no transaction list, so request the block by its hash with eth_getBlockByHash over the same WebSocket connection. The examples fetch each block without blocking the stream, because notifications arrive about every 200 ms.
Each line is a block number, its transaction count, and its first transaction hash. The block number advances by one about every 200 ms:
The first two transactions in every Base block are system deposit transactions of type 0x7e: the L1 attributes deposit and the BaseTime deposit. User transactions start at index 2.

Full transactions and receipts

To get full transaction objects instead of hashes, pass true as the second parameter of eth_getBlockByHash. To get every receipt in the block, with status, gasUsed, and logs, call eth_getBlockReceipts with the block hash:

Stream filtered event logs

eth_subscribe("logs") streams the event logs of each new block, filtered by address and topics the same way as eth_getLogs. This example follows USDC transfers on Base:
Each line is a block number, a transaction hash, and the transferred amount in USDC base units (6 decimals). A transaction that moves USDC several times emits several logs:
Each log carries the hash of the sealed block in blockHash, so you can match it to the block from newHeads.

Read the latest state over HTTP

For request-response reads, use the latest block tag, which advances every 200 ms. Since Denim, the pending tag no longer returns preconfirmed Flashblocks state — see Replace Flashblocks calls.

Production considerations

  • Settle against a finalized block. Every 200 ms block goes through the unsafe, safe, and finalized stages, and newHeads reports blocks as soon as they are produced. Before you treat a payment as settled, confirm its transaction in a finalized block.
  • Expect about five blocks per second. Process notifications asynchronously, as the examples do, so a slow handler doesn’t delay the stream.
  • Re-subscribe on reconnect. A subscription ends when the WebSocket drops, so re-send the eth_subscribe request after reconnecting, and fetch the blocks you missed by number.
  • Ranges count blocks. On Base, 10,000 blocks is about 33 minutes. See Chainstack limits that count blocks.

See also

Last modified on November 6, 2026