guides19 min read

Why a Token Balance Is Not an Accounting Record

Why a token balance is not an accounting record: balance queries, Transfer logs, allowances, ticker collisions, block-pinned cut-off and evidence.

M

Maksym Buhai

Accounting Engineer

September 10, 2026 · 19 min read

Cover reading "One billion LINK. The contract emitted nothing.", beside a tall solid charcoal panel and a column of four empty rounded rows drawn in dashed violet outline on a faint dotted grid

The phrase "on-chain ledger" is convenient and often misleading. A token balance is not an accounting record: it is one contract's answer to one question at one block. It carries no cost, no counterparty, no purpose and no lot identity, and those are what an accounting entry is made of.

A blockchain is not a general ledger

Ethereum records transactions, receipts, logs, contract code, and state. An ERC-20 contract can maintain token balances in that state. None of those objects is an accounting ledger in the financial-reporting sense.

A financial ledger records economic events under an accounting policy. The blockchain records technical state transitions under protocol and contract rules. Those two systems answer different questions. The same separation for native ETH is set out in Why the Ethereum Blockchain Is Not an Accounting Ledger.

The chain records that a token balance changed. It does not record whether the change was a purchase, revenue, an owner contribution, a loan receipt, an internal wallet transfer, collateral movement, a theft, a gift, a settlement of a payable, a transfer to a custodian, or a disposal producing gain or loss, and those are different journal entries.

The accounting system must supply the economic meaning.

balanceOf is a point-in-time state query

An ERC-20 contract normally exposes balanceOf(address).

That function is valuable because it answers a direct quantity question at a chosen block. If a reporting entity controls address A and the contract reports 10,000 units at the reporting block, the result is strong evidence of the quantity recorded by that contract for that address.

balanceOf supplies a quantity. It supplies no opening balance, no acquisitions or disposals, no lot basis or cost, no realised result, no fair value, no counterparty, no purpose, and no custody rights.

A current balance is therefore a control total, not a complete transaction register. That gap is the whole reason a token balance is not an accounting record.

The storage model explains why there is no built-in holder list

A conventional ERC-20 token commonly stores balances in a Solidity mapping from address to integer.

A mapping is not an ordinary table with an enumerable list of rows. The Solidity documentation is explicit that "the key data is not stored in a mapping, only its keccak256 hash is used to look up the value", and that "because of this, mappings do not have a length or a concept of a key or value being set". The values sit at computed addresses instead: the value for key k in the mapping declared at storage position p is located at "keccak256(h(k) . p) where . is concatenation", and the position p itself stays empty.

The keys are never written down anywhere, so there is nothing to enumerate. That is why no Ethereum function can return every holder of a token, and why every holder list in existence is a third party's reconstruction from history.

It also gives an auditor a second, independent read of a balance. Computing the storage location directly and reading it must return what balanceOf returns at the same block. Done against LINK on 9 September 2026 for the address 0xF977814e90dA44bFA03b6295A0616a897441aceC, balanceOf returned 10,000,000,000,000,000,000,000,000 raw units and the derived storage slot 0x563888e0f7cf9f7af91fd78748d5c42f1d7df4f94ff0717b6af6deb33c5baf8e returned the identical number. Where the two disagree, the contract is doing something the reconciliation has not modelled.

A third read is stronger still and is almost never collected. A node can return a cryptographic proof of an account and a storage slot against the block's own state summary, and that proof can be verified locally against the block header by anybody, years later, with no trust in the node that supplied it. For a material position it is the best evidence available, it costs nothing, and wallets and explorers discard it because they return only the number.

The methods an evidence package should name are the ones the specification actually defines. The Execution APIs specification groups the state methods as eth_getBalance, eth_getStorageAt, eth_getStorageValues, eth_getTransactionCount, eth_getCode, and eth_getProof, to which a reconciliation adds eth_call for a contract function, eth_getTransactionReceipt, and eth_getLogs. Every one of them answers only the question it is given.

Block explorers and analytics platforms build holder lists by indexing historical events and state. Those indexes are extremely useful and they are derived datasets produced by third parties. An accountant should keep the two apart: contract state read at a stated block is primary on-chain evidence, and a third-party reconstruction of historical activity is an indexer's interpretation of it.

Event logs are a narration of execution, not the state itself

EIP-20 requires ordinary transfers to fire a Transfer event. The standard's words are that transfer transfers the amount "and MUST fire the Transfer event", and that the event itself "MUST trigger when tokens are transferred, including zero value transfers".

This makes logs indispensable for reconstructing activity. Yet a log is not the token balance itself. The token's actual state is whatever the contract stores and returns. Logs are records emitted during execution, and they are provisional: a log carries a flag saying whether it was removed by a chain reorganisation, which a subledger built from logs has to handle and which an exported spreadsheet cannot, because the export is a snapshot.

For many well-behaved tokens, replaying Transfer events reproduces the expected balance history. The standard does not guarantee it. On creation, EIP-20 says only that "a token contract which creates new tokens SHOULD trigger a Transfer event with the _from address set to 0x0 when tokens are created". SHOULD, not MUST.

LINK is the worked counterexample, and it is the cluster's own reference token. LINK was deployed at block 4,281,611 on 16 September 2017 in transaction 0x5488510df045770efbff57f25d0c6d2c1404d58c1199b21eb8dc5072b22d91d7. A log query against the LINK contract for that block returns no results at all, while balanceOf for the deployer address 0xf55037738604fddfc4043d12f25124e94d7d1780 at that block returns 1,000,000,000,000,000,000,000,000,000 raw units and totalSupply() returns the same figure. One billion LINK came into existence and the contract emitted nothing.

The consequence is blunt. A subledger rebuilt purely from LINK Transfer logs computes the entire starting supply as zero, so a history that is internally complete can still fail to tie to totalSupply. A reconciliation system should never silently assume that:

sum of Transfer logs = authoritative token state

Instead, it should prove that relationship for the specific contract against block-pinned state, and treat the assumption as something to be tested rather than inherited.

A conforming Transfer event can move zero economic value

EIP-20 is explicit that "transfers of 0 values MUST be treated as normal transfers and fire the Transfer event". The presence of a Transfer event therefore does not mean value moved.

This matters because spam and address-poisoning activity can create large quantities of technically valid token-history entries that are economically irrelevant, and the cost of creating them is close to nothing. Two transactions read on 9 September 2026 show the economics:

  • transaction 0xc9080475b6f0d100352c508f35a29a89cb23f8f4a294fe33c42f3b498e598ea1, block 25,942,165, wrote 102 zero-value Transfer events into 102 different holders' histories across sixteen real, well-known token contracts including LINK, DAI, USDC, and USDT, for a total fee of 0.0001151874586152 ETH, roughly 0.0000011 ETH per entry
  • transaction 0x3b4d346cd40a3c3d737f69b08dbb788c632c051f94a18cc68d3295a9ea11f890, block 25,942,239, used seven counterfeit lookalike contracts and non-zero amounts to write 158 Transfer events for 0.0000639163797 ETH, about 0.0000004045 ETH per address

There is no economic barrier to poisoning a history. A subledger that treats every Transfer log as an accounting transaction will overstate activity and may create false counterparties, false receipts, or false disposals.

The screening process must inspect at least the contract identity, the raw amount, the scaled amount, the sender and recipient, whether the event reconciles to a state change, whether the token is recognised by the entity, and the economic purpose. The poisoning pattern has a specific tell: any counterparty address sharing its first four and last four hexadecimal characters with a genuine counterparty is a red flag, because that abbreviation is what every wallet and explorer displays.

A transaction can succeed while no token moves

Another dangerous assumption is that a successful transaction receipt proves a token transfer occurred. That is not safe, and it is not a quirk of old contracts.

EIP-20 does not require a failed transfer to revert. It says only that transfer "SHOULD throw" where the balance is insufficient, and it separately instructs callers, in capitals, that they "MUST handle false from returns (bool success)" and "MUST NOT assume that false is never returned". A token that returns false instead of reverting is conforming, not defective, and handling the false return is an obligation for every token rather than a legacy exception.

Both behaviours exist on live contracts. Simulating a transfer on 9 September 2026 from an address holding none of the token, at block 25,942,600, LINK, USDT, ENJ, USDC, MKR, WETH, OMG, REP, and GNO all reverted, while BAT, ZRX, and SNT returned false without failing. For those three, a failed transfer produces a transaction that is included in a block, a receipt whose status field says success, gas consumed and paid, no Transfer event, and no balance change. A process that keys off "the transaction succeeded" records a transfer that did not happen.

A separate deviation runs the other way. EIP-20 declares transfer to return a boolean and some very large tokens return nothing at all. Simulated from addresses genuinely holding each token at the same block, USDT, BNB, and OMG each returned zero bytes where the signature specifies a boolean, while LINK, ZIL, and USDC returned 32 bytes. That is a departure from the standard rather than a variant it permits, and the ecosystem's workaround is a library, OpenZeppelin's SafeERC20, whose own documentation records the compromise that "non-reverting calls are assumed to be successful". That is an assumption rather than a proof.

For accounting, the general rule is simple:

Transaction inclusion proves that code executed. It does not, by itself, prove that the expected accounting event occurred.

The reliable test is the event and the state, not the receipt status. For a plain token, a transfer occurred if and only if the token contract emitted a Transfer event in that transaction, and even then the two qualifications above apply.

The transaction destination may not identify the economic recipient

A holder can interact with a router, exchange, bridge, multisig, smart wallet, or other contract that then calls the token contract internally, a router being a contract that receives an instruction to swap one token for another and carries it out by calling the token contracts and liquidity pools on the sender's behalf.

The outer transaction can be addressed to one contract while the token moves between entirely different addresses. Transaction 0xf2cee20273a022e4a9743fdf7c3e30e2e5c62a44c1afb19452e6e84b1148c60a, block 25,942,298 on 9 September 2026, is the ordinary case: the sender is the address whose LINK moved, the destination is 0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45, which is not the LINK contract, and the LINK contract's address appears nowhere in the transaction's own fields. The token, the amount, and the counterparty exist only inside the encoded instruction and the logs.

The mirror image is equally real. Transaction 0xbfb776fff7763a0136d51dbe238d76c79e2451b2d31da56d68dc88635dc69e0f carries the four-byte instruction code for transfer(address,uint256) and its destination is not a token contract at all. An instruction code is the first four bytes of a hash. It identifies a function signature, not a standard, and any contract may expose the same one.

The ETH value field is no better a guide, because it has nothing to do with an ERC-20 token amount. A parser that looks only at the top-level sender, the top-level destination, and the top-level ETH value will miss or misinterpret many token movements.

A third party can move the holder's tokens

transferFrom creates a particularly important completeness problem, and it is the standard's design rather than a defect in it. EIP-20 conditions transferFrom only on the _from account having "deliberately authorized the sender of the message via some mechanism".

Suppose the entity approved a decentralised exchange (DEX) router months ago. Today the router or another approved spender uses that authority to transfer tokens from the entity's address. The spender originates the transaction. The spender pays the gas. The entity's address may have no outgoing transaction at all.

It happens in ordinary traffic. In transaction 0xcf1d9c1b0c75159c9584b3795999ed21280b426dfa5ce90ebda77f05a02f2c08, block 25,942,299 on 9 September 2026, 206.05084929278954 LINK left the address 0xe0523b85fad2015c0b6ae78e8c71ece5edbe5a67 in a transaction that address did not send, did not sign, and did not pay for.

The completeness question has to be asked from the token side:

wrong:  which transactions did the entity's addresses send?
right:  which token events changed the entity's addresses' balances?

The first question misses the outflow entirely. The second finds it, and the events it returns are then reconciled to authoritative balance state.

This is also why a transaction explorer's two tabs answer different questions. The ordinary transaction list is centred on top-level transactions involving the address. A token-transfer index is derived from event logs and includes transfers caused by transactions originated by somebody else. The ordinary transaction list is not a complete population of token movements, and completeness has to be tested from the token side as well as the address-origin side.

Allowances are state, and logs do not always tell the full story

EIP-20 requires the Approval event to "trigger on any successful call to approve(address _spender, uint256 _value)", so every grant is announced. The outstanding allowance can then change when transferFrom consumes it, and implementations differ in how that consumption is reflected.

Some contracts reduce the stored allowance on each use. The OpenZeppelin ERC20 implementation, which a large share of deployed tokens inherits, does not: its _spendAllowance function guards the decrement with if (currentAllowance < type(uint256).max) and documents the behaviour in its own comment as "does not update the allowance value in case of infinite allowance". An unlimited approval over a token built on it therefore never decreases, and there is no on-chain trace in the allowance of how much has been drawn.

LINK's contract does the opposite and subtracts on every draw, so an unlimited approval over LINK sits fractionally below the maximum once it has been used, and the amount drawn can be read from it. Which of the two a token does is a property of its code, and it has to be read rather than assumed.

Two consequences follow. A historical list of Approval events is not a complete statement of outstanding authority. And no protocol call enumerates an address's outstanding approvals, so the only method is a scan of approval logs over the life of each token contract followed by a live allowance read for every spender found, pinned to the reporting block. Order any log reconstruction by block number, then transaction index, then log index, and treat a live allowance against a zero balance as an open exposure rather than a closed one.

Off-chain permit signatures can be invisible until submitted

What the holder of an ERC-20 token actually owns sets out what a permit signature under EIP-2612 is, and the deadline and nonce that bound it. The point that belongs here is narrower and it is about completeness rather than about rights.

A holder can sign an off-chain authorisation. Until somebody submits it on chain, the public blockchain contains no record of the signed instruction. A blockchain reconciliation therefore cannot prove completeness over documents and commitments that never reached the chain, and signed permits are only one item on that list. Others include custody agreements, loan contracts, side letters, OTC trade confirmations, internal treasury approvals, pending transactions not yet included in a block, and multisig proposals that have not been executed.

One economic event can produce many technical records

A single swap can create one outer transaction, several internal contract calls, several ERC-20 Transfer events, liquidity-pool events, fee transfers, ETH or wrapped-ETH movements, approval changes, and one gas payment.

The accountant normally wants one coherent economic story. Taking the disposal in this series' own worked example, where 400 LINK is swapped for ETH through a router drawing on a standing allowance, the story is:

Dispose of 400 LINK
Receive 1.84771200 ETH
Pay 0.000225206954 ETH gas
Recognise gain or loss on the LINK
Establish basis in the ETH received
Derecognise the ETH used for gas

The chain may express that story in ten or twenty disconnected records.

The subledger's job is to group technical records into accounting events without losing evidence, and the grouping should be reversible. That swap might carry one accounting event ID linked to:

outer transaction hash
LINK transfer log
ETH or wrapped-ETH transfer log
router call
pool event
gas receipt
price observations

The accountant sees the coherent swap. The auditor or engineer can drill back to every technical record. That is safer than either extreme: dumping raw chain data into the general ledger, or collapsing the event so aggressively that the underlying evidence disappears.

One technical record can also represent no economic event

The reverse is also true.

An internal transfer from the entity's operating wallet to the entity's cold wallet may create a genuine on-chain token transfer while producing no disposal of the token for financial reporting, a cold wallet being an address whose private key is held offline and used rarely, as distinct from an operating wallet used day to day.

The entity still controls the same quantity before and after. The gas fee is different, because ETH has left the entity, so the fee still requires accounting even when the token movement itself is only an internal reclassification.

This is one of the most important distinctions in crypto accounting:

token movement          != token disposal
no token disposal       != no accounting event

Gas may still create an ETH event, and under United States rules the same own-address move is also a lot selection decision, because the ordering rule in Treas. Reg. 1.1012-1(j)(1) applies to a transfer as well as to a sale. What that does to basis is set out in ERC-20 token tax: lots, gas and character.

There is a related trap on the destination. A token sent to a contract that was not written to handle it is simply stuck, because the recipient's code never runs and cannot refuse the transfer. The Ethereum Foundation's own documentation states that "when ERC-20 tokens are sent to a smart contract that is not designed to handle ERC-20 tokens, those tokens can be permanently lost". That is a derecognition question rather than a reconciliation difference, and it should be identified as one.

The ticker is not the asset

Reconciliation should never group positions solely by symbol.

An attacker can deploy a contract with the same name, symbol, and decimals as a well-known token. A bridge can create another token with the same symbol on a different chain. A protocol can migrate from one contract to another while keeping the symbol.

The point is not theoretical, and the test that exposes it takes two calls. Read at block 25,942,317 on 9 September 2026, two contracts both return the name Tether USD, the symbol USDT, and 6 decimals, and both report an identical total supply of 88,306,028,997,331,899 raw units, to the last digit:

0xdac17f958d2ee523a2206206994597c13d831ec70x7518d5376c3e7a24c0614fb5ebc0c86fef2316a2
name()Tether USDTether USD
symbol()USDTUSDT
decimals()66
totalSupply()88,306,028,997,331,89988,306,028,997,331,899
Deployed code11,075 bytes1,160 bytes
balanceOf for three addresses holding real USDT0, 9,184,997,801,482,302 and 1,342,318,916,005,1840, 0 and 0

Every metadata field a spreadsheet or a naive import would use to identify the asset agrees exactly. The balances do not agree at all. In the same five-block window, five symbols were claimed by more than one contract and one of them was claimed by three.

The accounting key should therefore be at least:

chain ID + contract address

Then, and only then, should the system attach display metadata such as symbol and name.

Block explorers are evidence tools, not authoritative accounting systems

Block explorers are extremely useful. They decode contract calls, index token transfers, display source code, show addresses, and aggregate balances. The explorer is nonetheless a third-party interface over blockchain data.

A robust close process should preserve enough primary evidence to reproduce or independently confirm key assertions, especially for material balances. Screenshots alone are weak evidence because they can omit the chain, the contract, the block, the raw units, or the pricing methodology.

Verified source code has a narrow evidential meaning

A block explorer's "verified" badge is useful and easy to overstate. Source verification establishes that supplied source code and compiler settings reproduce the deployed bytecode. It does not establish that the code has been audited, that the contract has no vulnerabilities, that the economic claims in project documentation are true, that administrators will not misuse privileged functions, that a proxy's future implementation will behave the same way, or that the token has any particular accounting classification. Use verified source to understand mechanics, not as a substitute for accounting or legal analysis.

Node and indexer limitations belong in the control design

Historical state queries depend on infrastructure. The state methods accept a block parameter, and nothing in the specification obliges any node to answer for an old block. The go-ethereum documentation states that an ordinary node keeps "only the most recent 128 block states in memory", which is about twenty-five minutes of chain. Beyond that, only an archive node, or a service selling access to one, can answer what a balance was.

Commercial endpoints then set their own terms, and they differ from each other on the same question. Tested on 9 September 2026, https://eth.drpc.org answered both contract calls and log queries, https://ethereum-rpc.publicnode.com answered calls and refused log queries as archive requests, https://1rpc.io/eth answered calls and reported the log method as unavailable, and https://eth-mainnet.public.blastapi.io answered calls and capped log queries at a ten-block range. None of those answers is wrong. The completeness of a log-derived token subledger depends on which endpoint answered, and the archive depth, rate limit, and retention terms of the endpoint the entity relies on belong in the control description.

There is a completeness control that needs no trust in the provider, and it cannot produce a false negative. Every Ethereum block header carries a bloom filter over the addresses and topics of every log in that block, and the header is covered by the block hash. For every block in the period, test the header's filter for the token's contract address. Any block where the filter says the address is present and the data source returned nothing is a hole in the evidence. Tested on the last block of 2025, the header's own filter says the LINK address is present, one endpoint returned a log and another returned none.

Older history is not merely capped. It is being deleted. The Ethereum Foundation announced partial history expiry on 8 July 2025, under which clients may drop block data from before September 2022. LINK was deployed in September 2017, so five years of its history is data clients are now permitted to delete, and one public endpoint tested on 9 September 2026 had already done so, returning nothing at all for the transaction that created LINK. That is the same answer a node gives for a transaction that never happened.

For material closes, preserve the evidence when the close is performed instead of assuming it can be reproduced from a public endpoint years later. A defensible evidence package contains the raw request and response for each call, the transaction receipt, the relevant event logs, the block header data, a source-code snapshot or hash where material, custodian statements, and valuation data extracts. The objective is not to archive the whole blockchain. It is to retain enough evidence to reperform the material accounting assertions.

Cut-off requires a block, not merely a date

Ethereum does not stop at midnight for a financial close. A reporting process needs a reproducible mapping from the entity's cut-off time to a specific block, and a stated rule for choosing it, because there is no block at midnight. State whether the policy takes the last block on or before the reporting instant or the first block after it.

For a self-custodied token, the workpaper should record the reporting time and time zone, the selected block number, the block timestamp, the block hash, the token contract, the wallet address, and the raw balance returned at that block.

Without the block reference, a query rerun later can return a different balance because it reads a later state.

A block at the head of the chain is also not yet final. Ethereum switched on its proof-of-stake mechanism in 2022, and under it time is divided into slots of twelve seconds and epochs of thirty-two slots, with the first block of each epoch acting as a checkpoint. Only once a block is finalised does the documentation say it "cannot be reverted or changed without a majority slashing of stakers, making it economically inviable". Before that, the fork-choice rule can still replace the block a query was pinned to. A cut-off read taken at the reporting instant should therefore be re-confirmed against the same block number and block hash once that block is finalised, and a difference in the hash means the read has to be taken again.

Reconciliation should use both flow and state

A token balance is not an accounting record on its own, so a strong token reconciliation uses two independent views.

Flow view. Reconstruct movements during the period:

opening quantity
+ acquisitions and receipts
- disposals and transfers out
+/- other contract-specific movements
= expected closing quantity

State view. Read the authoritative contract state at the reporting block:

balanceOf(entity address, reporting block)

Then reconcile the two. Prove the closing balance from the opening balance plus every movement rather than adding up the movements alone, because the roll-forward and the pinned read are different assertions and both are needed.

Differences are a diagnostic, and the order of investigation matters

When the close does not agree, the useful question is which figure is wrong, because each symptom points at a different cause.

SymptomInvestigate, in this order
Token quantity wrong at an addressZero-value transfer events, transfers received under a transferFrom the entity did not send, unsolicited receipts, and whether the log population was truncated by an endpoint cap
Token quantity right but the asset is wrongThe contract address, the chain ID, and the decimals value. Re-run the identity tests, because a ticker match is not an identity match
Quantity and identity right but the lot ledger wrongWhich lot each disposal took, whether an own-address move was treated as a disposal, and whether the lot selection was documented no later than the transaction
Token ledger right but the ETH ledger wrongFailed transactions, the fee leg of every token movement, and gas on own-address transfers and allowance grants, which move no token at all
Lot ledger right but the economic position wrongStanding allowances, custody arrangements, contractual restrictions, and the upgradeability status at the reporting date
Quantity right but carrying amount wrongThe scope conclusion first, because an in-scope token is remeasured to fair value under ASC 350-60-35-1, introduced by ASU 2023-08, while an out-of-scope one is impaired under ASC 350-30 and ASC 350-30-35-20 states that "subsequent reversal of a previously recognized impairment loss is prohibited". Then the price convention, the principal-market conclusion, the impairment unit of account, and whether a prior impairment was reversed as IAS 36 paragraph 114 requires or wrongly reversed under US GAAP
Carrying amount right but the tax result wrongThe taxpayer classification, the per-wallet scoping of the tax lot ledger, and the jurisdiction's own ordering rule

That ordering turns the reconciliation difference into a diagnostic tool. A mismatch is not something to force with a manual journal entry until the technical cause is understood.

A minimum period-end control framework

For each material token:

  1. Freeze the reporting block, record the rule that chose it, and record the block number, hash, and timestamp.
  2. Confirm chain ID and contract address, and re-run the identity tests at that block.
  3. Obtain raw balances for every controlled address by calling balanceOf pinned to the block, from more than one endpoint, and record which endpoints answered.
  4. Confirm decimals and convert raw units, recording the source of the decimals figure.
  5. Re-test upgradeability and administrative powers at the reporting date rather than relying on the onboarding conclusion.
  6. Reconcile opening state plus period flows to closing state, and test the log population against the block headers' bloom filters.
  7. Review transfers initiated by third parties through transferFrom or similar mechanisms.
  8. Query material outstanding allowances live at the reporting block.
  9. Identify unsolicited or unknown-token receipts, and screen them for the address-poisoning pattern. Where no framework answers what to recognise, IAS 8 paragraphs 10 to 12 require the entity to develop a policy and disclose the judgement.
  10. Capture failed transactions, which consumed ETH and received nothing.
  11. Reconcile custodian balances separately from self-custodied balances.
  12. Reconcile the ETH ledger as well as the token ledger, recomputing each fee from the receipt as gas used multiplied by effective gas price rather than trusting a summary figure.
  13. Tie the quantity ledger to the valuation workpaper, and tie token and ETH entries to journal entries.
  14. Freeze the evidence package, including every block number, block hash, endpoint, and raw response.

Why a token balance is not an accounting record

A blockchain can provide unusually precise technical evidence, but precision is not the same as accounting meaning.

A token balance proves what one contract reports at one point in time. Event logs describe selected execution events, and the standard does not guarantee that they describe all of them. Transaction history shows transactions, and not the ones somebody else sent that moved the entity's tokens. None of them independently proves the complete economic story.

The accountant needs a subledger that translates technical state into economic events, reconciles flow to state, and joins on-chain evidence with contracts, custody records, valuation evidence, and accounting policy. The wider close workflow that subledger feeds is covered in our crypto accounting guide.

That is why a token balance is not an accounting record.

How Tokenbooks helps with reconciling a token balance

Tokenbooks starts from the same premise: a token balance is not an accounting record, so the computed history and the observed state stay as two separate numbers. The assets view shows the balance computed from transaction history, the balance the last sync observed, and the difference between them. At period close, balance mismatches and on-chain reconciliation mismatches are listed per asset, with booked, on-chain and difference columns, so a difference is a figure to investigate rather than something to force.

The evidence stays attached. A raw transactions view keeps the source transaction, its individual transfers, the event logs and the decoded contract calls next to the accounting interpretation, which is the drill-back path from a posting to what the chain said. Contract-internal ETH transfers are captured, so value that moves inside a contract call still reaches the books, and failed transactions are ingested with their gas booked as an expense.

What reaches that reconciliation from the chain is set out on the Ethereum integration page.

More in this series

Accounting Token Anatomy: ERC-20 Tokens. This article stands alone, but the series builds in order.

Previous (02.1.2): What Does the Holder of an ERC-20 Token Actually Own?

Next (02.1.4): ERC-20 Token Scope: ASC 350-60, IAS 38 and Carrying Amount

All seven articles

Sources and further reading

Frequently Asked Questions

Is a token balance enough evidence for the financial statements?
No. A balance query supplies a quantity at a chosen block and nothing else: no opening balance, no acquisitions or disposals, no lot basis or cost, no realised result, no fair value, no counterparty, no purpose and no custody rights. It is a control total, not a complete transaction register.
Can a subledger be rebuilt from Transfer events alone?
Not safely. EIP-20 only says that a contract which creates new tokens should trigger a Transfer event with the from address set to 0x0, so creation can emit nothing at all. LINK is the worked case: a subledger rebuilt purely from its Transfer logs computes the starting supply as zero.
Does a successful transaction prove a token transfer happened?
No. EIP-20 does not require a failed transfer to revert, and callers are told they must handle a false return. Simulated on 9 September 2026, BAT, ZRX and SNT returned false without failing, producing a success receipt, gas paid, no Transfer event and no balance change.
Why is the ordinary transaction list not a complete population?
Because a third party can move the holder's tokens. Once an address has approved a spender, that spender can call transferFrom, originate the transaction and pay the gas, leaving the entity's address with no outgoing transaction at all. Completeness has to be tested from the token side as well.
Is a ticker enough to identify the asset?
No. Read at block 25,942,317, two contracts both returned the name Tether USD, the symbol USDT, six decimals and an identical total supply, while their balances for the same three addresses did not agree at all. The accounting key should be at least chain ID plus contract address.

Related articles

This is not tax, legal, or accounting advice.
Tokenbooks builds accounting software; we are not a CPA firm and not a tax adviser. Treatment varies by jurisdiction, by entity, and over time, and the rules described here can change after publication. Confirm any position with your own accountant or tax adviser before you rely on it.