Skip to main content
This tutorial shows you how to watch an Ethereum address for incoming transfers as they land, using ethers v6 and a Chainstack WebSocket (WSS) endpoint. By the end you have two small Node.js scripts — one that catches incoming Ether (ETH) and one that catches incoming ERC-20 token transfers — that print each transfer the moment it is included in a block. TLDR
  • Subscribe to a Chainstack Ethereum WSS endpoint with ethers v6 to watch an address for incoming transfers in real time.
  • Catch native ETH transfers by subscribing to new blocks (newHeads) and scanning each block’s transactions for your address.
  • Catch ERC-20 token transfers by subscribing to the token’s Transfer logs filtered to your address, so the node does the filtering for you.
  • Includes the raw eth_subscribe (newHeads / logs) equivalent for stacks that don’t use ethers.

How real-time address monitoring works

Ethereum gives you two real-time streams over a WebSocket connection, and which one you use depends on the type of transfer you want to catch.
  • Native ETH transfers — a plain transaction with a value and a to field. ETH transfers are not events, so there is no log to filter. You subscribe to new blocks, fetch each block’s transactions, and keep the ones whose to is your address.
  • ERC-20 token transfers — emitted by the token contract as a Transfer event log, not as a transaction addressed to you. You subscribe to the logs stream filtered by the token contract and an indexed to topic, so the node returns only the transfers addressed to your account.
Both streams need a persistent connection, which is why address monitoring uses a WSS endpoint rather than HTTPS. Learn more about the tradeoff in Handle real-time data with WebSockets in JS & Python.

Get your Chainstack WebSocket endpoint

You need a WSS endpoint from an Ethereum node. Deploy a node on Chainstack, then copy the WSS endpoint from the node’s Access and credentials tab.

Sign up with Chainstack

Deploy a node

View node access and credentials

For always-on monitoring, a Trader Node gives dedicated resources and regional placement; a Global Node works on every plan. See Ethereum tooling for the full library and endpoint reference.

Set up the project

Create a project directory and install ethers:
Shell
This tutorial uses ethers v6. Confirm your version with npm ls ethers. In v6 the provider class is ethers.WebSocketProvider; in v5 it was ethers.providers.WebSocketProvider. The code here does not run on v5.

Monitor incoming ETH transfers

To catch incoming ETH, subscribe to new blocks and, for each block, check every transaction’s to against your address. Save the following as monitor-native.js, then paste your WSS endpoint into WSS_ENDPOINT. The example watches the Wrapped Ether (WETH) contract because it receives ETH constantly, so you see results within seconds — replace it with the address you want to monitor.
monitor-native.js
How the script works:
  • provider.on("block", ...) — fires once per new block with the block number. This is the ethers equivalent of a newHeads subscription.
  • getBlock(blockNumber, true) — the second argument prefetches full transaction objects, exposed as block.prefetchedTransactions. Without it you get only transaction hashes.
  • tx.value — a bigint, so compare it with 0n and format it with ethers.formatEther.
  • try/catch — swallows the UNSUPPORTED_OPERATION error that an in-flight getBlock throws when the provider is shutting down.
Run it:
Shell
Example output:
Block scanning catches direct ETH sends where the transaction’s to is your address. ETH moved to your address inside a contract call — an internal transfer — is not a top-level transaction and does not show up here; detecting those requires tracing. See Ethereum tooling for trace methods.

Monitor incoming ERC-20 token transfers

To catch incoming tokens, subscribe to the token’s Transfer logs with the indexed to topic set to your address. The node streams only the matching transfers, so you never scan a full block. Save the following as monitor-token.js. The example watches USDC transfers to an active address so you see results quickly — replace WATCHED_ADDRESS with the address you monitor, and set TOKEN_ADDRESS and TOKEN_DECIMALS for the token you care about.
monitor-token.js
How the filter works:
  • ethers.id("Transfer(address,address,uint256)") — the keccak-256 hash of the event signature, which is topic 0 for every ERC-20 Transfer.
  • topics: [transferTopic, null, ethers.zeroPadValue(WATCHED_ADDRESS, 32)] — position 0 matches the event, null matches any sender, and position 2 pins the indexed to to your address. Addresses are left-padded to 32 bytes in topics, which is what zeroPadValue does.
  • Decoding — the sender is in log.topics[1] and the amount is in log.data. Recover the address with ethers.getAddress, and format the amount with ethers.formatUnits and the token’s decimals.
  • TOKEN_DECIMALS — set this to the token’s decimals. USDC uses 6; most ERC-20 tokens use 18.
Run it:
Shell
Example output:
This log filter runs on the node, so you receive only the transfers addressed to you — more efficient than scanning every block. To watch several tokens, create one filter per token, or omit the to topic to receive every Transfer and filter client-side. For a deeper look at logs and topics, see Ethereum logs and filters.

Handle disconnections and shut down cleanly

WebSocket connections can drop, so a production monitor needs reconnect logic; and when you stop a monitor, close the provider so the process exits cleanly.
  • Reconnects — a dropped socket (codes like 1006, or ECONNRESET) ends the subscription silently. Re-create the WebSocketProvider and re-attach your handlers when the socket closes, or use a Trader Node dedicated gateway. The WebSockets tutorial has a full reconnect pattern.
  • Clean shutdown — call await provider.destroy() to close the socket and detach listeners. Do not call provider.removeAllListeners() right before destroy() — that races the in-flight eth_unsubscribe against the socket teardown and throws provider destroyed; cancelled request. Calling destroy() on its own is enough.

Subscribe with raw JSON-RPC (eth_subscribe)

If you are not using ethers, subscribe directly with the eth_subscribe method over the WSS connection — newHeads for blocks and logs for token transfers. This is what the ethers subscriptions do under the hood. Connect with wscat:
Shell
Subscribe to new block headers:
Subscribe to incoming USDC transfers for an address — the third topic is your address left-padded to 32 bytes:
The node replies with a subscription id, then pushes an eth_subscription message for each new block or matching log. For the full method reference, see eth_subscribe (“newHeads”), eth_subscribe (“logs”), and eth_getLogs for backfilling history.

What’s next

Ethereum logs and filters

Go deeper on event topics, indexed parameters, and log filtering.

Tracking Bored Apes: event logs

A worked example of decoding contract event logs end to end.

Real-time data with WebSockets

Connection limits and a production reconnect pattern.

Ethereum tooling

Libraries, endpoints, and trace methods for Ethereum development.
Last modified on July 10, 2026