A successful Ethereum receipt means the transaction's top-level execution succeeded; it does not prove the intended token amount reached its recipient. An ERC-20 call can return false without reverting, and logs reflect the emitting contract's behavior. Verify the token contract, decoded transfer evidence and attributable recipient balance change before marking delivery complete.
What the receipt actually reports
On Ethereum after Byzantium, receipt status answers whether the top-level call succeeded or failed. The success value is conventionally displayed as 0x1; failure is 0x0. This is the scope defined by EIP-658. A wallet's green success label is a presentation of that execution result, not an independent check of the payment instruction.
The official receipt API returns transaction and block identities, status, logs and gas information. Its receipt schema does not include an arbitrary token function's returned bytes. Consequently, reading status does not reveal the boolean returned by a token's transfer function.
An approval transaction can also succeed while moving no tokens because permission was the requested action. The token allowance guide explains that boundary. Start a transfer investigation by decoding what the transaction actually asked the contracts to do, including the token recipient inside the call. The transaction destination might be a token, router or another contract rather than the intended recipient.
Why returning false can leave a successful receipt
The ERC-20 specification requires callers to handle false boolean returns. Consider a token that rejects an operation by returning false without reverting or changing balances. A transaction directly calling that function can finish normally. Another contract calling the token can also finish normally if it ignores the false result. Execution completed; the requested token operation did not.
For a low-level call, keep its completion flag separate from the bytes it returns. Solidity 0.8.30 documents both results of .call. A true call-success flag can accompany bytes that decode to the token's false return. Checking only that flag misses the failed token operation.
This possibility does not mean every token fails this way. OpenZeppelin Contracts 5.x documents that its base ERC20 implementation reverts on failure. Its SafeERC20 library provides safeTransfer, which rejects a false return and supports tokens that return no value by accepting a non-reverting call. These are specific implementation behaviors.
The exact function matters. In the same current documentation, trySafeTransfer returns a boolean instead of reverting on an unsuccessful operation. A caller still needs to decide what that boolean means for the surrounding workflow. A library name alone does not establish that the whole transaction will fail when one payment fails.
If a caller's failure propagates to the top level, the receipt reports failure. EIP-140 specifies that reverted execution loses its state changes and logs. If an outer contract catches a failed subcall and continues, inspect the surviving actions separately. The event logs versus internal transactions comparison explains why a call trace and a receipt log answer different questions.
Five outcomes for the same 100-token instruction
This hypothetical ledger uses five independent scenarios. Reset the sender to 1,000 token units and the recipient to 40 before each case. Each token has six decimals, the requested amount is 100, and no other activity occurs. A separate fee collector starts at zero. Gas is excluded from these token balances.
The custom fee and dishonest cases deliberately have different token behavior from the conventional case. They are invented examples, not descriptions of a named asset or assertions that those behaviors satisfy every ERC-20 requirement.
| Independent case | Receipt status | Surviving log to recipient | Recipient after | Credited units |
|---|---|---|---|---|
| Conventional transfer | Success | Transfer of 100 | 140 | 100 |
| Token returns false; caller does not reject it | Success | None | 40 | 0 |
| Caller rejects false; revert reaches top level | Failure | None | 40 | 0 |
| Custom token deducts a 2% transfer fee | Success | Transfer of 98 | 138 | 98 |
| Dishonest contract emits a log without moving balances | Success | Apparent Transfer of 100 | 40 | 0 |
In the first case, the sender ends with 900 and the recipient with 140. Both false-return cases and the dishonest log-only case leave the sender at 1,000. In the fee case, the sender ends with 900, the recipient with 138 and the collector with 2. The fixture also emits a separate transfer log for the collector's 2 units.
Check the fee case in raw integers. The request is 100 × 1,000,000 = 100,000,000 base units. The fee is 100,000,000 × 2 / 100 = 2,000,000. The recipient receives 98,000,000, or 98 displayed units. The recipient balance becomes 40 + 98 = 138. Conservation holds: the closing 900 + 138 + 2 = 1,040 equals the opening 1,000 + 40.
The dishonest fixture emits the expected-looking event and returns normally but changes no balances. Its reported event does not establish delivery. This is a limitation of trusting arbitrary contract behavior, not an ordinary ERC-20 transfer pattern.
The table separates three questions that a single success badge collapses: did execution complete, what did the contract record, and what changed in the recipient's holding? A successful wrapper call can improve failure handling without proving that a custom token delivered the entire requested amount. For the broader inventory effects of custom mechanics, see fee-on-transfer and rebasing token accounting.
Decode the event against the intended asset
For the standard ERC-20 Transfer event, the sender and recipient are indexed fields and the amount is the non-indexed value. The standard also requires an event for zero-value transfers, so a matching event's existence alone says nothing about a positive amount. Verify the amount and units against the ERC-20 interface.
Before counting a log, match its emitting address to the intended token on the intended chain. Then use that contract's expected event layout. Solidity's event documentation explains that indexed fields become topics, other fields become encoded data, and correct interpretation requires knowing the event type and layout. A familiar event name or topic does not authenticate the emitting asset.
Check the decoded recipient, sender and integer amount rather than an explorer's symbol alone. Verify the token's unit convention; six decimals are an assumption of the example. In a routed operation, the first transfer could credit an intermediate contract. Several later transfers may be needed before the intended recipient receives anything. Preserve individual records until that route is reconciled.
Even a log from the intended address establishes what its code emitted, not trustworthy economics. A malicious implementation can report misleading events or balances. Confirming on-chain state does not establish market value, redeemability or the ability to make a future transfer.
Keep the block identity and event position with each record. The transaction hash versus log index guide covers identifying an individual event and handling changed block placement. Adding every repeated provider response to a payment total would count one event more than once.
Attribute a balance change before calling it delivery
For a known token implementation, calling balanceOf(recipient) on the token contract is a useful cross-check. Read the intended account at recorded block states, preserving raw units. The eth_call API supports a block parameter and returns the call's bytes without creating a chain transaction. Historical state availability still needs checking with the provider.
A block-end comparison combines all changes between its two endpoints. Extend the hypothetical fee case: the recipient begins with 40, receives the target transfer's 98, and receives another unrelated deposit of 20 later in the same block. Its closing balance is 40 + 98 + 20 = 158. The observed increase is 158 − 40 = 118, while the target transfer contributed 98.
Attributing all 118 units to the target would overstate its credit by 20. Identified transfer evidence explains that difference in this fixture. With a rebase, a transfer out, custom accounting or unknown activity, the reconciliation needs the corresponding explanation. A balance delta cannot supply that attribution by itself.
Reading the previous block and the inclusion block also does not provide the precise state immediately before and after one transaction when several transactions touch the account. Where isolation matters, obtain suitable transaction-level execution evidence or reproduce execution against the correct pre-state. Label a block-level observation honestly if those narrower observations are unavailable.
A fresh simulation after inclusion is another call against its selected state. It does not recover the original token return value merely because it uses the same input. Keep simulation results distinct from execution evidence.
Use a completion record with explicit evidence
Store these fields together for each intended payment. The worksheet works for an investigation or a transfer-processing integration without relying on one explorer's screen labels.
| Record | What to retain | Completion check |
|---|---|---|
| Instruction | Chain, token, recipient and raw intended amount | Correct asset and destination |
| Execution | Transaction hash, block hash and receipt status | Top-level outcome established |
| Contract path | Called function and handling of false or missing returns | Failure semantics understood |
| Events | Emitter, decoded parties, amount and event position | Relevant surviving records identified |
| State | Recipient balance observations and their block references | Units reconciled within the observed scope |
| Attribution | Other transfers, fees and balance adjustments | Target credit separated from unrelated changes |
| Acceptance | Required credit and confirmation policy | Recorded evidence satisfies the actual instruction |
Define the acceptance rule before sending. If the instruction requires the recipient to obtain 100 units, the hypothetical 98-unit credit is a shortfall even though the receipt succeeds. If a fee-aware instruction explicitly permits 98, record that different requirement. A status label cannot choose the requirement afterward.
For an unresolved successful receipt, investigate before issuing another transfer. The original operation may already have delivered some or all of the amount. Repeating it can create a second payment while leaving the first unexplained. For a failed receipt, review the failure and current state before a new attempt; earlier transactions remain separate records.
Close a payment with a concrete statement: the identified token credited this recipient with this attributable amount in this transaction, at this block, under the recorded acceptance rule. If the evidence supports only execution success, keep delivery unresolved until the missing evidence is obtained.
