feat: internal account list in address book#10929
Conversation
- Gate AddressBook save feature with feature flag in SendAmountDetails - Add aria-label to back button for accessibility - Consolidate AddressBookEntry type to single source of truth - Fix Date.now() timestamp inconsistency in addAddress reducer - Add duplicate address validation to prevent saving same address twice - Add defensive chainId validation in selectors 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Implements selector-based filtering and enhanced UX for internal accounts list in the Send modal. Key improvements: - Replace useMemo filtering with Redux selectors (selectInternalAccountsBySearchQuery) - Display UTXO account type (Legacy, SegWit, Native SegWit) below account name instead of pubkey - Enhanced search: find accounts by address, account type, or account number - Show all accounts when input has valid value without errors (improved display logic) - Add blockies avatars for non-UTXO accounts, ProfileAvatar for UTXO accounts - Extract InternalAccountButton component for better code organization - Add useInternalAccountReceiveAddress hook for async UTXO address fetching 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
📝 WalkthroughWalkthroughThis PR implements functionality to display internal accounts within the address book for the send flow. Changes include new UI components for rendering account lists and buttons, a hook for resolving receive addresses, utility functions for UTXO account operations, translation keys, and Redux selectors for filtering accounts. Changes
Sequence DiagramsequenceDiagram
participant User as User
participant AddressBook as AddressBook
participant InternalList as InternalAccountsList
participant Button as InternalAccountButton
participant Hook as useInternalAccountReceiveAddress
participant FormContext as React Hook Form
User->>AddressBook: Open address book
AddressBook->>InternalList: Render with chainId
InternalList->>InternalList: Filter accounts by chainId & search query
loop For each internal account
InternalList->>Button: Render InternalAccountButton
end
User->>Button: Click account
alt UTXO Account
Button->>Hook: Fetch receive address (enabled)
Hook->>Hook: Validate chain & metadata
Hook-->>Button: Return address
Button->>FormContext: Set address value
Button->>Button: Mark loading complete
else Non-UTXO Account
Button->>FormContext: Set address directly
end
Button->>AddressBook: Call onEntryClick callback
AddressBook->>User: Update recipient field
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches✅ Passed checks (5 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro Disabled knowledge base sources:
📒 Files selected for processing (5)
🚧 Files skipped from review as they are similar to previous changes (3)
🧰 Additional context used📓 Path-based instructions (5)**/*📄 CodeRabbit inference engine (.cursor/rules/naming-conventions.mdc)
Files:
**/*.{ts,tsx}📄 CodeRabbit inference engine (.cursor/rules/error-handling.mdc)
Files:
**/*.tsx📄 CodeRabbit inference engine (.cursor/rules/error-handling.mdc)
Files:
**/*.{tsx,jsx}📄 CodeRabbit inference engine (.cursor/rules/react-best-practices.mdc)
Files:
**/*.{ts,tsx,js,jsx}📄 CodeRabbit inference engine (.cursor/rules/react-best-practices.mdc)
Files:
🧠 Learnings (6)📓 Common learnings📚 Learning: 2025-10-21T23:21:22.304ZApplied to files:
📚 Learning: 2025-07-24T21:05:13.642ZApplied to files:
📚 Learning: 2025-08-17T23:39:00.407ZApplied to files:
📚 Learning: 2025-10-21T17:11:18.087ZApplied to files:
📚 Learning: 2025-08-08T15:00:49.887ZApplied to files:
⏰ 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). (1)
🔇 Additional comments (2)
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. Comment |
premiumjibles
left a comment
There was a problem hiding this comment.
Overall works as advertised and code looks sane 👌 . Few nitpicky things to action if you like but none blocking
src/components/Modals/Send/AddressBook/InternalAccountsList.tsx
Outdated
Show resolved
Hide resolved
src/components/Modals/Send/AddressBook/InternalAccountsList.tsx
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (4)
src/lib/utils/accounts.ts (1)
37-41: Type guard implementation is correct.The function properly narrows the type and checks both conditions. The
Boolean()wrapper is redundant since the expression already returns a boolean, but this is a minor style preference.If you prefer a cleaner implementation:
export const isUtxoAccountWithAddresses = ( account: Account<KnownChainIds>, ): account is Account<UtxoChainId> => { - return Boolean(isUtxoChainId(account?.chainId) && 'addresses' in account.chainSpecific) + return isUtxoChainId(account?.chainId) && 'addresses' in account.chainSpecific }src/components/Modals/Send/AddressBook/hooks/useInternalAccountReceiveAddress.tsx (2)
40-44: Remove redundant null checks inside queryFn.The queryFn at line 40 only executes when
enabled && !isInitializing && asset && wallet && accountId && accountMetadataare all truthy. The null checks at lines 42-44 are therefore redundant.Apply this diff to simplify the code:
queryFn: enabled && !isInitializing && asset && wallet && accountId && accountMetadata ? async () => { - if (!asset || !wallet || !accountId || !accountMetadata) { - return null - } - const assetChainId = asset.chainId
53-54: Replace error throw with null return for missing accountType.Throwing an error here will cause the query to fail and potentially crash the UI. For a better user experience, return
nullto handle the missing accountType gracefully, similar to the chain mismatch handling at lines 49-51.Apply this diff:
if (isUtxoAccountId(accountId) && !accountMetadata?.accountType) - throw new Error(`Missing accountType for UTXO account ${accountId}`) + return nullsrc/state/slices/portfolioSlice/selectors.ts (1)
1280-1301: Sort results by account number for consistent ordering.The selector currently returns results sorted by match relevance when a search query is present, and in arbitrary order when no search query is provided. For consistency and better UX, the results should be sorted by account number in ascending order.
Apply this diff to sort by account number:
export const selectInternalAccountsBySearchQuery = createDeepEqualOutputSelector( selectAccountIdsByChainIdFilter, selectPortfolioAccountMetadata, selectSearchQueryFromFilter, (accountIds, accountMetadata, searchQuery): AccountId[] => { - if (!searchQuery) return accountIds + const filteredAccountIds = searchQuery + ? matchSorter(accountIds, searchQuery, { + keys: [ + { key: (accountId: AccountId) => fromAccountId(accountId).account }, + { key: (accountId: AccountId) => accountIdToLabel(accountId) }, + { + key: (accountId: AccountId) => { + const metadata = accountMetadata[accountId] + return metadata?.bip44Params?.accountNumber?.toString() ?? '' + }, + }, + ], + threshold: matchSorter.rankings.CONTAINS, + }) + : accountIds - return matchSorter(accountIds, searchQuery, { - keys: [ - { key: (accountId: AccountId) => fromAccountId(accountId).account }, - { key: (accountId: AccountId) => accountIdToLabel(accountId) }, - { - key: (accountId: AccountId) => { - const metadata = accountMetadata[accountId] - return metadata?.bip44Params?.accountNumber?.toString() ?? '' - }, - }, - ], - threshold: matchSorter.rankings.CONTAINS, - }) + // Sort by account number for consistent ordering + return filteredAccountIds.sort((a, b) => { + const accountNumberA = accountMetadata[a]?.bip44Params?.accountNumber ?? 0 + const accountNumberB = accountMetadata[b]?.bip44Params?.accountNumber ?? 0 + return accountNumberA - accountNumberB + }) }, )Based on previous review feedback suggesting sorting by account number.
Description
This PR display the internal accounts in the address book, using quite a similar logic from what we have in the address book already but for accountIds we already know about!
Issue (if applicable)
closes #10920
Risk
Low - UI improvements only, no changes to transaction logic or data handling
Display of internal accounts in the Send modal for all chains (particularly UTXO chains: BTC, LTC, DOGE, BCH)
Testing
Manual testing in the Send modal:
Open the Send modal for various chains (BTC, ETH, LTC, etc.)
Verify internal accounts list displays correctly:
Test search functionality:
Verify display logic:
Engineering
Created new files:
src/components/Modals/Send/AddressBook/InternalAccountsList.tsx- Main list component using selectors instead of useMemosrc/components/Modals/Send/AddressBook/InternalAccountButton.tsx- Individual account button component with conditional avatar renderingsrc/components/Modals/Send/AddressBook/hooks/useInternalAccountReceiveAddress.tsx- Hook for async UTXO address fetchingModified files:
src/state/slices/portfolioSlice/selectors.ts- AddedselectInternalAccountsBySearchQueryselector using match-sorter for flexible search by address, account type, and account numbersrc/components/Modals/Send/AddressBook/AddressBook.tsx- Imports and renders newInternalAccountsListcomponentsrc/assets/translations/en/main.json- Added translation key for "Your Wallets" section headerTechnical details:
match-sorterlibrary for fuzzy search across multiple fieldsOperations
Screenshots (if applicable)
Summary by CodeRabbit