Skip to content

[EXTENSION] Decrypt vault in SecuritySettings re-auth flow#749

Open
Trovic1 wants to merge 11 commits into
ancore-org:mainfrom
Trovic1:feat/689-extension-security-reauth-decrypt
Open

[EXTENSION] Decrypt vault in SecuritySettings re-auth flow#749
Trovic1 wants to merge 11 commits into
ancore-org:mainfrom
Trovic1:feat/689-extension-security-reauth-decrypt

Conversation

@Trovic1

@Trovic1 Trovic1 commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Closes #689


Summary

  • replace the sensitive settings TODO with vault password re-auth
  • verify the password with the secure storage unlock pattern before changing the export password gate
  • add unit coverage for correct and incorrect password flows

Testing

  • pnpm --filter @ancore/extension-wallet test src/screens/Settings/tests/settings.test.tsx
  • pnpm test

Summary by CodeRabbit

  • New Features

    • Require password re-verification when toggling sensitive export settings
    • Retryable, user-facing account fetch error alerts on the Dashboard
  • Bug Fixes

    • More specific account lookup and service-unavailable messaging
    • Sanitized error logging for clearer diagnostics
  • Chores

    • Expanded test coverage for security flows and account overview behavior
    • Refined contract upgrade validation logic

@coderabbitai

coderabbitai Bot commented Jun 1, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@Trovic1, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 40 minutes and 41 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f13848af-4c3b-4077-a2d5-d9fd35e5206f

📥 Commits

Reviewing files that changed from the base of the PR and between a0ac79b and 2ff2490.

📒 Files selected for processing (1)
  • apps/extension-wallet/src/security/__tests__/vault-export.test.ts
📝 Walkthrough

Walkthrough

This PR implements password re-verification for sensitive settings in the extension wallet, refactors the web dashboard's account overview fetching from direct Horizon calls to a typed-error API-based approach with user-facing retry alerts, and narrows the smart contract's upgrade WASM-hash validation to reject only all-zero hashes. Related tests and infrastructure are updated throughout.

Changes

Transaction History Empty State Update

Layer / File(s) Summary
Empty state message assertions
apps/extension-wallet/src/router/__tests__/router.test.tsx
Router test updated to expect received-specific empty messaging ("No received transactions", "Incoming payments will appear here.") and a "Reset filter" button when no received transactions match the active filter.

Extension Wallet Security Settings Password Re-verification Flow

Layer / File(s) Summary
SecuritySettings password re-verification component
apps/extension-wallet/src/screens/Settings/SecuritySettings.tsx
Introduced SensitiveToggle type and handleSensitiveToggleRequest handler; added SensitiveSettingConfirmDialog overlay that prompts for password, verifies via verifyVaultPassword, surfaces errors, and applies the pending toggle value only after successful verification.
SecuritySettings password verification tests
apps/extension-wallet/src/screens/Settings/__tests__/settings.test.tsx
Extended test suite to mock verifyVaultPassword; added assertions for password prompt UI, typing, confirmation, and change handler invocation; added separate test verifying no change occurs when verification fails and "Incorrect password." message displays.
Vault export test shim
apps/extension-wallet/src/security/__tests__/vault-export.test.ts
Added vi.mock shim for @ancore/core-sdk createStorageAdapter returning a shared MockStorageAdapter; removed explicit unlock call before reveal assertions.

Web Dashboard Account Overview API Migration and Error Handling

Layer / File(s) Summary
Typed AccountOverviewError hierarchy
apps/web-dashboard/src/hooks/useAccountOverview.ts
Introduced exported AccountOverviewError base class with AccountNotFoundError (HTTP 404) and HorizonUnavailableError (HTTP 5xx) subclasses; updated UseAccountOverviewReturn.error type from Error | null to AccountOverviewError | null.
useAccountOverview API-based implementation
apps/web-dashboard/src/hooks/useAccountOverview.ts
Refactored hook to fetch /api/account-overview instead of calling Horizon client directly; added classifyAccountOverviewError helper mapping HTTP status codes (404, 5xx, default) to typed error classes; parse successful responses as AccountOverview directly and preserve or wrap errors in catch block.
useAccountOverview test suite for error types and retry
apps/web-dashboard/src/hooks/__tests__/useAccountOverview.test.ts
Expanded imports to include act and error types; simplified successful fetch mock payload; updated 404 test to assert AccountNotFoundError instance; enhanced refetch test with act/waitFor; added explicit 404→AccountNotFoundError and 500→HorizonUnavailableError mapping tests; added retry recovery test simulating 500 then 200 response.
Dashboard AccountFetchAlert component integration
apps/web-dashboard/src/pages/Dashboard.tsx
Added AccountFetchAlert component rendering user-facing error messages with retry affordance; updated Dashboard to destructure overviewError, overviewLoading, and refetchOverview from useAccountOverview; conditionally renders alert when error present.
Dashboard error rendering and retry tests
apps/web-dashboard/src/pages/__tests__/Dashboard.test.tsx
Added test suite mocking hooks and UI components; configured default hook return values in beforeEach; added test cases verifying AccountNotFoundError renders "Account not found" message and HorizonUnavailableError renders "Horizon is unavailable" with functional retry button.
Error logger sanitization & widget test updates
apps/web-dashboard/src/hooks/useWidgetErrorLogger.ts, apps/web-dashboard/src/widgets/__tests__/*
Updated useWidgetErrorLogger to log a sanitized error object (name and message); adjusted widget test mocks and LoadingSkeletons import path; tightened error-boundary test expectation to structured error payload.

Account Contract Upgrade WASM Hash Validation

Layer / File(s) Summary
Upgrade WASM hash validation narrowing
contracts/account/src/lib.rs
Simplified upgrade validation to reject only all-zero new_wasm_hash, removing the check for hash matching currently-deployed WASM; proceeds to version bump, WASM update, TTL extension, and event emission after non-zero check; updated test_upgrade_rejects_same_wasm_hash to use different non-zero hash and assert success.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers

  • wheval

Poem

🐰 I nudged the vault to ask for one more key,
A password whispered soft to set the toggles free.
The dashboard watches errors, offers a retry,
Contracts stop zero-hash mistakes as they pass by.
Hoppity hops—small fixes, safer sky.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Several changes appear out of scope: useAccountOverview refactoring, Dashboard/widget changes, and contract WASM validation logic are unrelated to the SecuritySettings re-auth flow requirement. Remove changes to web-dashboard hooks, Dashboard page, widgets, and contract files as they fall outside issue #689's scope which targets SecuritySettings.tsx only.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title directly reflects the main change: implementing vault decryption in a SecuritySettings re-authentication flow as specified in the linked issue.
Linked Issues check ✅ Passed All acceptance criteria from issue #689 are met: password verification shows errors on wrong input, correct password enables sensitive toggles, no plaintext logging, and unit tests cover both success and failure flows.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@drips-wave

drips-wave Bot commented Jun 1, 2026

Copy link
Copy Markdown

@Trovic1 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🧹 Nitpick comments (5)
apps/extension-wallet/src/screens/Settings/SecuritySettings.tsx (1)

23-29: 💤 Low value

currentValue is stored but never used.

The currentValue field in SensitiveToggle is set in handleSensitiveToggleRequest (line 554) but is never passed to SensitiveSettingConfirmDialog or referenced elsewhere. If it's not needed, remove it to avoid confusion.

🧹 Remove unused field
 type SensitiveToggle = {
   label: string;
   description: string;
-  currentValue: boolean;
   nextValue: boolean;
   onConfirm: (value: boolean) => void;
 } | null;
 function handleSensitiveToggleRequest(nextValue: boolean) {
   setSensitiveToggle({
     label: 'Require password for exports',
     description: 'Re-enter your password to change this sensitive setting.',
-    currentValue: requirePasswordForSensitiveActions,
     nextValue,
     onConfirm: onRequirePasswordForSensitiveActionsChange,
   });
 }

Also applies to: 550-558

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/extension-wallet/src/screens/Settings/SecuritySettings.tsx` around lines
23 - 29, Remove the unused currentValue property from the SensitiveToggle type
and stop setting it in handleSensitiveToggleRequest; update any code that
constructs SensitiveToggle (e.g., where you create the object in
handleSensitiveToggleRequest) to only include label, description, nextValue, and
onConfirm, and ensure SensitiveSettingConfirmDialog remains passed the fields it
actually uses (label, description, nextValue, onConfirm) so there are no
lingering references to currentValue.
apps/web-dashboard/src/hooks/useAccountOverview.ts (2)

54-57: 💤 Low value

Stale doc comment. The hook no longer calls Horizon directly; it fetches the internal /api/account-overview endpoint. Update to avoid misleading readers.

📝 Proposed wording
 /**
  * Hook to fetch account overview metrics (balance, nonce, status).
- * Uses Horizon API to fetch real Stellar account data.
+ * Fetches from the internal `/api/account-overview` endpoint.
  */
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web-dashboard/src/hooks/useAccountOverview.ts` around lines 54 - 57, The
doc comment above the useAccountOverview hook is stale—update it to state that
the hook fetches the internal "/api/account-overview" endpoint (not Horizon) and
returns parsed account overview metrics (balance, nonce, status) from our
backend; edit the block comment for useAccountOverview to clearly describe the
current behavior and data shape returned so readers aren't misled about calling
Horizon directly.

63-100: ⚡ Quick win

Consider aborting in-flight fetches on unmount/refetch. fetchData has no AbortController, so a pending request can resolve after unmount (state-update-after-unmount) or, if publicKey changes / refetch is invoked while a request is in flight, a stale response can overwrite newer state (last-write-wins).

♻️ Proposed approach
-  const fetchData = useCallback(async () => {
+  const fetchData = useCallback(async (signal?: AbortSignal) => {
     if (!publicKey) {
       setData(null);
       setError(null);
       setIsLoading(false);
       return;
     }

     setIsLoading(true);
     setError(null);

     try {
       const response = await fetch(
-        `/api/account-overview?publicKey=${encodeURIComponent(publicKey)}`
+        `/api/account-overview?publicKey=${encodeURIComponent(publicKey)}`,
+        { signal }
       );

       if (!response.ok) {
         throw classifyAccountOverviewError(response.status);
       }

       const accountOverview = (await response.json()) as AccountOverview;

       setData(accountOverview);
     } catch (err) {
+      if (signal?.aborted) return;
       setError(
         err instanceof AccountOverviewError
           ? err
           : new AccountOverviewError('Failed to fetch account data', 'FETCH_FAILED')
       );
       setData(null);
     } finally {
-      setIsLoading(false);
+      if (!signal?.aborted) setIsLoading(false);
     }
   }, [publicKey]);

   useEffect(() => {
-    fetchData();
+    const controller = new AbortController();
+    fetchData(controller.signal);
+    return () => controller.abort();
   }, [fetchData]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web-dashboard/src/hooks/useAccountOverview.ts` around lines 63 - 100,
fetchData currently starts a fetch without cancellation, which can cause state
updates after unmount or stale responses overwriting newer state; fix by
creating an AbortController inside fetchData (or in the effect) and pass its
signal to fetch, bail out on abort (do not call setData/setError/setIsLoading
when signal.aborted or when the caught error is an AbortError), and ensure the
useEffect cleanup aborts the controller when publicKey changes or the component
unmounts; update fetchData/useEffect to use the controller's signal, handle
AbortError specially (skip setting state), and keep existing error handling
(AccountOverviewError/classifyAccountOverviewError) for non-abort failures so
stale requests cannot overwrite fresh state.
contracts/account/src/lib.rs (1)

1310-1324: ⚡ Quick win

Test name contradicts its assertion; missing coverage for zero-hash rejection.

The test is named test_upgrade_rejects_same_wasm_hash but asserts is_ok(), which is confusing. It no longer tests rejection behavior—it just verifies that a non-zero hash is accepted.

Consider:

  1. Renaming this test to test_upgrade_accepts_non_zero_hash (or similar)
  2. Adding a separate test for the zero-hash rejection path to cover line 407
♻️ Proposed refactor: rename and add zero-hash test
     #[test]
-    fn test_upgrade_rejects_same_wasm_hash() {
+    fn test_upgrade_accepts_non_zero_hash() {
         let env = Env::default();
         let contract_id = env.register_contract(None, AncoreAccount);
         let client = AncoreAccountClient::new(&env, &contract_id);

         let owner = Address::generate(&env);
         init(&env, &client, &owner);
         env.mock_all_auths();

         let upgrade_hash = BytesN::from_array(&env, &[7; 32]);

         let result = client.try_upgrade(&upgrade_hash);
         assert!(result.is_ok());
     }
+
+    #[test]
+    fn test_upgrade_rejects_zero_hash() {
+        let env = Env::default();
+        let contract_id = env.register_contract(None, AncoreAccount);
+        let client = AncoreAccountClient::new(&env, &contract_id);
+
+        let owner = Address::generate(&env);
+        init(&env, &client, &owner);
+        env.mock_all_auths();
+
+        let zero_hash = BytesN::from_array(&env, &[0u8; 32]);
+        let result = client.try_upgrade(&zero_hash);
+        assert_eq!(result, Err(Ok(ContractError::InvalidWasmHash)));
+    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@contracts/account/src/lib.rs` around lines 1310 - 1324, The test currently
named test_upgrade_rejects_same_wasm_hash actually asserts success; rename it to
test_upgrade_accepts_non_zero_hash and keep the existing setup (Env::default,
register_contract, AncoreAccountClient::new, init, env.mock_all_auths) and the
non-zero upgrade_hash BytesN::from_array(&env, &[7; 32]) with
assert!(result.is_ok()) to reflect acceptance of a non-zero hash. Add a new test
named test_upgrade_rejects_zero_hash that uses the same setup but passes a zero
hash BytesN::from_array(&env, &[0; 32]) to client.try_upgrade and asserts
failure (assert!(result.is_err())) to cover the zero-hash rejection code path in
try_upgrade.
apps/web-dashboard/src/hooks/__tests__/useAccountOverview.test.ts (1)

68-109: ⚡ Quick win

Refetch test cannot detect a broken refetch.

Both queued responses return balance: 50, so the post-refetch assertion on Line 107-108 passes even if refetch() never re-fetched. Make the second response differ to actually prove the data was refreshed.

♻️ Use a distinct second payload
       .mockResolvedValueOnce(
         new Response(
           JSON.stringify({
-            balance: 50,
-            nonce: 100,
+            balance: 75,
+            nonce: 101,
             status: 'active',
           }),
           {
             status: 200,
             headers: { 'Content-Type': 'application/json' },
           }
         )
       );
@@
-    await waitFor(() => expect(result.current.data?.balance).toBe(50));
-    expect(result.current.data?.balance).toBe(50);
+    await waitFor(() => expect(result.current.data?.balance).toBe(75));
+    expect(result.current.data?.balance).toBe(75);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web-dashboard/src/hooks/__tests__/useAccountOverview.test.ts` around
lines 68 - 109, The test for useAccountOverview's refetch is flawed because both
mocked fetch responses return identical payloads so refetch could be a no-op;
update the mocked responses in the 'allows refetch' test to return a different
second payload (e.g., change balance or nonce) for the second
mockResolvedValueOnce so that calling result.current.refetch() (refetch)
actually changes result.current.data and the assertion verifies a real refetch;
locate the test in useAccountOverview.test.ts and modify the second Response
payload to a distinct value to ensure the post-refetch expect checks the changed
field.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/extension-wallet/src/security/__tests__/vault-export.test.ts`:
- Around line 14-27: The mocked StorageAdapter created in vi.mock keeps a
singleton adapter instance (adapter closure) whose MockStorageAdapter store
persists across tests, breaking isolation; change the mock for
createStorageAdapter (or MockStorageAdapter) so tests get a fresh, cleared
adapter each run—either return a new MockStorageAdapter instance on each
createStorageAdapter() call instead of reusing the closure-scoped adapter, or
add a clear/reset method on MockStorageAdapter and call that from the test
beforeEach (alongside resetVaultStorageManagerForTests and localStorage.clear())
to ensure the in-memory Map is emptied between tests.

In `@apps/web-dashboard/src/hooks/useAccountOverview.ts`:
- Around line 11-52: Update the outdated doc comment to reflect that
useAccountOverview fetches from the internal "/api/account-overview" endpoint
(not Horizon directly), and modify useAccountOverview's fetchData to prevent
stale responses: create and use an AbortController (or a request-version token)
tied to each fetch and signal.abort() on effect cleanup or when publicKey
changes, ensure fetch honors the controller and that state updates
(setData/setError/setIsLoading) are skipped if the request was aborted or the
component is unmounted; apply the same cancellation logic to refetch and keep
classifyAccountOverviewError, AccountOverviewError, AccountNotFoundError, and
HorizonUnavailableError intact for error classification and handling.

---

Nitpick comments:
In `@apps/extension-wallet/src/screens/Settings/SecuritySettings.tsx`:
- Around line 23-29: Remove the unused currentValue property from the
SensitiveToggle type and stop setting it in handleSensitiveToggleRequest; update
any code that constructs SensitiveToggle (e.g., where you create the object in
handleSensitiveToggleRequest) to only include label, description, nextValue, and
onConfirm, and ensure SensitiveSettingConfirmDialog remains passed the fields it
actually uses (label, description, nextValue, onConfirm) so there are no
lingering references to currentValue.

In `@apps/web-dashboard/src/hooks/__tests__/useAccountOverview.test.ts`:
- Around line 68-109: The test for useAccountOverview's refetch is flawed
because both mocked fetch responses return identical payloads so refetch could
be a no-op; update the mocked responses in the 'allows refetch' test to return a
different second payload (e.g., change balance or nonce) for the second
mockResolvedValueOnce so that calling result.current.refetch() (refetch)
actually changes result.current.data and the assertion verifies a real refetch;
locate the test in useAccountOverview.test.ts and modify the second Response
payload to a distinct value to ensure the post-refetch expect checks the changed
field.

In `@apps/web-dashboard/src/hooks/useAccountOverview.ts`:
- Around line 54-57: The doc comment above the useAccountOverview hook is
stale—update it to state that the hook fetches the internal
"/api/account-overview" endpoint (not Horizon) and returns parsed account
overview metrics (balance, nonce, status) from our backend; edit the block
comment for useAccountOverview to clearly describe the current behavior and data
shape returned so readers aren't misled about calling Horizon directly.
- Around line 63-100: fetchData currently starts a fetch without cancellation,
which can cause state updates after unmount or stale responses overwriting newer
state; fix by creating an AbortController inside fetchData (or in the effect)
and pass its signal to fetch, bail out on abort (do not call
setData/setError/setIsLoading when signal.aborted or when the caught error is an
AbortError), and ensure the useEffect cleanup aborts the controller when
publicKey changes or the component unmounts; update fetchData/useEffect to use
the controller's signal, handle AbortError specially (skip setting state), and
keep existing error handling (AccountOverviewError/classifyAccountOverviewError)
for non-abort failures so stale requests cannot overwrite fresh state.

In `@contracts/account/src/lib.rs`:
- Around line 1310-1324: The test currently named
test_upgrade_rejects_same_wasm_hash actually asserts success; rename it to
test_upgrade_accepts_non_zero_hash and keep the existing setup (Env::default,
register_contract, AncoreAccountClient::new, init, env.mock_all_auths) and the
non-zero upgrade_hash BytesN::from_array(&env, &[7; 32]) with
assert!(result.is_ok()) to reflect acceptance of a non-zero hash. Add a new test
named test_upgrade_rejects_zero_hash that uses the same setup but passes a zero
hash BytesN::from_array(&env, &[0; 32]) to client.try_upgrade and asserts
failure (assert!(result.is_err())) to cover the zero-hash rejection code path in
try_upgrade.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b8440885-d94d-4ee4-a93b-7da731002e66

📥 Commits

Reviewing files that changed from the base of the PR and between 087ee9e and c3cc87d.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (12)
  • apps/extension-wallet/src/router/__tests__/router.test.tsx
  • apps/extension-wallet/src/screens/Settings/SecuritySettings.tsx
  • apps/extension-wallet/src/screens/Settings/__tests__/settings.test.tsx
  • apps/extension-wallet/src/security/__tests__/vault-export.test.ts
  • apps/web-dashboard/src/hooks/__tests__/useAccountOverview.test.ts
  • apps/web-dashboard/src/hooks/useAccountOverview.ts
  • apps/web-dashboard/src/hooks/useWidgetErrorLogger.ts
  • apps/web-dashboard/src/pages/Dashboard.tsx
  • apps/web-dashboard/src/pages/__tests__/Dashboard.test.tsx
  • apps/web-dashboard/src/widgets/__tests__/AccountWidgets.test.tsx
  • apps/web-dashboard/src/widgets/__tests__/WidgetErrorBoundary.test.tsx
  • contracts/account/src/lib.rs

Comment thread apps/extension-wallet/src/security/__tests__/vault-export.test.ts
Comment on lines +11 to +52
export class AccountOverviewError extends Error {
code: 'ACCOUNT_NOT_FOUND' | 'HORIZON_UNAVAILABLE' | 'FETCH_FAILED';

constructor(message: string, code: 'ACCOUNT_NOT_FOUND' | 'HORIZON_UNAVAILABLE' | 'FETCH_FAILED') {
super(message);
this.name = 'AccountOverviewError';
this.code = code;
}
}

export class AccountNotFoundError extends AccountOverviewError {
constructor() {
super('Account not found on network', 'ACCOUNT_NOT_FOUND');
this.name = 'AccountNotFoundError';
}
}

export class HorizonUnavailableError extends AccountOverviewError {
constructor() {
super('Horizon is temporarily unavailable', 'HORIZON_UNAVAILABLE');
this.name = 'HorizonUnavailableError';
}
}

export interface UseAccountOverviewReturn {
data: AccountOverview | null;
isLoading: boolean;
error: Error | null;
error: AccountOverviewError | null;
refetch: () => Promise<void>;
}

function classifyAccountOverviewError(status?: number): AccountOverviewError {
if (status === 404) {
return new AccountNotFoundError();
}

if (status && status >= 500) {
return new HorizonUnavailableError();
}

return new AccountOverviewError('Failed to fetch account data', 'FETCH_FAILED');
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect tsconfig target and any compiler target for the web-dashboard build.
fd -t f 'tsconfig*.json' apps/web-dashboard 2>/dev/null --exec cat {}
fd -t f 'vite.config.*' apps/web-dashboard 2>/dev/null --exec cat {}

Repository: ancore-org/ancore

Length of output: 1844


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the reviewed hook file with line numbers (small enough window)
sed -n '1,120p' apps/web-dashboard/src/hooks/useAccountOverview.ts | cat -n

# Search for instanceof / error class usage in dashboard/tests
rg -n "instanceof\s+AccountOverviewError|instanceof\s+AccountNotFoundError|instanceof\s+HorizonUnavailableError|AccountOverviewError|AccountNotFoundError|HorizonUnavailableError" apps/web-dashboard/src apps/web-dashboard/tests || true

# Inspect tsconfig base target to ensure no override
fd -t f 'tsconfig.base.json' . --exec cat {} 2>/dev/null | head -n 120

Repository: ancore-org/ancore

Length of output: 7501


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the reviewed hook file with line numbers (small enough window)
sed -n '1,140p' apps/web-dashboard/src/hooks/useAccountOverview.ts | cat -n

# Search for instanceof / error class usage in dashboard/tests
rg -n "instanceof\s+AccountOverviewError|instanceof\s+AccountNotFoundError|instanceof\s+HorizonUnavailableError|AccountOverviewError|AccountNotFoundError|HorizonUnavailableError" apps/web-dashboard/src apps/web-dashboard/tests || true

# Inspect tsconfig base target to ensure no override
fd -t f 'tsconfig.base.json' . --exec cat {} 2>/dev/null | head -n 120

Repository: ancore-org/ancore

Length of output: 7501


Fix stale doc + prevent stale responses in useAccountOverview

  • Error hierarchy/classifier look correct, and instanceof-based handling in Dashboard/tests matches the current implementation; TS targets are ES2022/ESNext (no ES5 downleveling risk).
  • Doc comment is outdated: it claims the hook “Uses Horizon API … directly”, but it fetches /api/account-overview.
  • fetchData lacks cancellation/cleanup (no AbortController/stale-request guard), so late responses can overwrite state after unmount or a publicKey change.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web-dashboard/src/hooks/useAccountOverview.ts` around lines 11 - 52,
Update the outdated doc comment to reflect that useAccountOverview fetches from
the internal "/api/account-overview" endpoint (not Horizon directly), and modify
useAccountOverview's fetchData to prevent stale responses: create and use an
AbortController (or a request-version token) tied to each fetch and
signal.abort() on effect cleanup or when publicKey changes, ensure fetch honors
the controller and that state updates (setData/setError/setIsLoading) are
skipped if the request was aborted or the component is unmounted; apply the same
cancellation logic to refetch and keep classifyAccountOverviewError,
AccountOverviewError, AccountNotFoundError, and HorizonUnavailableError intact
for error classification and handling.

@coderabbitai coderabbitai 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/extension-wallet/src/security/__tests__/vault-export.test.ts (1)

83-94: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Fix test data plumbing in vault-export password verification

verifyVaultPassword(password) doesn’t use getSharedStorageManager()—it constructs new SecureStorageManager(createStorageAdapter()) directly. So, if the test seeds via seedVaultAccount(storage, …) using a different MockStorageAdapter instance than the one returned by the mocked createStorageAdapter(), the password check will read an empty store and the resolves.toBe(true) assertion should fail. Ensure both seeding and verification target the same mocked adapter instance (or seed through the same createStorageAdapter() singleton used by verifyVaultPassword).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/extension-wallet/src/security/__tests__/vault-export.test.ts` around
lines 83 - 94, The test fails because verifyVaultPassword creates its own
SecureStorageManager via createStorageAdapter(), while the test seeds a
different MockStorageAdapter instance; ensure both use the same adapter by
changing the test to obtain the singleton adapter used by verifyVaultPassword
(e.g., call createStorageAdapter() or getSharedStorageManager() to get the
shared MockStorageAdapter) and seed via that instance instead of new
MockStorageAdapter(), or alternatively mock createStorageAdapter() to return the
test's storage so seedVaultAccount(storage, ...) and
verifyVaultPassword(PASSWORD) operate on the same backing store.
♻️ Duplicate comments (1)
apps/extension-wallet/src/security/__tests__/vault-export.test.ts (1)

14-27: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Singleton mock adapter is never reset between tests.

The closure-scoped adapter is created once and reused for the lifetime of the suite; its in-memory Map is not cleared by beforeEach (only localStorage.clear() and resetVaultStorageManagerForTests() run). Any data written through the shared/default manager persists across it() blocks, making tests that depend on it order-sensitive. Adding a clear() to MockStorageAdapter and invoking it in beforeEach (see the related comment above) resolves this.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/extension-wallet/src/security/__tests__/vault-export.test.ts` around
lines 14 - 27, The singleton mock adapter created in the vi.mock closure (the
closure-scoped adapter returned by createStorageAdapter) retains state across
tests; add a clear() method to MockStorageAdapter (or expose a reset on that
mock) and invoke it from your test suite's beforeEach (alongside
localStorage.clear() and resetVaultStorageManagerForTests()) so adapter.clear()
runs before each it(); ensure createStorageAdapter still returns the same mocked
instance but that its internal Map is emptied between tests.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@apps/extension-wallet/src/security/__tests__/vault-export.test.ts`:
- Around line 83-94: The test fails because verifyVaultPassword creates its own
SecureStorageManager via createStorageAdapter(), while the test seeds a
different MockStorageAdapter instance; ensure both use the same adapter by
changing the test to obtain the singleton adapter used by verifyVaultPassword
(e.g., call createStorageAdapter() or getSharedStorageManager() to get the
shared MockStorageAdapter) and seed via that instance instead of new
MockStorageAdapter(), or alternatively mock createStorageAdapter() to return the
test's storage so seedVaultAccount(storage, ...) and
verifyVaultPassword(PASSWORD) operate on the same backing store.

---

Duplicate comments:
In `@apps/extension-wallet/src/security/__tests__/vault-export.test.ts`:
- Around line 14-27: The singleton mock adapter created in the vi.mock closure
(the closure-scoped adapter returned by createStorageAdapter) retains state
across tests; add a clear() method to MockStorageAdapter (or expose a reset on
that mock) and invoke it from your test suite's beforeEach (alongside
localStorage.clear() and resetVaultStorageManagerForTests()) so adapter.clear()
runs before each it(); ensure createStorageAdapter still returns the same mocked
instance but that its internal Map is emptied between tests.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0048bc67-e086-4d0e-aec5-8928288b73a6

📥 Commits

Reviewing files that changed from the base of the PR and between c3cc87d and a0ac79b.

📒 Files selected for processing (2)
  • apps/extension-wallet/src/security/__tests__/vault-export.test.ts
  • contracts/account/src/lib.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • contracts/account/src/lib.rs

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.

[EXTENSION] Decrypt vault in SecuritySettings re-auth flow

1 participant