We request balances for one synthetic wallet and then switch the interface to another wallet before the request completes. The old response arrives last. If the cache stores balances by token alone the new wallet briefly displays assets that belong to the previous account.

Our independent fixture treats each request as a measurement carrying a wallet identity and chain identity along with a generation number and block number. It makes no RPC calls. We control response order directly so a race is reproducible without depending on a slow public endpoint.

A token identity is not a balance identity

Token metadata describes an asset on a particular chain. A balance describes that asset for a particular account at a particular block. Persisting metadata can make the next page load useful while persisting balances requires enough context to show what was measured and when it became stale.

A token address by itself is insufficient across networks because the same address can identify different contracts on different chains. The cache key includes chain identity and token identity. The measurement envelope then adds the account and the request generation that was active when the read began.

function accept(request, token, block, amount) {
  if (
    request.generation !== generation ||
    request.account !== context.account ||
    request.chain !== context.chain
  )
    return false;
  const key = context.chain + ":" + token;
  const previous = balances.get(key);
  if (previous && block < previous.block) return false;
  balances.set(key, { block, amount });
  return true;
}

Changing the account increments the generation and clears account specific balances. Existing requests retain their old ticket. A late result fails the generation check even if it contains a plausible nonzero balance and a block number higher than an earlier response.

Cancellation and result validation serve different purposes

A React effect cleanup can stop subscriptions and mark its request context inactive. That prevents future work from updating a component after the context changes. It does not guarantee that an RPC provider has canceled work already in flight.

We validate the result at the write boundary as well. That makes the cache responsible for its own identity invariant even when several components share it. A view can unmount while another consumer retains the cache and a result handler must still avoid committing measurements for the wrong wallet.

We keep the request's account and chain together until the response reaches the cache because a successful network call proves that a node answered a question but does not prove that the question still belongs to the interface state visible when the answer arrives or that its block context is newer than the last accepted measurement.

The fixture also rejects an older block within the current generation. This simple numerical comparison does not handle reorganizations because block height alone cannot identify a canonical history. A system needing that guarantee tracks block hashes and defines its invalidation behavior when history changes.

Batch reads without discarding partial success

A token discovery scan can read many balances in batches and retain only the assets relevant to the interface. A subsequent subscription can refresh the smaller tracked set. Batch size is an operational choice that depends on RPC limits and response size and needs measurement before claiming a speed improvement.

One failed token call should have a defined effect on the result. Replacing a failure with zero makes an unavailable balance look like a measured absence. We retain a distinct failure state so the interface can keep a previous value marked stale or display an unavailable measurement.

Current response accepted; old wallet and old block rejected
7 assertions passed

BigInt values also need an explicit serialization representation because JSON has no native BigInt number type. The fixture persists raw quantities as decimal strings and restores them as integers. Formatting a value for display is a separate operation that uses the token's decimal metadata.

Separate cached convenience from transaction authority

A cached balance can help render a token selector but should not authorize a spend. The transaction path still needs current allowance and balance checks as appropriate to the asset. The chain ultimately evaluates those quantities when the transaction executes.

Our tests accept the current response and reject both a previous wallet response and an older block response. We verify that the rejected write leaves the accepted measurement unchanged. The model deliberately omits React rendering and RPC transport so a browser integration test must exercise those boundaries separately.

Switch chains while a multicall remains in flight and remount the balance view before its response arrives. Retain metadata under the correct chain identity while refusing to promote an obsolete account measurement into the current interface.

Sources and examples