Description
On release 2.4.50, the "Select default account" modal (Bitcoin vs Dollar/Stablesats) pops up every time the user taps "Receive", even after the user has already made a selection. The user's choice is not being persisted across sessions.
This is a regression — the modal should only appear once (after the user has >= 1 transaction) and remember the choice permanently.
Steps to Reproduce
- Open Blink on v2.4.50 (custodial account, Mainnet, with at least 1 transaction)
- Tap Receive
- The "Select default account" modal appears — choose Bitcoin or Dollar
- Go back to home screen
- Tap Receive again
- The modal appears again, despite having already chosen
Expected Behavior
The modal should appear once. After the user selects a default account, the choice is persisted and the modal does not reappear.
Actual Behavior
The modal reappears on every "Receive" tap. The persisted flag hasPromptedSetDefaultAccount is not being restored from the Apollo cache on app startup.
Affected Versions
- Regression introduced in: 2.4.46
- Still present in: 2.4.50 (latest)
Root Cause Analysis
The default account choice is persisted via a client-only Apollo cache field hasPromptedSetDefaultAccount, which is written to disk by apollo3-cache-persist. The trigger condition is in app/screens/home-screen/home-screen.tsx:411:
if (
!isSelfCustodial && target === "receiveBitcoin" && !hasPromptedSetDefaultAccount && // <-- reads false when cache not restored
numberOfTxs >= 1 && galoyInstanceId === "Main"
) {
toggleSetDefaultAccountModal()
return
}
Regressing commit
Commit c276cf1ee8 — PR #3769 ("feat(spark): UI/UX launch polish", 2026-05-20) changed cache restore in app/graphql/client.tsx:296-300 from unconditional to token-gated:
- await persistor.restore()
+ // Skip restore in self-custodial mode so the persisted custodial cache cannot leak.
+ if (token) {
+ await persistor.restore()
+ }
The token now comes from useEffectiveAuthToken() which resolves asynchronously. On cold start it transiently returns "" while the async account-type check (listSelfCustodialAccounts()) is in flight. If the Apollo client effect runs during this window, token is falsy and persistor.restore() is skipped — so hasPromptedSetDefaultAccount is never loaded back into the in-memory cache and defaults to false.
Key files | File | Relevance |
|------|-----------|
| app/graphql/client.tsx:296-312 | Token-gated persistor.restore() — the regression |
| app/graphql/hooks/use-effective-auth-token.ts | Async token resolution (transiently returns "") |
| app/screens/home-screen/home-screen.tsx:402-420 | Modal trigger condition |
| app/components/set-default-account-modal/set-default-account-modal.tsx | Modal component + persist write |
| app/graphql/client-only-query.ts:196-208 | setHasPromptedSetDefaultAccount() writer |
| app/graphql/cache.ts:107-109 | Cache read policy (defaults to false) |
Suggested Fix Direction
Ensure persistor.restore() runs for custodial sessions regardless of token-resolution timing. Options:
- Distinguish "still resolving" from "self-custodial (no token)" in
useEffectiveAuthToken() — e.g., return a tri-state (loading | token | null) — and only skip restore when explicitly in self-custodial mode, not during the loading phase.
- Restore unconditionally, and instead purge/namespace the cache by account type when entering self-custodial mode.
- Await token resolution before building the client, ensuring
token is accurate by the time the restore decision is made.
/cc @grimen @esaugomez31 @ihaveadifferentname
Description
On release 2.4.50, the "Select default account" modal (Bitcoin vs Dollar/Stablesats) pops up every time the user taps "Receive", even after the user has already made a selection. The user's choice is not being persisted across sessions.
This is a regression — the modal should only appear once (after the user has >= 1 transaction) and remember the choice permanently.
Steps to Reproduce
Expected Behavior
The modal should appear once. After the user selects a default account, the choice is persisted and the modal does not reappear.
Actual Behavior
The modal reappears on every "Receive" tap. The persisted flag
hasPromptedSetDefaultAccountis not being restored from the Apollo cache on app startup.Affected Versions
Root Cause Analysis
The default account choice is persisted via a client-only Apollo cache field
hasPromptedSetDefaultAccount, which is written to disk byapollo3-cache-persist. The trigger condition is inapp/screens/home-screen/home-screen.tsx:411:Regressing commit
Commit
c276cf1ee8— PR #3769 ("feat(spark): UI/UX launch polish", 2026-05-20) changed cache restore inapp/graphql/client.tsx:296-300from unconditional to token-gated:The
tokennow comes fromuseEffectiveAuthToken()which resolves asynchronously. On cold start it transiently returns""while the async account-type check (listSelfCustodialAccounts()) is in flight. If the Apollo client effect runs during this window,tokenis falsy andpersistor.restore()is skipped — sohasPromptedSetDefaultAccountis never loaded back into the in-memory cache and defaults tofalse.Key files | File | Relevance |
|------|-----------|
|
app/graphql/client.tsx:296-312| Token-gatedpersistor.restore()— the regression ||
app/graphql/hooks/use-effective-auth-token.ts| Async token resolution (transiently returns"") ||
app/screens/home-screen/home-screen.tsx:402-420| Modal trigger condition ||
app/components/set-default-account-modal/set-default-account-modal.tsx| Modal component + persist write ||
app/graphql/client-only-query.ts:196-208|setHasPromptedSetDefaultAccount()writer ||
app/graphql/cache.ts:107-109| Cache read policy (defaults tofalse) |Suggested Fix Direction
Ensure
persistor.restore()runs for custodial sessions regardless of token-resolution timing. Options:useEffectiveAuthToken()— e.g., return a tri-state (loading | token | null) — and only skip restore when explicitly in self-custodial mode, not during the loading phase.tokenis accurate by the time the restore decision is made./cc @grimen @esaugomez31 @ihaveadifferentname