- 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
Transferlogs 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
valueand atofield. 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 whosetois your address. - ERC-20 token transfers — emitted by the token contract as a
Transferevent log, not as a transaction addressed to you. You subscribe to thelogsstream filtered by the token contract and an indexedtotopic, so the node returns only the transfers addressed to your account.
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
Monitor incoming ETH transfers
To catch incoming ETH, subscribe to new blocks and, for each block, check every transaction’sto 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
provider.on("block", ...)— fires once per new block with the block number. This is the ethers equivalent of anewHeadssubscription.getBlock(blockNumber, true)— the second argument prefetches full transaction objects, exposed asblock.prefetchedTransactions. Without it you get only transaction hashes.tx.value— abigint, so compare it with0nand format it withethers.formatEther.try/catch— swallows theUNSUPPORTED_OPERATIONerror that an in-flightgetBlockthrows when the provider is shutting down.
Shell
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’sTransfer 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
ethers.id("Transfer(address,address,uint256)")— the keccak-256 hash of the event signature, which is topic 0 for every ERC-20Transfer.topics: [transferTopic, null, ethers.zeroPadValue(WATCHED_ADDRESS, 32)]— position 0 matches the event,nullmatches any sender, and position 2 pins the indexedtoto your address. Addresses are left-padded to 32 bytes in topics, which is whatzeroPadValuedoes.- Decoding — the sender is in
log.topics[1]and the amount is inlog.data. Recover the address withethers.getAddress, and format the amount withethers.formatUnitsand the token’s decimals. TOKEN_DECIMALS— set this to the token’s decimals. USDC uses 6; most ERC-20 tokens use 18.
Shell
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, orECONNRESET) ends the subscription silently. Re-create theWebSocketProviderand 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 callprovider.removeAllListeners()right beforedestroy()— that races the in-flighteth_unsubscribeagainst the socket teardown and throwsprovider destroyed; cancelled request. Callingdestroy()on its own is enough.
Subscribe with raw JSON-RPC (eth_subscribe)
If you are not using ethers, subscribe directly with theeth_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
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.