Skip to content

fix(wallet): local storage managed wallet bug + support multiple managed#2307

Merged
baktun14 merged 8 commits intomainfrom
fix/managed-user-local-storage
Dec 3, 2025
Merged

fix(wallet): local storage managed wallet bug + support multiple managed#2307
baktun14 merged 8 commits intomainfrom
fix/managed-user-local-storage

Conversation

@baktun14
Copy link
Contributor

@baktun14 baktun14 commented Dec 2, 2025

closes #2294

Summary by CodeRabbit

  • New Features

    • Per-network managed-wallet storage and improved merging so managed + custodial wallets and selections persist correctly across networks and users.
  • Bug Fixes

    • Refined storage update logic to avoid unintended removals and added graceful handling for malformed storage data.
  • Tests

    • Comprehensive wallet storage test suite added (multi-user, migrations, selection precedence, persistence) and new test seeders for managed and custodial wallets.

✏️ Tip: You can customize this high-level summary in your review settings.

@baktun14 baktun14 requested a review from a team as a code owner December 2, 2025 04:59
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 2, 2025

Walkthrough

Per-network, per-user managed-wallet persistence introduced: managed wallets moved to network-scoped maps in localStorage; wallet utilities updated (signatures changed) to read/write/merge managed and custodial wallets with error handling; a hook effect in useManagedWallet was simplified; tests and seeders added.

Changes

Cohort / File(s) Summary
Hook cleanup
apps/deploy-web/src/hooks/useManagedWallet.ts
Removed isFetched import/destructure and the conditional deletion branch relying on it; adjusted effect dependencies to [isCreated, wallet].
Managed-wallet storage refactor
apps/deploy-web/src/utils/walletUtils.ts
Introduced per-network managed-wallet storage key and network-scoped map format; changed signatures to getStorageManagedWallet(userId?, networkId?) and deleteManagedWalletFromStorage(userId, networkId?); updated getStorageWallets, updateStorageManagedWallet, updateStorageWallets, and ensureUserManagedWalletOwnership to operate on per-user, per-network managed-wallet maps, merge with custodial wallets, and add JSON parse/error handling.
Tests
apps/deploy-web/src/utils/walletUtils.spec.ts
Added comprehensive tests exercising read/write flows, migrations, corrupt-JSON handling, multi-user and per-network isolation, selection precedence, delete/update behavior, and regression scenarios with a localStorage mock.
Test seeders
apps/deploy-web/tests/seeders/*
apps/deploy-web/tests/seeders/index.ts, apps/deploy-web/tests/seeders/localWallet.ts
Exported localWallet seeder and added buildManagedLocalWallet and buildCustodialLocalWallet builder functions and type guards for test data generation.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Focus review on:
    • apps/deploy-web/src/utils/walletUtils.ts — signature changes, per-network key naming, map serialization/parsing, merging managed vs custodial wallets, and JSON error handling/migration paths.
    • apps/deploy-web/src/utils/walletUtils.spec.ts — test assumptions, localStorage mock correctness, and coverage of edge/corrupt cases.
    • apps/deploy-web/src/hooks/useManagedWallet.ts — effect timing and lifecycle after removing isFetched.

Possibly related PRs

Suggested reviewers

  • stalniy

Poem

🐇 I hopped through keys in nightly code,

Per-network burrows where wallets goad,
Managed and custodial tucked in a row,
Tests planted carrots so bugs won't show,
A tiny hop — persistence sowed.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main changes: fixing a managed wallet local storage bug and adding support for multiple managed wallets.
Linked Issues check ✅ Passed The PR addresses the linked issue #2294 by implementing per-network managed-wallet storage, preventing data loss on disconnections through improved storage handling and wallet merging logic.
Out of Scope Changes check ✅ Passed All changes are directly scoped to the managed wallet storage issue: wallet utils refactoring, hook cleanup, comprehensive test suite, and test seeders are all necessary for the fix.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/managed-user-local-storage

Comment @coderabbitai help to get the list of available commands and usage tips.

@@ -394,7 +394,6 @@
"resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.0.0.tgz",
Copy link
Contributor

@github-actions github-actions bot Dec 2, 2025

Choose a reason for hiding this comment

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

🔄 Carefully review the package-lock.json diff

Resolve the comment if everything is ok

+ node_modules/git-semver-tags/node_modules/conventional-commits-filter                    5.0.0   
+ node_modules/git-semver-tags/node_modules/conventional-commits-parser                    6.2.1   

Copy link
Contributor

@stalniy stalniy Dec 3, 2025

Choose a reason for hiding this comment

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

question: @baktun14 is this needed or sent by mistake? it doesn't seem related

@codecov
Copy link

codecov bot commented Dec 2, 2025

Codecov Report

❌ Patch coverage is 93.75000% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 48.01%. Comparing base (f269ffd) to head (5f7d051).
⚠️ Report is 9 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
apps/deploy-web/src/utils/walletUtils.ts 94.68% 5 Missing ⚠️
apps/deploy-web/src/hooks/useManagedWallet.ts 50.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2307      +/-   ##
==========================================
+ Coverage   47.60%   48.01%   +0.40%     
==========================================
  Files        1036     1038       +2     
  Lines       29357    29667     +310     
  Branches     7632     7617      -15     
==========================================
+ Hits        13976    14244     +268     
+ Misses      15000    14950      -50     
- Partials      381      473      +92     
Flag Coverage Δ
api 81.15% <ø> (-0.67%) ⬇️
deploy-web 27.00% <93.75%> (+0.57%) ⬆️
log-collector 75.35% <ø> (ø)
notifications 87.94% <ø> (ø)
provider-console 81.48% <ø> (ø)
provider-proxy 84.35% <ø> (ø)
Files with missing lines Coverage Δ
apps/deploy-web/src/hooks/useManagedWallet.ts 24.00% <50.00%> (+0.92%) ⬆️
apps/deploy-web/src/utils/walletUtils.ts 94.07% <94.68%> (+86.49%) ⬆️

... and 58 files with indirect coverage changes

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (2)
apps/deploy-web/src/utils/walletUtils.spec.ts (2)

243-250: Consider testing undefined explicitly.

The function signature allows undefined for userId. Line 246 tests undefined implicitly by calling with no args, but line 247 tests empty string. Consider making the undefined case explicit for clarity.

   it("returns undefined when userId is not provided", () => {
     const { cleanup } = setup();

-    expect(getStorageManagedWallet()).toBeUndefined();
+    expect(getStorageManagedWallet(undefined)).toBeUndefined();
     expect(getStorageManagedWallet("")).toBeUndefined();

     cleanup();
   });

434-453: Test assertion could be stricter.

The test verifies that results are equal but doesn't confirm that storage wasn't written twice. Consider verifying that setItem was called only once (on the first call).

   it("returns same object if no changes (optimization)", () => {
-    const { cleanup } = setup();
+    const { cleanup, mockLocalStorage } = setup();

     const wallet = {
       address: WALLET_ADDRESS_1,
       cert: "cert1",
       certKey: "key1",
       userId: USER_ID_1,
       creditAmount: 100,
       isTrialing: false,
       selected: true
     };

     const result1 = updateStorageManagedWallet(wallet);
+    const callCountAfterFirst = mockLocalStorage.setItem.mock.calls.length;
     const result2 = updateStorageManagedWallet(wallet);

     expect(result1).toEqual(result2);
+    expect(mockLocalStorage.setItem.mock.calls.length).toBe(callCountAfterFirst);

     cleanup();
   });
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 143e709 and f32c746.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (3)
  • apps/deploy-web/src/hooks/useManagedWallet.ts (3 hunks)
  • apps/deploy-web/src/utils/walletUtils.spec.ts (1 hunks)
  • apps/deploy-web/src/utils/walletUtils.ts (4 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

**/*.{ts,tsx,js}: Never use type any or cast to type any. Always define the proper TypeScript types.
Never use deprecated methods from libraries.
Don't add unnecessary comments to the code.

Files:

  • apps/deploy-web/src/hooks/useManagedWallet.ts
  • apps/deploy-web/src/utils/walletUtils.ts
  • apps/deploy-web/src/utils/walletUtils.spec.ts
**/*.spec.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/no-jest-mock.mdc)

Don't use jest.mock() in test files. Instead, use jest-mock-extended to create mocks and pass mocks as dependencies to the service under test

Use setup function instead of beforeEach in test files. The setup function must be at the bottom of the root describe block, should create an object under test and return it, accept a single parameter with inline type definition, avoid shared state, and not have a specified return type.

**/*.spec.{ts,tsx}: Use <Subject>.name in the root describe suite description instead of hardcoded class/service name strings to enable automated refactoring tools to find all references
Use either a method name or a condition starting with 'when' for nested suite descriptions in tests
Use present simple, 3rd person singular for test descriptions without prepending 'should'

Files:

  • apps/deploy-web/src/utils/walletUtils.spec.ts
🧠 Learnings (2)
📓 Common learnings
Learnt from: jzsfkzm
Repo: akash-network/console PR: 2039
File: apps/deploy-web/tests/ui/change-wallets.spec.ts:4-10
Timestamp: 2025-10-15T16:39:55.348Z
Learning: In the Akash Console E2E tests using the context-with-extension fixture, the first wallet is automatically created during fixture setup via `importWalletToLeap` in `apps/deploy-web/tests/ui/fixture/wallet-setup.ts`, so tests that call `frontPage.createWallet()` are creating a second wallet to test wallet switching functionality.
📚 Learning: 2025-10-15T16:39:55.348Z
Learnt from: jzsfkzm
Repo: akash-network/console PR: 2039
File: apps/deploy-web/tests/ui/change-wallets.spec.ts:4-10
Timestamp: 2025-10-15T16:39:55.348Z
Learning: In the Akash Console E2E tests using the context-with-extension fixture, the first wallet is automatically created during fixture setup via `importWalletToLeap` in `apps/deploy-web/tests/ui/fixture/wallet-setup.ts`, so tests that call `frontPage.createWallet()` are creating a second wallet to test wallet switching functionality.

Applied to files:

  • apps/deploy-web/src/utils/walletUtils.spec.ts
🧬 Code graph analysis (2)
apps/deploy-web/src/utils/walletUtils.ts (1)
packages/network-store/src/network.store.ts (1)
  • selectedNetworkId (56-58)
apps/deploy-web/src/utils/walletUtils.spec.ts (1)
apps/deploy-web/src/utils/walletUtils.ts (10)
  • getStorageWallets (114-150)
  • LocalWallet (27-27)
  • getSelectedStorageWallet (29-33)
  • getStorageManagedWallet (35-58)
  • updateStorageManagedWallet (60-86)
  • updateStorageWallets (164-183)
  • updateWallet (152-162)
  • deleteWalletFromStorage (185-203)
  • deleteManagedWalletFromStorage (88-112)
  • ensureUserManagedWalletOwnership (209-221)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (16)
  • GitHub Check: validate (apps/notifications) / validate-unsafe
  • GitHub Check: validate (packages) / validate-unsafe
  • GitHub Check: validate / validate-app
  • GitHub Check: validate / validate-app
  • GitHub Check: test-build
  • GitHub Check: test-build
  • GitHub Check: validate / validate-app
  • GitHub Check: test-build
  • GitHub Check: validate / validate-app
  • GitHub Check: test-build
  • GitHub Check: test-build
  • GitHub Check: validate / validate-app
  • GitHub Check: validate / validate-app
  • GitHub Check: test-build
  • GitHub Check: test-build
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (8)
apps/deploy-web/src/hooks/useManagedWallet.ts (2)

10-10: LGTM!

The import changes align with the refactored storage model—removing the unused deleteManagedWalletFromStorage and adding the per-user storage utilities.


40-50: Effect dependencies include object reference that may trigger unnecessary runs.

The wallet dependency is a memoized object (line 23), but its identity changes whenever queried or created changes. Since isEqual checks happen inside updateStorageManagedWallet, this should be safe, but worth noting.

The removal of the deletion branch that ran when !wallet && wasFetched is the key fix—this prevents accidental wallet deletion on disconnect/logout scenarios.

apps/deploy-web/src/utils/walletUtils.ts (3)

35-58: LGTM!

The per-user, per-network managed wallet retrieval is well-implemented with proper null checks and graceful JSON error handling.


122-147: LGTM!

Good defensive coding with try/catch around JSON parsing and proper fallback to original wallets on error.


209-220: LGTM!

The ownership enforcement correctly selects the managed wallet and deselects custodial wallets for the same network.

apps/deploy-web/src/utils/walletUtils.spec.ts (3)

18-26: LGTM!

Good test organization with meaningful constants for test data. The root describe uses a module name string which is acceptable for utility modules (as opposed to classes where .name would be preferred).


943-1030: Excellent regression test coverage.

These tests directly address the critical bug being fixed—ensuring managed wallets persist across disconnects and multiple users don't interfere with each other's data.


1144-1217: LGTM!

The setup function follows the coding guidelines: placed at the bottom of the describe block, creates isolated state per test via the Map, and returns cleanup function. Good use of jest-mock-extended for the localStorage mock with proper proxy handling for Object.keys() enumeration.

localStorage.setItem(managedWalletsKey, JSON.stringify(existingMap));
}

localStorage.setItem(`${selectedNetworkId}/wallets`, JSON.stringify(custodialWallets));

Check failure

Code scanning / CodeQL

Clear text storage of sensitive information High

This stores sensitive data returned by
an access to localCert
as clear text.
This stores sensitive data returned by
an access to localCert
as clear text.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (3)
apps/deploy-web/src/utils/walletUtils.ts (3)

82-84: Missing try/catch around JSON.parse.

As flagged in previous reviews, if walletsMapStr contains malformed JSON, this will throw an unhandled exception, unlike the read path in getStorageManagedWallet which gracefully handles corrupt data.


101-112: Missing try/catch around JSON.parse.

As flagged in previous reviews, malformed JSON will cause an unhandled exception. This should be wrapped in a try/catch block consistent with the pattern used in getStorageManagedWallet.


174-183: Missing try/catch around JSON.parse.

As flagged in previous reviews, this should handle malformed JSON gracefully consistent with the pattern used in getStorageManagedWallet.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f32c746 and e78e13b.

📒 Files selected for processing (1)
  • apps/deploy-web/src/utils/walletUtils.ts (4 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

**/*.{ts,tsx,js}: Never use type any or cast to type any. Always define the proper TypeScript types.
Never use deprecated methods from libraries.
Don't add unnecessary comments to the code.

Files:

  • apps/deploy-web/src/utils/walletUtils.ts
🧠 Learnings (2)
📓 Common learnings
Learnt from: jzsfkzm
Repo: akash-network/console PR: 2039
File: apps/deploy-web/tests/ui/change-wallets.spec.ts:4-10
Timestamp: 2025-10-15T16:39:55.348Z
Learning: In the Akash Console E2E tests using the context-with-extension fixture, the first wallet is automatically created during fixture setup via `importWalletToLeap` in `apps/deploy-web/tests/ui/fixture/wallet-setup.ts`, so tests that call `frontPage.createWallet()` are creating a second wallet to test wallet switching functionality.
📚 Learning: 2025-10-31T11:26:42.138Z
Learnt from: stalniy
Repo: akash-network/console PR: 2138
File: apps/api/src/billing/lib/batch-signing-client/batch-signing-client.service.ts:379-382
Timestamp: 2025-10-31T11:26:42.138Z
Learning: In TypeScript/JavaScript, the pattern of checking a cached value and then performing an async operation to fetch it without proper synchronization is race condition unsafe:
```typescript
private async getAddress() {
  if (!this.address) {
    this.address = await this.wallet.getFirstAddress();
  }
  return this.address;
}
```
Multiple concurrent calls can all pass the `if (!this.address)` check before any of them sets the value, leading to duplicate async operations. This should be flagged as a race condition. Proper synchronization (mutex, atomic promise caching, or guaranteed single-threaded execution) is required.

Applied to files:

  • apps/deploy-web/src/utils/walletUtils.ts
🧬 Code graph analysis (1)
apps/deploy-web/src/utils/walletUtils.ts (4)
apps/stats-web/src/config/env-config.schema.ts (1)
  • networkId (4-4)
apps/notifications/src/interfaces/rest/services/auth/auth.service.ts (1)
  • userId (18-25)
packages/network-store/src/network.store.ts (1)
  • selectedNetworkId (56-58)
apps/stats-web/src/store/network.store.ts (1)
  • networkStore (6-10)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (14)
  • GitHub Check: validate / validate-app
  • GitHub Check: test-build
  • GitHub Check: validate / validate-app
  • GitHub Check: test-build
  • GitHub Check: validate (apps/deploy-web) / validate-unsafe
  • GitHub Check: validate (apps/provider-console) / validate-unsafe
  • GitHub Check: validate (apps/api) / validate-unsafe
  • GitHub Check: test-build
  • GitHub Check: test-build
  • GitHub Check: test-build
  • GitHub Check: validate / validate-app
  • GitHub Check: test-build
  • GitHub Check: test-build
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (3)
apps/deploy-web/src/utils/walletUtils.ts (3)

29-31: LGTM!

Clean helper function for generating per-network storage keys.


39-62: LGTM!

The function correctly implements defensive guards (userId presence, window availability) and gracefully handles malformed JSON with try/catch.


126-153: LGTM!

The function correctly merges managed and custodial wallets with proper error handling. The logic ensures managed wallet selection takes precedence and gracefully falls back to custodial wallets on parse errors.

stalniy
stalniy previously approved these changes Dec 3, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b3bba1f and d01ed36.

📒 Files selected for processing (1)
  • apps/deploy-web/src/utils/walletUtils.ts (5 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

**/*.{ts,tsx,js}: Never use type any or cast to type any. Always define the proper TypeScript types.
Never use deprecated methods from libraries.
Don't add unnecessary comments to the code.

Files:

  • apps/deploy-web/src/utils/walletUtils.ts
🧠 Learnings (3)
📓 Common learnings
Learnt from: jzsfkzm
Repo: akash-network/console PR: 2039
File: apps/deploy-web/tests/ui/change-wallets.spec.ts:4-10
Timestamp: 2025-10-15T16:39:55.348Z
Learning: In the Akash Console E2E tests using the context-with-extension fixture, the first wallet is automatically created during fixture setup via `importWalletToLeap` in `apps/deploy-web/tests/ui/fixture/wallet-setup.ts`, so tests that call `frontPage.createWallet()` are creating a second wallet to test wallet switching functionality.
📚 Learning: 2025-10-31T11:26:42.138Z
Learnt from: stalniy
Repo: akash-network/console PR: 2138
File: apps/api/src/billing/lib/batch-signing-client/batch-signing-client.service.ts:379-382
Timestamp: 2025-10-31T11:26:42.138Z
Learning: In TypeScript/JavaScript, the pattern of checking a cached value and then performing an async operation to fetch it without proper synchronization is race condition unsafe:
```typescript
private async getAddress() {
  if (!this.address) {
    this.address = await this.wallet.getFirstAddress();
  }
  return this.address;
}
```
Multiple concurrent calls can all pass the `if (!this.address)` check before any of them sets the value, leading to duplicate async operations. This should be flagged as a race condition. Proper synchronization (mutex, atomic promise caching, or guaranteed single-threaded execution) is required.

Applied to files:

  • apps/deploy-web/src/utils/walletUtils.ts
📚 Learning: 2025-10-15T16:39:55.348Z
Learnt from: jzsfkzm
Repo: akash-network/console PR: 2039
File: apps/deploy-web/tests/ui/change-wallets.spec.ts:4-10
Timestamp: 2025-10-15T16:39:55.348Z
Learning: In the Akash Console E2E tests using the context-with-extension fixture, the first wallet is automatically created during fixture setup via `importWalletToLeap` in `apps/deploy-web/tests/ui/fixture/wallet-setup.ts`, so tests that call `frontPage.createWallet()` are creating a second wallet to test wallet switching functionality.

Applied to files:

  • apps/deploy-web/src/utils/walletUtils.ts
🧬 Code graph analysis (1)
apps/deploy-web/src/utils/walletUtils.ts (2)
packages/logging/src/services/logger/logger.service.ts (1)
  • error (141-143)
apps/deploy-web/src/services/app-di-container/browser-di-container.ts (1)
  • services (24-59)
🪛 GitHub Check: CodeQL
apps/deploy-web/src/utils/walletUtils.ts

[failure] 220-220: Clear text storage of sensitive information
This stores sensitive data returned by an access to localCert as clear text.
This stores sensitive data returned by an access to localCert as clear text.

🪛 GitHub Check: validate / validate-app
apps/deploy-web/src/utils/walletUtils.ts

[failure] 5-5:
Dependency cycle detected

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: validate (apps/deploy-web) / validate-unsafe
  • GitHub Check: test-build
  • GitHub Check: test-build
  • GitHub Check: validate / validate-app
  • GitHub Check: test-build
  • GitHub Check: validate / validate-app
  • GitHub Check: test-build
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (2)
apps/deploy-web/src/utils/walletUtils.ts (2)

220-220: Acknowledge: Sensitive data stored in clear text (CodeQL finding).

CodeQL flagged that certificate data (cert, certKey) is stored in localStorage as clear text. While localStorage is inherently unencrypted and this may be an accepted trade-off for browser-based wallet functionality, consider whether:

  1. The certificate data can be encrypted before storage
  2. A more secure storage mechanism (e.g., IndexedDB with encryption) would be appropriate
  3. The security implications are documented and accepted

If this is accepted risk, consider adding a comment or documentation noting the security trade-off.


247-261: LGTM!

The refactored implementation now properly handles managed wallet selection by iterating over all wallets and ensuring only the matching managed wallet has selected: true. This addresses the previous concern about multiple managed wallets potentially having selected: true.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
apps/deploy-web/src/utils/walletUtils.ts (1)

9-10: Consider using the DI container for service instances.

Creating new service instances directly works but means this module has separate instances of ErrorHandlerService rather than sharing the application's singleton. If ErrorHandlerService has any configuration or shared state, this could lead to inconsistencies.

If the circular dependency with browser-di-container can be resolved (e.g., by lazy import or moving the error reporting to a callback pattern), prefer retrieving these services from the container.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d01ed36 and 5f7d051.

📒 Files selected for processing (1)
  • apps/deploy-web/src/utils/walletUtils.ts (5 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

**/*.{ts,tsx,js}: Never use type any or cast to type any. Always define the proper TypeScript types.
Never use deprecated methods from libraries.
Don't add unnecessary comments to the code.

Files:

  • apps/deploy-web/src/utils/walletUtils.ts
🧠 Learnings (3)
📓 Common learnings
Learnt from: jzsfkzm
Repo: akash-network/console PR: 2039
File: apps/deploy-web/tests/ui/change-wallets.spec.ts:4-10
Timestamp: 2025-10-15T16:39:55.348Z
Learning: In the Akash Console E2E tests using the context-with-extension fixture, the first wallet is automatically created during fixture setup via `importWalletToLeap` in `apps/deploy-web/tests/ui/fixture/wallet-setup.ts`, so tests that call `frontPage.createWallet()` are creating a second wallet to test wallet switching functionality.
📚 Learning: 2025-10-31T11:26:42.138Z
Learnt from: stalniy
Repo: akash-network/console PR: 2138
File: apps/api/src/billing/lib/batch-signing-client/batch-signing-client.service.ts:379-382
Timestamp: 2025-10-31T11:26:42.138Z
Learning: In TypeScript/JavaScript, the pattern of checking a cached value and then performing an async operation to fetch it without proper synchronization is race condition unsafe:
```typescript
private async getAddress() {
  if (!this.address) {
    this.address = await this.wallet.getFirstAddress();
  }
  return this.address;
}
```
Multiple concurrent calls can all pass the `if (!this.address)` check before any of them sets the value, leading to duplicate async operations. This should be flagged as a race condition. Proper synchronization (mutex, atomic promise caching, or guaranteed single-threaded execution) is required.

Applied to files:

  • apps/deploy-web/src/utils/walletUtils.ts
📚 Learning: 2025-10-15T16:39:55.348Z
Learnt from: jzsfkzm
Repo: akash-network/console PR: 2039
File: apps/deploy-web/tests/ui/change-wallets.spec.ts:4-10
Timestamp: 2025-10-15T16:39:55.348Z
Learning: In the Akash Console E2E tests using the context-with-extension fixture, the first wallet is automatically created during fixture setup via `importWalletToLeap` in `apps/deploy-web/tests/ui/fixture/wallet-setup.ts`, so tests that call `frontPage.createWallet()` are creating a second wallet to test wallet switching functionality.

Applied to files:

  • apps/deploy-web/src/utils/walletUtils.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: validate (apps/deploy-web) / validate-unsafe
  • GitHub Check: test-build
  • GitHub Check: test-build
  • GitHub Check: validate / validate-app
  • GitHub Check: test-build
  • GitHub Check: validate / validate-app
  • GitHub Check: test-build
  • GitHub Check: validate / validate-app
  • GitHub Check: test-build
  • GitHub Check: test-build
  • GitHub Check: test-build
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (8)
apps/deploy-web/src/utils/walletUtils.ts (8)

44-70: LGTM!

The function correctly guards against missing parameters and SSR execution, handles malformed localStorage data gracefully with error reporting, and implements the new per-network lookup pattern.


72-111: LGTM!

The function correctly implements the per-network map update pattern with proper error handling, graceful fallbacks for malformed data, and preserves the selected state logic from the previous wallet.


113-148: LGTM!

The deletion logic correctly handles the per-network map, gracefully handles parse errors by removing the corrupted key, and properly cleans up both the map entry and the underlying wallet data.


186-191: LGTM!

The selected managed wallet handling correctly ensures only one wallet is selected at a time, deselecting all others when a managed wallet is marked as selected.


158-170: LGTM!

Custodial wallets are now properly protected with try/catch around JSON.parse, reporting errors and gracefully falling back to an empty array on malformed data.


220-252: LGTM!

The function correctly splits managed and custodial wallets, updates each storage location separately with proper error handling, and gracefully handles malformed data in the managed wallets map.


278-293: LGTM!

This function correctly addresses the previous concern about multiple managed wallets being selected simultaneously. The logic ensures only the managed wallet matching the userId is selected, with all other wallets (both managed and custodial) deselected.


180-181: The filter condition on line 181 is correct and intentional — it's not a bug. The test case "removes duplicate managed wallets from old storage" (lines 107-132 of the spec file) explicitly validates this behavior.

The filter !w.isManaged || !managedWalletAddresses.has(w.address) correctly handles migration from the legacy storage format where managed wallets were stored alongside custodial wallets. When managed wallets are moved to the new ${networkId}/managed-wallets storage, the filter prevents duplicates by removing managed wallets that now exist in the new location.

The suggested simplification to !w.isManaged would break this deduplication logic and reintroduce duplicates during migration.

@baktun14 baktun14 merged commit f771e34 into main Dec 3, 2025
66 of 67 checks passed
@baktun14 baktun14 deleted the fix/managed-user-local-storage branch December 3, 2025 15:43
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.

local storage issue when connection drops, only for the managed wallets

2 participants

Comments