Skip to content

feat: dedupe and epoch-fence address/balance fetches against wallet changes - #87

Merged
jonathunne merged 1 commit into
mainfrom
npc/address-balance-request-coordinator-v2
Aug 18, 2026
Merged

feat: dedupe and epoch-fence address/balance fetches against wallet changes#87
jonathunne merged 1 commit into
mainfrom
npc/address-balance-request-coordinator-v2

Conversation

@nulllpc

@nulllpc nulllpc commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Build on top of #79 and #77

This PR inherits the idea of request deduplication from PR 79 but build on top of PR 77 with much more consideration and design so we can leverage the deduplication as a common feature for all services, not just address. This was built on top of PR 77 because that PR contained significant improvements to the lifecycle of wallet which the deduplication plays along nicely

Summary

Adds request de-duplication and staleness-fencing for address and balance fetches, so a fetch still in flight when the wallet is deleted, locked, or switched can't write stale (or wrong-wallet) data into the store once it resolves.

Credit for the idea goes to @ashaltykou-innowise

Why

The WDK worklet holds exactly one loaded wallet at a time and has no concept of walletId - every RPC call is answered by whichever seed is currently loaded. That means:

  • Two concurrent requests for the same address/balance were firing duplicate RPCs.
  • A response arriving after the wallet changed could still write into the store, either resurrecting stale data or (for balances) attributing a result to the wrong wallet, since updateBalance resolved the active wallet at write time rather than at fetch kickoff.

What changed

  • RequestCoordinator (src/utils/requestCoordinator.ts): dedupes concurrent fetches by key, and only commits a result if a single global worklet epoch (src/utils/workletEpoch.ts) hasn't changed since the fetch started. The epoch is bumped from exactly one place - WorkletLifecycleService.reset() - which every wallet-changing flow (delete, lock, switch) already routes through on this branch, so it covers all three for free.
  • AddressService / BalanceService: rebuilt on top of the coordinator. Also fixes two real bugs found along the way:
    • a stuck walletLoading flag that could permanently block retries once a result was discarded as stale
    • balance writes being attributed to whatever wallet is active when the fetch resolves instead of the one active when it started
  • fetchBalance now shares the same per-network fetch/commit path as fetchBalances, so they dedupe against each other instead of risking a key collision.

@nulllpc nulllpc self-assigned this Aug 17, 2026
@nulllpc

nulllpc commented Aug 17, 2026

Copy link
Copy Markdown
Contributor Author

We should still merge the PR #77 first, and we can close the PR #79

@nulllpc
nulllpc requested a review from jonathunne August 18, 2026 06:42
@jonathunne

Copy link
Copy Markdown
Contributor

#77 is merged.

@ashaltykou-innowise ashaltykou-innowise left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍

Comment thread src/services/workletLifecycleService.ts
@nulllpc
nulllpc force-pushed the npc/address-balance-request-coordinator-v2 branch from 80b4904 to 5f360f1 Compare August 18, 2026 11:54
changes

- Add RequestCoordinator + a single global worklet epoch: dedupes
  concurrent fetches by cache key, and discards a result if the
  worklet's loaded wallet changed (switch/lock/delete) while the fetch
  was in flight
- Wire the epoch bump into WorkletLifecycleService.reset() - the one
  choke point every wallet-changing flow already routes through, so no
  other lifecycle code needs to change
- Fix AddressService: the loading flag could get stuck at `true` forever
  when a stale result was discarded, permanently blocking
  useAddressLoader from ever retrying that network/account
- Fix BalanceService: updateBalance/updateLastBalanceUpdate now receive
  the wallet captured at fetch kickoff instead of re-resolving
  activeWalletId at write time, which could attribute a balance to the
  wrong wallet after a switch
- Unify fetchBalance onto the same per-network fetch/commit path as
  fetchBalances so they dedupe against each other instead of risking a
  cache-key collision
@nulllpc
nulllpc force-pushed the npc/address-balance-request-coordinator-v2 branch from 5f360f1 to f3a2f18 Compare August 18, 2026 12:58

@jonathunne jonathunne left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@jonathunne
jonathunne merged commit 1823348 into main Aug 18, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants