Skip to content

Feat/switch desktop wallet#568

Merged
Agilulfo1820 merged 73 commits into
mainfrom
feat/switch-desktop-wallet
Dec 22, 2025
Merged

Feat/switch desktop wallet#568
Agilulfo1820 merged 73 commits into
mainfrom
feat/switch-desktop-wallet

Conversation

@Agilulfo1820

@Agilulfo1820 Agilulfo1820 commented Dec 22, 2025

Copy link
Copy Markdown
Member

Summary by CodeRabbit

  • New Features

    • Multi-wallet UI: view/select/add/remove wallets with confirmation, persistent active-wallet handling, desktop vs in-app switch flows, and visual wallet cards showing balances and active state.
    • Connect modal: optional prevent-auto-close behavior.
    • Transient inline feedback and a reusable feedback component for wallet switch notifications.
  • Localization

    • Added wallet-management translations for EN, DE, ES, FR, IT, JA, ZH.

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

@coderabbitai

coderabbitai Bot commented Dec 22, 2025

Copy link
Copy Markdown

Walkthrough

Adds multi‑wallet management and persistence: new SelectWallet and RemoveWalletConfirm flows, WalletCard UI, local wallet storage hook and APIs, storage-aware switching (desktop vs in‑app), modal preventAutoClose control, wallet-switch feedback UI, translations, and types/theme updates.

Changes

Cohort / File(s) Summary
SelectWallet UI & exports
packages/vechain-kit/src/components/AccountModal/Contents/SelectWallet/SelectWalletContent.tsx, packages/vechain-kit/src/components/AccountModal/Contents/SelectWallet/Components/WalletCard.tsx, packages/vechain-kit/src/components/AccountModal/Contents/SelectWallet/RemoveWalletConfirmContent.tsx, packages/vechain-kit/src/components/AccountModal/Contents/SelectWallet/index.ts
New SelectWalletContent, WalletCard, RemoveWalletConfirmContent and re-exports; implements listing, selecting, adding, and removing stored wallets plus navigation between select/remove flows.
AccountModal integration
packages/vechain-kit/src/components/AccountModal/AccountModal.tsx, packages/vechain-kit/src/components/AccountModal/Components/AccountSelector.tsx, packages/vechain-kit/src/components/AccountModal/Contents/Profile/ProfileContent.tsx, packages/vechain-kit/src/components/AccountModal/Contents/index.ts, packages/vechain-kit/src/components/AccountModal/Types/Types.ts
Extended content types (added select-wallet, remove-wallet-confirm, optional switchFeedback); AccountModal and selector/profile components route desktop flows to new select-wallet content and render new variants.
Wallet storage & switching hooks
packages/vechain-kit/src/hooks/api/wallet/useWalletStorage.ts, packages/vechain-kit/src/hooks/api/wallet/useSwitchWallet.ts, packages/vechain-kit/src/hooks/api/wallet/index.ts, packages/vechain-kit/src/hooks/api/wallet/useWallet.ts
New useWalletStorage hook (StoredWallet type + get/save/setActive/remove/initialize); integrated into useSwitchWallet and useWallet, added isInAppBrowser, storage-backed active-wallet logic, wallet_switched event handling, and new public APIs (getStoredWallets, setActiveWallet, removeWallet).
Connect modal & provider
packages/vechain-kit/src/components/ConnectModal/ConnectModal.tsx, packages/vechain-kit/src/components/ConnectModal/Contents/MainContent.tsx, packages/vechain-kit/src/providers/ModalProvider.tsx
Added preventAutoClose prop to ConnectModal/MainContent and ModalProvider APIs; ModalProvider accepts/propagates preventAutoClose via openConnectModal and exposes connectModalPreventAutoClose state; ConnectModal resets content on open.
Feedback & inline UI
packages/vechain-kit/src/components/common/InlineFeedback.tsx, packages/vechain-kit/src/components/common/WalletSwitchFeedback.tsx, packages/vechain-kit/src/components/common/index.ts, packages/vechain-kit/src/providers/FeedbackProvider.tsx
New InlineFeedback transient component, WalletSwitchFeedback wrapper, exports added, and FeedbackProvider with useFeedback hook to show/reset feedback.
Account UI tweaks & theme
packages/vechain-kit/src/components/common/AccountAvatar.tsx, packages/vechain-kit/src/components/AccountModal/Contents/Account/AccountMainContent.tsx, packages/vechain-kit/src/theme/card.ts
AccountAvatar resets image ref on wallet address change; AccountMainContent accepts optional switchFeedback and renders WalletSwitchFeedback; added vechainKitWalletCard theme variant.
Transactions, signing, DAppKit
packages/vechain-kit/src/hooks/thor/transactions/useSendTransaction.ts, packages/vechain-kit/src/hooks/signing/useSignMessage.ts, packages/vechain-kit/src/components/ConnectModal/Components/DappKitButton.tsx
useSendTransaction selects signerAddress for desktop DAppKit flows; signMessage dependency added for account?.address; DappKitButton effect deps extended to include source.
Translations
packages/vechain-kit/src/languages/{en,de,es,fr,it,ja,zh}.json
Added wallet-related localization keys across locales (Active Wallet, Add New Wallet, Remove Wallet, Select Wallet, Loading..., confirmation text, Account Changed, etc.).
Examples / minor
examples/homepage/src/app/components/features/LoginMethodsSection/index.ts, examples/homepage/src/app/languages/en.json
Minor formatting/trailing-newline adjustment and a small localization key addition.
Provider i18n change
packages/vechain-kit/src/providers/VeChainKitProvider.tsx
Removed i18n prop passed into DAppKitProvider (i18n={i18nConfig}).

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Profile as ProfileContent
    participant Selector as AccountSelector
    participant Select as SelectWalletContent
    participant Storage as useWalletStorage
    participant SwitchHook as useSwitchWallet
    participant WalletHook as useWallet

    User->>Profile: Click "Switch wallet"
    Profile->>Selector: request switch (desktop path)
    Selector->>Select: setCurrentContent('select-wallet')
    Select->>Storage: getStoredWallets()
    Storage-->>Select: stored wallets
    User->>Select: Click wallet "Select"
    Select->>Storage: setActiveWallet(address)
    Storage-->>Select: confirm active
    Select->>SwitchHook: switchWallet() or emit wallet_switched (desktop)
    alt In-app browser
        SwitchHook->>WalletHook: call dappKitSwitchWallet()
        WalletHook-->>Select: connection updated
    else Desktop
        SwitchHook->>WalletHook: dispatch wallet_switched event
        WalletHook-->>Select: effective address updated
    end
    Select-->>User: update UI / close or return
Loading
sequenceDiagram
    actor User
    participant Select as SelectWalletContent
    participant Confirm as RemoveWalletConfirmContent
    participant Storage as useWalletStorage
    participant WalletHook as useWallet

    User->>Select: Click "Remove" on wallet
    Select->>Confirm: navigate to remove-wallet-confirm (props)
    Confirm-->>User: show confirmation
    User->>Confirm: Confirm remove
    Confirm->>Storage: removeWallet(address)
    Storage-->>Confirm: removed
    alt No wallets left
        Confirm->>WalletHook: trigger disconnect/logout
        Confirm-->>User: modal closes
    else Other wallets present
        Storage->>Storage: setActiveWallet(newAddress) if needed
        Confirm-->>Select: navigate back
        Select-->>User: refreshed list & active wallet
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Areas requiring careful attention:
    • useWallet.ts and useWalletStorage.ts — persistence, event ordering, SSR guards, active-wallet selection edge cases.
    • useSwitchWallet.ts — new public API methods and desktop vs in‑app branching.
    • SelectWalletContent.tsx & RemoveWalletConfirmContent.tsx — removal/switch/add flow correctness and navigation/returnTo behavior.
    • ModalProvider / ConnectModal — propagation/reset of preventAutoClose and content reset on open.
    • Types wiring (AccountModal Types) to ensure props align across components.

Possibly related PRs

Suggested reviewers

  • victorkl400
  • mikeredmond
  • Vombato

Poem

🐇 I nibble keys and hop between,

wallets lined like clover green,
select, remove — a tidy track,
storage keeps my little snack,
hop to switch and bounce right back.

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Feat/switch desktop wallet' directly addresses the main feature being added: enabling wallet switching functionality for desktop DAppKit flows, which is the primary change across AccountModal, AccountSelector, ProfileContent, SelectWalletContent, and related components.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ 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 feat/switch-desktop-wallet

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.

@github-actions

github-actions Bot commented Dec 22, 2025

Copy link
Copy Markdown
Contributor

Size Change: +158 kB (+2.82%)

Total Size: 5.74 MB

Filename Size Change
packages/vechain-kit/dist/index-7c9VJYh1.d.cts 0 B -5.63 kB (removed) 🏆
packages/vechain-kit/dist/index-7c9VJYh1.d.cts.map 0 B -2.99 kB (removed) 🏆
packages/vechain-kit/dist/index-CeW_1Pls.d.cts 0 B -147 kB (removed) 🏆
packages/vechain-kit/dist/index-CeW_1Pls.d.cts.map 0 B -42.4 kB (removed) 🏆
packages/vechain-kit/dist/index-CoiOQ9xS.d.mts 0 B -147 kB (removed) 🏆
packages/vechain-kit/dist/index-CoiOQ9xS.d.mts.map 0 B -42.4 kB (removed) 🏆
packages/vechain-kit/dist/index-HjWSE8bG.d.mts 0 B -5.63 kB (removed) 🏆
packages/vechain-kit/dist/index-HjWSE8bG.d.mts.map 0 B -2.99 kB (removed) 🏆
packages/vechain-kit/dist/index.cjs 611 kB +14.2 kB (+2.39%)
packages/vechain-kit/dist/index.cjs.map 1.86 MB +61.1 kB (+3.4%)
packages/vechain-kit/dist/index.mjs 577 kB +12.7 kB (+2.24%)
packages/vechain-kit/dist/index.mjs.map 1.81 MB +59.1 kB (+3.38%)
packages/vechain-kit/dist/index-CaTFLH8g.d.cts 150 kB +150 kB (new file) 🆕
packages/vechain-kit/dist/index-CaTFLH8g.d.cts.map 43.7 kB +43.7 kB (new file) 🆕
packages/vechain-kit/dist/index-CWViOs1U.d.mts 5.63 kB +5.63 kB (new file) 🆕
packages/vechain-kit/dist/index-CWViOs1U.d.mts.map 2.99 kB +2.99 kB (new file) 🆕
packages/vechain-kit/dist/index-D2j-o880.d.mts 150 kB +150 kB (new file) 🆕
packages/vechain-kit/dist/index-D2j-o880.d.mts.map 43.7 kB +43.7 kB (new file) 🆕
packages/vechain-kit/dist/index-I8fe7GR2.d.cts 5.63 kB +5.63 kB (new file) 🆕
packages/vechain-kit/dist/index-I8fe7GR2.d.cts.map 2.99 kB +2.99 kB (new file) 🆕
ℹ️ View Unchanged
Filename Size Change
packages/vechain-kit/dist/assets 4.1 kB 0 B
packages/vechain-kit/dist/assets-aAdDxPJu.mjs 50.1 kB 0 B
packages/vechain-kit/dist/assets-aAdDxPJu.mjs.map 70.2 kB 0 B
packages/vechain-kit/dist/assets-DXVXPy3w.cjs 54.8 kB 0 B
packages/vechain-kit/dist/assets-DXVXPy3w.cjs.map 71.6 kB 0 B
packages/vechain-kit/dist/assets/index.cjs 716 B 0 B
packages/vechain-kit/dist/assets/index.d.cts 973 B 0 B
packages/vechain-kit/dist/assets/index.d.mts 973 B 0 B
packages/vechain-kit/dist/assets/index.mjs 718 B 0 B
packages/vechain-kit/dist/index.d.cts 20.4 kB +384 B (+1.91%)
packages/vechain-kit/dist/index.d.mts 20.4 kB +384 B (+1.91%)
packages/vechain-kit/dist/utils 4.1 kB 0 B
packages/vechain-kit/dist/utils-Bl-JeVTg.cjs 26.2 kB 0 B
packages/vechain-kit/dist/utils-Bl-JeVTg.cjs.map 63 kB 0 B
packages/vechain-kit/dist/utils-DAs6kMGs.mjs 21.1 kB 0 B
packages/vechain-kit/dist/utils-DAs6kMGs.mjs.map 62.7 kB 0 B
packages/vechain-kit/dist/utils/index.cjs 1.91 kB 0 B
packages/vechain-kit/dist/utils/index.d.cts 2.94 kB 0 B
packages/vechain-kit/dist/utils/index.d.mts 2.94 kB 0 B
packages/vechain-kit/dist/utils/index.mjs 1.93 kB 0 B

compressed-size-action

@Agilulfo1820 Agilulfo1820 marked this pull request as ready for review December 22, 2025 13:02

@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: 3

Caution

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

⚠️ Outside diff range comments (1)
packages/vechain-kit/src/components/ConnectModal/ConnectModal.tsx (1)

131-136: Missing preventAutoClose prop in fallback content.

The fallback MainContent rendered when renderContent() returns null does not receive the preventAutoClose prop, which could cause inconsistent auto-close behavior.

🔎 Proposed fix
         const fallbackContent = (
             <MainContent
                 setCurrentContent={setCurrentContent}
                 onClose={onClose}
+                preventAutoClose={preventAutoClose}
             />
         );
🧹 Nitpick comments (13)
packages/vechain-kit/src/languages/zh.json (1)

22-22: Clarify duplicate translation keys with different casing.

The translations for wallet management UI look appropriate. However, there are two similar keys with different casing:

  • Line 279: "Remove Wallet""移除钱包"
  • Line 281: "Remove wallet""移除钱包"

If these are intentional (e.g., title case for headings vs. sentence case for buttons), consider adding comments or using more distinctive key names to clarify their different contexts. If they serve the same purpose, consolidate them to avoid confusion.

Also applies to: 24-24, 44-44, 210-210, 247-247, 279-281, 294-294

packages/vechain-kit/src/languages/es.json (1)

22-22: Clarify duplicate translation keys with different casing.

The Spanish translations for wallet management UI look appropriate. However, the same duplicate key pattern appears here:

  • Line 279: "Remove Wallet""Eliminar Billetera"
  • Line 281: "Remove wallet""Eliminar billetera"

This pattern is consistent across all language files. If these keys serve different UI contexts (e.g., modal titles vs. button labels), consider using more distinctive key names (e.g., "Remove Wallet Title" vs "Remove Wallet Button") to clarify intent and improve maintainability.

Also applies to: 24-24, 44-44, 210-210, 247-247, 279-281, 294-294

packages/vechain-kit/src/languages/ja.json (1)

22-22: Japanese translations look good; duplicate key pattern noted.

The Japanese translations for wallet management UI are contextually appropriate. The duplicate key pattern ("Remove Wallet" vs "Remove wallet") is consistent across all language files, suggesting it may be intentional. However, the same recommendation applies: if these serve different contexts, use more distinctive key names to improve clarity.

Also applies to: 24-24, 44-44, 210-210, 247-247, 279-281, 294-294

packages/vechain-kit/src/languages/de.json (1)

22-22: German translations look good; duplicate key pattern consistent.

The German translations for wallet management UI are contextually appropriate. The duplicate key pattern is present here as well:

  • Line 279: "Remove Wallet""Brieftasche entfernen"
  • Line 281: "Remove wallet""Brieftasche entfernen"

Since this pattern appears in all language files, it's likely intentional. Still, for long-term maintainability, consider consolidating or using more explicit key names to distinguish their usage contexts.

Also applies to: 24-24, 44-44, 210-210, 247-247, 279-281, 294-294

packages/vechain-kit/src/components/AccountModal/Contents/Profile/ProfileContent.tsx (1)

120-150: Duplicate data-testid on different buttons.

Both the in-app browser switch button (line 134) and the DappKit switch button (line 147) have data-testid="switch-wallet-button". This will make it difficult to distinguish between them in tests.

Consider using distinct test IDs like "switch-wallet-inapp-button" and "switch-wallet-desktop-button".

🔎 Suggested fix
                             isLoading={isSwitching}
                             isDisabled={isSwitching}
-                            data-testid="switch-wallet-button"
+                            data-testid="switch-wallet-inapp-button"
                         >
                             {t('Switch')}
                         </Button>
                     ) : connection.isConnectedWithDappKit ? (
                         <Button
                             size="md"
                             width="full"
                             height="40px"
                             variant="vechainKitSecondary"
                             leftIcon={<Icon as={LuArrowLeftRight} />}
                             colorScheme="red"
                             onClick={handleSwitchWallet}
-                            data-testid="switch-wallet-button"
+                            data-testid="switch-wallet-desktop-button"
                         >
packages/vechain-kit/src/languages/en.json (1)

279-281: Potential duplicate localization keys with inconsistent casing.

Both "Remove Wallet" (line 279) and "Remove wallet" (line 281) exist. This could lead to inconsistent capitalization in the UI depending on which key is used. Consider consolidating to a single key and using CSS text-transform if different casing is needed in different contexts.

packages/vechain-kit/src/components/AccountModal/Contents/SelectWallet/RemoveWalletConfirmContent.tsx (2)

19-25: Consider removing the unused walletDomain prop or documenting deprecation.

The walletDomain prop is received but intentionally unused (prefixed as _walletDomain), with a comment indicating it's "kept for backward compatibility." If this is a new component, there's no backward compatibility concern. Consider either removing the prop or adding a @deprecated JSDoc annotation if it's needed for API stability.

Also applies to: 29-29


53-58: Consider using a theme token instead of hardcoded color.

The trash icon uses a hardcoded color '#ef4444' (line 55). For consistency with the rest of the theming system, consider using a semantic color token (e.g., vechain-kit-danger or similar if available).

🔎 Suggested approach
                     <Icon
                         as={LuTrash2}
-                        color={'#ef4444'}
+                        color="red.500"
                         fontSize={'60px'}
                         opacity={0.5}
                     />

Or use useToken if a semantic token exists for danger/error colors.

packages/vechain-kit/src/hooks/api/wallet/useWallet.ts (3)

186-211: Magic timeout value may cause race conditions.

The 1000ms timeout (Line 196-198) to reset isWalletSwitchInProgress is arbitrary. If wallet connection takes longer, it could cause the active wallet to be overwritten unexpectedly.

🔎 Consider using a more deterministic approach

Instead of a fixed timeout, consider resetting the flag when the connection state actually stabilizes, or use a ref to track the intended switch target:

-            // Reset the flag after a short delay to allow the connection to update
-            setTimeout(() => {
-                setIsWalletSwitchInProgress(false);
-            }, 1000);
+            // Flag will be reset when connection state matches the switched wallet

Then reset in the effect that handles connection changes when the effective address matches the switched address.


248-293: Effect dependency array includes unstable derived values.

Using activeAccountMetadata?.domain, activeAccountMetadata?.image, and activeAccountMetadata?.isLoading (Lines 286-288) as separate dependencies can cause unnecessary re-runs. The activeAccountMetadata object identity should be sufficient, or use the isLoading flag alone since that's what the condition checks.

🔎 Simplify dependencies
 }, [
     isConnectedWithDappKit,
     isInAppBrowser,
     connectedWalletAddress,
-    activeAccountMetadata?.domain,
-    activeAccountMetadata?.image,
     activeAccountMetadata?.isLoading,
     initializeCurrentWallet,
     saveWallet,
     setActiveWalletStorage,
     storedActiveWalletAddress,
+    isWalletSwitchInProgress,
 ]);

Note: isWalletSwitchInProgress is used in the effect but missing from dependencies.


295-314: Redundant wallet save operation.

This effect saves the wallet when storedActiveWalletAddress matches effectiveConnectedWalletAddress, but the previous effect (Lines 248-293) already calls saveWallet(connectedWalletAddress). This may result in duplicate localStorage writes.

Consider consolidating the save logic into a single effect to avoid redundant operations.

packages/vechain-kit/src/components/AccountModal/Contents/SelectWallet/SelectWalletContent.tsx (2)

67-85: Consider a more robust synchronization approach.

Using setTimeout with a hardcoded 100ms delay for storage synchronization is fragile and could lead to race conditions on slower devices or under heavy load. Consider having the event carry the updated wallet data directly, or using a shared state management solution.

That said, if the wallet_switched event is dispatched after storage is already updated, the delay may be unnecessary:

         const handleWalletSwitch = () => {
-            // Small delay to ensure storage is updated
-            setTimeout(() => {
-                refreshWallets();
-            }, 100);
+            refreshWallets();
         };

113-145: Remove unused account from dependency array.

account is listed in the dependency array but is not used in the callback body. This causes unnecessary re-creation of the callback when account changes.

🔎 Proposed fix
     }, [
         activeWalletAddress,
         activeWallet,
-        account,
         setActiveWallet,
         refresh,
         setCurrentContent,
+        returnTo,
         refreshWallets,
         saveWallet,
     ]);

Note: returnTo should also be added since it's used in the callback.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5d98ffd and fb634af.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (29)
  • examples/homepage/src/app/components/features/LoginMethodsSection/index.ts
  • packages/vechain-kit/src/components/AccountModal/AccountModal.tsx
  • packages/vechain-kit/src/components/AccountModal/Components/AccountSelector.tsx
  • packages/vechain-kit/src/components/AccountModal/Contents/Profile/ProfileContent.tsx
  • packages/vechain-kit/src/components/AccountModal/Contents/SelectWallet/Components/WalletCard.tsx
  • packages/vechain-kit/src/components/AccountModal/Contents/SelectWallet/RemoveWalletConfirmContent.tsx
  • packages/vechain-kit/src/components/AccountModal/Contents/SelectWallet/SelectWalletContent.tsx
  • packages/vechain-kit/src/components/AccountModal/Contents/SelectWallet/index.ts
  • packages/vechain-kit/src/components/AccountModal/Contents/index.ts
  • packages/vechain-kit/src/components/AccountModal/Types/Types.ts
  • packages/vechain-kit/src/components/ConnectModal/Components/DappKitButton.tsx
  • packages/vechain-kit/src/components/ConnectModal/ConnectModal.tsx
  • packages/vechain-kit/src/components/ConnectModal/Contents/MainContent.tsx
  • packages/vechain-kit/src/components/common/AccountAvatar.tsx
  • packages/vechain-kit/src/hooks/api/wallet/index.ts
  • packages/vechain-kit/src/hooks/api/wallet/useSwitchWallet.ts
  • packages/vechain-kit/src/hooks/api/wallet/useWallet.ts
  • packages/vechain-kit/src/hooks/api/wallet/useWalletStorage.ts
  • packages/vechain-kit/src/hooks/signing/useSignMessage.ts
  • packages/vechain-kit/src/hooks/thor/transactions/useSendTransaction.ts
  • packages/vechain-kit/src/languages/de.json
  • packages/vechain-kit/src/languages/en.json
  • packages/vechain-kit/src/languages/es.json
  • packages/vechain-kit/src/languages/fr.json
  • packages/vechain-kit/src/languages/it.json
  • packages/vechain-kit/src/languages/ja.json
  • packages/vechain-kit/src/languages/zh.json
  • packages/vechain-kit/src/providers/ModalProvider.tsx
  • packages/vechain-kit/src/theme/card.ts
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/migration-guide-to-v2.mdc)

**/*.{ts,tsx}: In VeChain Kit Version 2, use useThor instead of useConnex for contract interactions
For single contract read operations, use the useCallClause hook with the pattern: import dependencies, define ABI and method as const, create query key function using getCallClauseQueryKeyWithArgs, and wrap with useCallClause including data transformation in the select option
For multiple parallel contract calls, use the executeMultipleClausesCall utility wrapped in a useQuery hook with the pattern: define query key function, use executeMultipleClausesCall in queryFn mapping items to clause objects, and transform results
For transaction building and sending, use the useBuildTransaction hook with a clauseBuilder function that returns an array of clauses with optional comment fields describing the transaction action
Always provide an arguments array for contract calls, even when no parameters are required - use an empty array for parameter-less functions to enable TypeScript type checking
Always conditionally enable queries using the enabled property to prevent unnecessary contract calls, checking for all required parameters: enabled: !!requiredParam && !!otherRequiredParam
Use the select option in useCallClause or transform data in queryFn to handle data transformation, particularly for converting BigInt values to strings and normalizing contract return data
Maintain consistent query key patterns: use getCallClauseQueryKeyWithArgs for contract calls with arguments and getCallClauseQueryKey for calls without arguments to ensure proper caching and invalidation
Use TypeScript as const assertions for method names and as 0x${string}`` assertions for Ethereum addresses to ensure type safety in contract interactions

Files:

  • packages/vechain-kit/src/hooks/signing/useSignMessage.ts
  • packages/vechain-kit/src/components/AccountModal/Contents/index.ts
  • packages/vechain-kit/src/hooks/api/wallet/index.ts
  • packages/vechain-kit/src/hooks/thor/transactions/useSendTransaction.ts
  • packages/vechain-kit/src/components/ConnectModal/Contents/MainContent.tsx
  • packages/vechain-kit/src/components/AccountModal/Contents/SelectWallet/index.ts
  • packages/vechain-kit/src/theme/card.ts
  • packages/vechain-kit/src/components/ConnectModal/ConnectModal.tsx
  • packages/vechain-kit/src/components/AccountModal/Contents/SelectWallet/Components/WalletCard.tsx
  • packages/vechain-kit/src/hooks/api/wallet/useWallet.ts
  • packages/vechain-kit/src/components/AccountModal/Types/Types.ts
  • packages/vechain-kit/src/components/common/AccountAvatar.tsx
  • packages/vechain-kit/src/hooks/api/wallet/useSwitchWallet.ts
  • packages/vechain-kit/src/components/AccountModal/Contents/SelectWallet/RemoveWalletConfirmContent.tsx
  • packages/vechain-kit/src/providers/ModalProvider.tsx
  • packages/vechain-kit/src/components/AccountModal/AccountModal.tsx
  • packages/vechain-kit/src/hooks/api/wallet/useWalletStorage.ts
  • packages/vechain-kit/src/components/ConnectModal/Components/DappKitButton.tsx
  • examples/homepage/src/app/components/features/LoginMethodsSection/index.ts
  • packages/vechain-kit/src/components/AccountModal/Contents/Profile/ProfileContent.tsx
  • packages/vechain-kit/src/components/AccountModal/Contents/SelectWallet/SelectWalletContent.tsx
  • packages/vechain-kit/src/components/AccountModal/Components/AccountSelector.tsx
🧠 Learnings (2)
📚 Learning: 2025-12-01T13:01:33.771Z
Learnt from: CR
Repo: vechain/vechain-kit PR: 0
File: .cursor/rules/migration-guide-to-v2.mdc:0-0
Timestamp: 2025-12-01T13:01:33.771Z
Learning: Applies to **/*.{ts,tsx} : In VeChain Kit Version 2, use `useThor` instead of `useConnex` for contract interactions

Applied to files:

  • packages/vechain-kit/src/hooks/api/wallet/index.ts
  • packages/vechain-kit/src/hooks/thor/transactions/useSendTransaction.ts
  • packages/vechain-kit/src/hooks/api/wallet/useWallet.ts
  • packages/vechain-kit/src/hooks/api/wallet/useSwitchWallet.ts
  • packages/vechain-kit/src/components/ConnectModal/Components/DappKitButton.tsx
📚 Learning: 2025-12-01T13:01:33.771Z
Learnt from: CR
Repo: vechain/vechain-kit PR: 0
File: .cursor/rules/migration-guide-to-v2.mdc:0-0
Timestamp: 2025-12-01T13:01:33.771Z
Learning: Applies to **/*.{ts,tsx} : For transaction building and sending, use the `useBuildTransaction` hook with a clauseBuilder function that returns an array of clauses with optional comment fields describing the transaction action

Applied to files:

  • packages/vechain-kit/src/hooks/thor/transactions/useSendTransaction.ts
🧬 Code graph analysis (11)
packages/vechain-kit/src/components/ConnectModal/ConnectModal.tsx (1)
packages/vechain-kit/src/components/ConnectModal/Contents/MainContent.tsx (1)
  • MainContent (28-110)
packages/vechain-kit/src/components/AccountModal/Contents/SelectWallet/Components/WalletCard.tsx (5)
packages/vechain-kit/src/hooks/api/wallet/useWalletStorage.ts (1)
  • StoredWallet (11-15)
packages/vechain-kit/src/providers/VeChainKitProvider.tsx (1)
  • useVeChainKitConfig (204-210)
packages/vechain-kit/src/hooks/api/wallet/useWalletMetadata.ts (1)
  • useWalletMetadata (10-34)
packages/vechain-kit/src/components/common/AccountAvatar.tsx (1)
  • AccountAvatar (10-53)
packages/vechain-kit/src/utils/formattingUtils.tsx (2)
  • humanDomain (11-22)
  • humanAddress (5-9)
packages/vechain-kit/src/hooks/api/wallet/useWallet.ts (4)
packages/vechain-kit/src/hooks/api/wallet/useWalletStorage.ts (1)
  • useWalletStorage (17-138)
packages/vechain-kit/src/utils/ssrUtils.ts (1)
  • isBrowser (75-77)
packages/vechain-kit/src/hooks/thor/smartAccounts/useSmartAccount.ts (1)
  • useSmartAccount (51-60)
packages/vechain-kit/src/hooks/api/wallet/useWalletMetadata.ts (1)
  • useWalletMetadata (10-34)
packages/vechain-kit/src/components/AccountModal/Types/Types.ts (1)
packages/vechain-kit/src/components/AccountModal/Contents/SelectWallet/RemoveWalletConfirmContent.tsx (1)
  • RemoveWalletConfirmContentProps (19-25)
packages/vechain-kit/src/hooks/api/wallet/useSwitchWallet.ts (3)
packages/vechain-kit/src/hooks/api/wallet/useWalletStorage.ts (2)
  • StoredWallet (11-15)
  • useWalletStorage (17-138)
packages/vechain-kit/src/hooks/api/wallet/useWallet.ts (1)
  • useWallet (62-410)
packages/vechain-kit/src/utils/ssrUtils.ts (1)
  • isBrowser (75-77)
packages/vechain-kit/src/components/AccountModal/Contents/SelectWallet/RemoveWalletConfirmContent.tsx (5)
packages/vechain-kit/src/providers/VeChainKitProvider.tsx (1)
  • useVeChainKitConfig (204-210)
packages/vechain-kit/src/hooks/api/wallet/useWalletMetadata.ts (1)
  • useWalletMetadata (10-34)
packages/vechain-kit/src/utils/formattingUtils.tsx (2)
  • humanDomain (11-22)
  • humanAddress (5-9)
packages/vechain-kit/src/components/common/StickyHeaderContainer.tsx (1)
  • StickyHeaderContainer (16-140)
packages/vechain-kit/src/components/common/ModalBackButton.tsx (1)
  • ModalBackButton (8-23)
packages/vechain-kit/src/providers/ModalProvider.tsx (1)
packages/vechain-kit/src/components/ConnectModal/ConnectModal.tsx (1)
  • ConnectModalContentsTypes (17-43)
packages/vechain-kit/src/components/AccountModal/AccountModal.tsx (2)
packages/vechain-kit/src/components/AccountModal/Contents/SelectWallet/RemoveWalletConfirmContent.tsx (1)
  • RemoveWalletConfirmContent (27-99)
packages/vechain-kit/src/components/AccountModal/Contents/SelectWallet/SelectWalletContent.tsx (1)
  • SelectWalletContent (37-322)
packages/vechain-kit/src/hooks/api/wallet/useWalletStorage.ts (3)
packages/vechain-kit/src/providers/VeChainKitProvider.tsx (1)
  • useVeChainKitConfig (204-210)
packages/vechain-kit/src/config/network.ts (1)
  • NETWORK_TYPE (6-6)
packages/vechain-kit/src/utils/ssrUtils.ts (4)
  • isBrowser (75-77)
  • getLocalStorageItem (8-18)
  • setLocalStorageItem (23-32)
  • removeLocalStorageItem (37-46)
packages/vechain-kit/src/components/AccountModal/Contents/SelectWallet/SelectWalletContent.tsx (9)
packages/vechain-kit/src/components/AccountModal/Types/Types.ts (1)
  • AccountModalContentTypes (20-119)
packages/vechain-kit/src/hooks/modals/useAccountModalOptions.tsx (1)
  • useAccountModalOptions (3-10)
packages/vechain-kit/src/hooks/api/wallet/useWallet.ts (1)
  • useWallet (62-410)
packages/vechain-kit/src/hooks/api/wallet/useSwitchWallet.ts (1)
  • useSwitchWallet (20-75)
packages/vechain-kit/src/hooks/api/wallet/useWalletStorage.ts (2)
  • useWalletStorage (17-138)
  • StoredWallet (11-15)
packages/vechain-kit/src/providers/ModalProvider.tsx (1)
  • useModal (73-79)
packages/vechain-kit/src/hooks/api/wallet/useRefreshBalances.ts (1)
  • useRefreshBalances (4-28)
packages/vechain-kit/src/components/common/StickyHeaderContainer.tsx (1)
  • StickyHeaderContainer (16-140)
packages/vechain-kit/src/components/AccountModal/Contents/SelectWallet/Components/WalletCard.tsx (1)
  • WalletCard (28-128)
packages/vechain-kit/src/components/AccountModal/Components/AccountSelector.tsx (3)
packages/vechain-kit/src/hooks/api/wallet/useWallet.ts (1)
  • useWallet (62-410)
packages/vechain-kit/src/hooks/index.ts (1)
  • useWallet (17-17)
packages/vechain-kit/src/hooks/api/wallet/useSwitchWallet.ts (1)
  • useSwitchWallet (20-75)
🔇 Additional comments (49)
packages/vechain-kit/src/hooks/signing/useSignMessage.ts (1)

72-72: LGTM! Dependency optimization aligned with wallet switching.

The change from account to account?.address in the dependency array is a sound optimization that ensures the signMessage callback recreates specifically when the wallet address changes—critical for the wallet-switching feature introduced in this PR—while avoiding unnecessary recreations when the account object reference changes but the address remains constant.

packages/vechain-kit/src/theme/card.ts (1)

31-39: LGTM! New wallet card variant is well-structured.

The vechainKitWalletCard variant provides appropriate styling for clickable wallet cards with pointer cursor and relative positioning for potential child elements (badges, icons). The structure follows the existing pattern and aligns with the wallet selection UI introduced in this PR.

examples/homepage/src/app/components/features/LoginMethodsSection/index.ts (1)

1-1: LGTM! Formatting cleanup.

Trivial formatting change removing extra blank lines at EOF. No functional impact.

packages/vechain-kit/src/components/ConnectModal/Contents/MainContent.tsx (2)

25-25: LGTM! Prop addition enables flexible modal behavior.

The preventAutoClose prop with default value false preserves existing auto-close behavior while allowing callers to keep the modal open after connection when needed.

Also applies to: 28-28


48-51: LGTM! Correct conditional auto-close logic.

The effect correctly guards onClose with the preventAutoClose flag and includes it in the dependency array, ensuring the modal can remain open after connection when required by the caller.

packages/vechain-kit/src/components/common/AccountAvatar.tsx (1)

12-22: LGTM! Wallet address tracking ensures correct avatar on wallet switch.

The addition of walletAddressRef and the effect to reset previousImageRef when the wallet address changes is essential for the wallet switching feature. This prevents stale avatar images from being displayed when users switch between wallets, ensuring the UI remains consistent with the active wallet.

The implementation correctly:

  • Tracks the current wallet address with a ref
  • Resets the cached image reference when the address changes
  • Preserves the existing image caching behavior during metadata loading
packages/vechain-kit/src/hooks/api/wallet/index.ts (1)

22-22: LGTM!

The new useWalletStorage export is correctly added and aligns with the barrel file pattern used throughout the codebase.

packages/vechain-kit/src/components/AccountModal/Contents/index.ts (1)

14-14: LGTM!

The SelectWallet module export correctly exposes the new wallet selection components.

packages/vechain-kit/src/components/ConnectModal/Components/DappKitButton.tsx (1)

29-44: LGTM - dependency array fix and documentation.

Good addition of source to the dependency array since it's referenced in the callback. The comments helpfully document that wallet activation is now centralized in useWallet.ts.

packages/vechain-kit/src/components/AccountModal/Contents/SelectWallet/index.ts (1)

1-2: LGTM!

Clean barrel file structure exposing the new wallet content components.

packages/vechain-kit/src/components/AccountModal/AccountModal.tsx (3)

21-22: LGTM!

Imports for the new wallet content components are correctly added.


100-103: LGTM!

The remove-wallet-confirm case correctly renders RemoveWalletConfirmContent by spreading the props from currentContent.props, which aligns with the component's expected interface.


135-145: LGTM!

The select-wallet case properly passes the required props to SelectWalletContent. The prop mapping (setCurrentContent, onClose, returnTo, onLogoutSuccess) matches the component's expected interface from the relevant code snippets.

packages/vechain-kit/src/hooks/thor/transactions/useSendTransaction.ts (2)

169-178: Signer resolution logic for desktop DappKit looks correct.

The conditional logic ensures that on desktop (non-in-app browser) with DappKit, the user's selected/stored wallet (signerAccountAddress) is used as the signer, while in-app browser flows continue using the extension's current wallet (signer.address). This aligns with the PR's goal of enabling desktop wallet switching.

Please verify that in-app browser transaction flows still function correctly after this change, particularly that signer.address is used as intended when isInAppBrowser is true.


201-202: LGTM - dependency array updated correctly.

The new connection properties are correctly added to the dependency array since they're used in the signer resolution logic.

packages/vechain-kit/src/components/ConnectModal/ConnectModal.tsx (2)

14-14: LGTM!

The preventAutoClose prop is correctly typed as optional and properly defaulted to false.

Also applies to: 49-49


64-70: LGTM!

The preventAutoClose prop is correctly propagated to MainContent in both the null-check fallback and the 'main' case.

Also applies to: 79-79

packages/vechain-kit/src/components/AccountModal/Components/AccountSelector.tsx (3)

24-24: LGTM!

The new hook imports and destructuring correctly provide the isInAppBrowser flag and getAvailableMethods function needed for the branching logic.

Also applies to: 49-50


127-129: LGTM - visibility condition correctly updated.

The switch wallet button now shows when either:

  1. In-app browser with switchWallet method available, OR
  2. Connected with DappKit (for desktop wallet selection)

This enables the new desktop wallet switching flow while preserving the in-app browser behavior.


66-80: No issues found. The props structure is correct and consistent with the type definitions. The handleSwitchWallet function properly nests setCurrentContent within props when creating the state object (matching the 'select-wallet' type definition in Types.ts), and AccountModal.tsx correctly unpacks these nested props when rendering SelectWalletContent. This is the expected pattern: the state object uses nested props, while the component itself receives individual props extracted from that object.

packages/vechain-kit/src/components/AccountModal/Contents/Profile/ProfileContent.tsx (1)

60-65: LGTM!

The settings button is now consistently available, which aligns with the wallet management flow improvements.

packages/vechain-kit/src/components/AccountModal/Types/Types.ts (2)

36-46: LGTM!

The select-wallet content type is well-structured with appropriate props. The returnTo optional property with a literal union type provides good type safety for navigation flows.


104-107: LGTM!

The remove-wallet-confirm content type correctly references the RemoveWalletConfirmContentProps type, maintaining consistency with the component definition.

packages/vechain-kit/src/languages/en.json (1)

22-24: LGTM!

The new wallet-related localization strings are well-organized and follow the existing alphabetical ordering convention.

Also applies to: 44-44, 210-210, 247-247, 279-281, 294-294

packages/vechain-kit/src/components/AccountModal/Contents/SelectWallet/RemoveWalletConfirmContent.tsx (2)

39-41: Verify the humanDomain parameters are intentional.

humanDomain(domain, 20, 0) uses lengthAfter=0, meaning the domain will be truncated to show only the first 20 characters with no suffix. Based on the utility signature in formattingUtils.tsx, this would display first20chars••• without any ending portion. Confirm this is the intended display behavior.


27-98: LGTM overall!

The component structure is clean, follows established patterns (StickyHeaderContainer, ModalBackButton), and properly integrates with the translation system. The dynamic domain resolution via useWalletMetadata is a good approach.

packages/vechain-kit/src/providers/ModalProvider.tsx (3)

86-87: LGTM!

The connectModalPreventAutoClose state is properly managed: initialized to false, set when opening the modal, and reset to false when closing. This ensures clean state between modal sessions.

Also applies to: 105-105, 115-115


177-211: LGTM!

The context provider correctly exposes the new connectModalPreventAutoClose state and setter, and properly passes the flag to the ConnectModal component.


89-110: Missing dependencies in useCallback.

The openConnectModal callback uses setSource and connectV2 from useDAppKitWallet() but the dependency array is empty. While React state setters are stable and don't need to be included, setSource and connectV2 come from an external hook and could potentially change.

Run the following script to verify if these functions are stable:

#!/bin/bash
# Check the useDAppKitWallet hook implementation for memoization of setSource and connectV2
ast-grep --pattern $'export const useDAppKitWallet = () => {
  $$$
}'
packages/vechain-kit/src/components/AccountModal/Contents/SelectWallet/Components/WalletCard.tsx (2)

28-50: LGTM! Clean component structure with proper hook usage.

The component correctly:

  • Integrates wallet metadata and balance hooks
  • Uses theming tokens for consistent styling
  • Combines loading states appropriately

52-127: Well-implemented card with proper event handling.

Good use of stopPropagation on the remove button (Line 119) to prevent triggering onSelect when removing. The conditional rendering for active indicator vs remove button is clean.

packages/vechain-kit/src/languages/it.json (1)

22-24: Translations look correct.

The new Italian translations for wallet management features are properly added. Note that both "Remove Wallet" (Line 279) and "Remove wallet" (Line 281) are intentional for different UI contexts (title vs button label).

Also applies to: 44-44, 210-210, 247-247, 279-279, 281-281, 294-294

packages/vechain-kit/src/hooks/api/wallet/useWallet.ts (1)

161-170: Verify initial state synchronization on mount.

The initial state for storedActiveWalletAddress (Lines 168-170) is computed once during render. If isConnectedWithDappKit or isInAppBrowser change after the initial render, the state won't reflect the correct value until the effect on Lines 173-180 runs. This should work correctly due to the subsequent effect, but verify behavior during rapid connection state changes.

packages/vechain-kit/src/hooks/api/wallet/useSwitchWallet.ts (2)

20-52: LGTM! Clean separation of in-app browser vs desktop flows.

The hook correctly:

  • Delegates to dapp-kit's switchWallet for in-app browser environments
  • Provides storage utilities for desktop UI-based switching
  • Handles the async nature appropriately with loading state

54-74: Good event-based communication pattern.

The setActiveWallet wrapper properly persists to storage and dispatches the wallet_switched custom event for cross-component communication. This decoupled approach allows useWallet to react to switches initiated from anywhere.

packages/vechain-kit/src/languages/fr.json (1)

22-24: French translations are correct.

The new wallet management translations are properly localized. The translation quality looks good with appropriate French phrasing.

Also applies to: 44-44, 210-210, 247-247, 279-279, 281-281, 294-294

packages/vechain-kit/src/hooks/api/wallet/useWalletStorage.ts (7)

1-15: LGTM!

Imports are correctly structured using SSR-safe utilities, and the StoredWallet type is well-defined with the necessary fields for wallet tracking.


20-25: LGTM!

The storage key generation is correctly scoped by network type, ensuring wallet data is isolated per network.


36-41: LGTM!

Simple and safe implementation with proper SSR guard.


43-71: LGTM!

The save logic correctly handles both new and existing wallets with case-insensitive address matching. Setting isActive: false on save is appropriate since setActiveWallet handles activation separately.


73-90: LGTM!

Clean implementation with proper case-insensitive matching and dual storage for the active wallet state.


92-114: LGTM!

Proper cleanup logic that correctly handles the case when the removed wallet was the active one.


116-128: LGTM!

Safe initialization that only acts when no wallets are stored, preventing accidental overwrites.

packages/vechain-kit/src/components/AccountModal/Contents/SelectWallet/SelectWalletContent.tsx (6)

42-54: LGTM!

Hooks are properly initialized with appropriate initial state from storage.


56-65: LGTM!

Clean refresh logic that properly syncs state when the account changes.


87-111: LGTM!

Well-structured memoized computations with proper fallback logic for the active wallet address.


147-230: LGTM!

The removal flow is comprehensive, correctly handling edge cases like removing the active wallet and removing the last wallet. The navigation to confirmation screen with appropriate callbacks is well-implemented.


255-321: LGTM!

Clean UI structure with proper conditional rendering, localization, and appropriate handling of the active wallet's no-op onSelect.


28-41: The onClose prop is declared in the Props type but not used within the component body. However, it is required by the parent component's type contract. The parent component (AccountModal.tsx) explicitly passes onClose when instantiating SelectWalletContent, and the type definition in Types.ts declares it as a required prop. Removing it would break the parent component's interface. Consider documenting this prop or refactoring the parent component if it shouldn't be required.

Likely an incorrect or invalid review comment.

Comment on lines +36 to +51
const handleSwitchWallet = () => {
if (isInAppBrowser) {
switchWallet();
} else {
// Desktop: navigate to select wallet screen
setCurrentContent({
type: 'select-wallet',
props: {
setCurrentContent,
onClose: () => {},
returnTo: 'profile',
onLogoutSuccess,
},
});
}
};

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 | 🟡 Minor

The onClose callback is a no-op, which may cause issues.

The handleSwitchWallet function passes onClose: () => {} as a prop to the select-wallet content. This empty callback might prevent proper modal closure behavior when the user completes or cancels the wallet selection flow.

Consider whether this should invoke closeAccountModal or another appropriate handler.

🔎 Suggested fix
     const handleSwitchWallet = () => {
         if (isInAppBrowser) {
             switchWallet();
         } else {
             // Desktop: navigate to select wallet screen
             setCurrentContent({
                 type: 'select-wallet',
                 props: {
                     setCurrentContent,
-                    onClose: () => {},
+                    onClose: () => setCurrentContent('profile'),
                     returnTo: 'profile',
                     onLogoutSuccess,
                 },
             });
         }
     };

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
packages/vechain-kit/src/components/AccountModal/Contents/Profile/ProfileContent.tsx
around lines 36 to 51, the handleSwitchWallet function passes a no-op onClose
(()=>{}) into the 'select-wallet' content which prevents the modal from closing
after wallet selection/cancellation; replace the no-op with the proper modal
close handler (e.g. closeAccountModal) or forward the component's existing
onClose prop so the select-wallet flow can close the modal and perform cleanup,
ensuring you pass any required params (or wrap to call additional callbacks like
onLogoutSuccess if needed).

Comment on lines +232 to +253
const handleAddNewWallet = useCallback(async () => {
// Disconnect from dappkit first if connected
if (connection.isConnectedWithDappKit && !connection.isInAppBrowser) {
try {
dappKitDisconnect();
} catch (error) {
console.error('Error disconnecting from dappkit:', error);
}
}

// Small delay to ensure disconnect is processed
setTimeout(() => {
// Open ConnectModal without preventAutoClose so it closes automatically
// when a new wallet is connected after disconnect
openConnectModal('main', false);
}, 100);
}, [
openConnectModal,
connection.isConnectedWithDappKit,
connection.isInAppBrowser,
dappKitDisconnect,
]);

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 | 🟡 Minor

Inconsistent async handling of dappKitDisconnect.

dappKitDisconnect() is awaited in handleRemoveWallet (line 173) but not here. This inconsistency could cause the connect modal to open before disconnect completes, leading to unexpected behavior.

🔎 Proposed fix
     const handleAddNewWallet = useCallback(async () => {
         // Disconnect from dappkit first if connected
         if (connection.isConnectedWithDappKit && !connection.isInAppBrowser) {
             try {
-                dappKitDisconnect();
+                await dappKitDisconnect();
             } catch (error) {
                 console.error('Error disconnecting from dappkit:', error);
             }
         }
 
-        // Small delay to ensure disconnect is processed
-        setTimeout(() => {
-            // Open ConnectModal without preventAutoClose so it closes automatically
-            // when a new wallet is connected after disconnect
-            openConnectModal('main', false);
-        }, 100);
+        // Open ConnectModal without preventAutoClose so it closes automatically
+        // when a new wallet is connected after disconnect
+        openConnectModal('main', false);
     }, [
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleAddNewWallet = useCallback(async () => {
// Disconnect from dappkit first if connected
if (connection.isConnectedWithDappKit && !connection.isInAppBrowser) {
try {
dappKitDisconnect();
} catch (error) {
console.error('Error disconnecting from dappkit:', error);
}
}
// Small delay to ensure disconnect is processed
setTimeout(() => {
// Open ConnectModal without preventAutoClose so it closes automatically
// when a new wallet is connected after disconnect
openConnectModal('main', false);
}, 100);
}, [
openConnectModal,
connection.isConnectedWithDappKit,
connection.isInAppBrowser,
dappKitDisconnect,
]);
const handleAddNewWallet = useCallback(async () => {
// Disconnect from dappkit first if connected
if (connection.isConnectedWithDappKit && !connection.isInAppBrowser) {
try {
await dappKitDisconnect();
} catch (error) {
console.error('Error disconnecting from dappkit:', error);
}
}
// Open ConnectModal without preventAutoClose so it closes automatically
// when a new wallet is connected after disconnect
openConnectModal('main', false);
}, [
openConnectModal,
connection.isConnectedWithDappKit,
connection.isInAppBrowser,
dappKitDisconnect,
]);
🤖 Prompt for AI Agents
In
packages/vechain-kit/src/components/AccountModal/Contents/SelectWallet/SelectWalletContent.tsx
around lines 232-253, the handler calls dappKitDisconnect() without awaiting it
which can let openConnectModal run before disconnect finishes; update the code
to await dappKitDisconnect inside the existing try/catch (i.e., await
dappKitDisconnect();), then call openConnectModal only after the awaited
disconnect completes; remove the current setTimeout or, if a brief delay is
still required, use an awaited Promise-based sleep (await new Promise(r =>
setTimeout(r, 100))) before calling openConnectModal, and keep error logging in
the catch.

Comment on lines +27 to +34
const getStoredWallets = useCallback((): StoredWallet[] => {
if (!isBrowser()) return [];

const keys = getStorageKeys(network.type);
const cached = getLocalStorageItem(keys.wallets);
if (!cached) return [];
return JSON.parse(cached) as StoredWallet[];
}, [network.type, getStorageKeys]);

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

Add error handling for JSON.parse to prevent crashes from corrupted localStorage.

If localStorage contains invalid JSON (e.g., corrupted data or manual tampering), JSON.parse will throw and crash the application.

🔎 Proposed fix
     const getStoredWallets = useCallback((): StoredWallet[] => {
         if (!isBrowser()) return [];
 
         const keys = getStorageKeys(network.type);
         const cached = getLocalStorageItem(keys.wallets);
         if (!cached) return [];
-        return JSON.parse(cached) as StoredWallet[];
+        try {
+            return JSON.parse(cached) as StoredWallet[];
+        } catch (error) {
+            console.warn('Error parsing stored wallets:', error);
+            return [];
+        }
     }, [network.type, getStorageKeys]);
🤖 Prompt for AI Agents
In packages/vechain-kit/src/hooks/api/wallet/useWalletStorage.ts around lines 27
to 34, the call to JSON.parse on cached localStorage data can throw for
invalid/corrupted JSON; wrap the parse in a try/catch, and on parse failure
return an empty array (and optionally remove the corrupted storage key and log
the error) to prevent the app from crashing and to recover gracefully.

@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: 0

♻️ Duplicate comments (1)
packages/vechain-kit/src/components/AccountModal/Contents/Profile/ProfileContent.tsx (1)

36-51: The no-op onClose callback still prevents proper modal closure.

As flagged in a previous review, the empty onClose: () => {} callback at line 45 prevents the select-wallet screen from properly handling close or cancel actions. Users have no way to navigate back from the wallet selection flow.

Replace the no-op with a handler that returns to the profile view or closes the modal appropriately.

🔎 Suggested fix
         } else {
             // Desktop: navigate to select wallet screen
             setCurrentContent({
                 type: 'select-wallet',
                 props: {
                     setCurrentContent,
-                    onClose: () => {},
+                    onClose: () => setCurrentContent('profile'),
                     returnTo: 'profile',
                     onLogoutSuccess,
                 },
             });
         }
🧹 Nitpick comments (2)
packages/vechain-kit/src/components/AccountModal/Contents/Profile/ProfileContent.tsx (1)

120-149: Consider adding clarifying comments for the multi-branch wallet action logic.

The three-way conditional rendering of wallet actions (in-app switch, desktop switch, logout) is correct but complex. Each branch handles a distinct environment and capability scenario:

  • Branch A (lines 120–136): In-app browser with native wallet switching
  • Branch B (lines 137–149): Desktop DappKit requiring UI-based wallet selection
  • Branch C (lines 150–175): Fallback logout for other connection types

Adding brief inline comments explaining when each branch is reached would improve maintainability and make the branching logic clearer to future developers.

packages/vechain-kit/src/components/AccountModal/Components/AccountSelector.tsx (1)

66-80: Remove unnecessary optional chaining and non-null assertion.

Since setCurrentContent is now a required prop, the optional chaining on line 71 and the non-null assertion on line 74 are unnecessary. The logic correctly branches between in-app browser (direct switch) and desktop (modal navigation).

🔎 Proposed cleanup
 const handleSwitchWallet = () => {
     if (isInAppBrowser) {
         switchWallet();
     } else {
         // Desktop: navigate to select wallet screen
-        setCurrentContent?.({
+        setCurrentContent({
             type: 'select-wallet',
             props: {
-                setCurrentContent: setCurrentContent!,
+                setCurrentContent,
                 onClose,
                 returnTo: 'main',
             },
         });
     }
 };
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fb634af and 08d44fe.

📒 Files selected for processing (2)
  • packages/vechain-kit/src/components/AccountModal/Components/AccountSelector.tsx
  • packages/vechain-kit/src/components/AccountModal/Contents/Profile/ProfileContent.tsx
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/migration-guide-to-v2.mdc)

**/*.{ts,tsx}: In VeChain Kit Version 2, use useThor instead of useConnex for contract interactions
For single contract read operations, use the useCallClause hook with the pattern: import dependencies, define ABI and method as const, create query key function using getCallClauseQueryKeyWithArgs, and wrap with useCallClause including data transformation in the select option
For multiple parallel contract calls, use the executeMultipleClausesCall utility wrapped in a useQuery hook with the pattern: define query key function, use executeMultipleClausesCall in queryFn mapping items to clause objects, and transform results
For transaction building and sending, use the useBuildTransaction hook with a clauseBuilder function that returns an array of clauses with optional comment fields describing the transaction action
Always provide an arguments array for contract calls, even when no parameters are required - use an empty array for parameter-less functions to enable TypeScript type checking
Always conditionally enable queries using the enabled property to prevent unnecessary contract calls, checking for all required parameters: enabled: !!requiredParam && !!otherRequiredParam
Use the select option in useCallClause or transform data in queryFn to handle data transformation, particularly for converting BigInt values to strings and normalizing contract return data
Maintain consistent query key patterns: use getCallClauseQueryKeyWithArgs for contract calls with arguments and getCallClauseQueryKey for calls without arguments to ensure proper caching and invalidation
Use TypeScript as const assertions for method names and as 0x${string}`` assertions for Ethereum addresses to ensure type safety in contract interactions

Files:

  • packages/vechain-kit/src/components/AccountModal/Components/AccountSelector.tsx
  • packages/vechain-kit/src/components/AccountModal/Contents/Profile/ProfileContent.tsx
🧬 Code graph analysis (1)
packages/vechain-kit/src/components/AccountModal/Contents/Profile/ProfileContent.tsx (2)
packages/vechain-kit/src/hooks/api/wallet/useSwitchWallet.ts (1)
  • useSwitchWallet (20-75)
packages/vechain-kit/src/components/common/ModalSettingsButton.tsx (1)
  • ModalSettingsButton (10-67)
⏰ 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). (4)
  • GitHub Check: deploy
  • GitHub Check: Lint & Build
  • GitHub Check: build-size-check
  • GitHub Check: e2e-test
🔇 Additional comments (5)
packages/vechain-kit/src/components/AccountModal/Components/AccountSelector.tsx (5)

24-24: LGTM! New hooks properly imported.

The addition of useSwitchWallet and useDAppKitWallet hooks supports the new environment-aware wallet switching functionality.


49-50: LGTM! Hooks properly integrated.

The destructured values from useSwitchWallet and useDAppKitWallet are correctly used throughout the component to implement environment-aware wallet switching.


127-128: LGTM! Button visibility correctly handles both environments.

The condition properly shows the switch wallet button for:

  • In-app browser when switching is enabled
  • Desktop when connected via DAppKit

This aligns with the multi-wallet UI requirements.


132-132: LGTM! onClick correctly routed through environment-aware handler.

The button now invokes handleSwitchWallet, which properly branches between direct wallet switching (in-app browser) and modal navigation (desktop).


40-43: All callers properly provide the required props.

Verification confirms that setCurrentContent and onClose are required props for AccountSelector. The only caller, AccountMainContent, correctly passes both required props on lines 81-82. No breaking changes or missing prop issues found.

@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: 1

♻️ Duplicate comments (1)
packages/vechain-kit/src/components/AccountModal/Contents/Profile/ProfileContent.tsx (1)

41-56: The no-op onClose callback remains unresolved.

As flagged in a previous review, the onClose: () => {} at line 50 prevents proper navigation when the user cancels wallet selection. When the select-wallet flow invokes onClose, nothing happens, which could leave the user in an unexpected state.

🔎 Suggested fix
         } else {
             // Desktop: navigate to select wallet screen
             setCurrentContent({
                 type: 'select-wallet',
                 props: {
                     setCurrentContent,
-                    onClose: () => {},
+                    onClose: () => setCurrentContent('profile'),
                     returnTo: 'profile',
                     onLogoutSuccess,
                 },
             });
         }
🧹 Nitpick comments (5)
packages/vechain-kit/src/languages/it.json (1)

279-279: Verify the intentional use of duplicate keys with different capitalization.

Two similar translation keys exist with only capitalization differences:

  • Line 279: "Remove Wallet": "Rimuovi Portafoglio"
  • Line 281: "Remove wallet": "Rimuovi portafoglio"

While both translations are correct, this pattern is uncommon in i18n files. Please confirm whether these are intentionally maintained for different UI contexts (e.g., button labels vs menu items) or if one should be consolidated.

Also applies to: 281-281

packages/vechain-kit/src/providers/FeedbackProvider.tsx (2)

11-20: Consider exporting FeedbackContextType if needed elsewhere.

The AI summary indicates FeedbackContextType should be an exported entity, but it's currently not exported. If consuming components need to reference this type directly (e.g., for advanced patterns or testing), consider adding export to the type declaration.

🔎 Proposed addition
-type FeedbackContextType = {
+export type FeedbackContextType = {
     showFeedback: (message?: string) => void;
     shouldShowFeedback: boolean;
     message: string | null;
     resetFeedback: () => void;
 };

30-37: Consider exporting Props type and using a more specific name.

The AI summary lists Props as an exported entity, but it's not currently exported. Additionally, the generic name Props could be more descriptive.

🔎 Proposed refinement
-type Props = {
+export type FeedbackProviderProps = {
     children: Readonly<ReactNode>;
     /**
      * Optional callback to reset feedback when modal closes
      * If provided, feedback will be reset when the modal closes
      */
     resetOnModalClose?: boolean;
 };
 
-export const FeedbackProvider = ({
+export const FeedbackProvider = ({
     children,
     resetOnModalClose = false,
-}: Props) => {
+}: FeedbackProviderProps) => {
packages/vechain-kit/src/components/common/WalletSwitchFeedback.tsx (1)

13-17: Documentation mentions unimplemented behavior.

The JSDoc states the component "Handles both desktop (via props) and VeWorld in-app browser (via address change detection)", but only prop-based handling is implemented. Consider updating the comment to accurately reflect current behavior.

🔎 Proposed fix
 /**
  * Component that displays inline feedback when a wallet switch occurs.
- * Handles both desktop (via props) and VeWorld in-app browser (via address change detection).
+ * Handles wallet switch feedback on desktop via props.
  * Simply add this component where you want the feedback to appear.
  */
packages/vechain-kit/src/components/AccountModal/Contents/Profile/ProfileContent.tsx (1)

128-159: Use isInAppBrowser from useSwitchWallet for consistency.

At line 128, the condition uses connection.isInAppBrowser, but the component destructures isInAppBrowser from useSwitchWallet at line 38 and uses it in handleSwitchWallet at line 42. For consistency and to match the hook-based pattern, use the destructured value throughout the component.

🔎 Suggested fix
-                    {connection.isInAppBrowser && isSwitchWalletEnabled ? (
+                    {isInAppBrowser && isSwitchWalletEnabled ? (
                         <Button
                             size="md"
                             width="full"
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 08d44fe and 421f489.

📒 Files selected for processing (16)
  • packages/vechain-kit/src/components/AccountModal/AccountModal.tsx
  • packages/vechain-kit/src/components/AccountModal/Contents/Account/AccountMainContent.tsx
  • packages/vechain-kit/src/components/AccountModal/Contents/Profile/ProfileContent.tsx
  • packages/vechain-kit/src/components/AccountModal/Contents/SelectWallet/SelectWalletContent.tsx
  • packages/vechain-kit/src/components/AccountModal/Types/Types.ts
  • packages/vechain-kit/src/components/common/InlineFeedback.tsx
  • packages/vechain-kit/src/components/common/WalletSwitchFeedback.tsx
  • packages/vechain-kit/src/components/common/index.ts
  • packages/vechain-kit/src/languages/de.json
  • packages/vechain-kit/src/languages/en.json
  • packages/vechain-kit/src/languages/es.json
  • packages/vechain-kit/src/languages/fr.json
  • packages/vechain-kit/src/languages/it.json
  • packages/vechain-kit/src/languages/ja.json
  • packages/vechain-kit/src/languages/zh.json
  • packages/vechain-kit/src/providers/FeedbackProvider.tsx
🚧 Files skipped from review as they are similar to previous changes (6)
  • packages/vechain-kit/src/languages/ja.json
  • packages/vechain-kit/src/components/AccountModal/Contents/SelectWallet/SelectWalletContent.tsx
  • packages/vechain-kit/src/languages/de.json
  • packages/vechain-kit/src/languages/fr.json
  • packages/vechain-kit/src/languages/zh.json
  • packages/vechain-kit/src/languages/en.json
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/migration-guide-to-v2.mdc)

**/*.{ts,tsx}: In VeChain Kit Version 2, use useThor instead of useConnex for contract interactions
For single contract read operations, use the useCallClause hook with the pattern: import dependencies, define ABI and method as const, create query key function using getCallClauseQueryKeyWithArgs, and wrap with useCallClause including data transformation in the select option
For multiple parallel contract calls, use the executeMultipleClausesCall utility wrapped in a useQuery hook with the pattern: define query key function, use executeMultipleClausesCall in queryFn mapping items to clause objects, and transform results
For transaction building and sending, use the useBuildTransaction hook with a clauseBuilder function that returns an array of clauses with optional comment fields describing the transaction action
Always provide an arguments array for contract calls, even when no parameters are required - use an empty array for parameter-less functions to enable TypeScript type checking
Always conditionally enable queries using the enabled property to prevent unnecessary contract calls, checking for all required parameters: enabled: !!requiredParam && !!otherRequiredParam
Use the select option in useCallClause or transform data in queryFn to handle data transformation, particularly for converting BigInt values to strings and normalizing contract return data
Maintain consistent query key patterns: use getCallClauseQueryKeyWithArgs for contract calls with arguments and getCallClauseQueryKey for calls without arguments to ensure proper caching and invalidation
Use TypeScript as const assertions for method names and as 0x${string}`` assertions for Ethereum addresses to ensure type safety in contract interactions

Files:

  • packages/vechain-kit/src/components/AccountModal/Contents/Account/AccountMainContent.tsx
  • packages/vechain-kit/src/providers/FeedbackProvider.tsx
  • packages/vechain-kit/src/components/common/InlineFeedback.tsx
  • packages/vechain-kit/src/components/common/WalletSwitchFeedback.tsx
  • packages/vechain-kit/src/components/AccountModal/Types/Types.ts
  • packages/vechain-kit/src/components/AccountModal/Contents/Profile/ProfileContent.tsx
  • packages/vechain-kit/src/components/common/index.ts
  • packages/vechain-kit/src/components/AccountModal/AccountModal.tsx
🧬 Code graph analysis (5)
packages/vechain-kit/src/providers/FeedbackProvider.tsx (1)
packages/vechain-kit/src/providers/ModalProvider.tsx (1)
  • useModal (73-79)
packages/vechain-kit/src/components/common/WalletSwitchFeedback.tsx (1)
packages/vechain-kit/src/components/common/InlineFeedback.tsx (1)
  • InlineFeedback (41-100)
packages/vechain-kit/src/components/AccountModal/Types/Types.ts (1)
packages/vechain-kit/src/components/AccountModal/Contents/SelectWallet/RemoveWalletConfirmContent.tsx (1)
  • RemoveWalletConfirmContentProps (19-25)
packages/vechain-kit/src/components/AccountModal/Contents/Profile/ProfileContent.tsx (5)
packages/vechain-kit/src/components/AccountModal/Types/Types.ts (1)
  • AccountModalContentTypes (24-135)
packages/vechain-kit/src/hooks/api/wallet/useWallet.ts (1)
  • useWallet (62-410)
packages/vechain-kit/src/hooks/api/wallet/useSwitchWallet.ts (1)
  • useSwitchWallet (20-75)
packages/vechain-kit/src/components/common/ModalSettingsButton.tsx (1)
  • ModalSettingsButton (10-67)
packages/vechain-kit/src/components/common/WalletSwitchFeedback.tsx (1)
  • WalletSwitchFeedback (18-45)
packages/vechain-kit/src/components/AccountModal/AccountModal.tsx (3)
packages/vechain-kit/src/components/AccountModal/Contents/SelectWallet/SelectWalletContent.tsx (1)
  • SelectWalletContent (37-332)
packages/vechain-kit/src/components/AccountModal/Contents/Account/AccountMainContent.tsx (1)
  • AccountMainContent (35-109)
packages/vechain-kit/src/components/AccountModal/Contents/Profile/ProfileContent.tsx (1)
  • ProfileContent (31-190)
⏰ 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). (4)
  • GitHub Check: Lint & Build
  • GitHub Check: e2e-test
  • GitHub Check: build-size-check
  • GitHub Check: deploy
🔇 Additional comments (19)
packages/vechain-kit/src/languages/es.json (1)

22-22: LGTM! Spanish translations are accurate and consistent.

All new wallet-related Spanish translations are grammatically correct, contextually appropriate, and maintain consistency with existing translations in the file. The capitalization patterns match the English source keys, and the informal "tú" form is used consistently with the rest of the file.

Also applies to: 24-24, 44-44, 210-210, 247-247, 279-279, 281-281, 294-294, 327-327

packages/vechain-kit/src/languages/it.json (2)

22-22: LGTM! Italian translations are accurate.

The wallet-related translations are grammatically correct and appropriately capitalized for Italian UI labels. "Portafoglio" (wallet) is correctly used in singular and plural forms.

Also applies to: 24-24, 247-247, 294-294


44-44: LGTM! Status and confirmation messages are correctly translated.

The Italian translations are accurate:

  • The confirmation message uses appropriate formal phrasing
  • "Caricamento..." is the standard loading indicator
  • "Account cambiato" correctly uses the past participle agreeing with the masculine noun

Also applies to: 210-210, 327-327

packages/vechain-kit/src/providers/FeedbackProvider.tsx (3)

1-9: LGTM!

The imports are clean and all necessary dependencies are included. The pattern of importing useModal from the sibling provider is consistent with the existing codebase structure.


22-28: LGTM!

The hook implementation follows the exact same pattern as useModal from the codebase, ensuring consistency and proper error handling when used outside the provider context.


39-76: LGTM!

The provider implementation follows React best practices:

  • Proper use of useCallback with stable dependencies
  • Correct useEffect dependency array (including resetFeedback is appropriate despite it being stable)
  • Clean state management with separate state variables for clarity
  • The conditional reset logic on modal close is well-implemented
packages/vechain-kit/src/components/common/InlineFeedback.tsx (1)

41-50: Clean component structure with proper theming.

The component correctly uses Chakra UI's theming utilities (useToken, useColorModeValue) for color mode awareness. The animation keyframes are well-defined.

packages/vechain-kit/src/components/common/index.ts (1)

16-17: LGTM!

New component exports follow the existing barrel export pattern.

packages/vechain-kit/src/components/AccountModal/AccountModal.tsx (2)

100-103: LGTM!

The new remove-wallet-confirm case correctly spreads props to the content component.


135-164: Well-structured content routing with feedback support.

The implementation correctly handles both object-form content types (with switchFeedback props) and maintains the existing string-form cases for backward compatibility. The returnTo and onLogoutSuccess props are properly forwarded.

packages/vechain-kit/src/components/common/WalletSwitchFeedback.tsx (1)

18-44: Implementation is correct for the prop-based feedback flow.

The local state allows the feedback to be dismissed via onClose independently of when the parent updates the showFeedback prop, which is the intended UX.

packages/vechain-kit/src/components/AccountModal/Contents/Account/AccountMainContent.tsx (1)

82-84: LGTM!

Clean integration of WalletSwitchFeedback. The optional chaining correctly handles the case when switchFeedback is undefined.

packages/vechain-kit/src/components/AccountModal/Types/Types.ts (2)

20-22: LGTM!

Clean type definition for SwitchFeedback that's properly used across the modal content variants.


52-62: The setCurrentContent prop is not redundant and should remain in the type definition. SelectWalletContent actively uses this prop multiple times throughout the component to navigate between different modal states—including closing the modal, navigating to wallet removal confirmation, and returning to previous screens. This is a necessary prop for the component's navigation functionality, not a redundant pass-through.

Likely an incorrect or invalid review comment.

packages/vechain-kit/src/components/AccountModal/Contents/Profile/ProfileContent.tsx (5)

11-11: LGTM! Imports align with new wallet switching functionality.

The new imports for wallet switching hooks and feedback components are properly used throughout the component.

Also applies to: 14-17


28-28: LGTM! Prop definition and usage are correct.

The switchFeedback prop is properly typed and integrated into the component signature.

Also applies to: 34-34


38-39: LGTM! Hook usage is correct.

The hooks are properly called to access wallet switching functionality and state.


65-70: LGTM! Settings button is properly rendered.

The unconditional rendering of the settings button is appropriate, with internal state management handled by the component itself.


77-79: LGTM! Feedback component integration is correct.

The WalletSwitchFeedback component properly receives the showFeedback prop with safe optional chaining, and handles undefined/false values gracefully.

Comment on lines +52 to +61
useEffect(() => {
const timer = setTimeout(() => {
setIsVisible(false);
setTimeout(() => {
onClose?.();
}, 300); // Wait for animation to complete
}, duration);

return () => clearTimeout(timer);
}, [duration, onClose]);

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 | 🟡 Minor

Nested timeout may leak and invoke stale callback after unmount.

The inner setTimeout (line 55-57) is not cleaned up if the component unmounts after the outer timer fires but before the inner one completes. This can cause onClose to be invoked on an unmounted component.

🔎 Proposed fix
 export const InlineFeedback = ({
     message,
     duration = 2000,
     onClose,
 }: Props) => {
     const [isVisible, setIsVisible] = useState(true);
     const textPrimary = useToken('colors', 'vechain-kit-text-primary');
     const bgColor = useColorModeValue('#f0f9ff', '#0000009e');
     const borderColor = useColorModeValue('#bfdbfe', '#3b3b3b');
     const iconColor = useToken('colors', 'vechain-kit-primary');

     useEffect(() => {
+        let animationTimer: ReturnType<typeof setTimeout>;
         const timer = setTimeout(() => {
             setIsVisible(false);
-            setTimeout(() => {
+            animationTimer = setTimeout(() => {
                 onClose?.();
             }, 300); // Wait for animation to complete
         }, duration);

-        return () => clearTimeout(timer);
+        return () => {
+            clearTimeout(timer);
+            clearTimeout(animationTimer);
+        };
     }, [duration, onClose]);
🤖 Prompt for AI Agents
In packages/vechain-kit/src/components/common/InlineFeedback.tsx around lines 52
to 61, the inner setTimeout created to delay calling onClose after the animation
can leak and invoke a stale callback if the component unmounts after the outer
timer fires; capture both timeout IDs (outer and inner) in local variables (or
refs) and clear both in the effect cleanup, and ensure you call the latest
onClose by storing it in a ref (update the ref in render) before using it in the
inner timeout so the cleanup reliably prevents invoking onClose after unmount.

@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: 3

🧹 Nitpick comments (1)
packages/vechain-kit/src/hooks/api/wallet/useWallet.ts (1)

317-334: Consider consolidating or clarifying the purpose of this effect.

This effect appears to handle a specific edge case for saving the stored active wallet, but its relationship to the previous effect (lines 268-313) is not immediately clear. Both effects call saveWallet, which may lead to confusion about when and why each effect runs.

Consider:

  1. Adding a more detailed comment explaining when this effect runs that the previous effect doesn't cover, OR
  2. Consolidating the logic if possible to reduce duplication
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 421f489 and 4c3e112.

📒 Files selected for processing (1)
  • packages/vechain-kit/src/hooks/api/wallet/useWallet.ts
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/migration-guide-to-v2.mdc)

**/*.{ts,tsx}: In VeChain Kit Version 2, use useThor instead of useConnex for contract interactions
For single contract read operations, use the useCallClause hook with the pattern: import dependencies, define ABI and method as const, create query key function using getCallClauseQueryKeyWithArgs, and wrap with useCallClause including data transformation in the select option
For multiple parallel contract calls, use the executeMultipleClausesCall utility wrapped in a useQuery hook with the pattern: define query key function, use executeMultipleClausesCall in queryFn mapping items to clause objects, and transform results
For transaction building and sending, use the useBuildTransaction hook with a clauseBuilder function that returns an array of clauses with optional comment fields describing the transaction action
Always provide an arguments array for contract calls, even when no parameters are required - use an empty array for parameter-less functions to enable TypeScript type checking
Always conditionally enable queries using the enabled property to prevent unnecessary contract calls, checking for all required parameters: enabled: !!requiredParam && !!otherRequiredParam
Use the select option in useCallClause or transform data in queryFn to handle data transformation, particularly for converting BigInt values to strings and normalizing contract return data
Maintain consistent query key patterns: use getCallClauseQueryKeyWithArgs for contract calls with arguments and getCallClauseQueryKey for calls without arguments to ensure proper caching and invalidation
Use TypeScript as const assertions for method names and as 0x${string}`` assertions for Ethereum addresses to ensure type safety in contract interactions

Files:

  • packages/vechain-kit/src/hooks/api/wallet/useWallet.ts
🧠 Learnings (1)
📚 Learning: 2025-12-01T13:01:33.771Z
Learnt from: CR
Repo: vechain/vechain-kit PR: 0
File: .cursor/rules/migration-guide-to-v2.mdc:0-0
Timestamp: 2025-12-01T13:01:33.771Z
Learning: Applies to **/*.{ts,tsx} : In VeChain Kit Version 2, use `useThor` instead of `useConnex` for contract interactions

Applied to files:

  • packages/vechain-kit/src/hooks/api/wallet/useWallet.ts
🧬 Code graph analysis (1)
packages/vechain-kit/src/hooks/api/wallet/useWallet.ts (3)
packages/vechain-kit/src/hooks/api/wallet/useWalletStorage.ts (1)
  • useWalletStorage (17-138)
packages/vechain-kit/src/utils/ssrUtils.ts (1)
  • isBrowser (75-77)
packages/vechain-kit/src/hooks/api/wallet/useWalletMetadata.ts (1)
  • useWalletMetadata (10-34)
⏰ 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). (4)
  • GitHub Check: Lint & Build
  • GitHub Check: e2e-test
  • GitHub Check: build-size-check
  • GitHub Check: deploy
🔇 Additional comments (5)
packages/vechain-kit/src/hooks/api/wallet/useWallet.ts (5)

25-26: LGTM!

The new imports are properly used throughout the file to support wallet storage and SSR-safe browser detection.


165-168: LGTM!

The in-app browser detection properly uses optional chaining and provides a safe fallback, preventing errors in non-VeChain environments.


170-186: LGTM!

The stored active wallet state management correctly handles initialization and updates. The function dependency getActiveWallet is acceptable since it's memoized with useCallback in the useWalletStorage hook, ensuring it only changes when the network type changes.


241-262: LGTM!

The smart account and metadata queries correctly use effectiveConnectedWalletAddress to respect the stored active wallet selection. The conditional enabling is already handled within the useSmartAccount hook.


422-422: LGTM!

Good refactor to use the pre-computed isInAppBrowser variable instead of computing it inline. This ensures consistency throughout the hook and improves performance.

Comment on lines +188 to +217
// Track if a wallet switch is in progress to prevent overriding the user's selection
const [isWalletSwitchInProgress, setIsWalletSwitchInProgress] =
useState(false);

// Listen for wallet switch events
useEffect(() => {
if (!isBrowser() || !isConnectedWithDappKit || isInAppBrowser) return;

const handleWalletSwitch = (
event: CustomEvent<{ address: string }>,
) => {
setIsWalletSwitchInProgress(true);
setStoredActiveWalletAddress(event.detail.address);
// Reset the flag after a short delay to allow the connection to update
setTimeout(() => {
setIsWalletSwitchInProgress(false);
}, 1000);
};

window.addEventListener(
'wallet_switched',
handleWalletSwitch as EventListener,
);
return () => {
window.removeEventListener(
'wallet_switched',
handleWalletSwitch as EventListener,
);
};
}, [isConnectedWithDappKit, isInAppBrowser]);

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 | 🔴 Critical

Clean up the setTimeout to prevent memory leaks and state updates on unmounted components.

The timeout set on line 202 is not cleaned up when the component unmounts or when the effect re-runs. This can cause state updates on an unmounted component.

🔎 Proposed fix
 // Listen for wallet switch events
 useEffect(() => {
     if (!isBrowser() || !isConnectedWithDappKit || isInAppBrowser) return;
 
+    let timeoutId: NodeJS.Timeout | null = null;
+
     const handleWalletSwitch = (
         event: CustomEvent<{ address: string }>,
     ) => {
         setIsWalletSwitchInProgress(true);
         setStoredActiveWalletAddress(event.detail.address);
         // Reset the flag after a short delay to allow the connection to update
-        setTimeout(() => {
+        timeoutId = setTimeout(() => {
             setIsWalletSwitchInProgress(false);
+            timeoutId = null;
         }, 1000);
     };
 
     window.addEventListener(
         'wallet_switched',
         handleWalletSwitch as EventListener,
     );
     return () => {
+        if (timeoutId) {
+            clearTimeout(timeoutId);
+        }
         window.removeEventListener(
             'wallet_switched',
             handleWalletSwitch as EventListener,
         );
     };
 }, [isConnectedWithDappKit, isInAppBrowser]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Track if a wallet switch is in progress to prevent overriding the user's selection
const [isWalletSwitchInProgress, setIsWalletSwitchInProgress] =
useState(false);
// Listen for wallet switch events
useEffect(() => {
if (!isBrowser() || !isConnectedWithDappKit || isInAppBrowser) return;
const handleWalletSwitch = (
event: CustomEvent<{ address: string }>,
) => {
setIsWalletSwitchInProgress(true);
setStoredActiveWalletAddress(event.detail.address);
// Reset the flag after a short delay to allow the connection to update
setTimeout(() => {
setIsWalletSwitchInProgress(false);
}, 1000);
};
window.addEventListener(
'wallet_switched',
handleWalletSwitch as EventListener,
);
return () => {
window.removeEventListener(
'wallet_switched',
handleWalletSwitch as EventListener,
);
};
}, [isConnectedWithDappKit, isInAppBrowser]);
// Track if a wallet switch is in progress to prevent overriding the user's selection
const [isWalletSwitchInProgress, setIsWalletSwitchInProgress] =
useState(false);
// Listen for wallet switch events
useEffect(() => {
if (!isBrowser() || !isConnectedWithDappKit || isInAppBrowser) return;
let timeoutId: NodeJS.Timeout | null = null;
const handleWalletSwitch = (
event: CustomEvent<{ address: string }>,
) => {
setIsWalletSwitchInProgress(true);
setStoredActiveWalletAddress(event.detail.address);
// Reset the flag after a short delay to allow the connection to update
timeoutId = setTimeout(() => {
setIsWalletSwitchInProgress(false);
timeoutId = null;
}, 1000);
};
window.addEventListener(
'wallet_switched',
handleWalletSwitch as EventListener,
);
return () => {
if (timeoutId) {
clearTimeout(timeoutId);
}
window.removeEventListener(
'wallet_switched',
handleWalletSwitch as EventListener,
);
};
}, [isConnectedWithDappKit, isInAppBrowser]);
🤖 Prompt for AI Agents
In packages/vechain-kit/src/hooks/api/wallet/useWallet.ts around lines 188 to
217, the setTimeout used to reset isWalletSwitchInProgress is not cleaned up
which can cause state updates after unmount; store the timeout ID (e.g., in a
ref or local variable) when calling setTimeout, clear that timeout in the effect
cleanup (and before setting a new one when handler runs again), and guard the
state update by ensuring the component is still mounted (or simply rely on
clearing the timeout) so no setState runs after unmount.

Comment on lines +219 to +238
// Always prioritize the stored active wallet from cache when switching
// Use connected wallet when:
// 1. No stored active wallet exists (new connection)
// 2. Connected wallet is not in stored wallets list (new wallet after disconnect)
// 3. A switch is NOT in progress AND connected wallet differs from stored (reconnection with different wallet)
const storedWallets = getStoredWallets();
const isConnectedWalletInStoredList = storedWallets.some(
(w) =>
w.address.toLowerCase() === connectedWalletAddress?.toLowerCase(),
);

const effectiveConnectedWalletAddress =
// If switch is in progress, always use stored active wallet
isWalletSwitchInProgress && storedActiveWalletAddress
? storedActiveWalletAddress
: // If stored active wallet exists and connected wallet is in stored list, use stored (switch scenario)
storedActiveWalletAddress && isConnectedWalletInStoredList
? storedActiveWalletAddress
: // Otherwise use connected wallet (new connection or reconnection with different wallet)
connectedWalletAddress;

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

Memoize getStoredWallets() to avoid reading localStorage on every render.

Line 224 calls getStoredWallets() on every render, which reads from localStorage. This is a performance concern, especially as the component re-renders frequently due to wallet state changes.

🔎 Proposed fix using useMemo
+// Memoize stored wallets to avoid reading from localStorage on every render
+const storedWallets = useMemo(() => {
+    if (isConnectedWithDappKit && !isInAppBrowser) {
+        return getStoredWallets();
+    }
+    return [];
+}, [isConnectedWithDappKit, isInAppBrowser, getStoredWallets, connectedWalletAddress]);
+
-const storedWallets = getStoredWallets();
 const isConnectedWalletInStoredList = storedWallets.some(
     (w) =>
         w.address.toLowerCase() === connectedWalletAddress?.toLowerCase(),
 );

Note: Include connectedWalletAddress as a dependency to refresh when the wallet changes, ensuring the list stays current.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In packages/vechain-kit/src/hooks/api/wallet/useWallet.ts around lines 219 to
238, getStoredWallets() is called on every render which repeatedly reads
localStorage; wrap the call in a useMemo (e.g., const storedWallets = useMemo(()
=> getStoredWallets(), [connectedWalletAddress]) ) so the stored wallets list is
computed only when connectedWalletAddress changes (or other relevant deps), and
then use the memoized storedWallets in the subsequent logic to avoid unnecessary
localStorage reads.

Comment on lines +268 to +313
useEffect(() => {
if (
isConnectedWithDappKit &&
!isInAppBrowser &&
connectedWalletAddress &&
activeAccountMetadata &&
!activeAccountMetadata.isLoading
) {
// First try to initialize (only saves if no wallets exist and sets as active)
initializeCurrentWallet(connectedWalletAddress);
// Always save/update the wallet (in case it already exists or is a new connection)
saveWallet(connectedWalletAddress);

// Check if this is a new connection or a switch
// When switching, storedActiveWalletAddress is updated immediately via wallet_switched event
// and isWalletSwitchInProgress is set to true
// We should NOT override the stored active wallet when switching
const isNewConnection = !storedActiveWalletAddress;
const isSameAsStoredActive =
storedActiveWalletAddress &&
storedActiveWalletAddress.toLowerCase() ===
connectedWalletAddress.toLowerCase();

// Only set as active if:
// 1. It's a new connection (no stored active wallet), OR
// 2. The connected wallet matches the stored active wallet (same wallet, just ensuring it's saved), AND
// 3. A wallet switch is NOT in progress (to prevent overriding user's selection during switch)
if (
(isNewConnection || isSameAsStoredActive) &&
!isWalletSwitchInProgress
) {
setActiveWalletStorage(connectedWalletAddress);
}
}
}, [
isConnectedWithDappKit,
isInAppBrowser,
connectedWalletAddress,
activeAccountMetadata?.domain,
activeAccountMetadata?.image,
activeAccountMetadata?.isLoading,
initializeCurrentWallet,
saveWallet,
setActiveWalletStorage,
storedActiveWalletAddress,
]);

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 | 🔴 Critical

Add missing dependency and fix granular dependency issues.

Two issues with this effect:

  1. Critical: isWalletSwitchInProgress is used on line 297 but is missing from the dependency array. This causes a stale closure bug where the effect always sees the initial value of the flag.

  2. Major: The dependency array includes granular properties (activeAccountMetadata?.domain, activeAccountMetadata?.image, activeAccountMetadata?.isLoading). This is fragile and can cause the effect to run unnecessarily or miss important updates. React best practice is to depend on the whole object or use a more stable dependency.

🔎 Proposed fix
     }, [
         isConnectedWithDappKit,
         isInAppBrowser,
         connectedWalletAddress,
-        activeAccountMetadata?.domain,
-        activeAccountMetadata?.image,
-        activeAccountMetadata?.isLoading,
+        activeAccountMetadata.isLoading,
         initializeCurrentWallet,
         saveWallet,
         setActiveWalletStorage,
         storedActiveWalletAddress,
+        isWalletSwitchInProgress,
     ]);

Alternatively, you could add a condition inside the effect to check isLoading before proceeding:

 useEffect(() => {
     if (
         isConnectedWithDappKit &&
         !isInAppBrowser &&
         connectedWalletAddress &&
-        activeAccountMetadata &&
-        !activeAccountMetadata.isLoading
+        !activeAccountMetadata.isLoading
     ) {
         // ... rest of logic
     }
 }, [
     isConnectedWithDappKit,
     isInAppBrowser,
     connectedWalletAddress,
+    activeAccountMetadata.isLoading,
     initializeCurrentWallet,
     saveWallet,
     setActiveWalletStorage,
     storedActiveWalletAddress,
+    isWalletSwitchInProgress,
 ]);

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In packages/vechain-kit/src/hooks/api/wallet/useWallet.ts around lines 268 to
313, the effect is missing isWalletSwitchInProgress in its dependency array
(causing a stale-closure bug) and uses fragile granular deps
activeAccountMetadata?.domain, activeAccountMetadata?.image,
activeAccountMetadata?.isLoading; update the dependency array to include
isWalletSwitchInProgress and replace the granular activeAccountMetadata
properties with the full activeAccountMetadata object (i.e., use
activeAccountMetadata) so the effect responds correctly to metadata changes and
the wallet-switch flag.

@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: 0

♻️ Duplicate comments (3)
packages/vechain-kit/src/hooks/api/wallet/useWallet.ts (3)

198-223: Clean up the setTimeout to prevent memory leaks and state updates on unmounted components.

The timeout set on line 208 is not cleaned up when the component unmounts or when the effect re-runs. This can cause state updates on an unmounted component.

🔎 Proposed fix
 // Listen for wallet switch events
 useEffect(() => {
     if (!isBrowser() || !isConnectedWithDappKit || isInAppBrowser) return;
 
+    let timeoutId: NodeJS.Timeout | null = null;
+
     const handleWalletSwitch = (
         event: CustomEvent<{ address: string }>,
     ) => {
         setIsWalletSwitchInProgress(true);
         setStoredActiveWalletAddress(event.detail.address);
         // Reset the flag after a short delay to allow the connection to update
-        setTimeout(() => {
+        timeoutId = setTimeout(() => {
             setIsWalletSwitchInProgress(false);
+            timeoutId = null;
         }, 1000);
     };
 
     window.addEventListener(
         'wallet_switched',
         handleWalletSwitch as EventListener,
     );
     return () => {
+        if (timeoutId) {
+            clearTimeout(timeoutId);
+        }
         window.removeEventListener(
             'wallet_switched',
             handleWalletSwitch as EventListener,
         );
     };
 }, [isConnectedWithDappKit, isInAppBrowser]);

230-230: Memoize getStoredWallets() to avoid reading localStorage on every render.

Line 230 calls getStoredWallets() on every render, which reads from localStorage. This is a performance concern, especially as the component re-renders frequently due to wallet state changes.

🔎 Proposed fix using useMemo
+// Memoize stored wallets to avoid reading from localStorage on every render
+const storedWallets = useMemo(() => {
+    if (isConnectedWithDappKit && !isInAppBrowser) {
+        return getStoredWallets();
+    }
+    return [];
+}, [isConnectedWithDappKit, isInAppBrowser, getStoredWallets, connectedWalletAddress]);
+
-const storedWallets = getStoredWallets();
 const isConnectedWalletInStoredList = storedWallets.some(
     (w) =>
         w.address.toLowerCase() === connectedWalletAddress?.toLowerCase(),
 );

Note: Include connectedWalletAddress as a dependency to refresh when the wallet changes.


274-319: Add missing dependency and fix granular dependency issues.

Two issues with this effect:

  1. Critical: isWalletSwitchInProgress is used on line 303 but is missing from the dependency array (lines 308-319). This causes a stale closure bug where the effect always sees the initial value of the flag.

  2. Major: The dependency array includes granular properties (activeAccountMetadata?.domain, activeAccountMetadata?.image, activeAccountMetadata?.isLoading) on lines 312-314. This is fragile and can cause the effect to run unnecessarily or miss important updates.

🔎 Proposed fix
     }, [
         isConnectedWithDappKit,
         isInAppBrowser,
         connectedWalletAddress,
-        activeAccountMetadata?.domain,
-        activeAccountMetadata?.image,
-        activeAccountMetadata?.isLoading,
+        activeAccountMetadata.isLoading,
         initializeCurrentWallet,
         saveWallet,
         setActiveWalletStorage,
         storedActiveWalletAddress,
+        isWalletSwitchInProgress,
     ]);
🧹 Nitpick comments (1)
packages/vechain-kit/src/hooks/api/wallet/useWallet.ts (1)

321-340: Consider consolidating wallet save logic to avoid redundant storage writes.

This effect saves the wallet when storedActiveWalletAddress changes and matches effectiveConnectedWalletAddress. The previous effect (lines 274-319) already handles saving wallets on connection. This might lead to redundant localStorage writes.

Consider whether this second effect is necessary or if the logic can be consolidated into the first effect.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4c3e112 and 0bf64be.

📒 Files selected for processing (1)
  • packages/vechain-kit/src/hooks/api/wallet/useWallet.ts
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/migration-guide-to-v2.mdc)

**/*.{ts,tsx}: In VeChain Kit Version 2, use useThor instead of useConnex for contract interactions
For single contract read operations, use the useCallClause hook with the pattern: import dependencies, define ABI and method as const, create query key function using getCallClauseQueryKeyWithArgs, and wrap with useCallClause including data transformation in the select option
For multiple parallel contract calls, use the executeMultipleClausesCall utility wrapped in a useQuery hook with the pattern: define query key function, use executeMultipleClausesCall in queryFn mapping items to clause objects, and transform results
For transaction building and sending, use the useBuildTransaction hook with a clauseBuilder function that returns an array of clauses with optional comment fields describing the transaction action
Always provide an arguments array for contract calls, even when no parameters are required - use an empty array for parameter-less functions to enable TypeScript type checking
Always conditionally enable queries using the enabled property to prevent unnecessary contract calls, checking for all required parameters: enabled: !!requiredParam && !!otherRequiredParam
Use the select option in useCallClause or transform data in queryFn to handle data transformation, particularly for converting BigInt values to strings and normalizing contract return data
Maintain consistent query key patterns: use getCallClauseQueryKeyWithArgs for contract calls with arguments and getCallClauseQueryKey for calls without arguments to ensure proper caching and invalidation
Use TypeScript as const assertions for method names and as 0x${string}`` assertions for Ethereum addresses to ensure type safety in contract interactions

Files:

  • packages/vechain-kit/src/hooks/api/wallet/useWallet.ts
🧠 Learnings (1)
📚 Learning: 2025-12-01T13:01:33.771Z
Learnt from: CR
Repo: vechain/vechain-kit PR: 0
File: .cursor/rules/migration-guide-to-v2.mdc:0-0
Timestamp: 2025-12-01T13:01:33.771Z
Learning: Applies to **/*.{ts,tsx} : In VeChain Kit Version 2, use `useThor` instead of `useConnex` for contract interactions

Applied to files:

  • packages/vechain-kit/src/hooks/api/wallet/useWallet.ts
🧬 Code graph analysis (1)
packages/vechain-kit/src/hooks/api/wallet/useWallet.ts (5)
packages/vechain-kit/src/hooks/api/wallet/useWalletStorage.ts (1)
  • useWalletStorage (17-138)
packages/vechain-kit/src/hooks/utils/useGetNodeUrl.ts (1)
  • useGetNodeUrl (4-8)
packages/vechain-kit/src/utils/ssrUtils.ts (1)
  • isBrowser (75-77)
packages/vechain-kit/src/hooks/thor/smartAccounts/useSmartAccount.ts (1)
  • useSmartAccount (51-60)
packages/vechain-kit/src/hooks/api/wallet/useWalletMetadata.ts (1)
  • useWalletMetadata (10-34)
⏰ 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). (4)
  • GitHub Check: Lint & Build
  • GitHub Check: deploy
  • GitHub Check: build-size-check
  • GitHub Check: e2e-test
🔇 Additional comments (4)
packages/vechain-kit/src/hooks/api/wallet/useWallet.ts (4)

79-84: LGTM!

Clean integration of the wallet storage hook. The destructured functions are appropriately used throughout the component for wallet persistence.


88-92: LGTM!

The in-app browser detection is well-implemented with SSR safety via isBrowser() guard and appropriate memoization.


247-249: LGTM!

The usage of effectiveConnectedWalletAddress is consistent and properly integrated with smart account retrieval and metadata fetching.

Also applies to: 253-253, 262-263


428-428: LGTM!

Exposing isInAppBrowser as part of the connection state is consistent with the pattern and provides useful context to consumers.

@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: 1

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0bf64be and e6a1abb.

📒 Files selected for processing (3)
  • examples/homepage/i18n.ts
  • examples/homepage/src/app/languages/en.json
  • packages/vechain-kit/src/providers/VeChainKitProvider.tsx
💤 Files with no reviewable changes (1)
  • packages/vechain-kit/src/providers/VeChainKitProvider.tsx
✅ Files skipped from review due to trivial changes (1)
  • examples/homepage/src/app/languages/en.json
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/migration-guide-to-v2.mdc)

**/*.{ts,tsx}: In VeChain Kit Version 2, use useThor instead of useConnex for contract interactions
For single contract read operations, use the useCallClause hook with the pattern: import dependencies, define ABI and method as const, create query key function using getCallClauseQueryKeyWithArgs, and wrap with useCallClause including data transformation in the select option
For multiple parallel contract calls, use the executeMultipleClausesCall utility wrapped in a useQuery hook with the pattern: define query key function, use executeMultipleClausesCall in queryFn mapping items to clause objects, and transform results
For transaction building and sending, use the useBuildTransaction hook with a clauseBuilder function that returns an array of clauses with optional comment fields describing the transaction action
Always provide an arguments array for contract calls, even when no parameters are required - use an empty array for parameter-less functions to enable TypeScript type checking
Always conditionally enable queries using the enabled property to prevent unnecessary contract calls, checking for all required parameters: enabled: !!requiredParam && !!otherRequiredParam
Use the select option in useCallClause or transform data in queryFn to handle data transformation, particularly for converting BigInt values to strings and normalizing contract return data
Maintain consistent query key patterns: use getCallClauseQueryKeyWithArgs for contract calls with arguments and getCallClauseQueryKey for calls without arguments to ensure proper caching and invalidation
Use TypeScript as const assertions for method names and as 0x${string}`` assertions for Ethereum addresses to ensure type safety in contract interactions

Files:

  • examples/homepage/i18n.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). (4)
  • GitHub Check: e2e-test
  • GitHub Check: Lint & Build
  • GitHub Check: deploy
  • GitHub Check: build-size-check

Comment thread examples/homepage/i18n.ts
Comment on lines +42 to 50
if (typeof window === 'undefined') return 'en';

if (stored && supportedLanguages.includes(stored)) return stored;
if (browser && supportedLanguages.includes(browser)) return browser;
}
const stored = localStorage.getItem('i18nextLng');
const browser = navigator.language?.split('-')[0];

if (stored && supportedLanguages.includes(stored)) return stored;
if (browser && supportedLanguages.includes(browser)) return browser;

return 'en'; // fallback

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

Wrap localStorage access in try-catch to prevent initialization failures.

The localStorage.getItem() call on line 44 can throw exceptions in privacy mode, when storage is disabled, or in restricted security contexts. Since this function runs during i18n initialization, an unhandled exception would break the entire language detection system.

🔎 Proposed fix to add exception handling
 if (typeof window === 'undefined') return 'en';
 
-const stored = localStorage.getItem('i18nextLng');
+let stored: string | null = null;
+try {
+    stored = localStorage.getItem('i18nextLng');
+} catch {
+    // Storage access blocked - fall through to browser detection
+}
 const browser = navigator.language?.split('-')[0];
 
 if (stored && supportedLanguages.includes(stored)) return stored;
 if (browser && supportedLanguages.includes(browser)) return browser;

Note: The SSR guard on line 42 and optional chaining on line 45 for navigator.language are well-handled.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (typeof window === 'undefined') return 'en';
if (stored && supportedLanguages.includes(stored)) return stored;
if (browser && supportedLanguages.includes(browser)) return browser;
}
const stored = localStorage.getItem('i18nextLng');
const browser = navigator.language?.split('-')[0];
if (stored && supportedLanguages.includes(stored)) return stored;
if (browser && supportedLanguages.includes(browser)) return browser;
return 'en'; // fallback
if (typeof window === 'undefined') return 'en';
let stored: string | null = null;
try {
stored = localStorage.getItem('i18nextLng');
} catch {
// Storage access blocked - fall through to browser detection
}
const browser = navigator.language?.split('-')[0];
if (stored && supportedLanguages.includes(stored)) return stored;
if (browser && supportedLanguages.includes(browser)) return browser;
return 'en'; // fallback
🤖 Prompt for AI Agents
In examples/homepage/i18n.ts around lines 42 to 50,
localStorage.getItem('i18nextLng') can throw (e.g., in privacy mode), so wrap
the access in a try-catch: attempt to read localStorage inside the try, assign
to a local variable only if read succeeds, and on any error silently ignore and
continue to the browser/‘en’ fallbacks; ensure you do not change the existing
SSR guard or navigator optional chaining and preserve supportedLanguages checks.

@mikeredmond mikeredmond left a comment

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.

Looks great 🚀

@Agilulfo1820 Agilulfo1820 merged commit b20bb5a into main Dec 22, 2025
8 checks passed
@Agilulfo1820 Agilulfo1820 deleted the feat/switch-desktop-wallet branch December 22, 2025 15:28
@coderabbitai coderabbitai Bot mentioned this pull request Dec 23, 2025
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