Skip to content

Gate balance effects and FIO refresh on engine readiness (wallet cache v2) - #6080

Merged
j0ntz merged 2 commits into
developfrom
jon/wallet-cache-v2
Sep 23, 2026
Merged

j0ntz merged 2 commits into
developfrom
jon/wallet-cache-v2

Conversation

@j0ntz

@j0ntz j0ntz commented Jul 18, 2026 •

Copy link
Copy Markdown
Contributor

Technical Design Document

edge-wallet-cache-design.md

CHANGELOG

Does this branch warrant an entry to the CHANGELOG?

  • Yes
  • No

Dependencies

EdgeApp/edge-core-js#733

Requirements

If you have made any visual changes to the GUI. Make sure you have:

  • Tested on iOS device
  • Tested on Android device
  • Tested on small-screen device (iPod Touch)
  • Tested on large-screen device (tablet)

Description

Technical design doc

GUI-side patches for wallet cache v2 phase 1 (TDD section 7: pinned, live). With EdgeApp/edge-core-js#733, wallet objects exist before their engines load and waitForAllWallets resolves in that window, so the login-path surfaces that consumed engine state at resolve-time are gated on engine readiness:

  • checkActionEffect.ts (address-balance): reported the effect against balanceMap immediately after awaiting the wallet, which could now evaluate cached, possibly stale balances. It reports not-yet-effective until the engine has fully synced (same conservatism as the loan flow's waitForLoanAccountSync and the existing < 1 treatment in spend paths, TDD 7.4), letting the action queue's normal 15s poll re-check.
  • Services.tsx: the post-waitForAllWallets FIO refreshes (refreshConnectedWallets, refreshAllFioAddresses) call wallet.otherMethods.*, which the core guarantees is {} pre-engine. A new waitForWalletOtherMethods util watches otherMethods until the engine's methods land (10-minute safety-valve timeout, roughly matching how long waitForAllWallets could already take on large accounts before the cache existed).
  • FioService.ts: the periodic expired-domain check calls otherMethods.getFioAddresses the same way. This one was NOT in the TDD's section-7 audit; it was caught live on the simulator (red dev alert wallet.otherMethods.getFioAddresses is not a function seconds after a warm cached login). Being a 30s periodic task, it skips pre-engine wallets and lets the next cycle retry, which also avoids wedging its one-shot expiredChecking latch when no wallet is ready yet.

Remaining otherMethods call sites (FIO scenes, staking, WalletConnect) are user-navigation surfaces audited in the TDD as safe (null-probes, or flows that imply an engine exists) and are unchanged.

Tested on the iOS simulator against the linked core build (edge-funds, 194 wallets): cold login wrote all 194 walletCache.json files; warm relaunch rendered the full wallet list with names and balances from the cache while engines were still loading; no FIO alert through 140s of runtime; drilling into a wallet shows live engine-backed data on the same wallet object. Screenshots attached below.

Phase 2: tap-prioritization

The core now staggers cached wallets' engine startup through a limited-concurrency queue (EdgeApp/edge-core-js#733 phase 2). withWallet wraps every wallet-scoped scene, so opening one calls account.waitForCurrencyWallet(walletId), which moves that wallet's engine startup to the front of the queue. The call is a fire-and-forget hint; a deleted or broken wallet is already handled by the existing goBack effect.

Phase 6: provisional receive address (TDD section 7.5, decision 9.9)

The receive scene (RequestScene.tsx) waited on the engine for every address, so a rotating-address chain showed a loading state until the engine started. It now opts into the core's cached address (getAddresses({ allowCached: true }), EdgeApp/edge-core-js#733 phase 6) and renders it immediately.

  • On a rotating chain (!hasStableAddresses), the cached address is provisional: a static inline affordance sits under the address (a muted circled-i glyph, "Checking for your latest address", and a spinner; informational, not tappable, not a warning color). A 350ms grace timer gates it on, so a warm engine that confirms first never flashes it; the QR is capped to reserve the row's height so toggling never reflows it.
  • The confirmed address swaps in place only if it differs once the engine loads; on engine failure/timeout the row is removed and the cached address stays, with a 30s safety cap so the spinner never hangs.
  • Staleness guards (from review): each refresh takes a token and captures the wallet id, so a slow reconcile from a prior wallet (the withWallet instance is reused across wallet switches) or after unmount is ignored rather than overwriting the current address. A later refresh / addressChanged rotation takes the plain engine-gated path, so it never shows a stale address.

Rotating-chain behavior is provable in-app on a UTXO chain (BTC/LTC) with no plugin change; the stable-chain skip and the core gating are covered by unit tests. Depends on the core allowCached option (EdgeApp/edge-core-js#733) and, for stable chains, the plugin flags (draft EdgeApp/edge-currency-accountbased#1076).

TDD (pinned, live): implementation divergences and the decisions are documented inline in the affected sections.

Asana: https://app.asana.com/1/9976422036640/project/1213843652804305/task/1216673467164267

Post-review followup: the FIO refreshes read a populated wallet list

On a warm login the readiness wait in Services.tsx resolves before FioService's watch-driven effect has published ui.fio.fioWallets, so refreshConnectedWallets and refreshAllFioAddresses could run against an empty list. Services.tsx now dispatches UPDATE_FIO_WALLETS with the list it already computed, right before the refreshes; FioService's own dispatch stays and carries the same content.

Phase 7: the provisional receive affordance is reverted

The commit that added the provisional address UI ("Show a provisional receive address on warm login") was dropped from this branch, along with the core allowCached opt-in and the hasStableAddresses plugin flags (edge-currency-accountbased#1076, closed). Serving a cached address silently was accepted as an edge case, which removes what the affordance existed to disclose. The commit was dropped rather than reverted on top, so this branch's history never contains it.

RequestScene.tsx needs no replacement code. It calls getAddresses on mount and already subscribes to addressChanged, which is exactly the contract the core now provides: the first query is answered from the cache so the QR renders immediately on a warm login, and the wallet emits addressChanged if the engine goes on to derive a different address, so the scene re-queries and lands on it. That reconcile is in edge-core-js#733, and it fixes every consumer of the address rather than this one scene.


Note

Medium Risk
Changes login-time FIO refresh ordering, action-queue balance triggers, and wallet startup prioritization—behavior that can affect large accounts and automated flows, though failures are mostly deferred or logged rather than silent wrong outcomes.

Overview
Adapts the GUI for wallet cache v2, where wallet objects appear before their engines finish loading.

Engine readiness gates: Post-login FIO work (refreshConnectedWallets, refreshAllFioAddresses) and the periodic expired-domain check in FioService no longer call otherMethods on cold wallets. A new waitForWalletOtherMethods helper waits for engine-backed methods (with a long timeout), and FioService skips wallets until getFioAddresses exists, with a finally so the expired-check latch cannot stick. Action queue address-balance effects treat cached balances as not effective until syncStatus.totalRatio reaches DONE_THRESHOLD, then re-poll on the existing 15s delay.

Warm-login fix: Services dispatches UPDATE_FIO_WALLETS before the FIO refreshes so an empty Redux list does not run refreshes ahead of FioService's watch effect.

Tap prioritization: withWallet fire-and-forgets account.waitForCurrencyWallet(walletId) so opening a wallet-scoped scene moves that engine to the front of the post-login queue.

UX: Wallet list rows show an Engine Failed overlay from currencyWalletErrors when a cache-seeded wallet's engine fails. ESLint no longer exempts FioService after typing it as React.FC.

Reviewed by Cursor Bugbot for commit 4aa4a8e. Bugbot is set up for automated code reviews on this repo. Configure here.

Test evidence

b96a03b
Gate balance effects and FIO refresh on engine readiness
🪓 forced engineError to a non-null Error in WalletListCurrencyRow, since an engine failure has no natural trigger on the sim; reverted, tree clean.

agent proof 1216673467164267 01 cold login wallet list

agent proof 1216673467164267 02 warm login cached wallet list

agent proof 1216673467164267 03 wallet detail engine loaded

agent proof 1216673467164267 04 warm login after review fixes

warm login wallet list

🪓 engine error row
88cbb94
Prioritize an opened wallet's engine startup

agent proof 1216673467164267 05 warm login list

agent proof 1216673467164267 06 sepolia detail after tap

agent proof 1216673467164267 p6 01 ltc receive cached

agent proof 1216673467164267 p6 02 ltc receive warmlogin

🪓 🩹 HACK-FORCED: provisional affordance

receive address no affordance

warm login wallet list

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

Comment thread src/components/services/Services.tsx
Comment thread src/components/services/FioService.ts Outdated
Comment thread src/util/waitForWalletOtherMethods.ts
@j0ntz
j0ntz force-pushed the jon/wallet-cache-v2 branch from 00faa89 to 7f1149f Compare July 18, 2026 01:10
Comment thread src/components/services/Services.tsx
@j0ntz
j0ntz force-pushed the jon/wallet-cache-v2 branch from 7f1149f to 0a089ca Compare July 18, 2026 01:31
Comment thread src/controllers/action-queue/runtime/checkActionEffect.ts

@claude claude Bot 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.

Claude Code Review

Claude Code Review is paused for this repository. To reconnect it, an admin of this repository's GitHub organization (or the account owner, for personal repositories) who can also manage your Claude organization's Code Review settings needs to re-link GitHub in Code Review settings. This is a one-time step.

Tip: disable this comment in your organization's Code Review settings.

Comment thread src/components/services/Services.tsx
Comment thread src/util/waitForWalletOtherMethods.ts

@claude claude Bot 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.

Claude Code Review

Claude Code Review is paused for this repository. To reconnect it, an admin of this repository's GitHub organization (or the account owner, for personal repositories) who can also manage your Claude organization's Code Review settings needs to re-link GitHub in Code Review settings. This is a one-time step.

Tip: disable this comment in your organization's Code Review settings.

@cursor cursor Bot 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.

Stale Bugbot comment from a previous run.

Comment thread src/components/scenes/RequestScene.tsx Outdated
@j0ntz
j0ntz force-pushed the jon/wallet-cache-v2 branch 2 times, most recently from 0de615b to 5d1a879 Compare July 23, 2026 12:03
@j0ntz
j0ntz marked this pull request as draft August 1, 2026 01:01
@j0ntz
j0ntz force-pushed the jon/wallet-cache-v2 branch from 5d1a879 to 99b80e3 Compare August 1, 2026 01:02
@j0ntz
j0ntz marked this pull request as ready for review August 1, 2026 02:20
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@j0ntz
j0ntz force-pushed the jon/wallet-cache-v2 branch from 99b80e3 to 595cb91 Compare August 17, 2026 18:52

@claude claude Bot 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.

Claude Code Review

Claude Code Review is paused for this repository. To reconnect it, an admin of this repository's GitHub organization (or the account owner, for personal repositories) who can also manage your Claude organization's Code Review settings needs to re-link GitHub in Code Review settings. This is a one-time step.

Tip: disable this comment in your organization's Code Review settings.

@paullinator paullinator left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Reviewed with edge-core-js#733. The three patches here are right, and I audited the remaining waitForAllWallets / waitForCurrencyWallet / otherMethods call sites (loan dashboard, ramp selection hook, action-queue display/push/evaluate, loan-manager, CreateWalletCompletionScene, resolveName, FioAddressUtils, Cardano/Thorchain adaptors, WalletConnect, migrate scenes, FioActions) — all config-only or engine-gated internally, so nothing else needs patching.

One change needed, in a file this PR does not touch yet:

A cached wallet whose engine fails still renders as a healthy row. With #733, CURRENCY_ENGINE_FAILED sets engineFailure and account.currencyWalletErrors[walletId], but the wallet object was already emitted from the cache, so it stays in account.currencyWallets. WalletListSwipeable.tsx:157 picks WalletListSwipeableCurrencyRow whenever wallet != null, so WalletListSwipeableLoadingRow — the only list component that shows currencyWalletErrors — is now unreachable for any cached wallet. The user sees cached balances with a sync ring that never completes, and taps reject with the engine error. On develop the wallet object never existed in this case, so the error row showed.

The row should stay (wallets appearing before their engines is the point of the cache), but an engine failure is rare and should not happen, so the user needs to be told. Suggest WalletListSwipeableCurrencyRow reads useWatch(account, 'currencyWalletErrors') and, when currencyWalletErrors[wallet.id] != null, shows the error in place of the sync ring, the way the loading row does today. BalanceCard.tsx:75, useAccountSyncRatio.tsx:60, and DeepLinkingManager.tsx:47 already consult the error map independently of the wallet object, so they are fine.

FYI for anyone extending the otherMethods probes: across the yaob bridge Object.keys(wallet.otherMethods) is [] because methods are non-enumerable (true on develop too). Existence checks must probe the property, as waitForWalletOtherMethods does.

@j0ntz

j0ntz commented Sep 10, 2026

Copy link
Copy Markdown
Contributor Author

Fixed the error-row finding. WalletListCurrencyRow now watches currencyWalletErrors and renders the engine error in the card's existing overlay slot, the one paused and disabled wallets already use, so the cached row and its balances stay while the failure is visible. Putting it in the shared row rather than in WalletListSwipeableCurrencyRow covers every caller of that row, not just the wallet list. Thanks for the Object.keys(wallet.otherMethods) note: waitForWalletOtherMethods probes the property for exactly that reason, and the core-side threads on #733 are all answered.

@j0ntz
j0ntz force-pushed the jon/wallet-cache-v2 branch 2 times, most recently from 16c0b1b to 12fcb2a Compare September 10, 2026 19:07

@cursor cursor Bot 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.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 12fcb2a. Configure here.

Comment thread src/components/services/Services.tsx
@j0ntz
j0ntz force-pushed the jon/wallet-cache-v2 branch 4 times, most recently from 88cbb94 to a0299b6 Compare September 17, 2026 00:05

@paullinator paullinator left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Re-reviewed at a0299b6. The failed-engine row is fixed: WalletListCurrencyRow watches currencyWalletErrors and shows the error in the card's overlay slot ahead of paused/disabled, so the cached row and balances stay while the failure is visible. Same pattern BalanceCard and useAccountSyncRatio already use.

One cosmetic note, not blocking: the overlay prints the raw engineError.message, which can be long or technical for a card label. A localized "Engine failed" prefix would read better.

@j0ntz
j0ntz force-pushed the jon/wallet-cache-v2 branch from a0299b6 to 66d6106 Compare September 22, 2026 22:37

@claude claude Bot 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.

Claude Code Review

Claude Code Review is paused for this repository. To reconnect it, an admin of this repository's GitHub organization (or the account owner, for personal repositories) who can also manage your Claude organization's Code Review settings needs to re-link GitHub in Code Review settings. This is a one-time step.

Tip: disable this comment in your organization's Code Review settings.

@j0ntz

j0ntz commented Sep 22, 2026

Copy link
Copy Markdown
Contributor Author

Cosmetic note addressed in 66d6106. The overlay now renders a localized prefix instead of the bare message: new key fragment_wallets_wallet_engine_failed_1s: 'Engine Failed: %1$s' in en_US.ts, applied with sprintf in WalletListCurrencyRow, so a failed row reads "Engine Failed: " and stays in the same slot ahead of paused/disabled.

The full jest suite is green on the branch (99 suites, 722 tests), and verify-repo passes on both repos.

With the core's wallet cache (wallet cache v2 phase 1), wallet objects
exist before their engines load, and waitForAllWallets resolves in that
window. Three login-path surfaces consumed engine state immediately:

- The action queue's address-balance effect read balanceMap right after
  awaiting the wallet, which could evaluate a balance effect against
  cached, possibly stale balances. It now reports not-yet-effective
  until the engine has fully synced, matching the conservatism the loan
  flow already applies.
- The FIO address refresh called otherMethods on pre-engine wallets,
  which is {} in that window. Services now waits for each FIO wallet's
  engine-backed otherMethods (bounded by a generous safety-valve
  timeout) before refreshing.
- FioService's periodic expired-domain check called
  otherMethods.getFioAddresses the same way (caught live on the sim).
  It now skips pre-engine wallets and lets the next 30s cycle retry,
  which also avoids wedging its one-shot expiredChecking latch.
The core's new post-login queue staggers cached wallets' engine
startup. withWallet covers every wallet-scoped scene, so opening one
calls waitForCurrencyWallet, which moves that wallet's engine to the
front of the queue.
@j0ntz
j0ntz force-pushed the jon/wallet-cache-v2 branch from 66d6106 to 4aa4a8e Compare September 23, 2026 03:29
@j0ntz
j0ntz enabled auto-merge September 23, 2026 03:32
@j0ntz
j0ntz merged commit 0c4d2e9 into develop Sep 23, 2026
7 checks passed
@j0ntz
j0ntz deleted the jon/wallet-cache-v2 branch September 23, 2026 03:49
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.

2 participants