← Back to Learn

FluidRWA Learn

Transaction Simulation: Inspect an ERC-20 Transfer Before Signing

Illustrative developer comparing token-call results next to a disconnected signing device, not a wallet screenshot
Illustration, not a wallet screenshot or a live test result. No signing device is needed.

Rehearse the call before anyone signs

Your team intends to transfer one unit of a practice token. The draft call targets a contract, takes a recipient and an integer amount, and is supposed to send no native ETH. Before any signature, can you identify those fields and explain the difference between a successful return, a refusal by the token and an unavailable RPC provider?

The sample runs a small offline exercise first. It sends nothing over the network. An optional read-only mode asks an Ethereum Sepolia node to execute two calls against one observed block: an intended transfer and an attempt to transfer more than the sender balance. Ethereum's eth_call executes a message call without creating a transaction. It is not the same as signing, submitting or estimating final asset changes.

FluidRWA reviewed the primary specifications on November 4, 2026 and validated the offline fixture and mocked HTTP behavior. Fixture responses are deliberately authored examples, not EVM execution or captured blockchain output. The optional Sepolia path was not exercised against a live endpoint. No wallet, signing credential, payment or real funds were used.

Start offline; keep keys out of the lab

Use Node.js 22 or later and download the single JavaScript module to a directory you control. Inspect the file first. It has no dependencies to install and the default command only uses fictional addresses and in-memory responses. Do not send funds to any fixture address or reuse those addresses in the optional step.

For optional online mode, use an authorized HTTPS Sepolia RPC endpoint and a known test-only ERC-20 contract with a bool return from transfer. Use public addresses from a dedicated test setup, including a sender with a known positive practice-token balance and a different recipient. No private key, seed phrase, wallet connection, faucet visit or fresh transfer is needed to run the read-only calls. Stay offline if those prerequisites are not already available.

Some providers meter RPC calls. Confirm your account's terms before using it, or skip online mode entirely. A provider sees your requested addresses and calldata. Never copy a proprietary draft transaction or mainnet wallet address into the exercise. If the endpoint URL contains an API key, handle the whole URL as a secret.

Step 1: Run the offline fixture

The default command and --offline are equivalent. Expect OFFLINE FIXTURE in the mode field, 10 in balanceBaseUnits and 1 in amountBaseUnits. The intended call returns true in the fixture; the 11-base-unit case reports an execution revert. Both the block and its hash are fictional. The report labels the observed shape as expected, not safe.

Read the fixture code to see the difference between a recorded call request and a fabricated result. The function does not implement the EVM. It supplies deterministic responses so you can learn how the code handles results before trusting a particular RPC service.

node --check transaction-simulation-lab.mjs
node transaction-simulation-lab.mjs --offline

Step 2: Decode the draft action

The transaction call object's from is the simulated sender. Its to is the token contract; the recipient is inside the input, not in the outer to field. Its value is zero native ETH. The first four bytes of the input are the selector for transfer(address,uint256), followed by the recipient and amount in two padded 32-byte words. The example encodes only this fixed ABI signature. It is not a general calldata decoder.

The amount is in integer base units. With a hypothetical 6-decimal token, 1,000,000 base units display as 1 token, while this fixture's 1 base unit does not. Decimals are optional in ERC-20. Independently verify the test contract's unit convention before entering any amount. The script uses BigInt and prints base units as decimal strings to avoid rounding.

Compare the selector with the intended action. An approve call would set spending authority for a spender, not perform a direct transfer; transferFrom is a different function with its own sender and allowance context. This lab constructs transfer only. A real review also needs to identify nested calls, proxies, permissions and any unrelated approval. A selector alone is not proof of a contract's identity.

FieldExpected in the sampleStop if
NetworkEthereum Sepolia; 11155111 (0xaa36a7)Endpoint reports a different chain
Outer toReviewed test-token contractAddress belongs to another contract or has no code
Outer value0x0 native ETHThe proposed action includes unintended native value
Inputtransfer, intended recipient, exact base unitsSelector, recipient or amount differs from the approved request
FromDedicated public test addressSomeone asks for the key or an actual signature

Step 3: Inspect the block and the two outcomes

The lab checks the chain ID, takes a block number and records its hash. It checks for bytecode at the token address and queries balanceOf for the sender at that block. It then uses the same block for both transfer calls and the repeated balance read. The hash and chain are checked again at the end; a changed snapshot invalidates the report. A remote service can still misreport data, and these checks are not a proof of canonical chain state.

A standard true return is one ABI word with value 1; false is a word with value 0. ERC-20 requires callers to handle false. An empty or malformed return is not a pass in this deliberately strict lab, including for a token that does not follow the expected return convention. A recognized execution-revert response is distinct from a transport failure or provider rejection.

For the negative case the sample uses the sender balance plus one base unit. A known ordinary token should refuse it; if it returns true, stop and investigate. A false return or revert is not a proof of the specific cause. A pause, account restriction or other condition could have rejected the call instead. Compare with the positive case and obtain a trace if the cause matters.

OutputInterpretationNext action
returned-trueThis ABI call returned true in the selected stateConfirm details; do not treat it as authorization
returned-falseThe token returned false without a recognized revertHold and inspect the reason
execution-revertedRPC used execution-reverted error code 3Confirm whether the failure matches the exercise
Error / no reportInput, response, snapshot or provider failed a checkDo not classify it as a token outcome

Step 4: Optionally repeat on Sepolia, read-only

Only run this after independently checking the test deployment and the three distinct addresses. Convert already-verified addresses to lowercase for this example; lowercase is an input format, not a checksum or a verification step. Set a positive integer amount no greater than the reviewed test sender balance. The example does not provide a live contract address or claim that a particular token or endpoint will work.

The commands below use zsh. Replace the three address placeholders with your own verified test-only addresses and set the amount for that token. Enter the endpoint with hidden input and unset it when done. The allowlist contains only chain, block, bytecode and call methods. The code has no signing or broadcasting path, and refuses redirects. Never add sending methods to this exercise.

read -s 'SEPOLIA_RPC_URL?Sepolia HTTPS RPC URL: '
printf '\n'
export SEPOLIA_RPC_URL
export LAB_TOKEN='REPLACE_WITH_TEST_TOKEN'
export LAB_FROM='REPLACE_WITH_TEST_SENDER'
export LAB_RECIPIENT='REPLACE_WITH_TEST_RECIPIENT'
export LAB_AMOUNT_BASE_UNITS='1'
node transaction-simulation-lab.mjs --live
unset SEPOLIA_RPC_URL LAB_TOKEN LAB_FROM LAB_RECIPIENT LAB_AMOUNT_BASE_UNITS

Step 5: Explain what the result cannot show

The repeated balance read is against the same block. Its equality is a consistency check, not an estimate of the sender's post-transfer balance. The lab does not return simulated event logs, nested calls, fees or token balance deltas. Do not subtract the amount from the observed balance and label it a measured asset change. Fee-on-transfer, proxy logic and nonstandard contracts can make that reasoning wrong.

If a production decision depends on those effects, use a separately authorized trace or state-diff tool on an isolated test environment and review which accounts and assets changed. Confirm spender and allowance changes where relevant. Compare the reviewed payload with the exact payload a signer would receive. If any address, input, value or chain changes, the earlier report does not apply.

Even a richer simulation cannot guarantee future execution. Other activity, upgrades, block context and policy can change after the selected snapshot. Treat unknown effects and inconsistent outputs as a reason to pause. The exercise ends at review; it does not ask you to sign or broadcast anything.

Troubleshooting: fail closed

Wrong chain or no bytecode: stop and check the endpoint and the source of the test contract address. Do not bypass the guard, paste a mainnet token address or fund the sender to make the report pass. If the node rejects the input field or the block parameter, record the compatibility gap and stay with the offline fixture until you have a supported test RPC.

Empty return, malformed word or noncanonical boolean: check the intended ABI and address. This implementation does not normalize nonstandard return values to true. If the provider uses a different revert error format, the script treats it as a failed request rather than guessing that the token refused the transfer.

HTTP failure, timeout or snapshot mismatch: do not treat it as a negative simulation. Confirm the provider's response and start a fresh report against a new consistent snapshot. The script omits the endpoint and remote error text from the report; avoid pasting secrets into logs or screenshots when diagnosing it.

Check your work

A reviewer should be able to reproduce the offline result and explain why the token contract and recipient are different addresses. Record the chain, block/hash, from, to, exact input, value and the two outcome classes in your own review note. Mark the offline values as fixtures. For an actual test review, distinguish a token failure, an RPC failure and a mismatch between intent and payload.

Do not confuse this lab with an approval revocation, a Safe treasury authorization exercise or a receipt investigation. Those tasks happen at different points in the transaction lifecycle. Stop at the review boundary even if both outcomes match expectations.

FAQs

Does eth_call broadcast a token transfer?

No. It executes a message call against the selected state and returns a response without creating a blockchain transaction. No signing credential is used in this lab.

Does true mean a token transfer is safe?

No. It is an ABI return under one set of inputs and state. It does not establish authenticity, permissions, asset deltas or future execution.

Why test an amount above the balance?

It gives you a deliberate negative case. Compare it with the intended case and distinguish a false return or recognized revert from a transport or provider error.

Do I need an RPC key or testnet funds?

Not for offline mode. The optional read-only mode uses your authorized Sepolia endpoint and an existing funded practice-token address; it requires no signing or new funding and was not tested live by FluidRWA.

References

Next step

Turn the concept into a vendor shortlist

Use FluidRWA to compare relevant provider categories and move from research to procurement.

Explore smart contract development providers