The Analyst's Path
PulseChain
pDAI
AI × ON-CHAIN ANALYSIS · built for PulseChain

The On-Chain
Analyst's Path

Stop guessing. Start verifying. The chain doesn't lie — you just have to learn to read it.

Turn an AI into your blockchain co-analyst — and keep the receipts. A field manual for reading PulseChain straight off the chain, written for the community that actually holds the bags.

8
Stages 00-07
RPC verify
0
Trust-me-bros
Why this exists

The data has always been free. The skill to read it wasn't.

Most people in crypto run on vibes. They screenshot a chart, repeat a Telegram rumor, trust an explorer label, and call it research. Then they get rekt — not because the information wasn't there, but because they never went to the source. Here's the thing nobody tells you: every balance, every transfer, every contract owner, every “trust me bro” claim about a token is sitting on-chain right now, public, permanent, and queryable by anyone with a terminal and the will to ask. PulseChain (mainnet chain id 369, public RPC https://rpc.pulsechain.com) answers the same questions Ethereum does, with the same JSON-RPC interface, a compatible EVM, and the same ERC-20 calls. The data has always been free. What's been missing is the skill to read it — and the discipline to never accept a number you didn't pull yourself.

This guide closes that gap with two ideas working together. The first is AI as a force multiplier. You will not memorize JSON-RPC method names or hex-encode function selectors by hand — that's a waste of a human brain. Instead you'll learn a repeatable loop: the AI writes the command, you run it against the chain, the AI interprets the raw output. Three roles, one analyst's job, done in a fraction of the time it used to take. We start with raw curl and JSON-RPC so you actually understand what the chain is saying instead of trusting a dashboard to say it for you — then graduate to cast (Foundry) as your daily driver, because tools like cast to-dec, cast from-wei, and the cast decode-* family exist precisely so you never do hex-to-decimal or wei math in your head again. The tool computes; the human decides. That's the trade that makes you fast and keeps you honest.

The second idea is the one that actually protects you: the AI is verified, never trusted blindly. An AI will confidently hand you a wrong number, a hallucinated address, or a plausible-sounding claim with zero on-chain backing — and it will do it in a tone that sounds exactly like the truth. So will Crypto Twitter. The antidote is the same in both cases: a fact isn't a fact until you've pulled it from the chain yourself, and a critical fact isn't settled until multiple independent sources agree — on this path, three. That verification reflex — query the source, let the tool do the arithmetic, cross-check before you believe — is the entire moat. Anyone can ask an AI a question. Almost nobody checks the answer against ground truth. Learn to be the person who does, and you'll see through the noise that owns everyone else. One live caveat to keep in your bones the whole way through: PulseChain launched in May 2023 as a full fork of Ethereum's state, so every token that existed on Ethereum before that date was copied here at the fork block — and for any address untouched since, that balance is a frozen mirror snapshot, a real-looking number that may have zero economic meaning. Anything transferred, swapped, or minted on PulseChain since has diverged and is live — telling the two apart is exactly the skill this guide builds. And diverged does not mean safe: post-fork minting can inflate a forked token far past anything real — forked DAI now reads ~44 billion against only ~4.6 billion of real DAI — so a 'live' number can be just as economically empty as a frozen one. Frozen or diverged, price any forked-token figure against a live PulseX pool with real depth, never its face value. Read a number wrong on that one point and you'll fool yourself worse than any scammer could. The chain doesn't lie. But it will absolutely let you lie to yourself if you skip the verification.

Who this is for

For the PulseChain holder who's tired of running on someone else's word. You already know wallets, tokens, PulseX, and bridges — you don't need a beginner's tour of crypto. What you need is the ability to interrogate the chain directly: confirm a balance, trace a transfer, check who really controls a contract, and tell real value from a fork mirror — or from a post-fork-inflated number that only looks live — without waiting for an influencer to tell you what to think. You don't have to be a developer. You need a terminal, an AI you treat as a sharp-but-fallible assistant, and the patience to verify before you believe. If that's you, this is your path.

00
The Contract

Nothing is true until the chain says so

AI proposes, the chain disposes — every on-chain number an AI gives you is a hypothesis until the chain itself confirms it.

A language model has never seen the blockchain. It was trained on text — millions of explorer pages, GitHub repos, forum posts, docs. When you ask Claude “how much pDAI does this wallet hold?”, a base model on its own does not query PulseChain. It cannot: it has no RPC connection, no node, no mempool of its own. (Even wired to tools, it never knows chain state from training — it must fetch it, and you must verify what it fetched.) It produces the most statistically plausible continuation of your question — a string of digits that looks like a balance, formatted like a balance, confident like a balance. The number is generated, not read. This is the single fact that everything else in this guide hangs on: the model pattern-matches; it does not observe. Treat it as a brilliant analyst intern who has read every finance textbook ever written and will never, ever say “I don't know” — it will invent a precise-looking answer and deliver it with a straight face.

The failure modes are specific and they rhyme every time. Hallucinated balances: asked for a holding it cannot see, the model emits a number anchored on nothing. Wrong decimals: raw on-chain integers carry no decimal point. A token balance comes back as 25612906953169653772259. Is that 25,612 tokens (18 decimals)? 25.6 quadrillion (6 decimals)? 25 sextillion (0 decimals)? The model guesses the decimal count from the token's name or vibe, and a wrong guess is off by a factor of a trillion or more. Wei vs. ether confusion: the same integer-with-no-point problem, one layer down — the model slides a decimal point to wherever looks reasonable. Invented addresses: ask for “the WPLS contract” or “the router” and it may produce a checksummed 42-character address (the 0x prefix plus 40 hex characters) that is structurally perfect and completely fake. EIP-55 checksumming is just mixed-case applied to those 40 hex characters — it makes a string look validated without proving it points at anything real. It looks exactly like a real address because looking like a real address is precisely what it was trained to do. None of these are bugs you can prompt away. They are what next-token prediction is when pointed at data it was never given.

There is a second, sharper edge on PulseChain specifically. PulseChain launched in May 2023 as a full fork of Ethereum — the entire account state (balances, contract code, storage) at the fork block was copied onto a new chain with its own genesis and config, including chain ID 369 and its own fee parameters and PLS distribution. That copy is a one-time snapshot. For any address or contract that has seen no post-fork PulseChain activity, its on-chain values still reflect that fork-time snapshot; anything touched by a native PulseChain transaction since — a transfer, a swap, a mint — has diverged from Ethereum and moved on its own. An AI trained on Ethereum data will happily report a wallet's “holdings” while silently conflating three different things: the Ethereum mainnet value, the untouched fork-mirror value, and the live PulseChain value. These can be wildly different numbers for the same address and the same token. The model has no concept of which chain's current state you mean, because it is reading none of them. This is exactly where confident-but-wrong does the most damage — the answer is plausible, internally consistent, and attached to the wrong reality.

So here is the contract you sign with yourself before you write a single command. Nothing the AI tells you about the chain is true until the chain says it is true. The AI's job is to be a force multiplier on your judgment: it writes the query, it explains the contract, it interprets the decoded output, it spots the pattern across twenty transactions faster than you could. What it does not get to do is be the source of a number. The number comes from an RPC call you ran, against an endpoint you chose, decoded by a tool that does the arithmetic. The discipline is not “distrust AI.” The discipline is separation of duties: the model reasons, the chain attests, and a tool — never your head, never the model's head — does the hex-to-decimal and wei-to-token math. Adopt this now and the rest of the path is mechanical. Skip it and you will post a number that a hostile expert disassembles in one reply. Every later stage is just tooling on top of this one rule.

the mental model
# (No commands to run yet — Stage 0 is the contract, not the tooling.)
# This is the mental model you carry into every command that follows:
#
#   AI writes the query  ->  YOU run it against the chain  ->  AI reads the output back
#                                  ^                                   ^
#                            the chain attests                  a TOOL does the math
#
# Every number the AI hands you is a HYPOTHESIS labeled UNVERIFIED
# until an RPC call you ran has confirmed it.

Stage 0 deliberately ships no chain commands — its entire payload is the mindset. The one thing to internalize: the AI's number is a claim, not a fact. Stages 1+ give you the curl and cast machinery to discharge that claim. Treat any on-chain figure produced without a command behind it as UNVERIFIED, full stop.

resultNo output — this stage produces a habit, not a terminal result. The test is behavioral: when an AI states a balance, your reflex is “show me the call,” not “thanks.”
!
The trap. The seductive trap is fluency: the AI's answer is grammatical, precisely formatted, checksummed, and delivered without a flicker of doubt — and humans read confidence as correctness. People paste an AI-generated balance or address straight into a post or a trade because it looked verified. It wasn't. Confidence is a writing style the model learned, not a signal that it checked anything. The more plausible the number looks, the harder you should make it earn its place with an actual call.
AI
The AI flow. At this stage the AI is the analyst intern who never sleeps and never admits ignorance: it can explain what a balanceOf call does, draft the exact query you'll run next, and translate decoded hex back into English — but it is structurally incapable of knowing the chain's state from training — it must be handed fetched data, so it is never the source of a number. You use it to reason, frame, and interpret; you never let it attest. Every later stage hardens this same split — AI writes the command, you run it, AI interprets the result — with the chain and the decode tool, not the model, as the final authority on any value.
369
On PulseChain. You ask the intern: “What's the chain ID of PulseChain mainnet, and roughly what block is it on right now?” It answers instantly and confidently: “369, somewhere around block 26.9 million.” Plausible — and you must still refuse to write either down as fact until you've asked the chain yourself. The raw JSON-RPC reply for chain ID comes back as {"result":"0x171"} and the block height (at the single moment it was run) as {"result":"0x19ab51d"} — opaque hex, not the friendly decimals the model handed you. A tool decodes 0x171 to 369: stable and verifiable — it does not move. It decodes 0x19ab51d to 26916125 — correct for that instant, but the chain advances a block roughly every 10 seconds, so by the time you read this the height has already climbed past it. That is the whole lesson in miniature: chain ID is a fact you can pin; block height is a fact with a timestamp; and the intern's “around 26.9 million” was a vibe that happened to land near a moving target.
Takeaway The AI is a brilliant intern that lies with a straight face — every on-chain number it gives you is an unverified hypothesis until you've made the chain say it yourself.
01
Talk

Turn the chain's alien dialect into plain English

Before you analyze anything, use AI to turn the chain's alien dialect — selectors, event signatures, approvals, opcodes — into plain English, while never letting it narrate on-chain state it hasn't actually been handed.

The chain does not speak English. It speaks in 4-byte function selectors like 0xa9059cbb, 32-byte event topics like 0xddf252ad..., hex-encoded calldata, and integer amounts with implied decimals (18 for most ERC-20s, but ALWAYS read decimals() — never assume). Every wallet UI and explorer you've ever used is just a translation layer painted on top of this. The first job of an on-chain analyst is to read the raw layer directly — and this is exactly where an AI explainer earns its keep. Paste it a function signature, an event topic, a chunk of calldata, or a decompiled snippet and ask: what does this do, what is the risk, what would an attacker do with this? Claude is genuinely excellent at this because it's a concept and pattern question, not a state question. approve(address,uint256) means the same thing on every EVM chain in existence; explaining it requires zero live data.

Here is the discipline that separates an analyst from a mark. There are two completely different kinds of questions you can ask an AI, and they have opposite reliability profiles. Type A — 'what does this code/signature/pattern mean?' — is timeless knowledge baked into the model. Type B — 'what happened in transaction 0xabc...?' or 'how much of this token does this wallet hold?' — is live on-chain state the model has never seen. Ask Type B without attaching real fetched data and the model will fluently invent an answer: a plausible counterparty, a round-number amount, a confident story. It is not lying on purpose; it is pattern-completing in the absence of data, and the output is indistinguishable from a real finding until you check it against the chain. This is the single most dangerous failure mode in AI-assisted analysis. The rule is mechanical: concepts can be explained from knowledge; specific on-chain claims require data in the prompt or they are a guess.

So the workflow is never 'ask the AI what the transaction did.' It is: you fetch the raw bytes (raw curl + JSON-RPC to demystify what's really on the wire, then cast as the daily driver), you paste those bytes into the prompt, and the AI decodes and interprets what you gave it. The AI writes the command, you run it, the AI interprets the output — and you can always re-run the command yourself to confirm. Crucially, the AI never does the hex-to-decimal or wei-to-token arithmetic in its head; a tool computes (cast to-dec, cast from-wei, cast to-unit) and the AI explains the result. Models are unreliable at long-integer math, and there is no reason to trust a hallucinated number when a deterministic tool sits one command away. The head reasons; the tool calculates.

Good prompting for crypto is mostly about feeding context and forcing honesty. Don't ask 'is this token a scam?' — give the AI the contract's verified source or ABI, the relevant function bodies, the owner/admin addresses you pulled, and ask it to walk the privileged functions and flag what a malicious owner could do. Always add an explicit instruction: 'state your assumptions, flag anything you're inferring vs. reading, and say I-don't-know rather than guessing.' That single sentence converts the model from a confident bullshitter into a useful junior analyst. On PulseChain specifically, give it one extra piece of context it cannot know from training: the chain launched in May 2023 as a full fork-copy of Ethereum's entire state, so any token forked at launch STARTED as a mirror of Ethereum's May-2023 state — not live mainnet value. A balance is only still frozen if nothing on PulseChain has touched it since; supply or balances minted or moved here have diverged, and a diverged number can be inflated far past anything real. A well-prompted model usually reasons correctly about the mechanics, but will still tend to imply mainnet-scale dollar value unless you remind it the mirror is economically dead.

decode a selector
cast 4byte 0xa9059cbb

Decode an unknown 4-byte function selector into its human signature via the public 4byte directory. This is the AI's best friend: when you hand it raw calldata, the first 4 bytes are the selector — resolve it here, then ask the AI what the function does. Note: 4byte can return MULTIPLE candidate signatures for a selector (hash collisions), so confirm against the ABI when it matters.

resulttransfer(address,uint256)
selector from signature
cast sig "approve(address,uint256)"

The inverse direction: compute the 4-byte selector from a known signature locally (no network, no collisions). Use it to confirm that calldata you're staring at really is the function you think it is — match the first 4 bytes of the calldata against this.

result0x095ea7b3
hash an event topic
cast keccak "Transfer(address,address,uint256)"

Compute the 32-byte event topic0 hash for the canonical ERC-20 Transfer event. Every standard Transfer log on every EVM chain carries this exact topic. Paste a raw log's topics into the AI and this is how you confirm topic[0] is genuinely a Transfer and not a look-alike.

result0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
decode approval calldata
cast calldata-decode "approve(address,uint256)" 0x095ea7b3000000000000000000000000111122223333444455556666777788889999aaaaffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff

Decode a full approval calldata blob into its arguments. The spender is the address argument; the amount here is max-uint256 — an UNLIMITED approval. Note that cast prints the address EIP-55 checksummed (mixed case), which is why you let the tool render it instead of eyeballing the raw hex. Feed both the calldata and this decode to the AI and ask 'what is the risk?' — it will correctly flag that an unlimited approve lets the spender move that token out of the wallet at any future time. (calldata-decode is an alias of the canonical cast decode-calldata.)

result0x111122223333444455556666777788889999aAaa 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]
print max uint
cast max-uint

Print 2^256 - 1 (the default 256-bit max). Memorize this number's shape. When you see it (or its hex form, 64 f's) as an approval amount, it is an unlimited allowance — the most common ingredient in wallet-drainer phishing. The AI should treat any approve to an unknown spender at this value as a red flag.

result115792089237316195423570985008687907853269984665640564039457584007913129639935
raw chain id query
curl -s -X POST https://rpc.pulsechain.com -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","id":1,"method":"eth_chainId","params":[]}'

The rawest possible chain query — confirm you're actually talking to PulseChain mainnet over JSON-RPC. The result 0x171 = 369. Do this once to internalize what every cast command is doing under the hood: POSTing JSON-RPC and parsing a hex result.

result{"jsonrpc":"2.0","id":1,"result":"0x171"}
raw decimals call
curl -s -X POST https://rpc.pulsechain.com -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","id":1,"method":"eth_call","params":[{"to":"0x6B175474E89094C44Da98b954EedeAC495271d0F","data":"0x313ce567"},"latest"]}'

Call decimals() (selector 0x313ce567) on the forked DAI token via raw eth_call and see the truth the chain returns: a 32-byte hex word, NOT a friendly '18'. This is what the AI must decode, and why you never let it do the conversion in its head — you run cast to-dec on the result instead.

result{"jsonrpc":"2.0","id":1,"result":"0x0000000000000000000000000000000000000000000000000000000000000012"}
hex to decimal
cast to-dec 0x12

Convert the raw decimals() hex word to a human number. The tool computes; the AI explains. Never eyeball 0x12 as 18 in your head and never let the model assert it — pipe it through cast every time so the number is deterministic and verifiable.

result18
read token name
cast call 0x6B175474E89094C44Da98b954EedeAC495271d0F "name()(string)" --rpc-url https://rpc.pulsechain.com

The daily-driver version of the same idea: cast both makes the call AND decodes the ABI-encoded string for you. On PulseChain the forked DAI at 0x6B175474E89094C44Da98b954EedeAC495271d0F answers 'Dai Stablecoin' and reports a totalSupply of ~44.36 billion — a number that looks exactly like live mainnet DAI but isn't. Real DAI on Ethereum is only ~4.6 billion, and this forked token itself was ~5 billion right after the May-2023 fork; the extra ~39 billion was minted here on PulseChain since. So it is not a frozen copy of mainnet — it is a fork inflated far past anything that ever existed, and economically dead until a live PulseX pool prices it. That gap between the name and the number is the teaching artifact you feed the AI.

result"Dai Stablecoin"
wei to token amount
cast from-wei 35714285714285714316

Convert a raw 18-decimal amount integer (wei) into a token amount. This is the conversion you must NEVER do by hand and NEVER let the AI hallucinate — a wrong decimal place is the difference between 3.5 tokens and 35 billion. The tool is the only trustworthy source for this arithmetic. (For tokens with non-18 decimals, read decimals() first and use cast from-wei <amount> <decimals> or cast to-unit.)

result35.714285714285714316
!
The trap. Asking the AI to explain a specific TX hash, address, or balance WITHOUT pasting in real fetched data. The model will fabricate a fluent, confident, completely fictional story — invented counterparties, round-number amounts, a plausible motive — and it is indistinguishable from a real finding until you pull the actual bytes. Explaining a CONCEPT (what does approve do, what would an attacker do with an unlimited allowance) is rock-solid; claiming SPECIFIC on-chain state from no data is always a guess wearing a lab coat.
AI
The AI flow. AI is the explainer/translator, not the data source. You fetch raw bytes with curl/cast; you paste selectors, event topics, calldata, ABIs, and command output INTO the prompt; the AI decodes and interprets only what you gave it. The loop is: AI writes the command -> you run it -> AI interprets the output, and you can re-run to confirm. The AI never performs hex/wei/decimal arithmetic — cast to-dec / from-wei / calldata-decode compute deterministically, and the AI just narrates the result. Every prompt ends with 'state your assumptions and flag inference vs. read,' which converts the model from a confident guesser into an honest junior analyst.
369
On PulseChain. Call name() on the forked DAI token at 0x6B175474E89094C44Da98b954EedeAC495271d0F with cast call 0x6B175474E89094C44Da98b954EedeAC495271d0F "name()(string)" --rpc-url https://rpc.pulsechain.com and it answers, verbatim, "Dai Stablecoin" — then read its totalSupply and you get ~44.36 billion. Paste that to the AI with the one fact it can't know — PulseChain forked Ethereum's full state at the May-2023 launch — and ask it to explain the implication. A well-prompted model nails it: that 44.36-billion figure is not what it looks like. Real DAI on Ethereum is only ~4.6 billion, and this forked token itself was ~5 billion right after the May-2023 fork — the extra ~39 billion was minted here on PulseChain since. So it is not a frozen copy of mainnet, it is a fork inflated to roughly 9x the ~4.6 billion of real DAI on Ethereum today (about 8.8x its own ~5 billion fork value), and quoting it at mainnet dollar value is a category error twice over. The number looks exactly like live DAI; the AI's job is to make sure you don't treat it as such — price it against a live PulseX pool, never its face value.
Takeaway Use AI to decode the chain's language from knowledge, never to narrate the chain's state from thin air — concepts are free, specific claims cost data.
02
See

The chain is a public JSON-RPC server

A blockchain is just a JSON-RPC server you POST questions to — give the AI raw curl access and it can read the chain directly, no explorer, no middleman, no permission.

The chain is a public API, and eth_call is how you query state. PulseChain mainnet (chain id 369) runs a standard Ethereum JSON-RPC interface at https://rpc.pulsechain.com. You don't need a library, an SDK, an explorer, or an API key to read it — you POST a tiny JSON envelope and get a hex answer. Every read method shares one shape: {"jsonrpc":"2.0","method":"<method>","params":[...],"id":1}. The methods in the command list below are the entire vocabulary you need to see any wallet, token, contract, or transaction on the chain. eth_blockNumber tells you where the chain is. eth_chainId proves which network you're actually talking to. eth_getBalance reads native PLS. eth_call executes a read-only function (like balanceOf) against a contract without spending gas or sending a transaction. eth_getTransactionByHash and eth_getTransactionReceipt show you what a transaction requested versus what it did (the receipt carries status and the event logs). eth_getLogs is the firehose — every event the chain ever emitted, filterable by contract and topic. eth_getCode tells you if an address is a contract or an EOA. eth_getStorageAt reads raw contract storage slots — the getter-independent ground truth for the specific case where a contract lies through its public functions.

Why raw curl first, before any tool: it demystifies the machine. Every higher-level tool — cast, ethers.js, web3.py, the BlockScout UI — is ultimately wrapping these exact RPC calls. If you only ever use the wrapper, the chain stays a black box and you can't tell when the wrapper is wrong. Doing it once by hand, including building the balanceOf calldata yourself, removes the magic: you see that a contract call is nothing more than a 4-byte function selector followed by 32-byte-padded arguments, and that the answer is just a hex string you decode. That understanding is what lets you catch a tool — or an AI — when it hallucinates. Note one platform gotcha that bites PulseChain analysts specifically: Python's urllib/requests frequently chokes on TLS certificate verification in minimal environments, silently failing or erroring on the HTTPS handshake. curl is the reliable transport. Tell the AI to route RPC calls through curl, not hand-rolled Python HTTP.

The AI flow: AI writes the curl, you run it, AI interprets the hex — and you never trust a single uncorroborated read. This is the working loop for the whole bible. You describe what you want to know ('what's this wallet's WPLS balance at the latest block'); the AI produces an exact, copy-pasteable curl command; you run it in your own shell; you paste the raw hex result back; the AI decodes it. The discipline that makes this un-dunkable rather than just convenient: the AI never does the hex-to-decimal or wei-to-token arithmetic in its head, and you never present a number that came from one source. Decoding is a tool's job (Stage 3's cast to-dec / cast from-wei), and a finding worth stating out loud should be confirmed against more than one independent RPC endpoint. A single curl answer is a lead, not a fact. The chain doesn't lie, but a misread hex string, a stale block, or a flaky endpoint absolutely will.

Public RPC vs your own node — and why the explorer is a convenience, not an oracle. The public https://rpc.pulsechain.com is free and perfect for learning and light forensic work, but it's shared infrastructure: it rate-limits, it caps eth_getLogs ranges, and it can lag or drop under load. For serious or high-volume analysis you run your own PulseChain node (full archive if you need historical state) and point curl at http://localhost:8545 — then there are no limits and no third party between you and the truth. Critically, the RPC is the source of truth; the explorer (BlockScout at scan.pulsechain.com, API at api.scan.pulsechain.com/api/v2) is an indexer sitting on top of a node, and indexers can be incomplete, stale, or — on some instances — simply return 404 for an address they never indexed. When the RPC and the explorer disagree, the RPC is the layer to trust — it reads state directly while the explorer only indexes it. (A single RPC can still lag or misbehave; that is what the multi-endpoint cross-check in Stage 04 is for.)

One last framing on raw storage: it's a cross-check, not routine independence. It's tempting to call eth_getStorageAt a 'fully independent third source' alongside the public getter and the event log. For a vanilla, honest ERC-20 that isn't true — the getter and the raw slot read the same underlying storage, so they agree by construction and the slot adds nothing. Raw-slot reading earns its place only when the logic layer could be lying to you: a proxy whose implementation was swapped, an overridden getter, a hijacked contract. Then the slot — which the malicious logic can't fake without actually moving the value — becomes a genuinely independent check. Know which situation you're in before you claim 'triple-verified.'

read block number
curl -s -X POST https://rpc.pulsechain.com -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}'

The 'hello world' of reading a chain: ask PulseChain what block it's on. POST the JSON-RPC envelope; the result is the current block height in hex. This is your liveness check and your sanity check that the endpoint is up before you do anything else. AI flow: AI writes this, you run it, AI confirms the chain is live and hands the hex to a decoder (it does NOT eyeball the number — see the expected output).

result{"jsonrpc":"2.0","id":1,"result":"0x19ab51d"} — the result is hex. DON'T decode it in your head; run `cast to-dec 0x19ab51d` => 26916125. (Note: 0x19ab51d is 26,916,125, NOT 26,914,589 — that off-by-1536 slip is exactly the hand-decode error this bible forbids.) Your value will be higher; the chain advances roughly every 10s.
confirm chain id
curl -s -X POST https://rpc.pulsechain.com -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":1}'

Confirm you are actually talking to PulseChain mainnet and not some other fork or a wrong endpoint. The chain id is the unforgeable network fingerprint. Always verify this before trusting any data — pointing curl at the wrong RPC is the most common silent mistake.

result{"jsonrpc":"2.0","id":1,"result":"0x171"} — 0x171 = 369, PulseChain mainnet (verify: `cast to-dec 0x171` => 369). If you get anything else, you're on the wrong network.
read native pls balance
curl -s -X POST https://rpc.pulsechain.com -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","method":"eth_getBalance","params":["0x0000000000000000000000000000000000000000","latest"],"id":1}'

Read an address's native PLS balance directly from state. params = [address, block-tag]; 'latest' means current head. This is native PLS only — it does NOT include any ERC-20 token. For tokens you need eth_call (next command). Replace the zero address with your target. AI flow: AI writes it, you run it, AI runs the hex through a decoder (Stage 3) — never eyeballs the wei.

result{"jsonrpc":"2.0","id":1,"result":"0x9492784baaf089b52ff2a3"} — a hex wei amount. PLS has 18 decimals, so convert with `cast from-wei <hex>`, never by hand. (The zero address accumulates burned or mis-sent funds — it is just an illustrative target here; your wallet returns its own balance.)
read erc-20 balanceof
curl -s -X POST https://rpc.pulsechain.com -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","method":"eth_call","params":[{"to":"0xA1077a294dDE1B09bB078844df40758a5D0f9a27","data":"0x70a082310000000000000000000000000000000000000000000000000000000000000000"},"latest"],"id":1}'

The key skill of the stage: read an ERC-20 balanceOf by hand-building the calldata. data = selector + arg. The selector for balanceOf(address) is the first 4 bytes of keccak256("balanceOf(address)") = 0x70a08231. The single address argument is left-padded with zeros to 32 bytes (64 hex chars). Concatenate them and you have the call. Here the contract is the WPLS token (name() = "Wrapped Pulse", symbol() = "WPLS") and we're querying the zero address. To query YOUR wallet, drop the 0x of your address and left-pad it to 64 hex chars in place of the zeros. This is exactly what 'cast call ... balanceOf(address)' does under the hood — you're seeing the machinery.

result{"jsonrpc":"2.0","id":1,"result":"0x00000000000000000000000000000000000000000004f5248ac7bb6b235f394d"} — a 32-byte hex word holding the uint256 balance. Decode with `cast to-dec`, then apply 18 decimals with `cast from-wei`. A result of exactly 0x000...000 means zero balance, not an error.
check contract or eoa
curl -s -X POST https://rpc.pulsechain.com -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","method":"eth_getCode","params":["0xA1077a294dDE1B09bB078844df40758a5D0f9a27","latest"],"id":1}'

Tell a contract from an externally-owned account (wallet). Returns the deployed bytecode. A real wallet (EOA) returns 0x (no code) on PulseChain today — note that on chains with EIP-7702 an EOA can carry a small delegation blob, so read 0x as 'no ordinary contract here,' not a universal law; a contract returns a long bytecode blob. Essential first move when you don't yet know what an address IS — you can't call balanceOf on a wallet, and you shouldn't assume a 'token' address is actually a token until you've seen it has code.

resultFor the WPLS contract: a long hex string starting 0x6080... (thousands of hex chars). For a plain wallet: exactly "0x". An empty 0x = EOA or a never-deployed address.
read raw storage slot
curl -s -X POST https://rpc.pulsechain.com -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","method":"eth_getStorageAt","params":["0xA1077a294dDE1B09bB078844df40758a5D0f9a27","0x0","latest"],"id":1}'

Read a raw 32-byte storage slot — the getter-independent ground truth WHEN the getter could be lying. params = [address, slot-index-hex, block]. Slot layout is contract-specific: a slot may hold a packed short string (name/symbol) or a scalar like totalSupply. Important Solidity fact: a mapping (e.g. the balances mapping) occupies its declared slot only as a placeholder and stores NOTHING there — each entry lives at keccak256(abi.encode(key, slot)). There is no 'mapping root' value at the declared slot. So you must know (or derive) the exact slot you want. Raw-slot reads are your independent cross-check only against a proxy/hijacked/overridden getter; for a vanilla token the getter and the slot read the same storage and agree by construction.

result{"jsonrpc":"2.0","id":1,"result":"0x577261707065642050756c73650000000000000000000000000000000000001a"} — slot 0 of WPLS. The leading bytes ASCII-decode to "Wrapped Pulse"; the final byte 0x1a (=26) is Solidity's short-string length encoding (length x 2; "Wrapped Pulse" is 13 chars, 13 x 2 = 26, for strings under 32 bytes). Layout is per-contract — you must know the slot you want.
read transaction by hash
curl -s -X POST https://rpc.pulsechain.com -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","method":"eth_getTransactionByHash","params":["0xYOUR_TX_HASH_HERE_substitute_a_real_66char_hash"],"id":1}'

Pull what a transaction REQUESTED: from, to, value, gas, nonce, and the raw input calldata. This is the intent. It does NOT tell you whether the tx succeeded or what it actually changed — for that you need the receipt (next command). The hash above is a placeholder; swap in the 66-character hash (0x + 64 hex) of any tx you're investigating.

resultA JSON object with keys: from, to, value (hex wei), input (the calldata), nonce, blockNumber, gasPrice, etc. If the hash doesn't exist on this chain, result is null — a critical tell that a tx people claim happened is only on a different chain (or doesn't exist).
read transaction receipt
curl -s -X POST https://rpc.pulsechain.com -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","method":"eth_getTransactionReceipt","params":["0xYOUR_TX_HASH_HERE_substitute_a_real_66char_hash"],"id":1}'

Pull what the transaction actually DID: status (0x1 = success, 0x0 = reverted), gasUsed, and the full event logs array. The logs are where the real forensic signal lives — every Transfer, Approval, Swap, etc. A tx can exist (getTransactionByHash returns it) yet have reverted (status 0x0) and moved nothing. Always check status before believing a transaction's effect. Use the same placeholder hash you used above, substituted with your real one.

resultA JSON object with status:"0x1" (success) or "0x0" (reverted), gasUsed, and logs:[...]. Each log has address, topics[], data. Empty logs[] on a tx that claims to have moved tokens is a red flag.
filter event logs
curl -s -X POST https://rpc.pulsechain.com -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","method":"eth_getLogs","params":[{"fromBlock":"0x19ab400","toBlock":"0x19ab51d","address":"0xA1077a294dDE1B09bB078844df40758a5D0f9a27","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"]}],"id":1}'

The forensic firehose: every matching event in a block range. Here: all WPLS Transfer events between two blocks (~285 blocks apart). topic0 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef is keccak256("Transfer(address,address,uint256)") — the universal ERC-20 Transfer signature, identical on every EVM chain. Add more topics to filter by indexed from/to address. WARNING: public RPCs cap the block range or result count (often a few thousand blocks or an N-results ceiling) — if you get an error, narrow fromBlock/toBlock. Block numbers must be hex; get the current head from eth_blockNumber.

resultAn array of log objects, each with address, topics[3] (sig, indexed from, indexed to), data (the amount in hex), blockNumber, transactionHash, logIndex. Empty array [] = no transfers in that window. An error like 'query returned more than N results' means narrow your block range.
!
The trap. The BlockScout explorer API (api.scan.pulsechain.com/api/v2/addresses/<addr>) is a convenience, not ground truth — it can return 404, an empty record, or a stale balance for a pure token-holder that has no native PLS transaction to index. People see the explorer 404 or return '0' and conclude the wallet is empty, when the wallet is in fact holding millions in a token the indexer never recorded. RPC eth_call balanceOf reads directly from chain state and cannot miss it. Rule: balances come from eth_call / cast call against the RPC, never from the explorer's /addresses endpoint. A second trap on PulseChain specifically: because the chain is a full May-2023 fork-copy of Ethereum, almost every Ethereum address already has mirrored state and 'real-looking' balances here. Many are frozen May-2023 snapshots, not live engagement — but PulseChain has run independently since the fork, so a mirrored figure may also have been moved or inflated on PulseChain. Frozen or diverged, never read a mirrored balance as live value without pricing it against a live PulseX pool. And the cardinal trap of the whole stage: never let the AI (or yourself) convert hex->decimal or wei->token by hand. The draft of this very page once claimed 0x19ab51d = 26,914,589 — it's actually 26,916,125. One slipped digit invents or erases millions. The tool computes; the head does not.
AI
The AI flow. Stage 2 establishes the core loop used for the rest of the bible: (1) you state an intent in plain English ('get this wallet's WPLS balance', 'did this tx actually succeed', 'show me every Transfer from this contract in the last few hundred blocks'); (2) the AI translates it into an exact, copy-pasteable curl + JSON-RPC command — it knows the method, the params order, the selector to prepend, the topic0 hash, the hex block tags; (3) YOU run it in your own shell against your own chosen endpoint, so you control the trust boundary and the AI never holds your keys or your node; (4) you paste the raw hex/JSON back and the AI interprets it — but any interpretation that produces a number is deferred to a decoding tool (Stage 3's cast), never done as mental arithmetic. The AI is the command-writer and the explainer; it is verified at every step, never trusted blind. The discipline is concrete: when the AI is tempted to say '0x19ab51d is block 26,914,589', you make it say 'run cast to-dec 0x19ab51d' instead — because that hand-decode is wrong (the real value is 26,916,125) and only the tool catches it. The AI's genuine leverage here is building calldata you'd otherwise get wrong by hand: it computes the 0x70a08231 selector, left-pads the address to 32 bytes, and assembles the data field correctly every time.
369
On PulseChain. A community member posts 'this whale dumped everything, BlockScout shows the wallet at zero.' You ask the AI to verify. It writes an eth_call balanceOf against the WPLS token 0xA1077a294dDE1B09bB078844df40758a5D0f9a27 (name() = "Wrapped Pulse", symbol() = "WPLS") for that address on https://rpc.pulsechain.com (chain id 369). You run it; the RPC returns a non-zero 32-byte hex word, which the AI hands to cast to-dec / cast from-wei rather than decoding by hand. The explorer 404'd or showed zero only because the wallet has no native-PLS transaction for the indexer to anchor on — but the on-chain state, read directly, holds the tokens. One curl, and the 'dump' claim is dead. The explorer was the convenience that lied; the RPC was the ground truth that didn't.
Takeaway The chain is a free JSON-RPC server: POST a tiny envelope, get raw hex back, decode it with a tool not your head, and trust the RPC as the source of truth — not the explorer, which only indexes it.
03
Decode

A transaction is just hex until you decode it

A transaction is just hex until you decode it — the first 4 bytes name the function, the topics name the event, and cast turns all of it into English so you never guess what a 0x string actually did.

Every contract call is a blob of hex calldata, and the first 4 bytes are the function selector — the leading 8 hex characters of the keccak-256 hash of the canonical function signature, e.g. transfer(address,uint256) -> 0xa9059cbb. Everything after those 4 bytes is the ABI-encoded arguments, packed in 32-byte (64-hex-char) words. Decoding calldata is a two-part move: identify the function from the selector, then decode the argument words against that function's input types. The selector is a one-way hash, so you can't recover the signature from it by math — you look it up in a public signature directory (current cast queries openchain.xyz, formerly sig.eth.samczsun.com), which cast 4byte and cast 4byte-decode query for you. Note there is more than one public signature directory — openchain.xyz (which cast uses) and the separate 4byte.directory — so the same selector can surface a different candidate set depending on the tool. Critical caveat: selectors are only 4 bytes, so collisions exist — a single selector can map to several different text signatures. The directory returns all candidates; you confirm which one is real by checking it against the actual contract's verified ABI or its known behavior, never by blindly trusting the first hit.

Event logs are the other half of decoding, and they work differently from calldata. Each log has up to 4 topics plus a data field. topics[0] is the event signature hash: the full keccak-256 of the canonical event signature — for ERC-20 that is Transfer(address,address,uint256) = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef. Note this is the FULL hash, not truncated to 4 bytes like a function selector. The remaining topics carry the indexed parameters (for Transfer: from in topics[1], to in topics[2], each left-padded to 32 bytes), and the non-indexed parameters live ABI-encoded in data (for Transfer: the value). A frequent and embarrassing mistake is hashing Transfer(address,uint256) (two args) instead of the real three-argument event — you get a completely different topic0 and then filter for logs that never appear. Get the canonical signature exactly right: correct types, no spaces, no parameter names.

This is where cast (Foundry) graduates from optional to your daily driver. Raw curl taught you the chain is just JSON-RPC and hex; cast is the layer that decodes that hex without you ever doing base conversion or wei math in your head. The core verbs: cast call <addr> "<name>(<argTypes>)(<returnTypes>)" --rpc-url ... calls a read function and decodes the return for you — note the two SEPARATE parenthesized groups, arguments first then return types, e.g. symbol()(string) or balanceOf(address)(uint256); cast sig "<fn>" gives the 4-byte selector; cast keccak "<text>" and cast sig-event "<event>" give event topic hashes; cast 4byte <selector> and cast 4byte-decode <calldata> reverse-look-up unknown calldata; cast calldata-decode "<sig>" <calldata> decodes selector-prefixed input; cast abi-decode "<sig>" <data> decodes raw return data. The discipline that makes this un-dunkable: the tool computes, the head never does. You never eyeball 0xde0b6b3a7640000 and call it '1 token' — you run cast to-dec / cast from-wei and let the binary be right. The AI's job in this loop is to WRITE the cast command and INTERPRET the decoded result; the binary, not the model and not your mental arithmetic, produces the number.

Decoding is only useful if you know whose addresses you're looking at, so build a personal CONTEXT file — a plain-text or markdown address book mapping every full checksummed address to a human label (token contracts, routers, your own wallets, watched wallets, known bridges). You paste or feed this to the AI at the start of an analysis so when a decoded Transfer shows to = 0x..., the AI immediately tells you 'that's the PulseX Router' instead of leaving you a naked hex string. This is the single highest-leverage habit for AI-assisted analysis: the model is only as sharp as the labels you give it. But the address book is also where the worst trap lives — short labels. A nickname like 'ABCD' or 'the 5EF7 whale' is a human convenience that can silently point at the WRONG full address (a typo, a stale copy, a lookalike vanity prefix). Before you act on, post about, or alarm anyone over a label, resolve it back to the full 42-character checksummed address and verify that address on-chain. Always write addresses full-length, never truncated — a truncated address is unverifiable, and unverifiable equals non-existent.

compute function selector
cast sig "transfer(address,uint256)"

Compute a function selector from its canonical signature. This is the inverse of looking one up: keccak-256 of the signature text, first 4 bytes. Note the exact form — types only, no spaces, no argument names. This is the daily-driver way to know what selector a function call WILL start with.

result0xa9059cbb
reverse selector lookup
cast 4byte 0xa9059cbb

Reverse a 4-byte selector into a human-readable signature by querying the public signature directory (openchain.xyz). Use this when you see unknown calldata starting with 0x........ and need to know which function it is. If the selector has collisions, it lists ALL candidate signatures — you then confirm against the contract's real ABI.

resulttransfer(address,uint256)
decode raw calldata
cast 4byte-decode 0xa9059cbb000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000000000000000000000000000000000000000000000de0b6b3a7640000

Take a full raw calldata blob (selector + encoded args), resolve the function via the signature directory, AND decode the arguments in one shot. The fastest way to read a transaction's input field when you don't yet have the ABI. (Recipient here is a placeholder 0xaaaa...aaaa.)

result1) "transfer(address,uint256)" 0xaAaAaAaaAaAaAaaAaAAAAAAAAaaaAaAaAaaAaaAa 1000000000000000000 [1e18]
decode known calldata
cast calldata-decode "transfer(address,uint256)" 0xa9059cbb000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000000000000000000000000000000000000000000000de0b6b3a7640000

Decode selector-prefixed calldata when you ALREADY know the signature (e.g. from a verified ABI), skipping the directory lookup. The selector MUST be present in the calldata for this command. Use this once you've confirmed which function it is and want a clean, trustworthy decode.

result0xaAaAaAaaAaAaAaaAaAAAAAAAAaaaAaAaAaaAaaAa 1000000000000000000 [1e18]
compute event topic0
cast keccak "Transfer(address,address,uint256)"

Compute an event's topic0 = the FULL keccak-256 of the canonical event signature (not truncated to 4 bytes, unlike a function selector). The ERC-20 Transfer event has THREE parameters — get the signature exactly right or you'll filter for logs that never match. cast sig-event "Transfer(address,address,uint256)" produces the identical value.

result0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
call symbol view function
cast call 0xA1077a294dDE1B09bB078844df40758a5D0f9a27 "symbol()(string)" --rpc-url https://rpc.pulsechain.com

The daily-driver read. Call a contract's view function using a human-readable signature with return types in the SECOND parens — cast encodes the call, sends eth_call, and decodes the return for you. No manual hex, no manual selector. This is the cast equivalent of the raw curl eth_call from Stage 2, minus all the hand-decoding.

result"WPLS"
read token decimals
cast call 0xA1077a294dDE1B09bB078844df40758a5D0f9a27 "decimals()(uint8)" --rpc-url https://rpc.pulsechain.com

Read a token's decimals so you can interpret every balance and Transfer value correctly. You need this number BEFORE you trust any human-readable amount — a raw uint256 means nothing until divided by 10**decimals.

result18
raw decimals rpc call
curl -s -X POST https://rpc.pulsechain.com -H 'Content-Type: application/json' --data '{"jsonrpc":"2.0","id":1,"method":"eth_call","params":[{"to":"0xA1077a294dDE1B09bB078844df40758a5D0f9a27","data":"0x313ce567"},"latest"]}'

The same decimals() read at the raw JSON-RPC level (0x313ce567 is the decimals() selector). The node hands back undecoded hex. This shows what cast call is doing under the hood — and why you let a tool decode it instead of squinting at 0x...12.

result{"jsonrpc":"2.0","id":1,"result":"0x0000000000000000000000000000000000000000000000000000000000000012"}
convert hex to decimal
cast to-dec 0x0000000000000000000000000000000000000000000000000000000000000012

Convert the raw hex return above into decimal — the tool computes, never your head. 0x12 = 18. Pair to-dec (hex->decimal) with from-wei (wei->whole-token) so you never do base conversion or token math manually. This is the verification discipline made concrete.

result18
decode return and convert
cast abi-decode "balanceOf(address)(uint256)" 0x0000000000000000000000000000000000000000000000000de0b6b3a7640000 && cast from-wei 1000000000000000000

Decode raw return DATA (no selector — this is output, not input) into typed values, then convert wei to whole tokens. Use abi-decode when you captured a return blob (e.g. from raw curl) and want it typed; use from-wei to turn the 18-decimal integer into the human number.

result1000000000000000000 [1e18] 1.000000000000000000
!
The trap. A short label (e.g. "ABCD" or "the whale") silently points at the WRONG full address — a typo, a stale paste, or a lookalike vanity prefix — and you raise an alarm about the wrong wallet. Always resolve every nickname back to the full 42-character checksummed address and verify THAT address on-chain before acting. Second trap: hashing the wrong canonical signature — Transfer has THREE args (address,address,uint256), and one space or a missing parameter gives you a topic0/selector that matches nothing. Never truncate an address in output; a truncated address is unverifiable.
AI
The AI flow. AI writes the decode command, you run it, AI interprets the decoded result — and the binary, not the model, does every conversion. Concretely: you paste an unknown calldata blob or a log's topics+data into the chat; Claude writes the exact cast 4byte-decode / cast calldata-decode / cast sig-event command; you run it in your shell; Claude reads the decoded output and tells you what the transaction did in English, cross-referencing your CONTEXT address book to name each address. The AI never asserts a number it computed in its head — if an amount needs base conversion or wei scaling, it must emit a cast to-dec / cast from-wei command and let the tool produce the value. You feed your labeled address book in at the start so the AI's interpretation is grounded in your world, not generic guesses — and you treat its label resolution as a claim to verify, never trust blindly.
369
On PulseChain. Suppose you pull a transaction on PulseChain (chain id 369, https://rpc.pulsechain.com) and its input field is a raw blob starting 0xa9059cbb. You don't guess. You run cast 4byte-decode 0xa9059cbb... and learn it's transfer(address,uint256) moving 1e18 base units to some recipient address. To know what '1e18 base units' means in tokens, you read the token's decimals with cast call <token> "decimals()(uint8)" --rpc-url https://rpc.pulsechain.com, then cast from-wei — never dividing in your head. Take WPLS at 0xA1077a294dDE1B09bB078844df40758a5D0f9a27: it returns 18 decimals — but mind the category. WPLS is a PulseChain-NATIVE contract (its name() returns "Wrapped Pulse", the canonical wrapper for the native coin PLS, the direct analog of WETH), NOT a forked mirror. The forked tokens are the mirrored Ethereum ERC-20s sitting at their original Ethereum addresses, copied from Ethereum at the May-2023 fork and free to diverge since — e.g. forked DAI at 0x6B175474E89094C44Da98b954EedeAC495271d0F, whose supply has since been inflated well past mainnet on PulseChain. To find every transfer of a token, you filter logs on topic0 = cast sig-event "Transfer(address,address,uint256)" = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, reading from/to out of topics[1]/topics[2] and value out of data. And because PulseChain is a full fork-copy of Ethereum state from the May 2023 launch, a FORKED token's on-chain balances and supply started as mirrors of mainnet and may have diverged since — decoding tells you exactly what the calldata DID, but you still reason separately about whether that value means anything live. Native contracts like WPLS carry no such mirror caveat.
Takeaway The chain speaks hex; cast is your translator — let the tool decode and compute every selector, topic, and amount, label every address in full, and the AI just writes the command and reads back the meaning.
04
Verify

A number from one endpoint is a rumor

A number from one endpoint is not a fact — it is a rumor with a hex prefix; truth is what survives three independent RPCs, a getter-independent storage slot, and a tool doing every single conversion.

Single source equals zero sources. An RPC endpoint is a server run by a person, and a server can be lagged, pruned, behind a stale cache, archive-incomplete, or quietly pointed at a different fork. When you query a value from ONE endpoint you have learned exactly one thing: what that one box said. That is not a property of the chain — it is a property of the box. The discipline is brutally simple: fire the SAME call at three INDEPENDENT operators (different parties, not three URLs from one company) and require the raw return bytes to be identical. On PulseChain that means hitting endpoints run by genuinely different people — e.g. rpc.pulsechain.com (core team), rpc-pulsechain.g4mm4.io (G4MM4), and pulsechain-rpc.publicnode.com (PublicNode/Allnodes) — and confirming all three hand back the exact same word. Run totalSupply() of the forked DAI token across all three and every one returns 0x00000000000000000000000000000000000000008f5582f6f8d989d812632a53, byte-for-byte. THAT is a triple-match. Anything less is a draft, not a finding. One honest caveat so you don't over-sell it to a hostile reader: byte-identical returns prove agreement, not true independence — the only real bedrock is your own synced archive node. Multi-RPC catches lag, cache staleness, and fork divergence; it does not catch a coordinated infra failure across providers. It is far stronger than one source and short of absolute. Claim exactly that much.

The getter can lie; the slot cannot. Most contracts you will touch on a fork are proxies, and many are hostile. A getter like implementation(), owner(), or balanceOf() is just code — and code can be written to return whatever its author wants. The storage slot underneath it is the ground the EVM actually stands on. That is your getter-INDEPENDENT third source. For any EIP-1967 proxy the implementation address lives at a fixed slot: 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc, which is keccak256('eip1967.proxy.implementation') minus 1 (verify it yourself: cast keccak 'eip1967.proxy.implementation' gives 0x...bbd, and the slot is that minus one, 0x...bbc — the offset exists specifically so the slot can never collide with a normally-mapped variable). Read that slot directly with eth_getStorageAt or cast storage and you see the real implementation regardless of what any getter claims. Read it on the live Aave aDAI mirror 0x018008bfb33d285247A21d44E50697654f754e63 and you get 0x0000000000000000000000007effd7b47bfd17e52fb7559d3f924201b9dbff3d — a 32-byte word whose last 20 bytes are the implementation the chain is actually executing. When a getter and its backing slot disagree, the slot wins and the getter is the story you write up.

Never do the math in your head, and never let the AI do it either. This is the single most common way analysts and LLMs post wrong numbers: hex-to-decimal and wei-to-token conversions done by hand or hallucinated. Language models are pattern matchers, not calculators — they will silently drop a digit, fumble 18 decimals, or confidently render 44359733562592918446387964499 with a digit missing and never flinch. The rule: the TOOL computes, the head narrates. cast to-dec turns 0x8f5582f6f8d989d812632a53 into 44359733562592918446387964499. cast from-wei turns that into a human token amount. A typed signature like 'totalSupply()(uint256)' makes cast call decode the return for you in the same step. Your job — and the AI's job — is to pick the command and read the result, never to perform the arithmetic. THE ONE FOOTGUN that will bite a Verify chapter if it isn't named: cast from-wei defaults to 18 decimals. On the forked USDC and USDT mirrors that litter PulseChain — which are 6-decimal tokens — bare from-wei is wrong by a factor of 1e12. So always read the token's real decimals on-chain first (cast call <token> 'decimals()(uint8)') and pass that count explicitly: cast from-wei <amount> <decimals>, e.g. cast from-wei 1000000 6 returns 1.000000. (A related precision point: cast to-unit only converts between ether/gwei/wei — it is not a general decimal-scaler, so don't reach for it on tokens.) If a number in your post wasn't printed by a tool with the right decimal count, it isn't a number, it's a guess.

Label every claim by its provenance: SELF-VERIFIED, TAKEN-FROM-OTHERS, or INFERRED. The thing that separates an analyst from a hype account is honesty about epistemic status. SELF-VERIFIED means you ran the calls yourself and got a triple-match — you can stake your name on the bytes. TAKEN-FROM-OTHERS means a block explorer, a Dune query, or another person told you and you're repeating it without independent confirmation — useful, but quarantined until you re-run it. INFERRED means you reasoned it out from verified facts but did not directly observe it — the riskiest tier, because plausible reasoning about WHY something happened is exactly where confident-and-wrong lives. Keep causality (the 'why') strictly separate from observation (the 'what'): you can prove a slot holds an address; you usually cannot prove the intent behind it. A revert is its own trap. A single endpoint returning 'execution reverted' (JSON-RPC error code -32000) is meaningless on its own — one node can revert on a state it hasn't fully synced, and some clients return empty data rather than a revert for a missing function. A revert only graduates to a finding when ALL your endpoints revert consistently. Mark it, match it, or don't claim it.

triple-match raw curl
for url in https://rpc.pulsechain.com https://rpc-pulsechain.g4mm4.io https://pulsechain-rpc.publicnode.com; do echo "$url"; curl -s -X POST "$url" -H 'Content-Type: application/json' --data '{"jsonrpc":"2.0","id":1,"method":"eth_call","params":[{"to":"0x6B175474E89094C44Da98b954EedeAC495271d0F","data":"0x18160ddd"},"latest"]}'; echo; done

The triple-match in raw form. Same totalSupply() call (selector 0x18160ddd) against three INDEPENDENT operators. You compare the three result strings by eye: identical = verified, any difference = STOP and investigate (lag, fork, or cache). Independence is the whole point — three URLs from one company is still one source.

resultThree lines, each: {"jsonrpc":"2.0","id":1,"result":"0x00000000000000000000000000000000000000008f5582f6f8d989d812632a53"} All three result fields byte-for-byte identical (verified live). Field ORDER may differ between clients — compare the value of result, not the string position.
triple-match cast call
for url in https://rpc.pulsechain.com https://rpc-pulsechain.g4mm4.io https://pulsechain-rpc.publicnode.com; do echo "$url:"; cast call 0x6B175474E89094C44Da98b954EedeAC495271d0F "totalSupply()(uint256)" --rpc-url "$url"; done

The same triple-match as the daily driver. cast call with the typed signature "totalSupply()(uint256)" hits each endpoint AND decodes the return to decimal in one step — no manual hex math. Three identical decimal lines is your match; the tool did the conversion so you can't fat-finger it.

resulthttps://rpc.pulsechain.com: 44359733562592918446387964499 [4.435e28] (then the other two endpoints, identical) — verified live across all three. The [4.435e28] is cast's scientific-notation hint, not a second value.
read eip1967 slot
cast storage 0x018008bfb33d285247A21d44E50697654f754e63 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc --rpc-url https://rpc.pulsechain.com

The getter-independent third source. Reads the EIP-1967 implementation slot DIRECTLY instead of trusting an implementation() getter that a hostile proxy could fake. The slot constant is keccak256('eip1967.proxy.implementation') minus 1 (confirm with: cast keccak 'eip1967.proxy.implementation'). Whatever address comes back is the code the EVM is actually running. Cross-check it against any getter — if they disagree, the slot is truth.

result0x0000000000000000000000007effd7b47bfd17e52fb7559d3f924201b9dbff3d (left-padded 32-byte word; the implementation address is the last 20 bytes: 0x7effd7b47bfd17e52fb7559d3f924201b9dbff3d — verified live)
raw storage slot read
curl -s -X POST https://rpc.pulsechain.com -H 'Content-Type: application/json' --data '{"jsonrpc":"2.0","id":1,"method":"eth_getStorageAt","params":["0x018008bfb33d285247A21d44E50697654f754e63","0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc","latest"]}'

The raw JSON-RPC twin of the cast storage call, proving cast isn't doing anything magic — eth_getStorageAt returns the identical 32-byte word. Run this against your other two endpoints too: a slot that triple-matches AND is getter-independent is about as close to bedrock as on-chain analysis gets WITHOUT running your own node.

result{"jsonrpc":"2.0","id":1,"result":"0x0000000000000000000000007effd7b47bfd17e52fb7559d3f924201b9dbff3d"} (identical 20-byte tail to the cast storage output — verified live)
tool conversions battery
cast to-dec 0x8f5582f6f8d989d812632a53 && cast from-wei 44359733562592918446387964499 && cast from-wei 1000000 6 && cast to-base 0x171 dec

The 'tool computes, not the head' set. to-dec turns the raw hex word into an integer; from-wei applies 18 decimals (the DEFAULT — correct only for 18-decimal tokens like DAI) to get a human amount; from-wei 1000000 6 shows the decimals-aware form you MUST use for 6-decimal forked USDC/USDT; to-base 0x171 dec confirms a chainId hex is 369 (PulseChain mainnet) without counting nibbles in your head. Never type these conversions manually and never accept an AI's hand-done arithmetic — pipe through cast.

result44359733562592918446387964499 44359733562.592918446387964499 1.000000 369 (all four verified live on cast 1.7.1)
trigger a revert
cast call 0x6B175474E89094C44Da98b954EedeAC495271d0F "nonexistentFn()(uint256)" --rpc-url https://rpc.pulsechain.com

What a revert looks like, and why one revert is NOT a finding. A single endpoint reverting (error code -32000: execution reverted) can mean the function genuinely reverts OR that this one node is on a state where it does — and some clients return empty data instead of reverting for a missing selector. Re-run against your other two endpoints: only if ALL THREE revert consistently do you record 'reverts'. One revert is noise; a triple-revert is signal.

resultError: server returned an error response: error code -32000: execution reverted (raw JSON-RPC equivalent: {"error":{"code":-32000,"message":"execution reverted"}} — verified live; exit code 1)
!
The trap. Treating a clean single-endpoint number, an AI-computed conversion, a bare from-wei on a 6-decimal token, or one lonely revert as a fact — because the first answer looked fine. It wasn't fine, it was unconfirmed.
AI
The AI flow. The AI is the command-writer and the output-narrator, never the calculator and never the trusted oracle. Flow: you describe the value you need ('totalSupply of this token, verified'); the AI emits the exact multi-endpoint loop plus the typed-signature decode — but YOU run it, and the AI reads back only what the tool actually printed. Four hard rails: (1) the AI must NEVER perform hex/wei/decimal arithmetic in its head — it routes every conversion through cast and quotes the tool's output verbatim; LLMs drop and transpose digits with total confidence. (2) Before any from-wei, the AI must read the token's decimals() on-chain and pass the right count — never assume 18, because the forked USDC/USDT mirrors are 6-decimal and bare from-wei is off by 1e12. (3) The AI must label each value SELF-VERIFIED / TAKEN-FROM-OTHERS / INFERRED and refuse to upgrade a tier without the bytes to back it. (4) When the AI proposes a 'why', it flags it as INFERRED and keeps it separate from the observed 'what'. If the three endpoints disagree, or a getter contradicts its slot, the AI's job is to surface the conflict loudly — not to paper over it with a confident summary.
369
On PulseChain. You want to confirm the supply of the forked DAI token at 0x6B175474E89094C44Da98b954EedeAC495271d0F on PulseChain. You fire the same totalSupply() call at three independent operators — rpc.pulsechain.com, rpc-pulsechain.g4mm4.io, pulsechain-rpc.publicnode.com — and all three hand back the identical word 0x...8f5582f6f8d989d812632a53. You read decimals() and get 18, so cast from-wei is safe with its default; cast to-dec makes the integer 44359733562592918446387964499 and from-wei makes it ~44.36B tokens. That figure is now SELF-VERIFIED. But that 44.36B is not live DAI value, and it is not even a clean copy of mainnet: real DAI on Ethereum is ~4.6B and this token was ~5B right after the May-2023 fork (both cross-chain / historical reads — TAKEN-FROM-OTHERS, verify them against an Ethereum RPC; only the 44.36B is self-verified here), and the rest was minted on PulseChain since. So you do NOT claim '$44B of value' — the integer is a PulseChain-side inflation, and what it is worth today is a SEPARATE, INFERRED question routed through the live PulseX PLS market, not the on-chain number. (And had this been a 6-decimal forked USDC mirror instead, a bare from-wei would have over-stated it by a factor of 1e12 — which is exactly why you read decimals() first.) The triple-match proves the supply integer. It proves nothing about dollars. Keeping those two claims in different provenance tiers is the entire discipline.
Takeaway If a number didn't survive three independent endpoints, a getter-independent slot read, and a tool doing every conversion with the right decimal count, it isn't a finding — it's a draft you haven't earned the right to post yet.
05
Hunt

Follow the money — real value, not mirror value

Investigating a wallet is one move repeated: pull a transaction, decode every log it emitted, jump to each counterparty it touched, and sort the whole web by block number until a timeline tells the story — then price every balance against a live PulseX pool, because on a forked chain face value is a lie.

A hunt has exactly one starting point: one transaction hash or one address. Everything else is expansion. The receipt of a single tx is the richest object on the chain — it contains the from, the to, the gas actually burned, the status (1 = success, 0 = reverted), and the logs array, which is the event trail of every token that moved and every contract that fired. You never start by reading a wallet's 'token balances' page on an explorer; you start from one tx and let its logs hand you the next addresses to chase. eth_getTransactionReceipt is the seed; the logs are the branches. The discipline is: AI proposes the next address to pull, you run the call, AI reads the decoded output — the AI never asserts a transfer happened without the decoded log in front of it.

Decoding logs is the whole game, and it is mechanical, not magical. Every ERC-20 Transfer emits a log whose topics[0] is the fixed hash 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef — that is keccak256("Transfer(address,address,uint256)") and it is identical on every EVM chain. topics[1] is the sender, topics[2] is the receiver — both are 32-byte left-padded values, so the real 20-byte address is the last 40 hex characters. The data field is the amount, raw, in the token's smallest base units, which you must scale by the token's decimals before it means anything. A PulseX V2 swap emits a different fixed topic, 0xd78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822 = keccak256("Swap(address,uint256,uint256,uint256,uint256,address)"), and a pool reserve update emits Sync, 0x1c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1 = keccak256("Sync(uint112,uint112)"). You don't memorize these — you have cast compute them (cast keccak "Transfer(address,address,uint256)") and you filter logs by them. The function selector 0x38ed1739 (swapExactTokensForTokens) as the first 4 bytes of a tx's input calldata, with the PulseX router as the to, is your tell that a wallet routed through PulseX rather than transferring directly.

Clustering is connecting addresses that move as one hand, and the evidence lives in the timeline. Pull the counterparties out of the decoded transfers, then for each one fetch its transaction list and look for: shared funding source (multiple addresses first funded by the same wallet within minutes), round-trip flows (A sends to B, B swaps, proceeds return to A), and tight time-correlation (a burst of txs from five addresses in the same two blocks). The single most clarifying operation is sorting every transfer you've collected by blockNumber ascending, breaking ties by logIndex — a flat list of hashes is noise; the same list ordered by block is a narrative. Block number is the canonical clock; wall-clock timestamps are derived from it (you confirm a block's time with eth_getBlockByNumber). Build the timeline first, draw conclusions second. The AI is excellent at taking a pile of decoded transfers and emitting a block-sorted table; it is forbidden from inventing an ordering you haven't fed it. Note that clustering produces hypotheses, not proof — shared funding and tight timing are strong signals, but a single funder can be an exchange hot wallet or a public router, so corroborate before you call it one hand.

Then the PulseChain-specific reckoning, and it is the thing the community must internalize or get rugged by its own portfolio: REAL value vs MIRROR value. PulseChain launched in May 2023 as a full copy of Ethereum's entire state. Essentially every token balance that existed on Ethereum at the fork block also appears on PulseChain — a May-2023 snapshot, frozen unless PulseChain activity has since moved or inflated it, and often attached to wallets whose owners never touched PulseChain. A wallet showing 5 billion of some forked stablecoin, or a Mainnet whale's gigantic PLS-side balance, is almost always fork reflection, not engagement and not wealth. The only value that is real is value you can actually sell — and on PulseChain the deepest, most reliable liquidity for most tokens is a PulseX pool against PLS, so that is where you price it (other venues exist, but a deep PulseX pool is the honest floor). So the final step of every hunt is not reading balanceOf; it is reading the pool. You call getReserves() on the actual PulseX pair, take the ratio of the two reserves, and that — not the token's face value, not its Ethereum price — is what one unit is worth in PLS on this chain. No pool, or a pool with dust reserves, means the balance is worth approximately zero no matter how many zeros it carries. One caveat to stay honest: a thin pool can be inflated to fake a price, so a real valuation weighs reserve depth, not just the ratio — a pool you could drain with one swap is not a price, it's a trap.

seed the receipt
cast receipt 0xTXHASH...placeholder --rpc-url https://rpc.pulsechain.com

The seed of every hunt. Pulls the full receipt for one transaction: status (1=success / 0=reverted), from, to, gasUsed, and the complete logs array. Replace with a real hash from BlockScout. This is where you start — not a wallet's balance page.

resultA field-per-line block: blockNumber, from, to, gasUsed, status '1 (success)', and a 'logs' list where each entry has address, topics[] and data. The logs are your next leads.
raw receipt rpc
curl -s -X POST https://rpc.pulsechain.com -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","method":"eth_getTransactionReceipt","params":["0xTXHASH...placeholder"],"id":1}'

The raw JSON-RPC underneath cast receipt — run this once to see what the chain actually returns, so cast stops being a black box. Same data, undecoded: status and amounts are raw hex, topics are raw 32-byte hex.

result{"jsonrpc":"2.0","id":1,"result":{"status":"0x1","from":"0x..","to":"0x..","logs":[{"address":"0x..","topics":["0xddf252ad..","0x000..from","0x000..to"],"data":"0x..amount","blockNumber":"0x..","transactionHash":"0x..","logIndex":"0x.."}], ...}}. A hash that doesn't exist returns {"jsonrpc":"2.0","id":1,"result":null} (verified live).
compute event topics
cast keccak "Transfer(address,address,uint256)"

Compute the Transfer event's topic0 yourself instead of trusting a copy-pasted hash. The tool does the keccak; you never eyeball it. Do the same for Swap(address,uint256,uint256,uint256,uint256,address) and Sync(uint112,uint112) to get the PulseX swap and reserve-update topics.

result0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Swap -> 0xd78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822, Sync -> 0x1c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1)
filter transfers in
curl -s -X POST https://rpc.pulsechain.com -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","method":"eth_getLogs","params":[{"fromBlock":"0x19AB000","toBlock":"0x19AB53F","address":"0xTOKEN...placeholder","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",null,"0x000000000000000000000000WALLET40HEXplaceholder"]}],"id":1}'

Find every Transfer INTO a wallet for one token across a block range. topics[2] = receiver (32-byte left-padded, so prepend 12 zero bytes (24 zero hex characters, written 000000000000000000000000) to the 40-hex address); null in topics[1] = any sender. Swap the positions (address in topics[1], null in topics[2]) to find transfers OUT. Keep ranges modest — public RPCs cap the block span and result size.

result{"result":[{"address":"0x..","topics":["0xddf252ad..","0x..sender","0x..receiver"],"data":"0x..rawAmount","blockNumber":"0x19ab53d","transactionHash":"0x..","logIndex":"0x0"}, ...]} — empty array [] if no matches; an error object if the range is too wide for the public node.
decode log amount
cast from-wei $(cast to-dec 0x0000000000000000000000000000000000000000001777bd9eed3815c82c0698)

Turn a raw log 'data' amount into a number — ALWAYS with the tool, never by hand. cast to-dec converts the hex to a base-unit integer; cast from-wei then divides by 1e18, which is correct ONLY for 18-decimal tokens. cast from-wei defaults to 18 decimals, so for any non-18-decimal token you MUST pass the real count — cast from-wei <amount> <decimals> (or cast format-units <amount> <decimals>). Never rely on the default (e.g. 6-decimal USDC: cast format-units 1000000 6 -> 1). This is the verification discipline: the tool computes, your head does not.

resultto-dec yields 28370753349098983580763800; from-wei then prints 28370753.349098983580763800 (this token is 18-decimal). Picking the wrong decimals is one of the most common silent errors in wallet analysis — confirm the token's decimals() first.
read pool reserves
cast call 0x149B2C629e652f2E89E11cd57e5d4D77ee166f9F "getReserves()(uint112,uint112,uint32)" --rpc-url https://rpc.pulsechain.com

The reality check. This is the live WPLS/PLSX PulseX V2 pair. getReserves returns (reserve0, reserve1, blockTimestampLast). For any token, resolve its pair with cast call 0x29eA7545DEf87022BAdc76323F373EA1e707C523 "getPair(address,address)(address)" <tokenA> <tokenB> on the PulseX V2 factory (returns the zero address if no pair exists). Check token0()/token1() to know which reserve is which, then price = reserveOther / reserveToken — and weigh reserve depth, because a dust pool is not a real price.

resultThree numbers, e.g. 41729935739723910672159519020 30448192900755084084830498215 1782832425 — reserve0 (PLSX, since token0=0x95B303987A60C71504D99Aa1b13B4DA07b0790ab), reserve1 (WPLS, token1=0xA1077a294dDE1B09bB078844df40758a5D0f9a27), and a unix timestamp. Ratio reserve0/reserve1 = PLSX per WPLS. Empty/tiny reserves = the balance is worth ~0.
!
The trap. A Mainnet wallet shows billions in PLS and forked tokens on PulseChain and the untrained eye screams 'whale' — but it's fork reflection: a frozen May-2023 snapshot of that wallet's Ethereum state, with an owner who likely never touched PulseChain and a balance that may have zero PulseX liquidity behind it. Reading face value or balanceOf and stopping there is how you mistake a mirror for money. The cure is mandatory and mechanical: before you call any balance 'real,' price it against an actual PulseX pool with getReserves — and check the pool has real depth, because a thin pool's ratio is a number you can't actually exit at. No live pool with real liquidity, no value.
AI
The AI flow. At each step the AI writes the exact call (the receipt fetch, the eth_getLogs filter with correctly padded topics, the factory getPair lookup, the getReserves), you run it, and the AI reads the decoded result back — then proposes the next address to pull from the counterparties it just decoded. The AI's strongest move here is synthesis: hand it the pile of decoded transfers and it returns a block-sorted (then logIndex-sorted) timeline and a candidate cluster — framed as a hypothesis, not a verdict. Its hard constraints: it must convert amounts with cast to-dec / from-wei / format-units and never do hex-to-decimal or wei math in its head; it must use format-units <amount> <decimals> (not from-wei) for any token that isn't 18-decimal; it must not claim a transfer occurred without the decoded log present; and it must not call a balance valuable until a getReserves call on a real, liquid pool proves liquidity exists.
369
On PulseChain. You seed a hunt with one tx hash via cast receipt. Its logs decode to three events: a wallet sent a forked token to the PulseX router (the tx's input calldata starts with the 0x38ed1739 selector, swapExactTokensForTokens), the pair contract emitted a Swap (topic 0xd78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822), and WPLS came back. You pull eth_getLogs for that wallet across the surrounding blocks and find four sibling addresses all first funded by the same parent within two blocks — a clustering hypothesis. You sort every decoded transfer by blockNumber (ties broken by logIndex) and the timeline reads cleanly: fork-mirror token in, swap to WPLS, WPLS out to the parent. Then the reckoning: the wallet still 'holds' billions of a second forked token, so you resolve its PulseX pair via the V2 factory 0x29eA7545DEf87022BAdc76323F373EA1e707C523 with getPair and call getReserves — reserves are dust. Verdict: the swapped leg was real value (it had a live WPLS pool with depth), the billions are worth ~0 — a fork-token balance with only a dust pool behind it. Same wallet, two balances, opposite truths — and only getReserves told them apart.
Takeaway Start from one tx, decode its logs to get the next addresses, sort everything by block into a timeline — and price every balance against a live PulseX pool, because on a forked chain the only real value is what trades against PLS.
06
Scale

Turn one investigation into a verified machine

One investigation is a story; a script that runs the same verified battery against any address — and a cron that watches your list while you sleep — is a machine, and the only rule that survives automation is that the cross-RPC verify must live inside the script, not in your head.

You already know the manual moves: read a balance over JSON-RPC, pull recent transfers, decode a calldata blob, cross-check the number on a second RPC. Stage 6 is the moment you stop typing those moves one at a time. An investigation you can reproduce by hand is a skill; an investigation a script reproduces identically on the 1st address and the 400th is leverage. The unit of work shifts from 'a transaction' to 'a wallet', and then to 'a watchlist'. The deliverable of this stage is a single file — call it probe.sh — that takes one address and runs the standard battery: native PLS balance, a fixed list of token balances via balanceOf, nonce, recent Transfer logs decoded to from/to/amount, all printed in a stable, greppable shape. AI writes that file; you read every line before you trust it; you run it; AI interprets the output. The script is the artifact, the AI is the orchestrator and the interpreter — never the unaudited author of a number you publish.

The trap that kills credibility at scale is subtle: automation does not reduce your error rate, it multiplies your throughput at whatever error rate you already have. If your manual process quietly trusted a single RPC, your script now trusts a single RPC four hundred times an hour and calls it a dataset. The fix is structural, not behavioral — you cannot 'remember to verify' across a cron job that fires at 3am. The multi-RPC cross-check has to be a function inside the script: query the same balanceOf against three independent endpoints (e.g. https://rpc.pulsechain.com, https://pulsechain-rpc.publicnode.com, and https://rpc-pulsechain.g4mm4.io — all three return chain id 369), compare the raw hex, and emit MATCH or a loud MISMATCH that flags the row instead of printing a number. A value that didn't pass through that function never reaches your output. This is the same triple-verify discipline from Stage 5, demoted from a habit to a line of code — which is the only place a habit is safe at scale.

Watchlists turn a one-shot probe into surveillance. Keep a flat file of addresses (one per line, full 42-character form, never truncated — truncation is how you key a snapshot to the wrong wallet). The monitor loop is brutally simple: for each address, snapshot the cheap, monotonic state signals — eth_getTransactionCount (nonce) and native balance — into a small store (a tab-separated file or SQLite row keyed by address). On the next run, diff against the stored snapshot. Nonce moved? The wallet sent a transaction. Balance moved? Value flowed. A diff is your trigger; everything expensive (pulling the actual txs, decoding them, summarizing) happens only on a triggered address, not on every address every minute. That keeps a 400-wallet watchlist cheap enough to run on a cron without hammering public RPCs into rate-limiting you. cron schedules it; the diff decides whether there's anything worth waking the AI for.

Agentic orchestration is the top layer, and it is where Claude earns its place: plan -> fetch -> decode -> verify -> summarize, as an explicit loop the model drives. You hand it a triggered address and the question ('this nonce jumped 12 in an hour — what did it do?'). It plans the calls, runs your verified primitives (it calls probe.sh and cast, it does not freehand arithmetic), pulls the Transfer logs, decodes them, runs the cross-check, and writes a human summary with every load-bearing number tagged by how it was established — self-verified across 3 RPCs, TAKEN-FROM-OTHERS, or inferred. The agent's judgment is allowed to be probabilistic; the numbers it cites are not. That separation — soft reasoning over hard, tool-computed, multi-sourced facts — is the whole methodology compressed into a workflow. One sharp edge to internalize before you automate log pulls: filtering Transfer events by a wallet is the single most error-prone primitive in this stage, because the cast logs argument modes are easy to mix up and a mixed invocation fails silently — it returns a full unfiltered dump that merely looks filtered. Get that primitive provably right (see the commands), bake it into the script, point the cron at your list, and let the model do the reading. That is what 'scale' means here: not more wallets per hour, but the same un-dunkable rigor per wallet, automatically.

write probe script
cat > ~/probe.sh <<'EOF'
#!/usr/bin/env bash
# probe.sh <address> — standard verified battery for one wallet.
# Philosophy: the script computes; the human never does hex/wei math by hand.
set -euo pipefail
ADDR="${1:?usage: probe.sh <0xaddress>}"

# Three INDEPENDENT RPCs. The cross-check is built in, not bolted on.
RPCS=(
  "https://rpc.pulsechain.com"
  "https://pulsechain-rpc.publicnode.com"
  "https://rpc-pulsechain.g4mm4.io"
)
# Tokens to check by balanceOf — extend freely. (symbol  address)
TOKENS=(
  "WPLS 0xA1077a294dDE1B09bB078844df40758a5D0f9a27"
  "DAI  0x6B175474E89094C44Da98b954EedeAC495271d0F"
)

# --- triple-verify one read: same call on 3 RPCs, compare raw, print verdict ---
tv() { # tv <to> <sig(returns)> [args...]
  local to=$1 sig=$2; shift 2
  local out=(); local r
  for r in "${RPCS[@]}"; do
    out+=("$(cast call "$to" "$sig" "$@" --rpc-url "$r" 2>/dev/null || echo ERR)")
  done
  if [[ "${out[0]}" == "${out[1]}" && "${out[1]}" == "${out[2]}" && "${out[0]}" != ERR ]]; then
    printf '%s  [OK 3/3 MATCH]' "${out[0]}"
  else
    printf 'MISMATCH r1=%s r2=%s r3=%s  [XX VERIFY FAILED]' "${out[0]}" "${out[1]}" "${out[2]}"
  fi
}

echo "== probe $ADDR =="
echo "chain-id : $(cast chain-id --rpc-url "${RPCS[0]}")"
echo "nonce    : $(cast nonce "$ADDR" --rpc-url "${RPCS[0]}")"
echo "PLS      : $(cast from-wei "$(cast balance "$ADDR" --rpc-url "${RPCS[0]}")")"
for t in "${TOKENS[@]}"; do
  set -- $t; sym=$1; ca=$2
  echo "$sym : $(tv "$ca" 'balanceOf(address)(uint256)' "$ADDR")"
done
EOF
chmod +x ~/probe.sh
~/probe.sh 0xA1077a294dDE1B09bB078844df40758a5D0f9a27

The whole stage in one file. AI writes it, you read every line, then run it. tv() is the unskippable verify step: it fires the same balanceOf at three independent PulseChain RPCs and only prints the value if all three raw results match — otherwise it prints a loud MISMATCH instead of a number. cast does every conversion (from-wei, hex->dec), so no human or model ever does wei arithmetic by hand. Extend TOKENS / RPCS and the battery scales with zero new logic. (Note: WPLS is itself a contract that wraps PLS, so its native PLS line is large — that is the wrapped backing, not a bug.)

result== probe 0xA1077a294dDE1B09bB078844df40758a5D0f9a27 ==
chain-id : 369
nonce : 1
PLS : 7800595855885.229720431326044440
WPLS : 77595493298959173485893940 [7.759e25] [OK 3/3 MATCH]
DAI : 33333000000000000000000 [3.333e22] [OK 3/3 MATCH]
single balanceof leg
cast call 0xA1077a294dDE1B09bB078844df40758a5D0f9a27 "balanceOf(address)(uint256)" 0xA1077a294dDE1B09bB078844df40758a5D0f9a27 --rpc-url https://rpc.pulsechain.com

The single primitive the script wraps three times. This is what one leg of the cross-check looks like raw — useful to run standalone when tv() reports a MISMATCH and you need to see which RPC disagrees. (uint256) in the signature tells cast to decode the return to a decimal automatically.

result77595493298959173485893940 [7.759e25] — a single decimal value with cast's scientific-notation hint in brackets. Run it against a second --rpc-url; the digits must be byte-identical or you do not have a fact.
write watchlist monitor
cat > ~/watch.sh <<'EOF'
#!/usr/bin/env bash
# watch.sh — diff cheap state signals against last snapshot; only flag movers.
set -euo pipefail
RPC="https://rpc.pulsechain.com"
LIST=~/watchlist.txt           # one full 0x address per line
STORE=~/.wstate                # addr<TAB>nonce<TAB>balance per line
touch "$STORE"
while read -r ADDR; do
  [[ -z "$ADDR" || "$ADDR" == \#* ]] && continue
  N=$(cast nonce "$ADDR" --rpc-url "$RPC")
  B=$(cast balance "$ADDR" --rpc-url "$RPC")
  PREV=$(grep -F "$ADDR" "$STORE" || true)
  if [[ -n "$PREV" ]]; then
    PN=$(echo "$PREV" | cut -f2); PB=$(echo "$PREV" | cut -f3)
    if [[ "$N" != "$PN" || "$B" != "$PB" ]]; then
      echo "[$(date -u +%FT%TZ)] MOVED $ADDR nonce $PN->$N  bal $PB->$B"
    fi
  fi
  # upsert snapshot
  grep -vF "$ADDR" "$STORE" > "$STORE.tmp" || true
  printf '%s\t%s\t%s\n' "$ADDR" "$N" "$B" >> "$STORE.tmp"
  mv "$STORE.tmp" "$STORE"
done < "$LIST"
EOF
chmod +x ~/watch.sh
printf '0xA1077a294dDE1B09bB078844df40758a5D0f9a27\n' > ~/watchlist.txt
~/watch.sh

The watchlist monitor. It snapshots only the cheap, monotonic signals — nonce and native balance — and diffs against ~/.wstate. First run just seeds the store (silent); subsequent runs print a MOVED line only when a wallet actually sent a tx or its balance changed. Expensive work (pulling/decoding the new txs) is deferred to the triggered address, so a 400-line watchlist stays cheap. Full addresses only — a truncated key silently maps two wallets to one row.

resultFirst run: no output (store seeded). A later run after the wallet acts: [2026-06-30T14:02:11Z] MOVED 0xA1077a294dDE1B09bB078844df40758a5D0f9a27 nonce 1->2 bal 7800595950799904620837573656087->7800600000000000000000000000000
schedule cron job
( crontab -l 2>/dev/null; echo "*/5 * * * * /bin/bash $HOME/watch.sh >> $HOME/watch.log 2>&1" ) | crontab -
crontab -l | grep watch.sh

Schedules the monitor every 5 minutes via cron, appending movers to ~/watch.log. The ( crontab -l ; echo ... ) | crontab - idiom appends without clobbering existing jobs. Point Claude at watch.log: it reads the MOVED lines, and for each one runs probe.sh + a Transfer-log pull on just that address, decodes the transfers, re-verifies across RPCs, and writes you a summary — plan->fetch->decode->verify->summarize, triggered only by real movement.

result*/5 * * * * /bin/bash /Users/you/watch.sh >> /Users/you/watch.log 2>&1 — the installed crontab line echoed back, confirming the job is registered.
filter transfer logs
cast logs --rpc-url https://rpc.pulsechain.com --from-block 26915900 --to-block latest 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef 0x00000000000000000000000098a1a5ce4208f71fb658c91635b367a529c7f5a2 --address 0xA1077a294dDE1B09bB078844df40758a5D0f9a27

The 'fetch' step the agent runs once an address is flagged — and the one primitive you MUST get provably right. There are two mutually exclusive cast logs modes and mixing them fails SILENTLY. SIGNATURE mode (“Transfer(address,address,uint256)” followed by a plain address) does NOT reliably apply the indexed filter on current cast/Pulse — it returns the full unfiltered set that only looks filtered. RAW-TOPIC mode (shown here) is reliable: pass the topic0 event hash 0xddf252ad...523b3ef, then the already-32-byte-padded indexed from (left-pad the 20-byte address with 12 zero-bytes (24 zero hex-chars)), and set --address to the EMITTING TOKEN CONTRACT (not the wallet — an EOA emits no logs of its own). To capture the other direction (received), put a literal null in the from-slot and the padded wallet in the to-slot via raw eth_getLogs (next command). Confirm the filter worked by checking every topics[1] equals your padded address.

resultOnly the records whose topics[1] == the padded `from` (verified: a 5-record result over this window vs ~4000 unfiltered). Each record: address, blockNumber (decimal), data (the uint256 amount, hex), topics[0]=0xddf252ad...523b3ef (Transfer), topics[1]=indexed from, topics[2]=indexed to, transactionHash. Feed each `data` to `cast to-dec` / `cast from-wei` — never eyeball the hex. If topics[1] shows many different addresses, your filter did NOT apply — you are in the silent-failure case.
raw getlogs ground truth
curl -s -X POST https://rpc.pulsechain.com -H 'content-type: application/json' --data '{"jsonrpc":"2.0","id":1,"method":"eth_getLogs","params":[{"address":"0xA1077a294dDE1B09bB078844df40758a5D0f9a27","fromBlock":"0x19ade7c","toBlock":"latest","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",null,"0x00000000000000000000000098a1a5ce4208f71fb658c91635b367a529c7f5a2"]}]}' | jq '.result | length'

The raw JSON-RPC ground truth for log filtering — what cast wraps, and the form that NEVER silently mis-filters because you write the topic array yourself. topics is positional: index 0 is the event signature hash, index 1 is the indexed from, index 2 is the indexed to. A null in a slot means 'any value here', so [topic0, null, paddedTo] captures transfers INTO a wallet; [topic0, paddedFrom] captures transfers OUT. address is the emitting token contract; fromBlock/toBlock are hex (0x19ade7c = block 26,926,716). (Here --address is the WPLS token and 0x98a1a5ce4208f71fb658c91635b367a529c7f5a2 is the counterparty whose WPLS transfers you are isolating.) This is the verification backstop: when cast's argument modes confuse you, drop to this and the RPC honors the topic filter exactly.

resultA small integer (e.g. 6) — the count of Transfer logs INTO that padded `to` address in the window. Drop `null,` and use [topic0, paddedFrom] to count the OUT direction instead. Verified against live PulseChain: the to-filter form shown here collapses ~thousands of unfiltered logs to a handful, proving the RPC applies the topic filter; absence of a wallet's own logs under --address <EOA> is expected, since EOAs emit none.
discover token balances
curl -s "https://api.scan.pulsechain.com/api/v2/addresses/0xA1077a294dDE1B09bB078844df40758a5D0f9a27/token-balances" | jq -r '.[] | "\(.token.symbol)\t\(.value)\t\(.token.decimals)dp\t\(.token.address)"' | head

BlockScout v2 gives you the full token-holdings list in one call — the index BlockScout maintains, which is faster than guessing token addresses for balanceOf. The response is a top-level JSON array; each element has value and a nested token object with symbol/decimals/address. Use it to DISCOVER what to probe, then verify the balances that matter via the script's on-chain tv() (BlockScout is one source; on-chain across 3 RPCs is the fact). Note: pure token-holders with no native tx can 404 here — absence in BlockScout is not absence on-chain, so on-chain balanceOf remains the source of truth.

resultTab-separated rows, one per held token, e.g.: LUSD 566205915615579512175618205569302184 18dp 0x5f98805A4E8be255a32880FDeC7F6728C6568bA0 ... (verified: this address returns a 79-element list). The AI must handle a non-200 body — this endpoint can return a 502 HTML page under load; a robust script checks the HTTP code before piping to jq.
!
The trap. Automating without baking the verify INTO the script just scales your errors faster. A loop that trusts one RPC trusts it 400 times an hour and calls the result a dataset. The multi-RPC cross-check cannot be a habit you 'remember' — a 3am cron has no memory. Make it a function (tv() above) that emits MATCH or aborts with a loud MISMATCH, and let no value reach your output unless it passed through it. Second trap, and the one that actually got dunked in review: filtering Transfer logs by wallet with the WRONG cast logs mode. Signature mode (“Transfer(address,address,uint256)” + a plain address) does NOT reliably apply the indexed filter on current cast against PulseChain — it returns a full unfiltered dump that merely looks filtered, and the wallet you queried is just one from among hundreds. Use RAW-TOPIC mode (topic0 hash + a 32-byte-padded address, no signature), set --address to the emitting TOKEN contract (an EOA emits no Transfer logs of its own), and confirm every topics[1] equals your padded address — or drop to raw eth_getLogs, which honors the topic array exactly. Third trap: truncating addresses in the watchlist or snapshot store — a short key silently collides two wallets onto one row and quietly corrupts every diff after it.
AI
The AI flow. AI is the orchestrator across the whole battery, but never the unaudited author of a number. The flow: AI WRITES probe.sh / watch.sh (you read every line before trusting it), you run them, AI INTERPRETS the output. Once cron flags a mover in watch.log, the agent drives the loop itself — plan (what calls answer 'what did this wallet do?'), fetch (call probe.sh + a raw-topic-mode cast logs or eth_getLogs pull on the triggered address only, and self-check that the topic filter actually applied — many distinct topics[1] means it did NOT), decode (cast decode-calldata / to-dec / from-wei — tool computes, model never does arithmetic), verify (the built-in 3-RPC tv() cross-check), summarize (human-readable, every load-bearing number tagged self-verified / TAKEN-FROM-OTHERS / inferred). Soft probabilistic reasoning over hard tool-computed multi-sourced facts — and an explicit guard on the one primitive (log filtering) whose failure mode is silent.
369
On PulseChain. Keep a flat ~/watchlist.txt of full-length PulseChain addresses you care about — large WPLS holders, a bridge contract, a few DEX routers. Cron runs watch.sh every 5 minutes; it only snapshots nonce + native PLS balance and diffs them, so 400 wallets cost almost nothing. When one moves, the agent runs probe.sh <addr> (3-RPC-verified balances, all returning chain id 369 to confirm you are really on PulseChain mainnet) and a Transfer-log pull over just the new block window — using raw-topic mode (topic0 0xddf252ad...523b3ef + the 32-byte-padded wallet) so the indexed filter actually applies, then decodes each data field with cast from-wei, and writes you a one-paragraph summary — all numbers cross-checked across rpc.pulsechain.com, pulsechain-rpc.publicnode.com, and rpc-pulsechain.g4mm4.io before they are allowed into the report. Because PulseChain is a full May-2023 fork of Ethereum state, remember that forked-token balances STARTED as May-2023 mirror snapshots, but many have diverged — the forked DAI this very script probes now reads ~44.36B, minted far past mainnet. A large forked-token balance may be inert fork reflection OR post-fork inflation; either way it is not live value until a PulseX pool with real depth prices it — the agent should tag it, never quote face value.
Takeaway Scale means the same un-dunkable rigor per wallet, automatically — so the verify step must be a line of code inside the script, and the one silently-failing primitive (log filtering by wallet) must be proven, not assumed, before you let a cron run it 400 times.
07
Judge

Split mechanism, value, and causality

A contract doing exactly what someone claims (verified, 3/3 RPCs) tells you the mechanism is real — it says nothing about whether the value is real or the conclusion follows; the honest analyst splits those three questions and beats the hype analyst over time.

Every on-chain claim secretly bundles three separate assertions, and the con works by making you accept all three when you only verified one. (1) MECHANISM: does the contract actually do X? (2) VALUE: is the thing it produces worth real money? (3) CAUSALITY: does X therefore imply the conclusion Y? These are independent. A flash-loan helper that genuinely executes is mechanism-real. The tokens it moves can still be a frozen mirror worth a few dollars (value-false). And 'it moved tokens, therefore the project is solvent' can be a non-sequitur (causality-false). The entire skill of judgment is refusing to let a verified mechanism smuggle in an unverified value or an unearned causal claim. When you read 'this contract drains the vault, so the token is backed,' your job is to split it into three lines and verify each on its own.

Here is the single cleanest live example on PulseChain, and you can run it yourself in ten seconds. The forked 'DAI' contract at 0x6B175474E89094C44Da98b954EedeAC495271d0F (PulseChain forked Ethereum's state and reuses its exact contract addresses, so this address queried against rpc.pulsechain.com is the independent forked-DAI contract, not mainnet DAI) reports a totalSupply of 44359733562592918446387964499 base units — i.e. ~44.36 billion whole tokens once you scale by its 18 decimals. That number is REAL: three independent RPCs return identical hex, it decodes cleanly, it is genuinely the value stored on chain. A hype analyst stops here: '44 billion DAI on PulseChain.' But PulseChain is a full state-copy of Ethereum from May 2023, and this number is not the mainnet number: real DAI on Ethereum is on the order of a few billion (~4.6B), and this forked token was ~5 billion right after the fork — both cross-chain / historical reads you should confirm against an Ethereum RPC, not measured by the command above — so the extra ~39 billion was minted on PulseChain since, so 44.36B is a PulseChain-side inflation, not a photocopy of mainnet. Mechanism-real (the supply reads back, every time, 3/3), value-false (there is no mainnet DAI behind it; the only price that exists is what PulseX will actually pay in PLS for one unit), and the causal leap 'therefore 44 billion dollars of DAI exist on PulseChain' is simply false. Same number, three completely different verdicts depending on which of the three questions you ask. The number was never the lie — the framing around it was.

Verification status must be visible in the output, not implied. 'pDAI supply is ~44.36B' is a weaker statement than '44359733562592918446387964499 base units (~44.36B tokens) — 3/3 RPCs agree, raw hex identical, self-verified.' The second is un-dunkable because a hostile expert can re-run it and land on your exact bytes. Make the seam between knowledge-grades explicit in every finding: SELF-VERIFIED (you ran the command and saw the bytes), TAKEN-FROM-OTHERS (a source said it and you are passing it through), INFERRED (you reasoned to it but did not measure it). Collapsing these three is the most common way good analysts get dunked — they present an inference with the same confidence as a measurement, and one counterexample takes down the whole thread. A finding that says 'mechanism self-verified ✓ / value inferred, not measured / causality is an open question' is bulletproof precisely because it claims only what it proved.

The AI's failure mode here is confident narration: ask it 'is this token backed?' and it will happily write a story where the verified supply number 'proves' backing. That is the exact trap. Use the AI as a command-writer and an output-interpreter, never as a verdict-giver. The discipline: make the AI produce the raw command, you run it, then force the AI to answer only the three split questions against the bytes it can see — 'which of mechanism/value/causality does this output actually establish, and which does it not touch?' When the AI wants to assert a cause ('the price moved because…'), demand the on-chain evidence for the 'because.' If there is none, the correct output is an open question, not an invented mechanism. The honest analyst's edge is not being smarter — it is refusing to print the third claim when only the first was measured. Over months, the hype accounts get caught by their own overreach; the account that always shipped 'mechanism ✓ / value ✗ / causality open' is the one people trust when it finally says 'this one is real.'

read total supply
cast call 0x6B175474E89094C44Da98b954EedeAC495271d0F "totalSupply()(uint256)" --rpc-url https://rpc.pulsechain.com

Read the forked 'DAI' total supply. This is the MECHANISM-real number: it returns the same value every time and is genuinely stored on chain. cast prints the full integer — this is the raw uint256 in token base units (×10^18 for an 18-decimal token). It is NOT 44 billion dollars; it is a raw supply integer, inflated post-fork on PulseChain — not a mirror of mainnet and not $44B of value. Verifying it proves the supply reads back — nothing about value.

result44359733562592918446387964499 [4.435e28] — the raw uint256 in base units. A big, real number — a raw supply integer, inflated post-fork on PulseChain — not a mirror of mainnet and not $44B of value; divide by 10^decimals to get whole tokens.
read token decimals
cast call 0x6B175474E89094C44Da98b954EedeAC495271d0F "decimals()(uint8)" --rpc-url https://rpc.pulsechain.com

ALWAYS read decimals() before you scale — never assume 18. The base-unit→whole-token conversion is ÷10^decimals, and this is the only on-chain source for that exponent. Confirm it here (forked DAI returns 18) so the next step's scale is verified, not guessed. A 6-decimal token (USDC-style) would need ÷10^6, and assuming 18 would be wrong by a factor of 10^12.

result18 — forked DAI has 18 decimals, so whole tokens = base units ÷ 10^18. Verified, not assumed.
scale wei to tokens
cast from-wei 44359733562592918446387964499

NEVER divide by 10^18 in your head or let the AI do the arithmetic — the tool computes, the head does not. CAVEAT: from-wei (and to-unit ... ether) is a FIXED wei→ether ÷10^18 conversion — it is decimals-blind, NOT a per-token decimals lookup. It gives the right whole-token figure here ONLY because the previous step confirmed 18 decimals. For a non-18-decimal token you must scale by ÷10^decimals yourself (e.g. cast to-unit <value> mwei for 6 decimals). Arithmetic is a tool call, not a guess.

result44359733562.592918446387964499 — the ~44.36 billion figure in whole tokens. Correct here because decimals == 18; no hand-math.
raw rpc total supply
curl -s -X POST https://rpc.pulsechain.com -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1,"method":"eth_call","params":[{"to":"0x6B175474E89094C44Da98b954EedeAC495271d0F","data":"0x18160ddd"},"latest"]}'

The raw JSON-RPC behind the cast call (0x18160ddd is the totalSupply() selector). Demystifies what cast does for you. Run this against 3 independent RPCs; only when all three return byte-identical hex do you write '3/3 RPCs' next to the number. Single-source = does not exist.

result{"jsonrpc":"2.0","id":1,"result":"0x00000000000000000000000000000000000000008f5582f6f8d989d812632a53"} — decode (cast to-dec) and it is the same 44359733562592918446387964499. Identical hex across RPCs = verified.
call empty address
cast call 0x000000000000000000000000000000000000dEaD "decimals()(uint8)" --rpc-url https://rpc.pulsechain.com

The false-negative trap, made visible. Calling a token function on a genuinely empty address (no contract code). Raw eth_call would silently return 0x, which naively decodes to 0 — a 'zero balance' that is actually 'nothing is there.' cast refuses and tells you the truth. Always confirm code exists (cast code <addr>) before trusting a 0. CAVEAT: precompiles 0x01–0x09 (e.g. ecrecover at 0x..01) also report empty code via cast code yet DO execute — so 'empty getCode' alone is not proof of 'nothing there'; this 0x..dEaD address is a real empty account, the clean case.

resultError: contract 0x000000000000000000000000000000000000dead does not have any code — NOT a clean 0. The honest tool surfaces the absence instead of faking a value.
!
The trap. Treating a verified mechanism as a verified conclusion. You ran tv.sh, you got TRIPLE-MATCH, the contract genuinely moves the tokens — and then you write 'therefore the token is worth $1' or 'therefore the project is solvent.' You verified question 1 (mechanism) and silently shipped questions 2 and 3 (value, causality) as if they came free with it. They never do. Two adjacent traps: (a) presenting an INFERENCE with the confidence of a MEASUREMENT — one counterexample then collapses your entire thread; if you did not measure the value, say 'value not measured,' and if you did not establish the cause, write 'open question,' never an invented 'because.' (b) Letting a unit converter smuggle in an assumption — from-wei / to-unit ether are a fixed ÷10^18 and are blind to the token's actual decimals; always read decimals() first, or your 'verified' number can be off by orders of magnitude on a non-18-decimal token.
AI
The AI flow. The AI's default is to narrate a satisfying story that makes the verified number 'prove' the conclusion — that is the precise behavior you must suppress. Flow: (1) AI writes the read command; you run it. (2) You hand the raw bytes back and force the AI to answer only three split questions — 'Does this output establish the MECHANISM? the VALUE? the CAUSALITY? For each, yes/no and which exact byte shows it.' (3) When the AI reaches for a cause ('price moved because…'), demand the on-chain evidence for the word 'because'; if there is none, the AI's correct output is 'open question,' not a fabricated mechanism. (4) Make the AI read decimals() before it scales any raw amount — never let it assume 18 or trust a from-wei result without that check. (5) Tag every line SELF-VERIFIED / TAKEN-FROM-OTHERS / INFERRED. The AI computes and interprets; it never delivers the verdict unaudited, and it is never permitted to print claim #3 when only claim #1 was measured.
369
On PulseChain. Take any forked Aave/Compound-style market on PulseChain. You can self-verify (3/3 RPCs) that a cToken-style contract returns a huge totalSupply and that some helper contract genuinely calls into it — mechanism real. But check the underlying: query getCash() (or the actual reserve's balanceOf), read that token's decimals() before you scale, and you may find a few wei or a frozen-mirror balance whose only realizable price is what PulseX pays in PLS. So: MECHANISM real (the lending contract executes, supply reads back), VALUE near-zero (the 'collateral' is a May-2023 mirror, not redeemable dollars), CAUSALITY ('therefore the token is backed at $1') false — there is no rail from this forked state to real off-chain USD. Report it exactly that way: 'lending mechanism self-verified ✓ / underlying value measured ≈ mirror-dust / $1 backing = causally unsupported, open.' That three-line verdict is what makes you un-dunkable: a hostile expert re-runs your commands and lands on your bytes, and your honesty about what you did NOT prove is the part nobody can attack.
Takeaway Verify the mechanism, measure the value, and earn the causality separately — then publish all three verdicts with their evidence grade; the analyst who refuses to print the claim they didn't prove is the one the chain ultimately trusts.
Reference

Glossary

JSON-RPC
The request/response protocol Ethereum-style nodes speak: you POST a JSON object naming a method (e.g. eth_call) and params, and the node returns a JSON result. It is how every tool — curl, cast, BlockScout, your wallet — actually talks to the chain.
RPC endpoint
A URL that accepts JSON-RPC requests and answers them from a node's view of the chain (PulseChain's public one is https://rpc.pulsechain.com). Different endpoints can be at different blocks or run different software, which is exactly why you cross-check across several.
eth_call
A read-only JSON-RPC method that executes a contract function against current (or a past) state without sending a transaction, costing no gas and changing nothing. It is the workhorse for reading balances, token metadata, and any view/pure function.
calldata
The raw hex input handed to a contract call: a 4-byte function selector followed by the ABI-encoded arguments. It is what you put in the 'data' field of an eth_call and what cast builds for you so you never hand-assemble it.
Function selector
The first 4 bytes of keccak256 of a function's canonical signature — e.g. transfer(address,uint256) hashes to 0xa9059cbb — this is how a contract knows which function you want to run (4 bytes is a truncated hash, so collisions across different functions are possible but rare); mismatched or unknown selectors are a common cause of reverts.
ABI
The Application Binary Interface: the machine-readable description of a contract's functions and events (names, argument types, return types) that defines how to encode calldata and decode results. Without the right ABI, returned bytes are just opaque hex.
Event log / topic
Structured records a contract emits during a transaction, stored in the receipt rather than in state. The first topic is the keccak256 hash of the event signature (e.g. the Transfer event's topic0 is 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef); indexed arguments become additional topics you can filter on.
Storage slot
A 32-byte cell in a contract's persistent storage, addressed by a slot number, where all on-chain state ultimately lives. Reading a slot directly with eth_getStorageAt is a getter-independent third source of truth when you don't trust (or don't have) a function to call.
EOA vs contract
An Externally Owned Account is controlled by a private key and has no code; a contract account has bytecode that runs when called. You distinguish them with eth_getCode — empty code (0x) means EOA, non-empty means contract.
Proxy / implementation
A proxy is a thin contract holding the state and address that delegatecalls into a separate implementation (logic) contract, so the logic can be swapped while the address and balances stay fixed. Always check who can upgrade it and which implementation is live — the address you see may not be the code that runs.
wei / gwei
The integer subunits of the native coin: 1 coin = 10^18 wei, and 1 gwei = 10^9 wei (commonly used for gas prices). Chains store and return amounts in wei, so a 'balance' is a huge integer you convert, never read literally.
decimals
An ERC-20 metadata value (often 18) saying how many integer places represent one whole token, so a raw balance of 1000000000000000000 with 18 decimals is 1 token. You must read decimals() per token — assuming 18 silently corrupts every amount.
ERC-20
The standard interface for fungible tokens — functions like balanceOf, transfer, approve, totalSupply, decimals, symbol — that lets one tool handle any compliant token. PulseX pairs, stablecoins, and forked tokens all expose this interface.
Transfer event
The ERC-20 log Transfer(address indexed from, address indexed to, uint256 value) emitted on every token movement, with from/to as topics and the amount in the data field. Summing and tracing these logs — not just reading final balances — is how you reconstruct where value actually went.
Nonce
A per-account counter of transactions sent (for EOAs) that also orders and de-duplicates them. A nonce of 0 means an account has never sent a transaction — a fast tell that it is a passive holder or a forked mirror with no native activity. It does NOT mean the address is empty — a nonce-0 account can still hold large token balances.
Checksum address
An address whose hex letters are selectively upper/lower-cased per EIP-55 so the casing itself encodes a typo-detecting checksum. Tools reject a mixed-case address that fails the checksum; always write addresses in full, never truncated.
Revert
When a call or transaction aborts and rolls back all its state changes, often with an error message or custom error. A revert is a real finding only when it reproduces consistently across multiple independent RPCs — a single endpoint can revert for transient or node-specific reasons.
Mainnet fork
A chain created by copying another chain's entire state at a point in time and then evolving independently. PulseChain forked Ethereum's state at its May 2023 launch, so every Ethereum contract and balance was duplicated onto PulseChain at genesis.
Mirror / phantom value
A balance or token whose on-chain figure has no real backing, liquidity, or exit — whether it was passively copied and frozen at the fork, or actively minted/inflated on the fork afterward. Either way the number looks real but you cannot sell it; only a live PulseX pool with real depth tells you what it is worth.
PulseChain
A full fork/copy of Ethereum (mainnet chain id 369, launched May 2023) with its own validators, cheaper fees, and native coin PLS. Because it forked Ethereum's state, forked tokens start as inert mirror snapshots of Ethereum's May-2023 state, but PulseChain activity changes them — balances get moved and supplies get minted or inflated (forked DAI now reads ~44B vs ~4.6B of real DAI), so a forked figure may be frozen OR diverged, and neither is live value.
PLS
PulseChain's native coin, used to pay gas, with amounts denominated in wei exactly like ETH. It is the only asset on the chain with no token contract — you read it via eth_getBalance, not balanceOf.
PulseX
PulseChain's primary decentralized exchange (a Uniswap-style AMM), where token prices and real liquidity are discovered. Checking a token's PulseX pool is how you tell a tradeable asset from a phantom mirror with no market.
BlockScout
The open-source block explorer powering PulseChain's explorer at scan.pulsechain.com, with a JSON API at api.scan.pulsechain.com. Useful for browsing and indexed history, but pure token-holders with no native transactions may not be indexed — confirm balances via RPC, not only the explorer.
Foundry
A fast Rust-based Ethereum development toolkit whose components include cast (CLI), forge, and anvil. For analysts it is the daily driver for reading chains and, crucially, for doing the hex/wei/decimal conversions so you never compute them by hand.
cast
Foundry's command-line tool for interacting with chains and encoding/decoding data: cast call, cast sig, cast keccak, cast to-dec, cast from-wei, and more. The discipline is that cast computes the conversions and hashes — the tool does the arithmetic, never your head and never the model's.
Multi-RPC verification
Confirming a value by querying several independent RPC endpoints and accepting it only when they agree. A single-source number is treated as unverified; agreement across three endpoints is what turns a reading into a stated fact.
Hallucination
When an AI states a plausible-sounding but fabricated value, address, or fact not grounded in real data. The countermeasure is the flow itself: the AI writes the command, you run it, the AI interprets verifiable output — every load-bearing number must come from a tool, never from the model's memory.
Self-verified vs inferred vs taken
Self-verified means you personally pulled the value from the chain and cross-checked it; inferred means you reasoned it from other facts; taken means you accepted someone else's claim. Every analysis should label which is which, and never present an inference or a causal 'why' as if it were measured.
Bridge
A contract system that locks or burns an asset on one chain and credits a representation on another. On a fork, a bridge's other side often does not exist on the real chain, so 'bridged out' funds can be a mirror artifact rather than a genuine cross-chain exit — verify the counterpart contract on the real chain.
Liquidity pool
A smart contract holding paired token reserves that an AMM like PulseX uses to let people swap, with price set by the reserve ratio. Pool depth is the real constraint on how much value can actually be exited — a token with a huge supply but a shallow pool cannot be cashed out near its nominal value.
Slippage
The gap between a swap's expected price and the price you actually get, caused by your trade moving the pool's reserve ratio. Large trades against thin pools suffer severe slippage, which is why a phantom token's theoretical worth collapses the moment you try to sell size.
Gas
The unit measuring computational work a transaction consumes; total fee = gas used × gas price, paid in the native coin (PLS on PulseChain). Read-only eth_call costs no gas, which is why analysis can read the entire chain for free.
Block
An ordered batch of transactions added to the chain, identified by a number and hash, defining a single consistent state snapshot. Pinning your reads to a specific block number keeps multi-step analysis internally consistent even as new blocks arrive.
Receipt
The record produced after a transaction is mined, containing its status (success/revert), gas used, and emitted event logs. Fetched via eth_getTransactionReceipt, it is where you read the logs that reconstruct what a transaction actually did.
keccak256
The Ethereum hash function underlying function selectors, event topics, and storage-slot derivation (note: it is the original Keccak, not the later NIST SHA-3 standard). You let cast keccak compute it rather than guessing — e.g. its hash of transfer(address,uint256) supplies the first 4 bytes — the 0xa9059cbb selector.