Skip to content

Cache and restore wallets#703

Open
paullinator wants to merge 2 commits into
masterfrom
paul/cacheWallets
Open

Cache and restore wallets#703
paullinator wants to merge 2 commits into
masterfrom
paul/cacheWallets

Conversation

@paullinator

@paullinator paullinator commented Feb 4, 2026

Copy link
Copy Markdown
Member

Improve login performance by caching wallet state on a per account level in unencrypted JSON file and rehydrate before wallets load.

CHANGELOG

Does this branch warrant an entry to the CHANGELOG?

  • Yes
  • No

Dependencies

none

Description

none

Note

High Risk
Changes the core login and EdgeAccount wallet surface with delegating proxies and background load; stale cache or yaob/delegation bugs could affect wallet lifecycle, balances shown at login, or disk writes after logout.

Overview
Adds per-account wallet caching so login can show wallets before currency engines finish loading. State is written to accountCache/<storageWalletId>/walletCache.json (unencrypted JSON with balances, tokens, public wallet info—no private keys) and validated with new cleaners.

Login path: The account pixie loads the cache after storage wallets init; on success it builds cached EdgeCurrencyWallet / config proxies and calls makeAccountApi with cacheSetup while loadAllFiles runs in the background. Missing or invalid cache keeps the existing synchronous flow.

Proxies: Cached wallets implement the full wallet API—getters use disk data until real wallets exist; async calls and disklets delegate via a shared poller (makeRealObjectPoller). Setters that the GUI reads (paused, name, fiatCurrencyCode, enabledTokenIds) call update(wallet) for yaob. A throttled cacheSaver sub-pixie persists changes after keysLoaded.

Account API: activeWalletIds and currencyWallets merge cache with pixie output until every active wallet has a real engine.

Also adds docs/wallet-cache.md, extensive fake-plugin/engine-gate tests, exports asEdgeToken, and minor swap-close logging plus a fake-plugin token balance return fix.

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


Comment thread src/core/cache/cache-wallet-loader.ts
@cursor

This comment has been minimized.

Comment thread src/core/account/account-pixie.ts
Comment thread src/core/account/account-api.ts
@cursor

This comment has been minimized.

Comment thread src/core/account/account-pixie.ts
@cursor

This comment has been minimized.

@paullinator
paullinator force-pushed the paul/cacheWallets branch 3 times, most recently from db35d1c to 38a0e8c Compare February 19, 2026 00:08
Comment thread src/core/cache/cached-currency-wallet.ts Outdated
Comment thread src/core/cache/cache-utils.ts
@cursor

This comment has been minimized.

Comment thread src/core/cache/cached-currency-config.ts
Comment thread src/core/account/account-pixie.ts
@cursor

This comment has been minimized.

@paullinator
paullinator force-pushed the paul/cacheWallets branch 3 times, most recently from 294dc9f to e82fdcd Compare February 19, 2026 13:27
Comment thread src/core/cache/cached-currency-config.ts
@cursor

This comment has been minimized.

Comment thread src/core/account/account-pixie.ts
@cursor

This comment has been minimized.

Comment thread src/core/cache/cached-currency-config.ts
Comment thread src/core/cache/cached-currency-wallet.ts
@cursor

cursor Bot commented Feb 23, 2026

Copy link
Copy Markdown

Bugbot Autofix prepared fixes for 2 of the 2 bugs found in the latest run.

  • ✅ Fixed: Cached config returns empty userSettings
    • Changed the userSettings getter to return undefined instead of {} when the real config is unavailable, preserving the semantic distinction between 'no settings' and 'settings exist'.
  • ✅ Fixed: Cached wallet created date may be invalid
    • Added validation to check if the parsed date is valid using isNaN(parsedDate.getTime()), returning undefined for invalid date strings instead of an Invalid Date object.

Create PR

Or push these changes by commenting:

@cursor push e6e1588d29
Preview (e6e1588d29)
diff --git a/src/core/cache/cached-currency-config.ts b/src/core/cache/cached-currency-config.ts
--- a/src/core/cache/cached-currency-config.ts
+++ b/src/core/cache/cached-currency-config.ts
@@ -141,7 +141,7 @@
     // User settings (delegate when available, write delegates):
     get userSettings(): object | undefined {
       const realConfig = tryGetRealConfig()
-      return realConfig != null ? realConfig.userSettings : {}
+      return realConfig != null ? realConfig.userSettings : undefined
     },
 
     async changeUserSettings(settings: object): Promise<void> {

diff --git a/src/core/cache/cached-currency-wallet.ts b/src/core/cache/cached-currency-wallet.ts
--- a/src/core/cache/cached-currency-wallet.ts
+++ b/src/core/cache/cached-currency-wallet.ts
@@ -155,7 +155,8 @@
   } = cacheData
 
   const shortId = walletId.slice(0, WALLET_ID_DISPLAY_LENGTH)
-  const createdDate = new Date(createdString)
+  const parsedDate = new Date(createdString)
+  const createdDate = isNaN(parsedDate.getTime()) ? undefined : parsedDate
 
   // Track mutable state locally. When the GUI calls a setter, we update
   // the local value immediately and call update(wallet) to push it

@paullinator

Copy link
Copy Markdown
Member Author

/rebase

result[walletId] = wallet
}
}
return result

@swansontec swansontec Feb 26, 2026

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.

Because YAOB diffs objects shallowly (===), this needs to be referrentially stable. So you need to store the object somewhere and replace it each time something inside changes. Otherwise we'll either get superfluous updates or missing updates (either would be bad).

paullinator and others added 2 commits July 20, 2026 17:31
Add missing return in fake plugin getBalance for token balance.
Improve @ts-expect-error comment and log swap quote close errors.

Co-authored-by: Cursor <cursoragent@cursor.com>
Improve login performance by caching wallet state on a per account level in unencrypted JSON file and rehydrate before wallets load.

@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 prepared a fix for the issue found in the latest run.

  • ✅ Fixed: waitForAllWallets ends too early
    • Updated waitForAllWallets to wait for pixie-backed real wallets rather than cached stubs and added a passing cache-first regression test.

Create PR

Or push these changes by commenting:

@cursor push 92a98b7caf
Preview (92a98b7caf)
diff --git a/src/core/account/account-api.ts b/src/core/account/account-api.ts
--- a/src/core/account/account-api.ts
+++ b/src/core/account/account-api.ts
@@ -800,10 +800,11 @@
     async waitForAllWallets(): Promise<void> {
       return await new Promise((resolve, reject) => {
         const check = (): void => {
+          const pixieWallets =
+            ai.props.output.accounts[accountId]?.currencyWallets ?? {}
           const busyWallet = this.activeWalletIds.find(
             id =>
-              this.currencyWallets[id] == null &&
-              this.currencyWalletErrors[id] == null
+              pixieWallets[id] == null && this.currencyWalletErrors[id] == null
           )
           if (busyWallet == null) {
             for (const cleanup of cleanups) cleanup()

diff --git a/test/core/cache/wallet-cache.test.ts b/test/core/cache/wallet-cache.test.ts
--- a/test/core/cache/wallet-cache.test.ts
+++ b/test/core/cache/wallet-cache.test.ts
@@ -134,6 +134,52 @@
     await account2.logout()
   })
 
+  it('waitForAllWallets waits for real engines', async function () {
+    this.timeout(10000)
+
+    const world = await makeFakeEdgeWorld([fakeUser], quiet)
+    const context = await world.makeEdgeContext({
+      ...contextOptions,
+      plugins: { fakecoin: true }
+    })
+
+    // First login - populate cache
+    const account1 = await context.loginWithPIN(fakeUser.username, fakeUser.pin)
+    const walletInfo = account1.getFirstWalletInfo('wallet:fakecoin')
+    if (walletInfo == null) throw new Error('No wallet')
+
+    await account1.waitForCurrencyWallet(walletInfo.id)
+    await account1.currencyConfig.fakecoin.changeUserSettings({ balance: 9999 })
+
+    // Wait for cache saver to write (throttled to 50ms in tests):
+    await snooze(CACHE_SAVE_WAIT_MS)
+    await account1.logout()
+
+    // Second login - use gate to block engine
+    const { gate, release } = createEngineGate()
+    fakePluginTestConfig.engineGate = gate
+
+    const account2 = await context.loginWithPIN(fakeUser.username, fakeUser.pin)
+    expect(account2.currencyWallets[walletInfo.id]).not.equals(undefined)
+
+    let completed = false
+    const waitPromise = account2.waitForAllWallets().then(() => {
+      completed = true
+    })
+
+    await snooze(BRIDGE_SETTLE_MS)
+    const completedBeforeRelease = completed
+    release()
+    await waitPromise
+
+    expect(completedBeforeRelease).equals(
+      false,
+      'waitForAllWallets should wait for real engines'
+    )
+
+    await account2.logout()
+  })
+
   it('delegates to real wallet after engine loads', async function () {
     this.timeout(10000)

You can send follow-ups to the cloud agent here.

Want higher recall? High effort reviews run extra passes and find more bugs. A team admin can switch effort levels in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 73669de. Configure here.

: undefined,

// Added for backward compatibility for plugins using core 1.x
// @ts-expect-error

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

waitForAllWallets ends too early

Medium Severity

After a cache-first login, waitForAllWallets treats cached stub wallets as loaded because currencyWallets merges cache entries and activeWalletIds can come from the cache before keys load. It resolves before real currency engines exist, unlike the pre-change behavior tied to pixie-backed wallets only.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 73669de. Configure here.

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