Blockchain for Payments Professionals · Chapter 2 of 4Draft
Same tools, real money: reading Ethereum mainnet
Chapter 1 happened in a sandbox. This chapter points the exact same commands at the real Ethereum network — the one carrying tens of billions of dollars of stablecoin float — and reads it directly, with no explorer, no vendor dashboard, no intermediary interpreting the data for you: a real USDC transfer dissected from raw bytes, live stablecoin movements decoded from the newest block, and fresh USDC caught being created out of nothing.
17 min readBy NewRemit Research
Nothing about the tooling changes. That is the whole point. The only thing that changes is the URL behind one environment variable. By the end you will have dissected a real USDC transfer from raw bytes, decoded a live stablecoin movement from the newest block on the chain, and — almost by accident — watched fresh USDC being created out of nothing. You will also have collected the single most important caveat in stablecoin analytics, which Chapter 3 then quantifies.
What we skip on purpose: writing or deploying contracts, signing anything on mainnet, proxy internals, historical log pagination, reorg handling, entity attribution. Read side only, current data only.
Interactive A
Swap the backend
Diagram of the four-layer read stack. A toggle switches between the Chapter 1 sandbox and Chapter 2 mainnet: Cast and JSON-RPC stay visibly unchanged while the backend swaps from Anvil to an RPC provider and the chain swaps from a local sandbox to Ethereum Mainnet.
Cast
the same CLI, byte for byte
JSON-RPC
the same message standard
Anvil
local node on your laptop
Local chain
chainId 31337 · fake money
One environment variable changed. Everything above it didn't.
Flip between the Chapter 1 sandbox and this chapter's mainnet setup. Cast and JSON-RPC don't move; only the two layers behind the URL change.
Source: Stack as run in the mainnet lab, 14 August 2026.
Lesson 2.1
Change one URL, keep everything else
Why this matters: graduating from the sandbox to tens of billions of dollars of stablecoin float is one environment variable — and the two operational lessons that arrive with it (gateways fail, the chain moves) shape every monitoring system you'll ever build.
In Chapter 1 the stack was:
Cast → JSON-RPC → Anvil (local chain, chainId 31337)This chapter’s stack:
Cast → JSON-RPC over HTTPS → RPC provider → Ethereum Mainnet (chainId 1)Anvil wasn’t a toy imitation of Ethereum — it exposed the same interface real nodes expose. So graduating to mainnet is one line:
Hands-on: run it yourself (optional)
export ETH_RPC_URL="https://ethereum-rpc.publicnode.com" # example public endpoint — can change or rate-limit
cast chain-id --rpc-url $ETH_RPC_URL # → 1
cast block-number --rpc-url $ETH_RPC_URL # → 25729619Two lessons arrived immediately, both operational:
1. The provider is a gateway, not the network. Our first endpoint choice was down (a tunnel error). Ethereum was fine; our chosen door into it wasn’t. Payments translation: your processor’s API being down does not mean the card network has failed. For any production monitoring system, RPC provider selection — retention, rate limits, archive access, fallbacks — is a real architecture decision, not plumbing detail.
2. The chain moves while you look at it. Two block-number queries seconds apart returned 25,729,619 and then 25,729,627. latest is a moving reference, like asking “what’s the current FX rate” — the answer has a timestamp, not permanence. Every “latest” query in this chapter carries that asterisk.
One mechanical note, taught once and then assumed: raw JSON-RPC speaks hexadecimal ("0x1889a5b" = 25,729,627). Cast decodes it for you; when we drop to raw RPC calls, we decode it ourselves.
Lesson 2.2
Whose balance is it? Custody vs the chain
Why this matters: the difference between an app balance and a chain balance is the omnibus-account structure payments people already know — and it decides what on-chain analytics can and cannot see.
We took a real deposit address issued by a Binance account (Ethereum network selected) and asked the chain directly:
Hands-on: run it yourself (optional)
export ADDRESS=0x3e0a…195b # substitute your own deposit address
cast balance $ADDRESS --ether --rpc-url $ETH_RPC_URL # → 0.000000000000000000
cast nonce $ADDRESS --rpc-url $ETH_RPC_URL # → 0The Binance app says the customer owns ETH. Ethereum says this address holds zero and has never sent a transaction. Both are true.
A custodial exchange runs an internal customer ledger and separately manages the actual on-chain assets across wallets it controls — deposits are detected, credited internally, then often swept into consolidated treasury wallets. Payments professionals already know this structure by name: it’s an omnibus account. The customer’s claim lives in the institution’s books; the settlement-rail position lives somewhere else entirely.
customer account ledger ≠ external settlement-rail positionThis distinction does heavy lifting later: reconciliation, treasury wallet identification, and why “on-chain volume” systematically misses activity that never leaves an exchange’s internal ledger.
One precision worth keeping: nonce = 0 means the address never originated a transaction. It can still have received ETH, received tokens, or appeared in logs — receiving does not increment a nonce.
Interactive B
Two ledgers, one customer
Two-pane diagram. Left: the exchange app's internal ledger with three customer claims. Right: the chain's view — the deposit address at balance zero, nonce zero, with a sweep arrow into a consolidated treasury wallet. Selecting a customer pulses the treasury wallet, not the deposit address.
What the app shows
Internal customer ledger — claims, not coins (illustrative)
What the chain shows
Real deposit address, read directly off mainnet
Tap a customer row to trace where that claim actually lives on the chain.
Tap a customer row and watch which node answers on the chain side: the treasury wallet pulses, the deposit address never does. Claims map to consolidated positions — not to the address the app displayed.
Source: Deposit-address readings (balance 0, nonce 0) observed on mainnet 14 August 2026; customer claims and the treasury balance are illustrative.
Lesson 2.3
Dissecting a real USDC transfer
Why this matters: a stablecoin payment hides from the obvious transaction fields; reading one from raw bytes is the analyst capability this series is building toward.
For the rest of the chapter we use one real mainnet transaction as our specimen: 0x8552…5382. The transaction object contains a surprise if you’re expecting Chapter 1’s simple transfer:
from = 0x6872…f7dE
to = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 ← the USDC contract
value = 0 ETH
input = 0xa9059cbb… ← 68 bytes of calldataThe sender did not send anything to the recipient. They sent zero ETH to a smart contract, with an encoded instruction attached. The USDC contract executed that instruction and updated its own internal accounting.
USDC balances are contract state. A USDC transfer is an Ethereum transaction that calls the USDC contract.
Decoding the instruction
The calldata splits into three parts, decodable locally without touching the chain. The selector is the first 4 bytes of the hashed function signature — the contract’s routing key for which function to run. This is your first contact with ABI literacy; Chapter 3 makes it systematic.
Hands-on: run it yourself (optional)
cast tx $TX --rpc-url $ETH_RPC_URL # fetch the transaction (needs RPC)
cast 4byte-calldata 0xa9059cbb… # decode calldata (purely local — no chain needed)Interactive C
68 bytes, three meanings
The specimen transaction's 68 bytes of calldata as a wrapped hex string in three colored segments — a 4-byte function selector, a 32-byte recipient word and a 32-byte amount word. Selecting a segment opens its decoding steps; the amount segment reveals the decimals division in two stages, mirroring the lab sequence.
0x
Select a segment of the calldata to decode it.
Select a segment of the specimen transaction's calldata to decode it: the function selector routes, the recipient word names the real beneficiary, and the amount word holds back its final answer until you fetch decimals() — exactly the order the lab did it. Counterparty addresses are truncated for privacy.
Source: Real calldata from specimen transaction 0x8552…5382, observed 14 August 2026.
The receipt: two currencies in one payment
The receipt (cast receipt $TX) confirms execution: status 1, gasUsed 62,260 against a gas limit of 1,050,000. Chapter 1’s fee arithmetic applies unchanged — but now with a payments twist. This single payment involves two economically distinct values in two different assets:
18.567969 USDC → the payment value
≈ 0.00070 ETH → the network execution fee (≈ $2.10 at an illustrative $3,000/ETH)The payer needs a balance in both. This mismatch is why “gas abstraction” (someone else sponsoring the fee) is an active product battleground in stablecoin payment UX.
Trap callout — cumulativeGasUsed: the receipt also shows ~20.3M gas. That’s the running total for the whole block up to this transaction (ours sat at index 72), not our cost. For per-transaction analysis, gasUsed is the field.
And the standing caveat from Chapter 1 still applies: status = 1 proves blockchain execution succeeded. It says nothing about off-ramp, FX, compliance release, or beneficiary credit. On-chain success ≠ payment completion.
Lesson 2.4
Statement lines: the log inside the receipt
Why this matters: logs are the statement lines of the chain — and the intent-vs-evidence distinction between calldata and logs decides what a monitoring system should index.
The receipt carried one log, emitted by the USDC contract. ERC-20 defines Transfer(address indexed from, address indexed to, uint256 value). Ethereum stores it with topics[0] as the keccak256 hash of the canonical signature Transfer(address,address,uint256) — the event’s fingerprint, identical for every ERC-20 token on every chain. Indexed parameters go into topics (so they can be filtered); the rest goes into data.
Interactive D
From raw log to statement line
Two aligned columns: the raw log — topics 0 to 2 and the data word — on the left, the decoded Transfer statement line on the right. Selecting a raw field draws a connector to its decoded counterpart and explains the mapping; topics[0] explains the keccak256 signature derivation.
Raw log — what the node returns
Statement line — what it means
Select a raw field to see which part of the statement line it decodes into.
Select any raw field to see which part of the decoded Transfer it becomes — topics[0] explains where the event fingerprint comes from, and the data word walks through the decimals division.
Source: Real log from specimen transaction 0x8552…5382, observed 14 August 2026; addresses truncated.
Verifying the amount ourselves
The raw value 0x11b5321 = 18,567,969 raw units. Raw units of what? We ask the contract directly:
Hands-on: run it yourself (optional)
cast call $USDC "symbol()(string)" --rpc-url $ETH_RPC_URL # → "USDC"
cast call $USDC "decimals()(uint8)" --rpc-url $ETH_RPC_URL # → 6cast call maps to eth_call: a free, unsigned, read-only simulation against current state. No key, no gas, no state change.
18,567,969 ÷ 10^6 = 18.567969 USDCNever assume 18 decimals — USDC uses 6, other tokens differ. Decimals are contract metadata you must fetch, not convention you may assume.
Intent vs. evidence
Now hold the calldata and the log side by side:
CALLDATA — what the caller requested: transfer(Bob, amount)
LOG — what the contract emitted: Transfer(Alice, Bob, amount)Related, but not the same object. Calldata proves intent; logs prove execution. A monitoring system built on calldata alone counts requests; one built on logs counts outcomes. Every serious tracker is built on logs.
Lesson 2.5
Searching the chain, and what we found there
Why this matters: monitoring starts from criteria, not transaction hashes. One eth_getLogs query against the newest block surfaced a live mint, a routed pair — and the caveat every stablecoin headline ignores.
So far every log we’ve seen was reached via a known transaction hash. Monitoring works the other way around: start from criteria, find matching logs. The RPC method for that is eth_getLogs, and it’s the foundation of every transfer tracker, mint/burn monitor, and freeze-event alert we’ll build.
We asked mainnet: give me every USDC Transfer event in the latest block.
Hands-on: run it yourself (optional)
cast rpc eth_getLogs \
'{"fromBlock":"latest","toBlock":"latest","address":"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"]}' \
--rpc-url $ETH_RPC_URLNote the filter grammar: contract address + topics[0] = “this event, from this contract.”
It returned a large array of live stablecoin movements. We decoded one by hand — from 0x51c7… to 0xe055…, raw value 0x79c8d25a = 2,043.204186 USDC — reconstructing a real dollar movement from raw bytes, independently of any explorer or vendor. That is the analyst capability this whole series is building toward.
Three findings in that single block preview everything Chapter 3 formalizes:
1. One transaction, multiple transfers. The same transaction hash appeared in two Transfer logs (A→B, then B→C, same amount) — a routed movement. Another single transaction emitted many Transfer events. 1 transaction = 1 payment is dead on arrival.
2. We caught a mint. One log showed from = 0x0000…0000 — the zero address. By ERC-20 convention, a Transfer from the zero address is token creation: 4,171.03 USDC minted into existence while we watched. The inverse (transfer to the zero address) is a burn. Neither is a payment, yet both are Transfer events.
3. Therefore:
transaction count ≠ Transfer-event count ≠ economic payment countTransfer events include mints, burns, exchange sweeps, DeFi routing, and treasury shuffling. On-chain transfer evidence must be economically classified before anyone calls it payment volume. This is the on-chain twin of Episode 2’s volume-collapse ladder, and Chapter 3’s closing lesson quantifies it.
Interactive E
Search the newest block, then classify what you found
An eth_getLogs filter rendered as a readable card with a plain-English translation, a simulated Run button, and seven resulting Transfer events. Each row carries classification chips — Payment?, Mint, Routing, Unknown — with instant feedback; the zero-address mint row is visually flagged, and a closing tally counts candidate payments.
eth_getLogs filter
- fromBlock
- "latest"
- toBlock
- "latest"
- address
- 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48
- topics[0]
- 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
In plain English: Every Transfer event emitted by the USDC contract in the newest block on the chain.
Run the filter (simulated — no network calls), then classify each of the seven Transfer events it returns. The zero-address row and the repeated transaction hash are the two teaching moments; the tally at the end is the chapter's punchline.
Source: The 2,043.204186 USDC transfer and the 4,171.03 USDC mint were observed on mainnet 14 August 2026; the remaining rows are representative of the observed patterns.
One production caveat before we go. When we tried the same query against a historical block, our free provider refused: archive access requires authentication. Supporting the JSON-RPC standard does not mean unrestricted access to all history. Providers differ on retention, log-range limits, rate limits, and pricing — which is why the corridor monitor will treat provider selection as an architecture decision with a fallback, not a config value.
Graduation test
Can you read mainnet unaided?
Q1Your RPC provider starts returning errors. What can you conclude about Ethereum's health?
Q2In a USDC transfer transaction, why is the ETH value field zero?
Q3Calldata vs log — which should a monitoring system index, and why?
Q4A Transfer event shows from = 0x0000…0000. What happened?
Q5Why is sum(all Transfer events) not stablecoin payment volume?
Progress
0 of 5 answered
Five single-choice questions drawn from this chapter's graduation check, immediate explanations and direct links to the lesson worth revisiting. Nothing is stored.
Source: Questions map to the graduation outcomes of this chapter.
Glossary — Chapter 2 additions
Only terms new since Chapter 1; the full series glossary lives at the series level.
| Term | Plain meaning |
|---|---|
| RPC provider | Infrastructure exposing blockchain RPC endpoints — your gateway, not the network. Differs by retention, limits, pricing. |
eth_call | Free read-only simulation of a contract call against current state. No signing, no gas, no state change. |
eth_getLogs | Search interface for logs by block range, contract address, and topic filters. |
| Function selector | First 4 bytes of the hashed function signature; routes calldata to the right contract function. |
| Topic | Indexed log field. topics[0] = event signature hash; further topics = indexed event parameters. |
| Decimals | Contract metadata mapping raw integer units to human amounts. USDC = 6. Never assume. |
| Zero address | 0x0000…0000. Transfer from it = mint; to it = burn, by ERC-20 convention. |
| Omnibus / internal ledger | Custodian’s customer accounting, distinct from its on-chain wallet positions. |
| Archive access | Provider capability to serve historical state/log queries beyond standard retention. |
| Proxy contract | Stable public address delegating execution to an upgradeable implementation contract. (Glimpsed only; later-phase territory.) |
Bridge to Chapter 3
Everything you did manually here has a one-line programmatic equivalent:
cast tx → getTransaction()
cast receipt → getTransactionReceipt()
cast call → readContract()
eth_getLogs → getLogs()Same JSON-RPC underneath, same objects, same caveats. Chapter 3 rebuilds this chapter in TypeScript — because a monitoring system can’t type Cast commands at 3 a.m.