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.
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.
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.
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.
# (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.
{"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.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.
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.
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.
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.
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.)
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.
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.
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.
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.
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.
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.)
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.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.'
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).
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.)
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.
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.
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.
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.
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.
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.
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.
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.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.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.
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; doneThe 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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.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.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.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.
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 0xA1077a294dDE1B09bB078844df40758a5D0f9a27The 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.)
chain-id : 369
nonce : 1
PLS : 7800595855885.229720431326044440
WPLS : 77595493298959173485893940 [7.759e25] [OK 3/3 MATCH]
DAI : 33333000000000000000000 [3.333e22] [OK 3/3 MATCH]
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.
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.shThe 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.
( 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.
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.
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.
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.
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.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.~/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.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.'
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.
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.
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.
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.
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.