Skip to content

Conversation

@NeOMakinG
Copy link
Collaborator

@NeOMakinG NeOMakinG commented Oct 13, 2025

Description

This PR adds the address book into the send flow:

  • A way to add an address to it
  • A way to delete addresses (by having a confirm modal)
  • Be able to select and search an address from the address book
  • Add migrations for the address book
  • A flag to disable it for now in production but enable on develop
  • Bring back ens resolving at address step, show them in every other steps
  • Internal account resolutions for any accounts that are not UTXOs

Issue (if applicable)

closes #10694

Risk

Low, new feature behind a flag

High Risk PRs Require 2 approvals

What protocols, transaction types, wallets or contract interactions might be affected by this PR?

Testing

  • Go in the send flow
  • Add an address to the input, notice the "save" button
  • Click save, add an address to your address book
  • Add multiple addresses
  • Try to search by typing in the input, by name or address
  • Notice if the address in the input is matching we are showing every addresses from the address book
  • If the address is full and valid, the address book is showing all addresses
  • Notice the address book show addresses only for the current namespace, so if you are sending some UTXOs we are not showing EVM addresses
  • Clicking on the selected address book component is showing the input so you can change the address
  • Input is autofocusing on click
  • You can delete an address by clicking the trash icon and confirming
  • Try to input an ENS address
  • Try to input an internal address (account you have activated in the current wallet)
  • Both mobile and desktop works the same

Engineering

Operations

  • 🏁 My feature is behind a flag and doesn't require operations testing (yet)

Screenshots (if applicable)

image image image image image image image image image image

Summary by CodeRabbit

  • New Features

    • Address book: save, search, select, and delete addresses with confirmation and a save modal; persisted storage and migrations; feature flag to toggle.
  • UI

    • New translations and labels (Save, Delete, address book strings).
    • Avatars and improved recipient display in confirmation screens.
    • Unified address input and QR-scan interactions for a consistent send experience.
  • Chores

    • Simplified send flow navigation for consistent behavior across viewports.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 13, 2025

📝 Walkthrough

Walkthrough

Adds an Address Book feature flag and implementation: new persisted Redux slice with migrations, types, selectors and utils; UI components and modals for save/select/delete flows; wiring into the send modal and inputs; translation keys; and removal/simplification of responsive send routing.

Changes

Cohort / File(s) Summary
Env & Config
\.env.development, \.env.production, src/config.ts, src/vite-env.d.ts
Added VITE_FEATURE_ADDRESS_BOOK (dev=true, prod=false), validator in src/config.ts, and ImportMetaEnv typing.
Translations
src/assets/translations/en/main.json
Added save/delete and address-book / send-form translation keys.
Redux: Slice & Types
src/state/slices/addressBookSlice/*
src/state/slices/addressBookSlice/addressBookSlice.ts, .../selectors.ts, .../types.ts, .../utils.ts
New addressBook slice, AddressBookEntry type, isDuplicateEntry util, add/delete/clear reducers, and chain-aware selectors (search, by-chain, internal/external lookups).
Redux: Integration & Persistence
src/state/reducer.ts, src/state/store.ts, src/state/migrations/*, src/state/selectors.ts, src/state/slices/common-selectors.ts, src/state/slices/preferencesSlice/preferencesSlice.ts, src/test/mocks/store.ts
Registered persisted addressBook slice (localforage) with migrations (clearAddressBook), added feature flag to preferences, partitioned account IDs selector, new selectAccountAddressParamFromFilter, and test store updates.
Modals: Registration & Types
src/context/ModalProvider/ModalContainer.tsx, src/context/ModalProvider/types.ts
Registered lazy-loaded AddressBookSaveModal and extended Modals type with addressBookSave.
UI: Address Book Components
src/components/Modals/Send/AddressBook/AddressBook.tsx, .../AddressBookEntryButton.tsx, .../ConfirmDelete.tsx, AddressBookSaveModal.tsx
Added AddressBook list, entry button, confirm-delete modal, and save-address modal with form/validation and dispatch to addAddress.
Send Flow: Routing & Common
src/components/Modals/Send/SendCommon.tsx, src/components/Modals/Send/Send.tsx, src/components/Modals/Send/QrCode/QrCode.tsx, src/components/Modals/Send/Form.tsx
Removed responsive route branching, renamed mobileSendRoutesinitialEntries, added Amount route, and simplified routing/initialEntries usage.
Send Flow: Inputs & Views
src/components/Modals/Send/AddressInput/AddressInput.tsx, src/components/Modals/Send/views/Address.tsx, src/components/Modals/Send/views/SendAmountDetails.tsx, src/components/Modals/Send/views/Confirm.tsx
Extended AddressInput props (resolvedAddress, chainId, isReadOnly, shouldShowSaveButton, pe), added address-book integration to Address view, unified AddressInput usage, and avatar/label rendering in Confirm.
Removed Component
src/components/Modals/Send/components/AddressInputWithDropdown.tsx
Removed desktop dropdown component and its public interface; functionality consolidated into AddressInput / AddressBook.
Misc UI
src/components/CryptoFiatInput/CryptoFiatInput.tsx
Minor toggle UI refactor: combined label+icon into a single Button.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant User
    participant SendModal
    participant AddressBookUI
    participant ModalProvider
    participant Redux
    Note over SendModal: Address selection / save / delete flows (feature-flag gated)
    User->>SendModal: Open Send -> Address step
    SendModal->>AddressBookUI: Request entries (chainId)
    AddressBookUI->>SendModal: Render list
    User->>AddressBookUI: Click entry
    AddressBookUI->>SendModal: Populate address input
    SendModal->>SendModal: Navigate -> AmountDetails

    Note over SendModal,ModalProvider: Save new address flow
    User->>SendModal: Enter/paste address -> click Save
    SendModal->>ModalProvider: Open AddressBookSaveModal
    User->>ModalProvider: Submit label
    ModalProvider->>Redux: dispatch addAddress(entry)
    Redux->>Redux: isDuplicateEntry (EVM-aware)
    alt Not duplicate
        Redux-->>ModalProvider: saved
    else Duplicate
        Redux-->>ModalProvider: rejected
    end
    ModalProvider-->>SendModal: close modal -> input updated

    Note over SendModal,AddressBookUI: Delete flow
    User->>AddressBookUI: Click delete on entry
    AddressBookUI->>ModalProvider: Open ConfirmDelete
    User->>ModalProvider: Confirm delete
    ModalProvider->>Redux: dispatch deleteAddress(entry)
    Redux-->>AddressBookUI: removed -> UI updates
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Areas needing extra attention:
    • addressBook slice reducers (add/delete) and EVM cross-chain deletion semantics
    • isDuplicateEntry normalization and EVM vs non‑EVM handling
    • new selectors (partitioned account IDs, memoization) and their consumers
    • persistence/migration wiring (clearAddressBook) and store integration
    • feature-flag gating across UI and state when flag is false
    • API surface changes to AddressInput props and removed AddressInputWithDropdown

Possibly related PRs

Suggested reviewers

  • premiumjibles
  • 0xApotheosis

Poem

🐇 I hopped through code at break of dawn,

Collected names and blocky art drawn,
From paste to save, each contact in tow,
Now sends find friends wherever they go,
A rabbit cheers — the address book's grown!

Pre-merge checks and finishing touches

❌ Failed checks (3 warnings)
Check name Status Explanation Resolution
Title Check ⚠️ Warning The pull request title "fix: address book baby" is problematic for several reasons. First, it uses the "fix:" prefix when this changeset is implementing a new feature (the address book functionality in the send flow), not fixing a bug, so "feat:" would be more appropriate. Second, the term "baby" is informal and vague, providing no meaningful information about the changes. While "address book" is mentioned and directly relates to the main change, the title does not clearly convey to a teammate scanning the history that this implements the desktop send flow redesign with address book integration. Consider updating the title to something like "feat: implement address book in send flow" or "feat: add address book with save/delete functionality" to accurately reflect the nature of the change (a feature addition, not a fix) and provide clarity about the primary change being introduced.
Out of Scope Changes Check ⚠️ Warning Most changes are directly related to implementing the address book feature and the send flow redesign. However, the modification to src/components/CryptoFiatInput/CryptoFiatInput.tsx (restructuring the toggle UI to combine text and icon into a single Button) appears unrelated to the address book feature objectives. This change merely reorganizes the button layout without altering functionality and does not appear necessary for implementing the address book or send flow redesign requirements described in issue #10694. Remove or justify the CryptoFiatInput.tsx toggle UI restructuring change, as it appears to be unrelated to the address book feature implementation and send flow redesign. If this change is necessary for a different reason, consider moving it to a separate pull request to keep the scope focused on the address book feature.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues Check ✅ Passed The pull request implements all major requirements from issue #10694. The address book functionality enables searching and selecting saved addresses (selectAddressBookEntriesBySearchQuery, AddressBook component), a "Save" button appears when manually typing or pasting a new address (shouldShowSaveButton prop in AddressInput), the save modal opens and persists the address (AddressBookSaveModal), addresses can be deleted via a confirmation modal (ConfirmDelete component), and the amount and address fields are consolidated into a single SendAmountDetails component. Additional features include address book entry display with avatars, namespacing by chain (selectAddressBookEntriesByChainId), migration support, and the feature flag for production/develop control. All coding-related requirements from the linked issue appear to be met.
✨ 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 address-book

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 07c3424 and 97fd979.

📒 Files selected for processing (3)
  • src/assets/translations/en/main.json (3 hunks)
  • src/components/Modals/Send/views/Address.tsx (8 hunks)
  • src/test/mocks/store.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/error-handling.mdc)

**/*.{ts,tsx}: ALWAYS use Result<T, E> pattern for error handling in swappers and APIs
ALWAYS use Ok() and Err() from @sniptt/monads for monadic error handling
ALWAYS use custom error classes from @shapeshiftoss/errors
ALWAYS provide meaningful error codes for internationalization
ALWAYS include relevant details in error objects
ALWAYS wrap async operations in try-catch blocks
ALWAYS use AsyncResultOf utility for converting promises to Results
ALWAYS provide fallback error handling
ALWAYS use timeoutMonadic for API calls
ALWAYS provide appropriate timeout values for API calls
ALWAYS handle timeout errors gracefully
ALWAYS validate inputs before processing
ALWAYS provide clear validation error messages
ALWAYS use early returns for validation failures
ALWAYS log errors for debugging
ALWAYS use structured logging for errors
ALWAYS include relevant context in error logs
Throwing errors instead of using monadic patterns is an anti-pattern
Missing try-catch blocks for async operations is an anti-pattern
Generic error messages without context are an anti-pattern
Not handling specific error types is an anti-pattern
Missing timeout handling is an anti-pattern
No input validation is an anti-pattern
Poor error logging is an anti-pattern
Using any for error types is an anti-pattern
Missing error codes for internationalization is an anti-pattern
No fallback error handling is an anti-pattern
Console.error without structured logging is an anti-pattern

**/*.{ts,tsx}: ALWAYS use camelCase for variables, functions, and methods
ALWAYS use descriptive names that explain the purpose for variables and functions
ALWAYS use verb prefixes for functions that perform actions
ALWAYS use PascalCase for types, interfaces, and enums
ALWAYS use descriptive names that indicate the structure for types, interfaces, and enums
ALWAYS use suffixes like Props, State, Config, Type when appropriate for types and interfaces
ALWAYS use UPPER_SNAKE_CASE for constants and configuration values
ALWAYS use d...

Files:

  • src/components/Modals/Send/views/Address.tsx
  • src/test/mocks/store.ts
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/error-handling.mdc)

**/*.tsx: ALWAYS wrap components in error boundaries
ALWAYS provide user-friendly fallback components in error boundaries
ALWAYS log errors for debugging in error boundaries
ALWAYS use useErrorToast hook for displaying errors
ALWAYS provide translated error messages in error toasts
ALWAYS handle different error types appropriately in error toasts
Missing error boundaries in React components is an anti-pattern

**/*.tsx: ALWAYS use PascalCase for React component names
ALWAYS use descriptive names that indicate the component's purpose
ALWAYS match the component name to the file name
Flag components without PascalCase
Flag default exports for components

Files:

  • src/components/Modals/Send/views/Address.tsx
**/*

📄 CodeRabbit inference engine (.cursor/rules/naming-conventions.mdc)

**/*: ALWAYS use appropriate file extensions
Flag files without kebab-case

Files:

  • src/components/Modals/Send/views/Address.tsx
  • src/test/mocks/store.ts
  • src/assets/translations/en/main.json
**/*.{tsx,jsx}

📄 CodeRabbit inference engine (.cursor/rules/react-best-practices.mdc)

**/*.{tsx,jsx}: ALWAYS use useMemo for expensive computations, object/array creations, and filtered data
ALWAYS use useMemo for derived values and computed properties
ALWAYS use useMemo for conditional values and simple transformations
ALWAYS use useCallback for event handlers and functions passed as props
ALWAYS use useCallback for any function that could be passed as a prop or dependency
ALWAYS include all dependencies in useEffect, useMemo, useCallback dependency arrays
NEVER use // eslint-disable-next-line react-hooks/exhaustive-deps unless absolutely necessary
ALWAYS explain why dependencies are excluded if using eslint disable
ALWAYS use named exports for components
NEVER use default exports for components
KEEP component files under 200 lines when possible
BREAK DOWN large components into smaller, reusable pieces
EXTRACT complex logic into custom hooks
USE local state for component-level state
LIFT state up when needed across multiple components
USE Context for avoiding prop drilling
ALWAYS wrap components in error boundaries for production
ALWAYS handle async errors properly
ALWAYS provide user-friendly error messages
ALWAYS use virtualization for lists with 100+ items
ALWAYS implement proper key props for list items
ALWAYS lazy load heavy components
ALWAYS use React.lazy for code splitting
Components receiving props are wrapped with memo

Files:

  • src/components/Modals/Send/views/Address.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/react-best-practices.mdc)

USE Redux only for global state shared across multiple places

Files:

  • src/components/Modals/Send/views/Address.tsx
  • src/test/mocks/store.ts
🧠 Learnings (43)
📓 Common learnings
Learnt from: NeOMakinG
PR: shapeshift/web#10231
File: src/components/AssetSearch/components/AssetList.tsx:2-2
Timestamp: 2025-08-08T15:00:49.887Z
Learning: Project shapeshift/web: NeOMakinG prefers avoiding minor a11y/UI nitpicks (e.g., adding aria-hidden to decorative icons in empty states like src/components/AssetSearch/components/AssetList.tsx) within feature PRs; defer such suggestions to a follow-up instead of blocking the PR.
Learnt from: NeOMakinG
PR: shapeshift/web#10323
File: src/components/ButtonWalletPredicate/ButtonWalletPredicate.tsx:7-7
Timestamp: 2025-08-27T09:47:06.275Z
Learning: In shapeshift/web project, NeOMakinG consistently prefers to defer UI/UX improvements and refactoring work (like the Drawer.Close hack fix in ButtonWalletPredicate.tsx) to follow-up PRs rather than expanding the scope of feature PRs, even when the improvements would enhance robustness.
Learnt from: premiumjibles
PR: shapeshift/web#10386
File: src/components/MultiHopTrade/components/VerifyAddresses/VerifyAddresses.tsx:272-294
Timestamp: 2025-08-29T07:07:49.332Z
Learning: In UTXO sell address verification flow in VerifyAddresses.tsx, the user wants address verification to be marked as "verified/complete" before starting the change address fetch, not after. The verification step and change address fetch should be treated as separate sequential operations in the UI flow.
Learnt from: NeOMakinG
PR: shapeshift/web#10323
File: src/pages/RFOX/components/Stake/components/StakeSummary.tsx:112-114
Timestamp: 2025-08-22T13:00:44.879Z
Learning: NeOMakinG prefers to keep PR changes minimal and focused on the core objectives, avoiding cosmetic or defensive code improvements that aren't directly related to the PR scope, even when they would improve robustness.
Learnt from: NeOMakinG
PR: shapeshift/web#10128
File: .cursor/rules/error-handling.mdc:266-274
Timestamp: 2025-07-29T10:35:22.059Z
Learning: NeOMakinG prefers less nitpicky suggestions on documentation and best practices files, finding overly detailed suggestions on minor implementation details (like console.error vs logger.error) too granular for cursor rules documentation.
Learnt from: NeOMakinG
PR: shapeshift/web#10380
File: src/pages/Dashboard/components/AccountList/AccountTable.tsx:60-0
Timestamp: 2025-09-02T08:34:08.157Z
Learning: NeOMakinG prefers code review comments to focus only on actual PR changes, not pre-existing code issues, unless there are critical security or correctness concerns directly related to the new functionality.
Learnt from: NeOMakinG
PR: shapeshift/web#10234
File: src/components/MultiHopTrade/hooks/useGetTradeQuotes/hooks/useTrackTradeQuotes.ts:42-86
Timestamp: 2025-08-08T11:41:22.794Z
Learning: NeOMakinG prefers not to include refactors in move-only PRs; such suggestions should be deferred to follow-up issues instead of being applied within the same PR.
Learnt from: NeOMakinG
PR: shapeshift/web#10380
File: src/components/Table/Table.theme.ts:177-180
Timestamp: 2025-09-02T12:38:46.940Z
Learning: NeOMakinG prefers to defer technical debt and CSS correctness issues (like improper hover selectors) to follow-up PRs when the current PR is already large and focused on major feature implementation, even when the issues are valid from a usability/technical perspective.
📚 Learning: 2025-10-21T17:11:18.087Z
Learnt from: gomesalexandre
PR: shapeshift/web#10871
File: src/components/Modals/Send/hooks/useSendDetails/useSendDetails.tsx:426-428
Timestamp: 2025-10-21T17:11:18.087Z
Learning: In src/components/Modals/Send/hooks/useSendDetails/useSendDetails.tsx, within the handleInputChange function, use .toFixed() without arguments (not .toString()) when converting BigNumber amounts for input field synchronization. This avoids exponential notation in the input while preserving precision for presentational components like <Amount.Crypto /> and <Amount.Fiat /> to format appropriately.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-08-29T07:07:49.332Z
Learnt from: premiumjibles
PR: shapeshift/web#10386
File: src/components/MultiHopTrade/components/VerifyAddresses/VerifyAddresses.tsx:272-294
Timestamp: 2025-08-29T07:07:49.332Z
Learning: In UTXO sell address verification flow in VerifyAddresses.tsx, the user wants address verification to be marked as "verified/complete" before starting the change address fetch, not after. The verification step and change address fetch should be treated as separate sequential operations in the UI flow.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-08-03T22:09:37.542Z
Learnt from: CR
PR: shapeshift/web#0
File: .cursor/rules/error-handling.mdc:0-0
Timestamp: 2025-08-03T22:09:37.542Z
Learning: Applies to **/*.{ts,tsx} : ALWAYS validate inputs before processing

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-09-12T10:44:46.723Z
Learnt from: gomesalexandre
PR: shapeshift/web#10461
File: src/plugins/walletConnectToDapps/components/WalletConnectSigningModal/content/SendTransactionContent.tsx:0-0
Timestamp: 2025-09-12T10:44:46.723Z
Learning: gomesalexandre dismissed a clipboard error handling suggestion in PR #10461 for SendTransactionContent.tsx, demonstrating that the current navigator.clipboard.writeText implementation works as expected and preferring to keep it simple without additional try/catch error handling.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-08-03T22:09:37.542Z
Learnt from: CR
PR: shapeshift/web#0
File: .cursor/rules/error-handling.mdc:0-0
Timestamp: 2025-08-03T22:09:37.542Z
Learning: Applies to **/*.{ts,tsx} : No input validation is an anti-pattern

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-08-08T11:41:36.971Z
Learnt from: NeOMakinG
PR: shapeshift/web#10234
File: src/components/MultiHopTrade/hooks/useGetTradeQuotes/hooks/useTrackTradeQuotes.ts:88-109
Timestamp: 2025-08-08T11:41:36.971Z
Learning: In MultiHopTrade Confirm flow (src/components/MultiHopTrade/components/TradeConfirm/TradeConfirm.tsx), the Confirm route does not remount; navigating away goes to the swapper input page. Therefore, persistent deduplication across remounts for quote tracking is unnecessary; a ref-based single-mount dedupe is sufficient.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-09-08T15:53:09.362Z
Learnt from: gomesalexandre
PR: shapeshift/web#10442
File: src/components/TradeAssetSearch/components/GroupedAssetList/GroupedAssetList.tsx:34-35
Timestamp: 2025-09-08T15:53:09.362Z
Learning: In DefaultAssetList.tsx, the GroupedAssetList component already receives the activeChainId prop correctly on line ~58, contrary to automated analysis that may flag it as missing.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-08-15T07:51:16.374Z
Learnt from: gomesalexandre
PR: shapeshift/web#10278
File: src/components/AssetHeader/hooks/useQuickBuy.ts:97-99
Timestamp: 2025-08-15T07:51:16.374Z
Learning: The selectPortfolioUserCurrencyBalanceByAssetId selector in src/state/slices/portfolioSlice/selectors.ts expects a filter object with an assetId property, not a raw AssetId string. The selector signature is (state: ReduxState, filter) where filter should have an assetId property. This pattern is consistent across portfolio selectors that use selectAssetIdParamFromFilter. Passing a filter object like { assetId: someAssetId } is the correct usage pattern.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-07-24T09:43:11.699Z
Learnt from: CR
PR: shapeshift/web#0
File: .cursor/rules/swapper.mdc:0-0
Timestamp: 2025-07-24T09:43:11.699Z
Learning: Applies to packages/swapper/src/swappers/*/{*.ts,endpoints.ts} : Verify chain ID filtering in filterAssetIdsBySellable and filterBuyAssetsBySellAssetId methods.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-08-10T21:09:25.643Z
Learnt from: premiumjibles
PR: shapeshift/web#10215
File: src/components/MultiHopTrade/hooks/useGetTradeRateInput.ts:65-67
Timestamp: 2025-08-10T21:09:25.643Z
Learning: In the MultiHopTrade components, `selectInputBuyAsset` and `selectInputSellAsset` selectors from `tradeInputSlice` always return defined values because they have default values in the initial state (BTC for buyAsset, ETH for sellAsset, with fallback to defaultAsset). Null checks for these assets are unnecessary when using these selectors.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-08-15T07:51:16.374Z
Learnt from: gomesalexandre
PR: shapeshift/web#10278
File: src/components/AssetHeader/hooks/useQuickBuy.ts:97-99
Timestamp: 2025-08-15T07:51:16.374Z
Learning: The selectPortfolioUserCurrencyBalanceByAssetId selector in src/state/slices/portfolioSlice/selectors.ts accepts a filter object with an assetId property, not a raw AssetId string. The selector signature is (state: ReduxState, filter) where filter is expected to have an assetId property. Passing a filter object like { assetId: someAssetId } is the correct usage pattern.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-08-08T15:00:49.887Z
Learnt from: NeOMakinG
PR: shapeshift/web#10231
File: src/components/AssetSearch/components/AssetList.tsx:2-2
Timestamp: 2025-08-08T15:00:49.887Z
Learning: Project shapeshift/web: NeOMakinG prefers avoiding minor a11y/UI nitpicks (e.g., adding aria-hidden to decorative icons in empty states like src/components/AssetSearch/components/AssetList.tsx) within feature PRs; defer such suggestions to a follow-up instead of blocking the PR.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-08-15T07:51:16.374Z
Learnt from: gomesalexandre
PR: shapeshift/web#10278
File: src/components/AssetHeader/hooks/useQuickBuy.ts:97-99
Timestamp: 2025-08-15T07:51:16.374Z
Learning: The selectPortfolioUserCurrencyBalanceByAssetId selector in src/state/slices/portfolioSlice/selectors.ts accepts a filter object with an assetId property (signature: (state, { assetId })), not a raw AssetId string. Passing a filter object like { assetId: someAssetId } is the correct usage pattern.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-08-22T12:58:36.070Z
Learnt from: NeOMakinG
PR: shapeshift/web#10323
File: src/components/Layout/Header/ActionCenter/components/Notifications/RewardDistributionNotification.tsx:33-55
Timestamp: 2025-08-22T12:58:36.070Z
Learning: In RewardDistributionNotification component (src/components/Layout/Header/ActionCenter/components/Notifications/RewardDistributionNotification.tsx), NeOMakinG confirmed that runeAsset is expected to always be defined when the component renders, so defensive guards against undefined runeAsset are not needed.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-08-26T19:04:38.672Z
Learnt from: kaladinlight
PR: shapeshift/web#10369
File: packages/chain-adapters/src/cosmossdk/CosmosSdkBaseAdapter.ts:167-176
Timestamp: 2025-08-26T19:04:38.672Z
Learning: In packages/chain-adapters/src/cosmossdk/CosmosSdkBaseAdapter.ts, when processing assets from data.assets.reduce(), the team prefers using empty catch blocks to gracefully skip any assets that fail processing, rather than specific error type handling, to avoid useless noise and ensure robust asset filtering.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-09-04T17:29:59.479Z
Learnt from: NeOMakinG
PR: shapeshift/web#10380
File: src/components/TradeAssetSearch/hooks/useGetPopularAssetsQuery.tsx:28-33
Timestamp: 2025-09-04T17:29:59.479Z
Learning: In shapeshift/web, the useGetPopularAssetsQuery function in src/components/TradeAssetSearch/hooks/useGetPopularAssetsQuery.tsx intentionally uses primaryAssets[assetId] instead of falling back to assets[assetId]. The design distributes primary assets across chains by iterating through their related assets and adding the primary asset to each related asset's chain. This ensures primary assets appear in all chains where they have related assets, supporting the grouped asset system.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-10-23T14:27:19.073Z
Learnt from: gomesalexandre
PR: shapeshift/web#10857
File: src/plugins/walletConnectToDapps/eventsManager/useWalletConnectEventsHandler.ts:101-104
Timestamp: 2025-10-23T14:27:19.073Z
Learning: In WalletConnect wallet_switchEthereumChain and wallet_addEthereumChain requests, the chainId parameter is always present as per the protocol spec. Type guards checking for missing chainId in these handlers (like `if (!evmNetworkIdHex) return`) are solely for TypeScript compiler satisfaction, not real runtime edge cases.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-08-27T09:51:36.901Z
Learnt from: NeOMakinG
PR: shapeshift/web#10376
File: packages/contracts/src/viemClient.ts:39-40
Timestamp: 2025-08-27T09:51:36.901Z
Learning: NeOMakinG confirmed that issues flagged by CodeRabbit that predate the PR changes and are unrelated to the PR's core objective should be addressed separately, not within the current PR scope.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-08-27T09:47:06.275Z
Learnt from: NeOMakinG
PR: shapeshift/web#10323
File: src/components/ButtonWalletPredicate/ButtonWalletPredicate.tsx:7-7
Timestamp: 2025-08-27T09:47:06.275Z
Learning: In shapeshift/web project, NeOMakinG consistently prefers to defer UI/UX improvements and refactoring work (like the Drawer.Close hack fix in ButtonWalletPredicate.tsx) to follow-up PRs rather than expanding the scope of feature PRs, even when the improvements would enhance robustness.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-09-17T22:40:30.149Z
Learnt from: gomesalexandre
PR: shapeshift/web#10569
File: src/plugins/walletConnectToDapps/components/WalletConnectSigningModal/WalletConnectModalSigningFooter.tsx:121-129
Timestamp: 2025-09-17T22:40:30.149Z
Learning: gomesalexandre maintains strict scope discipline even for style/UI PRs in shapeshift/web, declining functionally correct UX improvements (like keeping Cancel button enabled during gas simulation loading) when they fall outside the PR's stated styling objectives, demonstrating his consistent pattern of deferring valid but tangential improvements to separate efforts.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-08-25T12:59:43.842Z
Learnt from: NeOMakinG
PR: shapeshift/web#10323
File: src/pages/Fox/components/RFOXSection.tsx:234-248
Timestamp: 2025-08-25T12:59:43.842Z
Learning: NeOMakinG considers suggestions to refactor working modal state management from useState to useDisclosure as too nitpicky, preferring to keep such cosmetic code improvements out of feature PRs unless they address actual issues.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-08-22T13:00:44.879Z
Learnt from: NeOMakinG
PR: shapeshift/web#10323
File: src/pages/RFOX/components/Stake/components/StakeSummary.tsx:112-114
Timestamp: 2025-08-22T13:00:44.879Z
Learning: NeOMakinG prefers to keep PR changes minimal and focused on the core objectives, avoiding cosmetic or defensive code improvements that aren't directly related to the PR scope, even when they would improve robustness.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-09-02T12:38:46.940Z
Learnt from: NeOMakinG
PR: shapeshift/web#10380
File: src/components/Table/Table.theme.ts:177-180
Timestamp: 2025-09-02T12:38:46.940Z
Learning: NeOMakinG prefers to defer technical debt and CSS correctness issues (like improper hover selectors) to follow-up PRs when the current PR is already large and focused on major feature implementation, even when the issues are valid from a usability/technical perspective.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-08-08T15:00:22.321Z
Learnt from: NeOMakinG
PR: shapeshift/web#10231
File: src/components/MultiHopTrade/components/TradeInput/components/HighlightedTokens.tsx:14-14
Timestamp: 2025-08-08T15:00:22.321Z
Learning: In shapeshift/web reviews for NeOMakinG, avoid nitpicks to change deep-relative imports to '@/…' alias paths within feature/non-refactor PRs; defer such style-only changes to a dedicated follow-up refactor unless they fix an issue.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-09-02T08:34:08.157Z
Learnt from: NeOMakinG
PR: shapeshift/web#10380
File: src/pages/Dashboard/components/AccountList/AccountTable.tsx:60-0
Timestamp: 2025-09-02T08:34:08.157Z
Learning: NeOMakinG prefers code review comments to focus only on actual PR changes, not pre-existing code issues, unless there are critical security or correctness concerns directly related to the new functionality.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-08-22T12:58:26.590Z
Learnt from: NeOMakinG
PR: shapeshift/web#10323
File: src/components/Layout/Header/ActionCenter/components/GenericTransactionActionCard.tsx:108-111
Timestamp: 2025-08-22T12:58:26.590Z
Learning: In the RFOX GenericTransactionDisplayType flow in src/components/Layout/Header/ActionCenter/components/GenericTransactionActionCard.tsx, the txHash is always guaranteed to be present according to NeOMakinG, so defensive null checks for txLink are not needed in this context.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-09-13T16:45:17.166Z
Learnt from: gomesalexandre
PR: shapeshift/web#10461
File: src/plugins/walletConnectToDapps/components/WalletConnectSigningModal/StructuredMessage/StructuredMessage.tsx:0-0
Timestamp: 2025-09-13T16:45:17.166Z
Learning: gomesalexandre appreciates safety-focused suggestions for UI rendering in WalletConnect components, specifically defensive programming approaches that prevent null/undefined values from displaying as literal "null"/"undefined" strings in the user interface.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-08-25T23:32:13.876Z
Learnt from: premiumjibles
PR: shapeshift/web#10361
File: src/pages/Markets/components/CardWithSparkline.tsx:83-92
Timestamp: 2025-08-25T23:32:13.876Z
Learning: In shapeshift/web PR #10361, premiumjibles considered the nested button accessibility issue (ChartErrorFallback retry Button inside Card rendered as Button in CardWithSparkline.tsx) out of scope for the error boundaries feature PR, consistent with deferring minor a11y improvements to follow-up PRs.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-09-12T11:56:19.437Z
Learnt from: gomesalexandre
PR: shapeshift/web#10461
File: src/plugins/walletConnectToDapps/utils/tenderly/index.ts:0-0
Timestamp: 2025-09-12T11:56:19.437Z
Learning: gomesalexandre rejected verbose try/catch error handling for address validation in Tenderly integration (PR #10461), calling the approach "ugly" but still implemented safety measures in commit ad7e424b89, preferring cleaner safety implementations over defensive programming patterns.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-09-10T15:35:46.223Z
Learnt from: gomesalexandre
PR: shapeshift/web#10458
File: src/plugins/walletConnectToDapps/components/modals/EIP155SignTypedDataConfirmation.tsx:55-55
Timestamp: 2025-09-10T15:35:46.223Z
Learning: gomesalexandre prefers fail-fast early returns over graceful degradation when critical data is missing in WalletConnect flows (like peer metadata in EIP155SignTypedDataConfirmation.tsx). He favors "safety first, always double-wrap" approach and believes missing peer metadata indicates bigger problems that should be surfaced explicitly rather than masked with partial UI rendering.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-09-02T00:02:44.889Z
Learnt from: premiumjibles
PR: shapeshift/web#10380
File: src/components/AssetSearch/components/GroupedAssetRow.tsx:116-0
Timestamp: 2025-09-02T00:02:44.889Z
Learning: premiumjibles prefers avoiding store.getState() calls in favor of useAppSelector where reactivity is needed, to prevent accidentally breaking reactive updates in components.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-09-08T22:00:48.005Z
Learnt from: gomesalexandre
PR: shapeshift/web#10418
File: src/plugins/walletConnectToDapps/components/header/WalletConnectToDappsHeaderButton.tsx:0-0
Timestamp: 2025-09-08T22:00:48.005Z
Learning: gomesalexandre dismissed an aria-label accessibility suggestion with "meh" in PR #10418 for WalletConnectToDappsHeaderButton.tsx, consistent with the team's pattern of deferring minor a11y improvements to follow-up PRs rather than expanding feature PR scope.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-09-12T12:00:33.924Z
Learnt from: gomesalexandre
PR: shapeshift/web#10461
File: src/plugins/walletConnectToDapps/components/modals/SendTransactionConfirmation.tsx:42-50
Timestamp: 2025-09-12T12:00:33.924Z
Learning: gomesalexandre prefers maintaining consistency with existing code patterns in WalletConnect modals, including side-effects-during-render for error handling (showErrorToast + handleReject), rather than introducing isolated refactors that would make the codebase inconsistent.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-09-12T13:16:27.004Z
Learnt from: gomesalexandre
PR: shapeshift/web#10461
File: src/plugins/walletConnectToDapps/components/modals/EIP712MessageDisplay.tsx:21-24
Timestamp: 2025-09-12T13:16:27.004Z
Learning: gomesalexandre declined to add error boundaries to WalletConnect modals in PR #10461, stating "no error boundaries in this pr ser", consistent with his preference to keep PR scope focused and defer tangential improvements to separate efforts.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-09-12T12:00:33.924Z
Learnt from: gomesalexandre
PR: shapeshift/web#10461
File: src/plugins/walletConnectToDapps/components/modals/SendTransactionConfirmation.tsx:42-50
Timestamp: 2025-09-12T12:00:33.924Z
Learning: gomesalexandre prefers maintaining consistency with existing code patterns across WalletConnect modal components, including side-effects-during-render for error handling (showErrorToast + handleReject calls before return null), rather than introducing isolated refactors that would create inconsistency in the codebase.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-10-07T03:44:27.350Z
Learnt from: 0xApotheosis
PR: shapeshift/web#10760
File: src/components/ManageHiddenAssets/ManageHiddenAssetsList.tsx:78-84
Timestamp: 2025-10-07T03:44:27.350Z
Learning: In the ShapeShift web codebase, the following are stable references and do not need to be included in useCallback/useMemo dependency arrays:
- `navigate` from `useBrowserRouter()` hook
- Modal control objects (like `walletDrawer`) from `useModal()` hook (including their `isOpen`, `close`, and `open` methods)
- These are backed by stable context providers

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-09-04T10:18:34.140Z
Learnt from: gomesalexandre
PR: shapeshift/web#10427
File: src/hooks/useActionCenterSubscribers/useSwapActionSubscriber.tsx:40-40
Timestamp: 2025-09-04T10:18:34.140Z
Learning: In the shapeshift/web codebase, src/state/slices/selectors.ts uses wildcard exports (`export * from './[sliceName]/selectors'`) to re-export all selectors from individual slice selector files, making them available through the barrel import. This means selectors like selectTxByFilter from txHistorySlice/selectors are properly accessible via '@/state/slices/selectors' even though they don't appear in explicit named exports.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-08-05T22:41:35.473Z
Learnt from: premiumjibles
PR: shapeshift/web#10187
File: src/pages/Assets/Asset.tsx:1-1
Timestamp: 2025-08-05T22:41:35.473Z
Learning: In the shapeshift/web codebase, component imports use direct file paths like '@/components/ComponentName/ComponentName' rather than barrel exports. The AssetAccountDetails component should be imported as '@/components/AssetAccountDetails/AssetAccountDetails', not from a directory index.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-08-14T17:54:32.563Z
Learnt from: gomesalexandre
PR: shapeshift/web#10276
File: src/pages/ThorChainLP/components/ReusableLpStatus/ReusableLpStatus.tsx:97-108
Timestamp: 2025-08-14T17:54:32.563Z
Learning: In ReusableLpStatus component (src/pages/ThorChainLP/components/ReusableLpStatus/ReusableLpStatus.tsx), the txAssets dependency is stable from first render because poolAsset, baseAsset, actionSide, and action are all defined first render, making the current txAssetsStatuses initialization pattern safe without needing useEffect synchronization.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-08-27T09:47:59.394Z
Learnt from: NeOMakinG
PR: shapeshift/web#10323
File: src/components/Modal/components/DialogCloseButton.tsx:29-37
Timestamp: 2025-08-27T09:47:59.394Z
Learning: In shapeshift/web, for DialogCloseButton.tsx, the onClick handler must remain on Drawer.Close (not moved to IconButton with asChild) because stopPropagation() needs to be called at the Drawer.Close level to properly handle drawer closing behavior; moving event handling to the child IconButton would break the event flow that Drawer.Close expects.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-10-21T23:21:22.304Z
Learnt from: premiumjibles
PR: shapeshift/web#10759
File: src/components/Modals/Send/hooks/useFormSend/useFormSend.tsx:41-50
Timestamp: 2025-10-21T23:21:22.304Z
Learning: In the shapeshift/web repository, the translation workflow follows an "English-first" approach: English translations in src/assets/translations/en/main.json are updated first in PRs, and translations for the other supported languages (de, es, fr, id, ja, ko, pt, ru, tr, uk, zh) are updated "after the fact" in follow-up work. Temporary mismatches between English and other language translation keys/formats during active development are expected and acceptable.
<!--

Applied to files:

  • src/assets/translations/en/main.json
📚 Learning: 2025-07-24T21:05:13.642Z
Learnt from: 0xApotheosis
PR: shapeshift/web#10073
File: src/components/Layout/Header/ActionCenter/components/Details/ClaimDetails.tsx:10-11
Timestamp: 2025-07-24T21:05:13.642Z
Learning: In the ShapeShift web repository, translation workflow follows a two-step process: 1) First PR adds only English translations to src/assets/translations/en/main.json, 2) Globalization team handles follow-up PRs to add keys to remaining language files (de, es, fr, id, ja, ko, pt, ru, tr, uk, zh). Don't suggest verifying all locale files simultaneously during initial feature PRs.

Applied to files:

  • src/assets/translations/en/main.json
🧬 Code graph analysis (1)
src/components/Modals/Send/views/Address.tsx (3)
src/state/store.ts (1)
  • useAppSelector (143-143)
src/state/slices/addressBookSlice/selectors.ts (2)
  • selectIsAddressInAddressBook (64-97)
  • selectInternalAccountIdByAddress (104-131)
src/components/Modals/Send/AddressBook/AddressBook.tsx (1)
  • AddressBook (34-154)
⏰ 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)
  • GitHub Check: Install and Cache
🔇 Additional comments (3)
src/test/mocks/store.ts (2)

168-168: LGTM!

The AddressBook feature flag addition follows the existing naming pattern and correctly initializes to false in the mock state.


365-371: LGTM!

The addressBook slice structure correctly follows the persistence pattern used by other slices in the mock, with proper _persist metadata and the byAccountId map matching the expected shape.

src/components/Modals/Send/views/Address.tsx (1)

107-111: Save CTA gating looks solid.

Tying the visibility to !isInAddressBook && !internalAccountId keeps the save flow aligned with the modal guard and prevents the internal-account double-submit dead end. Nicely done.


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.

Base automatically changed from send-flow-mobile to develop October 20, 2025 09:38
@NeOMakinG NeOMakinG marked this pull request as ready for review October 21, 2025 13:03
@NeOMakinG NeOMakinG requested a review from a team as a code owner October 21, 2025 13:03
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

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

⚠️ Outside diff range comments (1)
src/components/Modals/Send/views/SendAmountDetails.tsx (1)

199-229: Harden async address validation; ensure setIsValidating is always cleared

If parseAddressInputWithChainId throws, setIsValidating stays true and derived fields may be stale. Wrap in try/catch/finally.

Apply this diff:

@@
   validate: {
     validateAddress: async (rawInput: string) => {
       if (!asset) return
       if (rawInput === '') return
@@
-      setIsValidating(true)
-      setValue(SendFormFields.To, '')
-      setValue(SendFormFields.VanityAddress, '')
-      const { assetId, chainId } = asset
-      const parseAddressInputWithChainIdArgs = {
-        assetId,
-        chainId,
-        urlOrAddress,
-        disableUrlParsing: true,
-      }
-      const { address, vanityAddress } = await parseAddressInputWithChainId(
-        parseAddressInputWithChainIdArgs,
-      )
-      setIsValidating(false)
-      setValue(SendFormFields.To, address)
-      setValue(SendFormFields.VanityAddress, vanityAddress)
-      const invalidMessage = 'common.invalidAddress'
-      return address ? true : invalidMessage
+      setIsValidating(true)
+      setValue(SendFormFields.To, '')
+      setValue(SendFormFields.VanityAddress, '')
+      const { assetId, chainId } = asset
+      const args = { assetId, chainId, urlOrAddress, disableUrlParsing: true }
+      try {
+        const { address, vanityAddress } = await parseAddressInputWithChainId(args)
+        setValue(SendFormFields.To, address)
+        setValue(SendFormFields.VanityAddress, vanityAddress)
+        return address ? true : 'common.invalidAddress'
+      } catch (e) {
+        // Optionally log with structured logger if available
+        setValue(SendFormFields.To, '')
+        setValue(SendFormFields.VanityAddress, '')
+        return 'common.invalidAddress'
+      } finally {
+        setIsValidating(false)
+      }
     },
   },

As per coding guidelines (async ops must be guarded).

🧹 Nitpick comments (21)
src/components/Modals/Send/components/QRCodeIcon.tsx (2)

8-16: Simplify sizing; drop useMemo+sx in favor of Icon.boxSize.

Use Chakra’s boxSize on Icon and remove the memoized sx object.

-  const iconSx = useMemo(
-    () => ({
-      svg: {
-        width: '24px',
-        height: '24px',
-      },
-    }),
-    [],
-  )

19-29: Use Icon.boxSize and mark decorative icon as hidden from AT.

This avoids extra sx and improves a11y if the icon is purely visual.

-    <Flex
-      bg={qrBackground}
-      borderRadius='full'
-      color='text.primary'
-      boxSize='44px'
-      alignItems='center'
-      justifyContent='center'
-      sx={iconSx}
-    >
-      <Icon as={TbScan} />
+    <Flex
+      bg={qrBackground}
+      borderRadius='full'
+      color='text.primary'
+      boxSize='44px'
+      alignItems='center'
+      justifyContent='center'
+    >
+      <Icon as={TbScan} boxSize='24px' aria-hidden='true' focusable='false' />
     </Flex>
src/state/migrations/clearAddressBook.ts (1)

1-9: Clear migration is minimal and correct.

Resetting to initialState is fine for v0. Consider importing PersistPartial from the public entry (redux-persist) for future-proofing, unless other migrations already use the es/ path.

src/vite-env.d.ts (1)

119-120: Place the new feature flag with the other feature flags.

VITE_FEATURE_ADDRESS_BOOK currently sits in the “URLs and API keys” section. Move it to the top “Feature flags” block to keep flags grouped and discoverable.

src/components/Modals/Send/Form.tsx (1)

270-276: Avoid any in catch; use unknown with a type guard.

Prevents unsafe access and aligns with TS guidelines.

Apply this diff:

-      } catch (e: any) {
-        setAddressError(e.message)
+      } catch (e: unknown) {
+        const message = e instanceof Error ? e.message : 'Unknown error'
+        setAddressError(message)
       }
src/components/Modals/Send/AddressBook/ConfirmDelete.tsx (1)

22-25: Make delete confirm resilient to async/slow operations.

If onDelete ever becomes async, add a loading state and disable buttons to prevent double submits; type it to allow Promise<void>. Example:

-import { useCallback } from 'react'
+import { useCallback, useState } from 'react'
@@
-type ConfirmDeleteProps = {
+type ConfirmDeleteProps = {
   entryName: string
-  onDelete: () => void
+  onDelete: () => Promise<void> | void
   onClose: () => void
   isOpen: boolean
 }
@@
-export const ConfirmDelete = ({ entryName, onDelete, onClose, isOpen }: ConfirmDeleteProps) => {
+export const ConfirmDelete = ({ entryName, onDelete, onClose, isOpen }: ConfirmDeleteProps) => {
+  const [isDeleting, setIsDeleting] = useState(false)
@@
-  const handleConfirm = useCallback(() => {
-    onDelete()
-    onClose()
-  }, [onDelete, onClose])
+  const handleConfirm = useCallback(async () => {
+    if (isDeleting) return
+    setIsDeleting(true)
+    await onDelete()
+    setIsDeleting(false)
+    onClose()
+  }, [isDeleting, onDelete, onClose])
@@
-          <Button flex={1} onClick={onClose}>
+          <Button flex={1} onClick={onClose} isDisabled={isDeleting}>
             <Text translation='common.cancel' />
           </Button>
-          <Button flex={1} colorScheme='red' onClick={handleConfirm}>
+          <Button flex={1} colorScheme='red' onClick={handleConfirm} isLoading={isDeleting}>
             <Text translation='common.delete' />
           </Button>

Ensure translation keys modals.send.confirmDelete.title and .message exist in all supported locales.

Also applies to: 41-46, 1-3, 12-17

src/components/Modals/Send/AddAddressModal.tsx (2)

96-105: Trim input at source to avoid whitespace-only names.

Add setValueAs to normalize the name:

-              <Input
-                {...register('name', {
+              <Input
+                {...register('name', {
                   required: translate('modals.send.addContact.nameRequired'),
                   minLength: {
                     value: 1,
                     message: translate('modals.send.addContact.nameRequired'),
                   },
+                  setValueAs: (v: string) => v?.trim() ?? '',
                 })}

70-83: Guard against duplicate contacts (same chainId + address).

Before dispatching, check if an entry already exists and short‑circuit with a toast.

-  const onSubmit = useCallback(
-    (data: FormData) => {
+  const onSubmit = useCallback(
+    (data: FormData) => {
+      // Prevent duplicates for same chain/address
+      // If you prefer namespace-level dedupe, adapt the selector accordingly.
+      const exists =
+        addressBookSlice.selectors
+          .selectAddressBookEntriesByChainId(
+            // @ts-expect-error typed selector usage at call-site
+            (window as any).__reduxStore__?.getState?.() ?? {},
+            chainId,
+          )
+          ?.some(e => e.address.toLowerCase() === address.toLowerCase()) ?? false
+      if (exists) {
+        // Optionally use useErrorToast for UX
+        return
+      }
       dispatch(
         addressBookSlice.actions.addAddress({
           name: data.name,
           address,
           chainId,
         }),
       )
       onSuccess?.()
       handleClose()
     },

Alternatively, compute exists via a local useAppSelector and close over it. I can wire this with a proper toast if desired.

src/components/Modals/Send/views/Confirm.tsx (2)

120-123: Normalize address compare to avoid case-mismatch misses.

EVM addresses are case-insensitive (checksums vary). Normalize both sides:

-  const selectedAddressBookEntry = useMemo(() => {
-    return addressBookEntries.find(entry => entry.address === to)
-  }, [addressBookEntries, to])
+  const selectedAddressBookEntry = useMemo(() => {
+    const toNorm = (to ?? '').toLowerCase()
+    return addressBookEntries.find(entry => entry.address.toLowerCase() === toNorm)
+  }, [addressBookEntries, to])

83-91: Minor naming nit: avoid confusion with react-hook-form’s handleSubmit.

Prop handleSubmit can be mistaken for RHF’s handleSubmit. Consider onConfirm or onSubmitTx for clarity. No functional change.

Also applies to: 195-217

src/components/Modals/Send/views/Address.tsx (2)

93-98: Normalize address comparison when detecting “custom address”.

Avoid false positives due to checksum/case differences:

-  const isCustomAddress = useMemo(() => {
-    if (!input || !address || !asset?.chainId) return false
-    const existsInAddressBook = addressBookEntries.some(entry => entry.address === address)
-    return !existsInAddressBook
-  }, [input, address, addressBookEntries, asset?.chainId])
+  const isCustomAddress = useMemo(() => {
+    if (!input || !address || !asset?.chainId) return false
+    const addrNorm = address.toLowerCase()
+    const existsInAddressBook = addressBookEntries.some(
+      entry => entry.address.toLowerCase() === addrNorm,
+    )
+    return !existsInAddressBook
+  }, [input, address, addressBookEntries, asset?.chainId])

48-49: Naming nit (optional).

showSaveButton could be isSaveButtonVisible for boolean clarity. Skip if you prefer current naming.

src/components/Modals/Send/AddressBook/AddressBook.tsx (3)

29-33: Avoid redefining AddressBookEntry; import the slice type or Pick<> what you need

Local AddressBookEntry shadows the slice’s AddressBookEntry (adds chainId, createdAt). This invites type drift.

Apply this diff:

@@
-import { addressBookSlice } from '@/state/slices/addressBookSlice/addressBookSlice'
+import type { AddressBookEntry as AddressBookEntryModel } from '@/state/slices/addressBookSlice/addressBookSlice'
+import { addressBookSlice } from '@/state/slices/addressBookSlice/addressBookSlice'
@@
-export type AddressBookEntry = {
-  id: string
-  name: string
-  address: string
-}
+type AddressBookEntry = Pick<AddressBookEntryModel, 'id' | 'name' | 'address'>

Update local usages if needed (e.g., selectedDeleteEntry) to this alias. Based on learnings.


128-132: Type the form context with SendInput for safer access

useFormContext is untyped here; add the SendInput generic for proper errors/value typing.

Apply this diff:

@@
-  const {
-    control,
-    formState: { errors },
-  } = useFormContext()
+  const {
+    control,
+    formState: { errors },
+  } = useFormContext<SendInput>()

And import the type:

@@
-import { SendFormFields } from '../SendCommon'
+import { SendFormFields } from '../SendCommon'
+import type { SendInput } from '@/components/Modals/Send/Form'

As per coding guidelines.


216-223: Non-null assertions for selectedDeleteEntry props and reset selection on close

Inside the conditional render, selectedDeleteEntry is guaranteed; remove optional chaining to satisfy types. Also clear selectedDeleteEntry on close to avoid stale state.

Apply this diff:

@@
-      {selectedDeleteEntry && (
+      {selectedDeleteEntry && (
         <ConfirmDelete
           isOpen={isOpen}
-          onDelete={handleDelete(selectedDeleteEntry?.id)}
-          onClose={onClose}
-          entryName={selectedDeleteEntry?.name}
+          onDelete={handleDelete(selectedDeleteEntry!.id)}
+          onClose={handleCloseConfirm}
+          entryName={selectedDeleteEntry!.name}
         />
       )}
@@
+  const handleCloseConfirm = useCallback(() => {
+    onClose()
+    setSelectedDeleteEntry(null)
+  }, [onClose])
src/components/Modals/Send/views/SendAmountDetails.tsx (2)

252-279: Avoid any; strongly type Controller render for amount fields

Use ControllerRenderProps typing for the two possible amount fields.

Apply this diff:

@@
-  const renderController = useCallback(
-    ({ field: { onChange, value } }: { field: any }) => {
+  type AmountField = SendFormFields.AmountCryptoPrecision | SendFormFields.FiatAmount
+  const renderController = useCallback(
+    ({
+      field: { onChange, value },
+    }: {
+      field: ControllerRenderProps<FieldValues, AmountField>
+    }) => {
       return (
         <NumberFormat

As per coding guidelines.


350-361: Remove nested click targets; put onClick on the Button

Attaching onClick to HStack and nesting a Button is redundant and can confuse a11y/analytics.

Apply this diff:

-                <HStack justify='center' mt={2} spacing={2} onClick={toggleIsFiat} cursor='pointer'>
-                  <Button variant='ghost' size='sm' p={1} minW='auto' h='auto' px={2}>
+                <HStack justify='center' mt={2} spacing={2}>
+                  <Button variant='ghost' size='sm' p={1} minW='auto' h='auto' px={2} onClick={toggleIsFiat}>
                     <ChakraText fontSize='sm' color='text.subtle' me={2}>
src/state/slices/addressBookSlice/addressBookSlice.ts (1)

27-38: Prevent duplicate entries for same address/chainId

Currently addAddress will add duplicates if invoked repeatedly with the same address/chainId. Consider guarding and updating name instead.

Example adjustment:

@@
-    addAddress: create.reducer(
+    addAddress: create.reducer(
       (state, action: PayloadAction<Omit<AddressBookEntry, 'id' | 'createdAt'>>) => {
-        const id = `${action.payload.chainId}_${action.payload.address}_${Date.now()}`
+        const existingId = state.ids.find(
+          id => state.byId[id].address === action.payload.address && state.byId[id].chainId === action.payload.chainId,
+        )
+        if (existingId) {
+          state.byId[existingId].name = action.payload.name
+          return
+        }
+        const id = `${action.payload.chainId}_${action.payload.address}_${Date.now()}`
         const entry: AddressBookEntry = {
           ...action.payload,
           id,
           createdAt: Date.now(),
         }
         state.byId[id] = entry
         state.ids.push(id)
       },
     ),
src/components/Modals/Send/AddressInput/AddressInput.tsx (3)

234-249: Include onClick in deps to avoid stale prop capture

renderController closes over props.onClick but excludes it from deps. Destructure onClick and add it to deps to keep behavior correct without over-rendering.

Apply this diff:

@@
-export const AddressInput = ({
+export const AddressInput = ({
   rules,
   placeholder,
   enableQr = false,
   isReadOnly = false,
   resolvedAddress,
   chainId,
   onSaveContact,
   onFocus,
   onBlur,
   onPaste,
   ...props
 }: AddressInputProps) => {
@@
-            onClick={props.onClick ?? handleDisplayClick}
+            onClick={onClick ?? handleDisplayClick}
@@
-    // eslint-disable-next-line react-hooks/exhaustive-deps
-    [
+    // eslint-disable-next-line react-hooks/exhaustive-deps
+    [
       placeholder,
       isInvalid,
       isReadOnly,
       isFocused,
       isDirty,
       translate,
       onFocus,
       onBlur,
       onPaste,
       resolvedAddress,
       addressBookEntry,
       onSaveContact,
+      onClick,
     ],

Add the destructure:

@@
-}: AddressInputProps) => {
+}: AddressInputProps) => {
   const navigate = useNavigate()
   const translate = useTranslate()
+  const { onClick } = props

Also applies to: 175-186, 180-181


73-76: Ref type and translation key nits

  • The input renders as a textarea; type the ref accordingly.
  • Use a consistent translation key for “To” (align with modals.send.sendForm.to used elsewhere).

Apply this diff:

@@
-  const inputRef = useRef<HTMLInputElement>(null)
+  const inputRef = useRef<HTMLTextAreaElement | HTMLInputElement>(null)
@@
-              <Text color='text.subtle' w='full' pl={4} fontSize='sm'>
-                {translate('trade.to')}
+              <Text color='text.subtle' w='full' pl={4} fontSize='sm'>
+                {translate('modals.send.sendForm.to')}
               </Text>

Also applies to: 148-167


34-43: Remove unused prop pe or wire it through

pe is declared but never used; drop it or pass it down to the rendered input/container for consistency.

Example removal:

-  pe?: SpaceProps['pe']

Or forward via {...props} where appropriate.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between adbe969 and 2faea90.

📒 Files selected for processing (28)
  • .env.development (1 hunks)
  • .env.production (1 hunks)
  • src/assets/translations/en/main.json (3 hunks)
  • src/components/Modals/QrCode/QrCode.tsx (2 hunks)
  • src/components/Modals/Send/AddAddressModal.tsx (1 hunks)
  • src/components/Modals/Send/AddressBook/AddressBook.tsx (1 hunks)
  • src/components/Modals/Send/AddressBook/ConfirmDelete.tsx (1 hunks)
  • src/components/Modals/Send/AddressInput/AddressInput.tsx (3 hunks)
  • src/components/Modals/Send/Form.tsx (2 hunks)
  • src/components/Modals/Send/Send.tsx (2 hunks)
  • src/components/Modals/Send/SendCommon.tsx (1 hunks)
  • src/components/Modals/Send/components/AddressInputWithDropdown.tsx (0 hunks)
  • src/components/Modals/Send/components/QRCodeIcon.tsx (1 hunks)
  • src/components/Modals/Send/views/Address.tsx (9 hunks)
  • src/components/Modals/Send/views/Confirm.tsx (6 hunks)
  • src/components/Modals/Send/views/SendAmountDetails.tsx (7 hunks)
  • src/config.ts (1 hunks)
  • src/context/ModalProvider/ModalContainer.tsx (2 hunks)
  • src/context/ModalProvider/types.ts (2 hunks)
  • src/state/migrations/clearAddressBook.ts (1 hunks)
  • src/state/migrations/index.ts (2 hunks)
  • src/state/reducer.ts (5 hunks)
  • src/state/slices/addressBookSlice/addressBookSlice.ts (1 hunks)
  • src/state/slices/addressBookSlice/selectors.ts (1 hunks)
  • src/state/slices/preferencesSlice/preferencesSlice.ts (2 hunks)
  • src/state/store.ts (1 hunks)
  • src/test/mocks/store.ts (2 hunks)
  • src/vite-env.d.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • src/components/Modals/Send/components/AddressInputWithDropdown.tsx
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/error-handling.mdc)

**/*.{ts,tsx}: ALWAYS use Result<T, E> pattern for error handling in swappers and APIs
ALWAYS use Ok() and Err() from @sniptt/monads for monadic error handling
ALWAYS use custom error classes from @shapeshiftoss/errors
ALWAYS provide meaningful error codes for internationalization
ALWAYS include relevant details in error objects
ALWAYS wrap async operations in try-catch blocks
ALWAYS use AsyncResultOf utility for converting promises to Results
ALWAYS provide fallback error handling
ALWAYS use timeoutMonadic for API calls
ALWAYS provide appropriate timeout values for API calls
ALWAYS handle timeout errors gracefully
ALWAYS validate inputs before processing
ALWAYS provide clear validation error messages
ALWAYS use early returns for validation failures
ALWAYS log errors for debugging
ALWAYS use structured logging for errors
ALWAYS include relevant context in error logs
Throwing errors instead of using monadic patterns is an anti-pattern
Missing try-catch blocks for async operations is an anti-pattern
Generic error messages without context are an anti-pattern
Not handling specific error types is an anti-pattern
Missing timeout handling is an anti-pattern
No input validation is an anti-pattern
Poor error logging is an anti-pattern
Using any for error types is an anti-pattern
Missing error codes for internationalization is an anti-pattern
No fallback error handling is an anti-pattern
Console.error without structured logging is an anti-pattern

**/*.{ts,tsx}: ALWAYS use camelCase for variables, functions, and methods
ALWAYS use descriptive names that explain the purpose for variables and functions
ALWAYS use verb prefixes for functions that perform actions
ALWAYS use PascalCase for types, interfaces, and enums
ALWAYS use descriptive names that indicate the structure for types, interfaces, and enums
ALWAYS use suffixes like Props, State, Config, Type when appropriate for types and interfaces
ALWAYS use UPPER_SNAKE_CASE for constants and configuration values
ALWAYS use d...

Files:

  • src/context/ModalProvider/ModalContainer.tsx
  • src/state/migrations/clearAddressBook.ts
  • src/state/store.ts
  • src/components/Modals/Send/Form.tsx
  • src/components/Modals/Send/SendCommon.tsx
  • src/context/ModalProvider/types.ts
  • src/components/Modals/Send/views/SendAmountDetails.tsx
  • src/state/slices/addressBookSlice/selectors.ts
  • src/config.ts
  • src/components/Modals/Send/components/QRCodeIcon.tsx
  • src/components/Modals/Send/AddressBook/ConfirmDelete.tsx
  • src/components/Modals/Send/views/Address.tsx
  • src/components/Modals/Send/AddressInput/AddressInput.tsx
  • src/state/migrations/index.ts
  • src/vite-env.d.ts
  • src/components/Modals/Send/views/Confirm.tsx
  • src/components/Modals/QrCode/QrCode.tsx
  • src/components/Modals/Send/AddressBook/AddressBook.tsx
  • src/state/slices/preferencesSlice/preferencesSlice.ts
  • src/state/reducer.ts
  • src/components/Modals/Send/Send.tsx
  • src/test/mocks/store.ts
  • src/components/Modals/Send/AddAddressModal.tsx
  • src/state/slices/addressBookSlice/addressBookSlice.ts
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/error-handling.mdc)

**/*.tsx: ALWAYS wrap components in error boundaries
ALWAYS provide user-friendly fallback components in error boundaries
ALWAYS log errors for debugging in error boundaries
ALWAYS use useErrorToast hook for displaying errors
ALWAYS provide translated error messages in error toasts
ALWAYS handle different error types appropriately in error toasts
Missing error boundaries in React components is an anti-pattern

**/*.tsx: ALWAYS use PascalCase for React component names
ALWAYS use descriptive names that indicate the component's purpose
ALWAYS match the component name to the file name
Flag components without PascalCase
Flag default exports for components

Files:

  • src/context/ModalProvider/ModalContainer.tsx
  • src/components/Modals/Send/Form.tsx
  • src/components/Modals/Send/SendCommon.tsx
  • src/components/Modals/Send/views/SendAmountDetails.tsx
  • src/components/Modals/Send/components/QRCodeIcon.tsx
  • src/components/Modals/Send/AddressBook/ConfirmDelete.tsx
  • src/components/Modals/Send/views/Address.tsx
  • src/components/Modals/Send/AddressInput/AddressInput.tsx
  • src/components/Modals/Send/views/Confirm.tsx
  • src/components/Modals/QrCode/QrCode.tsx
  • src/components/Modals/Send/AddressBook/AddressBook.tsx
  • src/components/Modals/Send/Send.tsx
  • src/components/Modals/Send/AddAddressModal.tsx
**/*

📄 CodeRabbit inference engine (.cursor/rules/naming-conventions.mdc)

**/*: ALWAYS use appropriate file extensions
Flag files without kebab-case

Files:

  • src/context/ModalProvider/ModalContainer.tsx
  • src/state/migrations/clearAddressBook.ts
  • src/state/store.ts
  • src/components/Modals/Send/Form.tsx
  • src/components/Modals/Send/SendCommon.tsx
  • src/context/ModalProvider/types.ts
  • src/components/Modals/Send/views/SendAmountDetails.tsx
  • src/state/slices/addressBookSlice/selectors.ts
  • src/assets/translations/en/main.json
  • src/config.ts
  • src/components/Modals/Send/components/QRCodeIcon.tsx
  • src/components/Modals/Send/AddressBook/ConfirmDelete.tsx
  • src/components/Modals/Send/views/Address.tsx
  • src/components/Modals/Send/AddressInput/AddressInput.tsx
  • src/state/migrations/index.ts
  • src/vite-env.d.ts
  • src/components/Modals/Send/views/Confirm.tsx
  • src/components/Modals/QrCode/QrCode.tsx
  • src/components/Modals/Send/AddressBook/AddressBook.tsx
  • src/state/slices/preferencesSlice/preferencesSlice.ts
  • src/state/reducer.ts
  • src/components/Modals/Send/Send.tsx
  • src/test/mocks/store.ts
  • src/components/Modals/Send/AddAddressModal.tsx
  • src/state/slices/addressBookSlice/addressBookSlice.ts
**/*.{tsx,jsx}

📄 CodeRabbit inference engine (.cursor/rules/react-best-practices.mdc)

**/*.{tsx,jsx}: ALWAYS use useMemo for expensive computations, object/array creations, and filtered data
ALWAYS use useMemo for derived values and computed properties
ALWAYS use useMemo for conditional values and simple transformations
ALWAYS use useCallback for event handlers and functions passed as props
ALWAYS use useCallback for any function that could be passed as a prop or dependency
ALWAYS include all dependencies in useEffect, useMemo, useCallback dependency arrays
NEVER use // eslint-disable-next-line react-hooks/exhaustive-deps unless absolutely necessary
ALWAYS explain why dependencies are excluded if using eslint disable
ALWAYS use named exports for components
NEVER use default exports for components
KEEP component files under 200 lines when possible
BREAK DOWN large components into smaller, reusable pieces
EXTRACT complex logic into custom hooks
USE local state for component-level state
LIFT state up when needed across multiple components
USE Context for avoiding prop drilling
ALWAYS wrap components in error boundaries for production
ALWAYS handle async errors properly
ALWAYS provide user-friendly error messages
ALWAYS use virtualization for lists with 100+ items
ALWAYS implement proper key props for list items
ALWAYS lazy load heavy components
ALWAYS use React.lazy for code splitting
Components receiving props are wrapped with memo

Files:

  • src/context/ModalProvider/ModalContainer.tsx
  • src/components/Modals/Send/Form.tsx
  • src/components/Modals/Send/SendCommon.tsx
  • src/components/Modals/Send/views/SendAmountDetails.tsx
  • src/components/Modals/Send/components/QRCodeIcon.tsx
  • src/components/Modals/Send/AddressBook/ConfirmDelete.tsx
  • src/components/Modals/Send/views/Address.tsx
  • src/components/Modals/Send/AddressInput/AddressInput.tsx
  • src/components/Modals/Send/views/Confirm.tsx
  • src/components/Modals/QrCode/QrCode.tsx
  • src/components/Modals/Send/AddressBook/AddressBook.tsx
  • src/components/Modals/Send/Send.tsx
  • src/components/Modals/Send/AddAddressModal.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/react-best-practices.mdc)

USE Redux only for global state shared across multiple places

Files:

  • src/context/ModalProvider/ModalContainer.tsx
  • src/state/migrations/clearAddressBook.ts
  • src/state/store.ts
  • src/components/Modals/Send/Form.tsx
  • src/components/Modals/Send/SendCommon.tsx
  • src/context/ModalProvider/types.ts
  • src/components/Modals/Send/views/SendAmountDetails.tsx
  • src/state/slices/addressBookSlice/selectors.ts
  • src/config.ts
  • src/components/Modals/Send/components/QRCodeIcon.tsx
  • src/components/Modals/Send/AddressBook/ConfirmDelete.tsx
  • src/components/Modals/Send/views/Address.tsx
  • src/components/Modals/Send/AddressInput/AddressInput.tsx
  • src/state/migrations/index.ts
  • src/vite-env.d.ts
  • src/components/Modals/Send/views/Confirm.tsx
  • src/components/Modals/QrCode/QrCode.tsx
  • src/components/Modals/Send/AddressBook/AddressBook.tsx
  • src/state/slices/preferencesSlice/preferencesSlice.ts
  • src/state/reducer.ts
  • src/components/Modals/Send/Send.tsx
  • src/test/mocks/store.ts
  • src/components/Modals/Send/AddAddressModal.tsx
  • src/state/slices/addressBookSlice/addressBookSlice.ts
🧠 Learnings (3)
📓 Common learnings
Learnt from: NeOMakinG
PR: shapeshift/web#10323
File: src/pages/RFOX/components/Stake/components/StakeSummary.tsx:112-114
Timestamp: 2025-08-22T13:00:44.879Z
Learning: NeOMakinG prefers to keep PR changes minimal and focused on the core objectives, avoiding cosmetic or defensive code improvements that aren't directly related to the PR scope, even when they would improve robustness.
📚 Learning: 2025-09-04T10:18:34.140Z
Learnt from: gomesalexandre
PR: shapeshift/web#10427
File: src/hooks/useActionCenterSubscribers/useSwapActionSubscriber.tsx:40-40
Timestamp: 2025-09-04T10:18:34.140Z
Learning: In the shapeshift/web codebase, src/state/slices/selectors.ts uses wildcard exports (`export * from './[sliceName]/selectors'`) to re-export all selectors from individual slice selector files, making them available through the barrel import. This means selectors like selectTxByFilter from txHistorySlice/selectors are properly accessible via '@/state/slices/selectors' even though they don't appear in explicit named exports.

Applied to files:

  • src/state/slices/addressBookSlice/selectors.ts
📚 Learning: 2025-08-27T13:49:48.668Z
Learnt from: NeOMakinG
PR: shapeshift/web#10375
File: src/state/migrations/index.ts:214-215
Timestamp: 2025-08-27T13:49:48.668Z
Learning: The ShapeShift web app uses an automated versioning system for Redux Persist migrations. The version is calculated as `Math.max(...Object.keys(clearAssetsMigrations).map(Number))`, which automatically uses the highest migration number as the version. This eliminates the need to manually update persistConfig versions when adding new migrations - the system automatically bumps the version when new migration numbers are added to the migration objects.

Applied to files:

  • src/state/reducer.ts
🧬 Code graph analysis (18)
src/context/ModalProvider/ModalContainer.tsx (2)
src/components/Modals/Send/AddAddressModal.tsx (1)
  • AddAddressModal (46-134)
src/utils/makeSuspenseful.tsx (1)
  • makeSuspenseful (42-62)
src/state/migrations/clearAddressBook.ts (1)
src/state/slices/addressBookSlice/addressBookSlice.ts (2)
  • AddressBookState (13-16)
  • initialState (18-21)
src/state/store.ts (1)
src/state/reducer.ts (1)
  • slices (53-68)
src/context/ModalProvider/types.ts (1)
src/components/Modals/Send/AddAddressModal.tsx (1)
  • AddAddressModalProps (27-31)
src/components/Modals/Send/views/SendAmountDetails.tsx (2)
src/hooks/useModal/useModal.tsx (1)
  • useModal (6-24)
src/components/Modals/Send/AddressInput/AddressInput.tsx (1)
  • AddressInput (53-273)
src/state/slices/addressBookSlice/selectors.ts (3)
src/state/slices/addressBookSlice/addressBookSlice.ts (1)
  • addressBookSlice (23-57)
src/state/reducer.ts (1)
  • ReduxState (207-207)
src/state/selectors.ts (1)
  • selectSearchQueryFromFilter (96-96)
src/components/Modals/Send/AddressBook/ConfirmDelete.tsx (4)
src/components/Modal/components/Dialog.tsx (1)
  • Dialog (149-149)
src/components/Modal/components/DialogHeader.tsx (2)
  • DialogHeader (15-48)
  • DialogHeaderMiddle (54-58)
src/components/Modal/components/DialogFooter.tsx (1)
  • DialogFooter (9-23)
src/components/Text/Text.tsx (1)
  • Text (19-83)
src/components/Modals/Send/views/Address.tsx (5)
src/hooks/useModal/useModal.tsx (1)
  • useModal (6-24)
src/state/store.ts (1)
  • useAppSelector (143-143)
src/state/slices/addressBookSlice/selectors.ts (1)
  • selectAddressBookEntriesByChainNamespace (16-33)
packages/caip/src/constants.ts (1)
  • ethChainId (60-60)
src/components/Modals/Send/AddressBook/AddressBook.tsx (1)
  • AddressBook (121-226)
src/components/Modals/Send/AddressInput/AddressInput.tsx (4)
src/components/Modals/Send/Form.tsx (1)
  • SendInput (46-64)
src/state/store.ts (1)
  • useAppSelector (143-143)
src/state/slices/addressBookSlice/selectors.ts (1)
  • selectAddressBookEntriesByChainNamespace (16-33)
src/components/Text/Text.tsx (1)
  • Text (19-83)
src/state/migrations/index.ts (1)
src/state/migrations/clearAddressBook.ts (1)
  • clearAddressBook (6-9)
src/components/Modals/Send/views/Confirm.tsx (2)
src/state/store.ts (1)
  • useAppSelector (143-143)
src/state/slices/addressBookSlice/selectors.ts (1)
  • selectAddressBookEntriesByChainNamespace (16-33)
src/components/Modals/QrCode/QrCode.tsx (1)
src/components/Modals/Send/SendCommon.tsx (1)
  • sendRoutes (11-18)
src/components/Modals/Send/AddressBook/AddressBook.tsx (5)
src/state/slices/addressBookSlice/addressBookSlice.ts (2)
  • AddressBookEntry (5-11)
  • addressBookSlice (23-57)
src/state/store.ts (2)
  • useAppDispatch (151-151)
  • useAppSelector (143-143)
src/state/slices/addressBookSlice/selectors.ts (2)
  • selectAddressBookEntriesByChainNamespace (16-33)
  • selectAddressBookEntriesBySearchQuery (43-62)
src/components/Text/Text.tsx (1)
  • Text (19-83)
src/components/Modals/Send/AddressBook/ConfirmDelete.tsx (1)
  • ConfirmDelete (19-51)
src/state/slices/preferencesSlice/preferencesSlice.ts (1)
src/config.ts (1)
  • getConfig (209-211)
src/state/reducer.ts (2)
src/state/slices/addressBookSlice/addressBookSlice.ts (2)
  • addressBookSlice (23-57)
  • AddressBookState (13-16)
src/state/migrations/index.ts (1)
  • clearAddressBookMigrations (265-267)
src/components/Modals/Send/Send.tsx (1)
src/components/Modals/Send/SendCommon.tsx (1)
  • sendRoutes (11-18)
src/components/Modals/Send/AddAddressModal.tsx (3)
src/state/store.ts (1)
  • useAppDispatch (151-151)
src/hooks/useModal/useModal.tsx (1)
  • useModal (6-24)
src/state/slices/addressBookSlice/addressBookSlice.ts (1)
  • addressBookSlice (23-57)
src/state/slices/addressBookSlice/addressBookSlice.ts (1)
src/components/Modals/Send/AddressBook/AddressBook.tsx (1)
  • AddressBookEntry (29-33)
🪛 dotenv-linter (4.0.0)
.env.production

[warning] 4-4: [UnorderedKey] The VITE_FEATURE_ADDRESS_BOOK key should go before the VITE_FEATURE_MIXPANEL key

(UnorderedKey)

.env.development

[warning] 9-9: [UnorderedKey] The VITE_FEATURE_ADDRESS_BOOK key should go before the VITE_FEATURE_THORCHAIN_TCY_ACTIVITY key

(UnorderedKey)

⏰ 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)
  • GitHub Check: Install and Cache
🔇 Additional comments (17)
src/components/Modals/Send/components/QRCodeIcon.tsx (1)

5-31: Ensure this renders under an error boundary.

Confirm a parent boundary wraps this component within the Send modal stack. As per coding guidelines.

src/state/slices/addressBookSlice/selectors.ts (1)

16-33: LGTM on namespace-scoped filtering.

Using chainNamespace as the cache key matches the selector’s semantics.

src/state/store.ts (1)

57-57: LGTM — clearing addressBook in clearState is consistent with other slices.

src/state/migrations/index.ts (1)

265-267: Migration wiring verified and correct.

The clearAddressBookMigrations is properly imported (line 15) and integrated into addressBookPersistConfig with version calculation (line 147) and migration function creation (line 148). The config is applied to the addressBook reducer (line 173).

src/state/reducer.ts (4)

15-24: Address book slice and migrations wiring look consistent with existing patterns.

Imports align with other “clear*” migrations and slice types. No issues.

Also applies to: 27-29


67-68: Registering the slice in slices map is correct.

Keeps parity with other slices; good for dev tooling/tests relying on slices.


144-150: Persist config follows the auto-versioning migration pattern.

Using Math.max(Object.keys(clearAddressBookMigrations)) + createMigrate matches our migration strategy. LGTM.


173-174: Reducer registration is correct.

Type param AddressBookState + persistReducer usage is sound.

src/test/mocks/store.ts (2)

167-168: Feature flag added to mocks is aligned.

preferences.featureFlags.AddressBook presence matches the new env flag and selectors.


363-370: Mock state for addressBook matches slice shape.

Includes _persist, byId, and ids. This should prevent test rehydration issues.

src/components/Modals/Send/Form.tsx (1)

208-214: Unified navigation to Address route looks good.

Replacing responsive branching with navigate(SendRoutes.Address, { replace: true }) simplifies flow. Verify desktop parity as per issue #10694.

src/components/Modals/Send/AddressBook/ConfirmDelete.tsx (1)

27-50: LGTM — dialog composition and copy look solid.

Structure, callbacks, and i18n usage are clean.

src/components/Modals/Send/AddAddressModal.tsx (2)

85-134: LGTM — modal wiring, validation, and dispatch look good.

Avatar blockies, reset-on-close, and disabled submit on invalid are correct.


46-50: Error boundary coverage.

Confirm this modal is rendered within an ErrorBoundary per guidelines; if not, wrap it or ensure the Modal system provides one.

src/components/Modals/Send/views/Confirm.tsx (1)

270-307: LGTM — integration with address book and avatar rendering.

Tooltip, MiddleEllipsis, name rendering, and avatar source logic look good.

Also applies to: 140-147, 273-283

src/components/Modals/Send/views/Address.tsx (1)

210-219: LGTM — feature flag gating, save-contact flow, and QR button UX.

Gating via useFeatureFlag('AddressBook'), save flow using modal, and empty-change handling are well integrated.

Also applies to: 243-246, 249-266

src/components/Modals/Send/SendCommon.tsx (1)

8-18: Review comment is incorrect. SendRoutes.Amount is unused and should not be included in sendRoutes.

The sendRoutes array is correctly defined with only the routes actively used in the Send modal flow. Verification shows:

  • SendRoutes.Amount is defined in the enum but never referenced anywhere in the codebase
  • No navigation logic targets /send/amount or attempts to navigate to SendRoutes.Amount
  • The sendRoutes array serves as initialEntries for MemoryRouter in Send.tsx and QrCode.tsx
  • Other similar modals in the codebase follow the same pattern—their route arrays only include routes they actually use (e.g., Trade.tsx, ClaimRoutes.tsx)

Including unused routes in initialEntries adds unnecessary bloat without functional benefit. The current implementation is correct.

Likely an incorrect or invalid review comment.

@gomesalexandre gomesalexandre self-requested a review October 21, 2025 13:25
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (2)
src/components/Modals/Send/views/Confirm.tsx (1)

422-425: Guard fromAccountId against undefined accountId (prevents crash).

fromAccountId('') will throw when accountId is falsy. Short‑circuit before calling.

-              <InlineCopyButton
-                isDisabled={!accountId || isUtxoAccountId(accountId)}
-                value={fromAccountId(accountId ?? '').account}
-              >
+              <InlineCopyButton
+                isDisabled={!accountId || isUtxoAccountId(accountId)}
+                value={accountId ? fromAccountId(accountId).account : ''}
+              >
src/components/Modals/Send/views/SendAmountDetails.tsx (1)

192-195: Fix characters remaining when memo is empty (NaN bug).

Number(memo?.length) yields NaN when undefined, causing 0 remaining instead of 256.

-  const remainingMemoChars = useMemo(() => bnOrZero(MAX_MEMO_LENGTH - Number(memo?.length)), [memo])
+  const remainingMemoChars = useMemo(
+    () => bnOrZero(MAX_MEMO_LENGTH - (memo?.length ?? 0)),
+    [memo],
+  )
🧹 Nitpick comments (11)
src/components/Modals/Send/AddressBook/AddressBookEntryButton.tsx (1)

77-79: Add accessibility attributes to the delete button.

The delete button lacks an aria-label attribute, which is important for screen readers to announce the button's purpose.

Apply this diff:

-      <Button size='sm' onClick={handleDelete(entryKey)} sx={deleteButtonSx} flexShrink={0}>
+      <Button 
+        size='sm' 
+        onClick={handleDelete(entryKey)} 
+        sx={deleteButtonSx} 
+        flexShrink={0}
+        aria-label={`Delete ${label}`}
+      >
         <Icon as={FaTrash} boxSize={4} />
       </Button>
src/state/slices/addressBookSlice/utils.ts (1)

14-16: Consider using Result pattern for error handling.

The function throws an error for invalid entry types. Per coding guidelines, error handling should use the Result pattern with Ok() and Err() from @sniptt/monads. However, if this is an internal utility where invalid types represent a programming error (not a runtime condition), throwing may be acceptable.

As per coding guidelines.

src/components/Modals/Send/views/Confirm.tsx (1)

85-86: Nit: Name reflects ms, not seconds.

Constant is in milliseconds; rename for clarity.

-const LONG_PRESS_SECONDS_THRESHOLD = 2000
+const LONG_PRESS_MS_THRESHOLD = 2000
...
-      threshold: LONG_PRESS_SECONDS_THRESHOLD,
+      threshold: LONG_PRESS_MS_THRESHOLD,
src/components/Modals/Send/AddressBookSaveModal.tsx (2)

145-147: Disable “Save” when address maps to an internal account.

Prevents silent no‑op on submit and aligns UI with behavior.

-            <Button type='submit' colorScheme='blue' width='full' size='lg' isDisabled={!isValid}>
+            <Button
+              type='submit'
+              colorScheme='blue'
+              width='full'
+              size='lg'
+              isDisabled={!isValid || !!internalAccountId}
+            >

118-118: Add accessible name to Avatar.

Improves a11y for screen readers with a meaningful fallback.

-            <Avatar src={avatarUrl} size='lg' />
+            <Avatar src={avatarUrl} size='lg' name={address} />
src/components/Modals/Send/AddressBook/AddressBook.tsx (2)

61-63: Normalize address casing when matching selected entry.

Case‑insensitive match avoids missed hits on EVM checksummed vs lowercased inputs.

-  const selectedEntry = useMemo(() => {
-    if (!input) return undefined
-    return addressBookEntries?.find(entry => entry.isExternal && entry.address === input)
-  }, [addressBookEntries, input])
+  const selectedEntry = useMemo(() => {
+    if (!input) return undefined
+    const normalized = input.toLowerCase()
+    return addressBookEntries?.find(
+      entry => entry.isExternal && entry.address.toLowerCase() === normalized,
+    )
+  }, [addressBookEntries, input])

90-94: Optional: simplify entries selection logic.

This is equivalent and removes the selectedEntry dependency.

-  const entries = useMemo(() => {
-    if (selectedEntry || !input || (input && !addressError)) return addressBookEntries
-    return addressBookSearchEntries
-  }, [selectedEntry, addressBookEntries, addressBookSearchEntries, input, addressError])
+  const entries = useMemo(() => {
+    // Show all when no input or when input is a valid full address; otherwise show search
+    return !input || !addressError ? addressBookEntries : addressBookSearchEntries
+  }, [addressBookEntries, addressBookSearchEntries, input, addressError])
src/components/Modals/Send/views/SendAmountDetails.tsx (1)

82-90: Replace any with explicit types for better TS safety.

Tighten typings for AmountInput props and Controller render param. As per coding guidelines.

-const AmountInput = (props: any) => {
+type AmountInputProps = React.ComponentProps<typeof Input> & { value?: string | number }
+const AmountInput = (props: AmountInputProps) => {
-  const renderController = useCallback(
-    ({ field: { onChange, value } }: { field: any }) => {
+  const renderController = useCallback(
+    ({
+      field: { onChange, value },
+    }: {
+      field:
+        | ControllerRenderProps<FieldValues, SendFormFields.AmountCryptoPrecision>
+        | ControllerRenderProps<FieldValues, SendFormFields.FiatAmount>
+    }) => {

Also applies to: 253-271

src/components/Modals/Send/views/Address.tsx (1)

213-217: Consider inlining the empty handler.

handleEmptyChange is only called once at line 240. Inlining it would reduce indirection without sacrificing clarity:

- const handleEmptyChange = useCallback(() => {
-   setValue(SendFormFields.Input, '', { shouldValidate: true })
-   setValue(SendFormFields.To, '')
-   setValue(SendFormFields.VanityAddress, '')
- }, [setValue])
+ const handleEmptyChange = useCallback(() => {
+   setValue(SendFormFields.Input, '', { shouldValidate: true })
+   setValue(SendFormFields.To, '')
+   setValue(SendFormFields.VanityAddress, '')
+ }, [setValue])

Or inline directly at the call site if preferred.

src/components/Modals/Send/AddressInput/AddressInput.tsx (2)

175-184: Document the paste workaround more explicitly.

The setTimeout workaround on lines 179-180 is necessary but fragile. If the paste handler timing changes (e.g., due to React updates or form library changes), this could break.

Consider adding a more explicit comment linking to any related issues or documenting the exact race condition being avoided:

  const handlePaste = useCallback(() => {
    if (isFocused && inputRef.current) {
-      // Add this at the end of the callstack so we leave space for the paste event to be handled
-      // by the onChange event handler of the form before blurring which would cancel the paste event
+      // WORKAROUND: Delay blur to avoid canceling paste event.
+      // The onChange handler must process the pasted value before blur resets the input mode.
+      // If this breaks, check if react-hook-form's paste handling timing changed.
      setTimeout(() => {
        inputRef.current?.blur()
        setIsFocused(false)
      }, 0)
    }
  }, [isFocused])

210-344: Consider extracting rendering modes into separate components.

The renderController callback spans 134 lines with three distinct rendering modes (editable input, address book entry, compact display). This violates the Single Responsibility Principle and makes the component harder to maintain and test.

Consider extracting each mode into a separate component:

  • EditableAddressInput
  • AddressBookEntryDisplay
  • CompactAddressDisplay

This would improve testability, readability, and allow better isolation of concerns. However, given the project preference for minimal changes (per learnings), this refactor may be deferred unless the component becomes harder to maintain.

Based on learnings

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between f373009 and a73e838.

📒 Files selected for processing (21)
  • src/components/Modals/QrCode/QrCode.tsx (1 hunks)
  • src/components/Modals/Send/AddressBook/AddressBook.tsx (1 hunks)
  • src/components/Modals/Send/AddressBook/AddressBookEntryButton.tsx (1 hunks)
  • src/components/Modals/Send/AddressBookSaveModal.tsx (1 hunks)
  • src/components/Modals/Send/AddressInput/AddressInput.tsx (3 hunks)
  • src/components/Modals/Send/Send.tsx (1 hunks)
  • src/components/Modals/Send/SendCommon.tsx (1 hunks)
  • src/components/Modals/Send/views/Address.tsx (9 hunks)
  • src/components/Modals/Send/views/Confirm.tsx (6 hunks)
  • src/components/Modals/Send/views/SendAmountDetails.tsx (7 hunks)
  • src/context/ModalProvider/ModalContainer.tsx (2 hunks)
  • src/context/ModalProvider/types.ts (2 hunks)
  • src/index.tsx (1 hunks)
  • src/state/selectors.ts (1 hunks)
  • src/state/slices/addressBookSlice/addressBookSlice.ts (1 hunks)
  • src/state/slices/addressBookSlice/selectors.ts (1 hunks)
  • src/state/slices/addressBookSlice/types.ts (1 hunks)
  • src/state/slices/addressBookSlice/utils.ts (1 hunks)
  • src/state/slices/assetsSlice/types.ts (1 hunks)
  • src/state/slices/common-selectors.ts (1 hunks)
  • src/test/mocks/store.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/test/mocks/store.ts
  • src/components/Modals/QrCode/QrCode.tsx
  • src/components/Modals/Send/Send.tsx
  • src/context/ModalProvider/ModalContainer.tsx
  • src/state/slices/addressBookSlice/selectors.ts
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/error-handling.mdc)

**/*.{ts,tsx}: ALWAYS use Result<T, E> pattern for error handling in swappers and APIs
ALWAYS use Ok() and Err() from @sniptt/monads for monadic error handling
ALWAYS use custom error classes from @shapeshiftoss/errors
ALWAYS provide meaningful error codes for internationalization
ALWAYS include relevant details in error objects
ALWAYS wrap async operations in try-catch blocks
ALWAYS use AsyncResultOf utility for converting promises to Results
ALWAYS provide fallback error handling
ALWAYS use timeoutMonadic for API calls
ALWAYS provide appropriate timeout values for API calls
ALWAYS handle timeout errors gracefully
ALWAYS validate inputs before processing
ALWAYS provide clear validation error messages
ALWAYS use early returns for validation failures
ALWAYS log errors for debugging
ALWAYS use structured logging for errors
ALWAYS include relevant context in error logs
Throwing errors instead of using monadic patterns is an anti-pattern
Missing try-catch blocks for async operations is an anti-pattern
Generic error messages without context are an anti-pattern
Not handling specific error types is an anti-pattern
Missing timeout handling is an anti-pattern
No input validation is an anti-pattern
Poor error logging is an anti-pattern
Using any for error types is an anti-pattern
Missing error codes for internationalization is an anti-pattern
No fallback error handling is an anti-pattern
Console.error without structured logging is an anti-pattern

**/*.{ts,tsx}: ALWAYS use camelCase for variables, functions, and methods
ALWAYS use descriptive names that explain the purpose for variables and functions
ALWAYS use verb prefixes for functions that perform actions
ALWAYS use PascalCase for types, interfaces, and enums
ALWAYS use descriptive names that indicate the structure for types, interfaces, and enums
ALWAYS use suffixes like Props, State, Config, Type when appropriate for types and interfaces
ALWAYS use UPPER_SNAKE_CASE for constants and configuration values
ALWAYS use d...

Files:

  • src/state/selectors.ts
  • src/state/slices/addressBookSlice/types.ts
  • src/state/slices/assetsSlice/types.ts
  • src/components/Modals/Send/SendCommon.tsx
  • src/components/Modals/Send/AddressBookSaveModal.tsx
  • src/components/Modals/Send/AddressBook/AddressBookEntryButton.tsx
  • src/state/slices/addressBookSlice/addressBookSlice.ts
  • src/components/Modals/Send/AddressInput/AddressInput.tsx
  • src/components/Modals/Send/views/Address.tsx
  • src/state/slices/common-selectors.ts
  • src/index.tsx
  • src/state/slices/addressBookSlice/utils.ts
  • src/context/ModalProvider/types.ts
  • src/components/Modals/Send/views/SendAmountDetails.tsx
  • src/components/Modals/Send/AddressBook/AddressBook.tsx
  • src/components/Modals/Send/views/Confirm.tsx
**/*

📄 CodeRabbit inference engine (.cursor/rules/naming-conventions.mdc)

**/*: ALWAYS use appropriate file extensions
Flag files without kebab-case

Files:

  • src/state/selectors.ts
  • src/state/slices/addressBookSlice/types.ts
  • src/state/slices/assetsSlice/types.ts
  • src/components/Modals/Send/SendCommon.tsx
  • src/components/Modals/Send/AddressBookSaveModal.tsx
  • src/components/Modals/Send/AddressBook/AddressBookEntryButton.tsx
  • src/state/slices/addressBookSlice/addressBookSlice.ts
  • src/components/Modals/Send/AddressInput/AddressInput.tsx
  • src/components/Modals/Send/views/Address.tsx
  • src/state/slices/common-selectors.ts
  • src/index.tsx
  • src/state/slices/addressBookSlice/utils.ts
  • src/context/ModalProvider/types.ts
  • src/components/Modals/Send/views/SendAmountDetails.tsx
  • src/components/Modals/Send/AddressBook/AddressBook.tsx
  • src/components/Modals/Send/views/Confirm.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/react-best-practices.mdc)

USE Redux only for global state shared across multiple places

Files:

  • src/state/selectors.ts
  • src/state/slices/addressBookSlice/types.ts
  • src/state/slices/assetsSlice/types.ts
  • src/components/Modals/Send/SendCommon.tsx
  • src/components/Modals/Send/AddressBookSaveModal.tsx
  • src/components/Modals/Send/AddressBook/AddressBookEntryButton.tsx
  • src/state/slices/addressBookSlice/addressBookSlice.ts
  • src/components/Modals/Send/AddressInput/AddressInput.tsx
  • src/components/Modals/Send/views/Address.tsx
  • src/state/slices/common-selectors.ts
  • src/index.tsx
  • src/state/slices/addressBookSlice/utils.ts
  • src/context/ModalProvider/types.ts
  • src/components/Modals/Send/views/SendAmountDetails.tsx
  • src/components/Modals/Send/AddressBook/AddressBook.tsx
  • src/components/Modals/Send/views/Confirm.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/error-handling.mdc)

**/*.tsx: ALWAYS wrap components in error boundaries
ALWAYS provide user-friendly fallback components in error boundaries
ALWAYS log errors for debugging in error boundaries
ALWAYS use useErrorToast hook for displaying errors
ALWAYS provide translated error messages in error toasts
ALWAYS handle different error types appropriately in error toasts
Missing error boundaries in React components is an anti-pattern

**/*.tsx: ALWAYS use PascalCase for React component names
ALWAYS use descriptive names that indicate the component's purpose
ALWAYS match the component name to the file name
Flag components without PascalCase
Flag default exports for components

Files:

  • src/components/Modals/Send/SendCommon.tsx
  • src/components/Modals/Send/AddressBookSaveModal.tsx
  • src/components/Modals/Send/AddressBook/AddressBookEntryButton.tsx
  • src/components/Modals/Send/AddressInput/AddressInput.tsx
  • src/components/Modals/Send/views/Address.tsx
  • src/index.tsx
  • src/components/Modals/Send/views/SendAmountDetails.tsx
  • src/components/Modals/Send/AddressBook/AddressBook.tsx
  • src/components/Modals/Send/views/Confirm.tsx
**/*.{tsx,jsx}

📄 CodeRabbit inference engine (.cursor/rules/react-best-practices.mdc)

**/*.{tsx,jsx}: ALWAYS use useMemo for expensive computations, object/array creations, and filtered data
ALWAYS use useMemo for derived values and computed properties
ALWAYS use useMemo for conditional values and simple transformations
ALWAYS use useCallback for event handlers and functions passed as props
ALWAYS use useCallback for any function that could be passed as a prop or dependency
ALWAYS include all dependencies in useEffect, useMemo, useCallback dependency arrays
NEVER use // eslint-disable-next-line react-hooks/exhaustive-deps unless absolutely necessary
ALWAYS explain why dependencies are excluded if using eslint disable
ALWAYS use named exports for components
NEVER use default exports for components
KEEP component files under 200 lines when possible
BREAK DOWN large components into smaller, reusable pieces
EXTRACT complex logic into custom hooks
USE local state for component-level state
LIFT state up when needed across multiple components
USE Context for avoiding prop drilling
ALWAYS wrap components in error boundaries for production
ALWAYS handle async errors properly
ALWAYS provide user-friendly error messages
ALWAYS use virtualization for lists with 100+ items
ALWAYS implement proper key props for list items
ALWAYS lazy load heavy components
ALWAYS use React.lazy for code splitting
Components receiving props are wrapped with memo

Files:

  • src/components/Modals/Send/SendCommon.tsx
  • src/components/Modals/Send/AddressBookSaveModal.tsx
  • src/components/Modals/Send/AddressBook/AddressBookEntryButton.tsx
  • src/components/Modals/Send/AddressInput/AddressInput.tsx
  • src/components/Modals/Send/views/Address.tsx
  • src/index.tsx
  • src/components/Modals/Send/views/SendAmountDetails.tsx
  • src/components/Modals/Send/AddressBook/AddressBook.tsx
  • src/components/Modals/Send/views/Confirm.tsx
🧠 Learnings (8)
📓 Common learnings
Learnt from: NeOMakinG
PR: shapeshift/web#10323
File: src/pages/RFOX/components/Stake/components/StakeSummary.tsx:112-114
Timestamp: 2025-08-22T13:00:44.879Z
Learning: NeOMakinG prefers to keep PR changes minimal and focused on the core objectives, avoiding cosmetic or defensive code improvements that aren't directly related to the PR scope, even when they would improve robustness.
📚 Learning: 2025-08-15T07:51:16.374Z
Learnt from: gomesalexandre
PR: shapeshift/web#10278
File: src/components/AssetHeader/hooks/useQuickBuy.ts:97-99
Timestamp: 2025-08-15T07:51:16.374Z
Learning: The selectPortfolioUserCurrencyBalanceByAssetId selector in src/state/slices/portfolioSlice/selectors.ts expects a filter object with an assetId property, not a raw AssetId string. The selector signature is (state: ReduxState, filter) where filter should have an assetId property. This pattern is consistent across portfolio selectors that use selectAssetIdParamFromFilter. Passing a filter object like { assetId: someAssetId } is the correct usage pattern.

Applied to files:

  • src/state/selectors.ts
📚 Learning: 2025-08-05T23:36:13.214Z
Learnt from: premiumjibles
PR: shapeshift/web#10187
File: src/state/slices/preferencesSlice/selectors.ts:21-25
Timestamp: 2025-08-05T23:36:13.214Z
Learning: The AssetId type from 'shapeshiftoss/caip' package is a string type alias, so it can be used directly as a return type for cache key resolvers in re-reselect selectors without needing explicit string conversion.

Applied to files:

  • src/state/slices/assetsSlice/types.ts
📚 Learning: 2025-09-04T17:35:57.584Z
Learnt from: NeOMakinG
PR: shapeshift/web#10380
File: src/state/slices/common-selectors.ts:164-178
Timestamp: 2025-09-04T17:35:57.584Z
Learning: In shapeshift/web, while the Asset type allows relatedAssetKey to be AssetId | null | undefined, the actual asset generation and processing system ensures relatedAssetKey is never undefined in practice. All asset creation utilities explicitly set it to either a specific AssetId (for grouped assets) or null (for primary/unique assets). The only undefined assignments occur during intermediate processing phases and get resolved during the final asset generation step.

Applied to files:

  • src/state/slices/assetsSlice/types.ts
📚 Learning: 2025-09-04T17:35:57.584Z
Learnt from: NeOMakinG
PR: shapeshift/web#10380
File: src/state/slices/common-selectors.ts:164-178
Timestamp: 2025-09-04T17:35:57.584Z
Learning: In shapeshift/web, while the Asset type allows relatedAssetKey to be AssetId | null | undefined, the actual asset generation and processing system ensures relatedAssetKey is never undefined in practice. All asset creation utilities explicitly set it to either a specific AssetId (for grouped assets) or null (for primary/unique assets). The only exception is temporary undefined assignment during portal asset processing, which gets resolved later in the pipeline.

Applied to files:

  • src/state/slices/assetsSlice/types.ts
📚 Learning: 2025-10-21T17:11:18.087Z
Learnt from: gomesalexandre
PR: shapeshift/web#10871
File: src/components/Modals/Send/hooks/useSendDetails/useSendDetails.tsx:426-428
Timestamp: 2025-10-21T17:11:18.087Z
Learning: In src/components/Modals/Send/hooks/useSendDetails/useSendDetails.tsx, within the handleInputChange function, use .toFixed() without arguments (not .toString()) when converting BigNumber amounts for input field synchronization. This avoids exponential notation in the input while preserving precision for presentational components like <Amount.Crypto /> and <Amount.Fiat /> to format appropriately.

Applied to files:

  • src/components/Modals/Send/SendCommon.tsx
  • src/components/Modals/Send/views/SendAmountDetails.tsx
📚 Learning: 2025-08-08T15:00:22.321Z
Learnt from: NeOMakinG
PR: shapeshift/web#10231
File: src/components/MultiHopTrade/components/TradeInput/components/HighlightedTokens.tsx:14-14
Timestamp: 2025-08-08T15:00:22.321Z
Learning: In shapeshift/web reviews for NeOMakinG, avoid nitpicks to change deep-relative imports to '@/…' alias paths within feature/non-refactor PRs; defer such style-only changes to a dedicated follow-up refactor unless they fix an issue.

Applied to files:

  • src/state/slices/addressBookSlice/addressBookSlice.ts
📚 Learning: 2025-09-08T15:53:09.362Z
Learnt from: gomesalexandre
PR: shapeshift/web#10442
File: src/components/TradeAssetSearch/components/GroupedAssetList/GroupedAssetList.tsx:34-35
Timestamp: 2025-09-08T15:53:09.362Z
Learning: In DefaultAssetList.tsx, the GroupedAssetList component already receives the activeChainId prop correctly on line ~58, contrary to automated analysis that may flag it as missing.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
🧬 Code graph analysis (10)
src/components/Modals/Send/AddressBookSaveModal.tsx (5)
src/state/store.ts (2)
  • useAppDispatch (151-151)
  • useAppSelector (143-143)
src/hooks/useModal/useModal.tsx (1)
  • useModal (6-24)
src/state/slices/addressBookSlice/selectors.ts (1)
  • selectInternalAccountIdByAddress (89-122)
src/state/slices/addressBookSlice/types.ts (1)
  • AddressBookEntry (3-17)
src/state/slices/addressBookSlice/addressBookSlice.ts (1)
  • addressBookSlice (28-97)
src/state/slices/addressBookSlice/addressBookSlice.ts (4)
src/state/slices/addressBookSlice/types.ts (1)
  • AddressBookEntry (3-17)
src/lib/utils/evm/index.ts (1)
  • getSupportedEvmChainIds (196-198)
packages/caip/src/constants.ts (1)
  • CHAIN_NAMESPACE (77-82)
src/state/slices/addressBookSlice/utils.ts (1)
  • isDuplicateEntry (5-37)
src/components/Modals/Send/AddressInput/AddressInput.tsx (6)
src/components/Modals/Send/Form.tsx (1)
  • SendInput (46-64)
src/state/store.ts (1)
  • useAppSelector (143-143)
src/state/slices/addressBookSlice/selectors.ts (2)
  • selectExternalAddressBookEntryByAddress (128-143)
  • selectInternalAccountIdByAddress (89-122)
src/state/slices/portfolioSlice/selectors.ts (1)
  • selectPortfolioAccountMetadata (109-117)
src/lib/utils/utxo.ts (1)
  • isUtxoAccountId (24-25)
src/state/slices/portfolioSlice/utils/index.ts (1)
  • accountIdToLabel (80-119)
src/components/Modals/Send/views/Address.tsx (5)
src/hooks/useModal/useModal.tsx (1)
  • useModal (6-24)
src/state/store.ts (1)
  • useAppSelector (143-143)
src/state/slices/addressBookSlice/selectors.ts (2)
  • selectIsAddressInAddressBook (52-82)
  • selectInternalAccountIdByAddress (89-122)
packages/caip/src/constants.ts (1)
  • ethChainId (60-60)
src/components/Modals/Send/AddressBook/AddressBook.tsx (1)
  • AddressBook (34-157)
src/state/slices/common-selectors.ts (1)
src/state/selector-utils.ts (1)
  • createDeepEqualOutputSelector (5-7)
src/state/slices/addressBookSlice/utils.ts (2)
src/state/slices/addressBookSlice/types.ts (1)
  • AddressBookEntry (3-17)
packages/caip/src/constants.ts (1)
  • CHAIN_NAMESPACE (77-82)
src/context/ModalProvider/types.ts (1)
src/components/Modals/Send/AddressBookSaveModal.tsx (1)
  • AddressBookSaveModalProps (29-33)
src/components/Modals/Send/views/SendAmountDetails.tsx (2)
src/hooks/useModal/useModal.tsx (1)
  • useModal (6-24)
src/components/Modals/Send/AddressInput/AddressInput.tsx (1)
  • AddressInput (59-367)
src/components/Modals/Send/AddressBook/AddressBook.tsx (6)
src/state/store.ts (2)
  • useAppDispatch (151-151)
  • useAppSelector (143-143)
src/state/slices/addressBookSlice/types.ts (1)
  • AddressBookEntry (3-17)
src/state/slices/addressBookSlice/selectors.ts (2)
  • selectAddressBookEntriesByChainId (20-28)
  • selectAddressBookEntriesBySearchQuery (30-46)
src/state/slices/addressBookSlice/addressBookSlice.ts (1)
  • addressBookSlice (28-97)
src/components/Text/Text.tsx (1)
  • Text (19-83)
src/components/Modals/Send/AddressBook/ConfirmDelete.tsx (1)
  • ConfirmDelete (19-51)
src/components/Modals/Send/views/Confirm.tsx (6)
src/state/store.ts (1)
  • useAppSelector (143-143)
src/state/slices/addressBookSlice/selectors.ts (2)
  • selectExternalAddressBookEntryByAddress (128-143)
  • selectInternalAccountIdByAddress (89-122)
src/state/slices/portfolioSlice/selectors.ts (1)
  • selectPortfolioAccountMetadata (109-117)
src/lib/utils/utxo.ts (1)
  • isUtxoAccountId (24-25)
src/state/slices/portfolioSlice/utils/index.ts (1)
  • accountIdToLabel (80-119)
src/components/InlineCopyButton.tsx (1)
  • InlineCopyButton (19-64)
🔇 Additional comments (11)
src/state/slices/assetsSlice/types.ts (1)

1-1: LGTM! Import path improvement.

Updating the import from the internal dist/cjs path to the package root entry is a good refactoring that decouples the code from the package's internal build structure and follows best practices.

src/index.tsx (1)

26-26: Verify if this change is intentional.

Enabling react-scan appears unrelated to the address book feature described in the PR objectives. This looks like a development/debugging change that may have been committed accidentally.

src/state/slices/addressBookSlice/types.ts (1)

3-17: Well-structured discriminated union.

The type definition correctly uses discriminated unions to distinguish between internal and external address book entries, ensuring type safety when accessing the accountId property.

src/state/selectors.ts (1)

85-85: LGTM!

The new selector follows the established pattern and is properly typed via the ParamFilter type definition.

src/components/Modals/Send/SendCommon.tsx (1)

8-8: Verify if the new route should be added to initialEntries.

The new Amount route is added to the SendRoutes enum but is not included in the initialEntries array (lines 11-18). Confirm whether this is intentional or if the route should be added to the array.

src/state/slices/common-selectors.ts (1)

75-78: LGTM!

The selector correctly filters non-EVM account IDs and follows the same pattern as selectEvmAccountIds for consistency.

src/context/ModalProvider/types.ts (1)

16-16: LGTM!

The modal registration follows the established pattern for modal integration.

Also applies to: 47-47

src/state/slices/addressBookSlice/utils.ts (1)

22-31: EVM address deduplication logic is correct.

The cross-chain duplicate detection for EVM addresses correctly uses case-insensitive comparison, which is appropriate since EVM addresses are case-insensitive.

src/state/slices/addressBookSlice/addressBookSlice.ts (1)

32-62: LGTM: EVM fan‑out + duplicate protection are solid.

  • addAddress: dedup + EVM-wide insert behavior aligns with intended UX.
  • deleteAddress: cascades across EVM chains by normalized address.

No blockers from me.

Also applies to: 63-91

src/components/Modals/Send/views/SendAmountDetails.tsx (1)

205-229: The review comment's concern about error handling is based on a misunderstanding of the actual function behavior. parseAddressInputWithChainId does not throw errors—it handles all error cases internally and returns safe values, including { address: '', vanityAddress: '', chainId } when address validation fails. This is explicitly documented in the reference implementation (Address.tsx, line 160): "this does not throw, everything inside is handled."

However, the suggestion to add try-finally remains architecturally valid:

  1. Coding guidelines alignment: The project guidelines state "ALWAYS wrap async operations in try-catch blocks," which suggests defensive programming as a general pattern.

  2. Consistency concern: There's inconsistency in the codebase—SharedRecipientAddress.tsx includes try-catch around the same function call, while Address.tsx (the reference implementation) does not.

  3. Risk mitigation: While the primary function is safe, the helper functions it awaits internally could theoretically throw uncaught errors, leaving setIsValidating in a stuck state.

Before applying this refactor, verify with the team whether:

  • The architecture intentionally trusts parseAddressInputWithChainId to never throw (as Address.tsx suggests), or
  • Defensive try-catch should be enforced consistently across all call sites.
src/components/Modals/Send/AddressInput/AddressInput.tsx (1)

145-149: Verify focus management doesn't conflict with other interactions.

The effect on lines 145-149 programmatically focuses the input whenever isFocused is true and the input isn't already focused. This could interfere with user interactions (e.g., clicking other elements, using keyboard navigation) if another component or user action changes focus after isFocused becomes true.

Ensure this doesn't cause focus stealing during:

  • Modal transitions or overlays (e.g., AddressBookSaveModal)
  • Keyboard navigation between form fields
  • QR code scanning flow

If issues arise, consider guarding the focus call with additional conditions (e.g., checking if the modal is the active element).

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/components/Modals/Send/AddressInput/AddressInput.tsx (1)

79-79: Strengthen type safety for form values.

The type assertion as Partial<SendInput> bypasses TypeScript's type checking. Provide explicit field names to useWatch for type safety.

Apply this diff:

-  const { vanityAddress, input: value } = useWatch() as Partial<SendInput>
+  const vanityAddress = useWatch<SendInput, SendFormFields.VanityAddress>({ 
+    name: SendFormFields.VanityAddress 
+  })
+  const value = useWatch<SendInput, SendFormFields.Input>({ 
+    name: SendFormFields.Input 
+  })

This ensures type safety and aligns with the pattern used elsewhere. As per coding guidelines.

🧹 Nitpick comments (3)
src/components/Modals/Send/AddressBook/AddressBook.tsx (2)

50-53: Strengthen type safety for useWatch call.

The type assertion as string bypasses TypeScript checking. Provide an explicit generic to useWatch for better type safety.

Apply this diff:

-  const input = useWatch({
-    control,
-    name: SendFormFields.Input,
-  }) as string
+  const input = useWatch<SendInput, SendFormFields.Input>({
+    control,
+    name: SendFormFields.Input,
+  })

89-93: Consider adding a clarifying comment for the filtering logic.

The conditional logic determining when to show full entries vs. search results is non-obvious. A brief comment explaining the intent would help future maintainers.

+  // Show full entries when: address selected, no input yet, or valid address entered
+  // Show filtered search results when: input exists but has validation errors
   const entries = useMemo(() => {
     if (selectedEntry || !input || (input && !addressError)) return addressBookEntries
 
     return addressBookSearchEntries
   }, [selectedEntry, addressBookEntries, addressBookSearchEntries, input, addressError])
src/components/Modals/Send/AddressInput/AddressInput.tsx (1)

212-326: Consider extracting render modes into separate components.

The renderController callback contains three distinct rendering modes (~115 lines). Extracting each mode into a dedicated sub-component would improve readability and testability.

For example:

  • EditableAddressInput for lines 218-248
  • AddressBookEntryDisplay for lines 250-284
  • CompactAddressDisplay for lines 286-325

Based on learnings.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between a73e838 and c58d3e8.

📒 Files selected for processing (2)
  • src/components/Modals/Send/AddressBook/AddressBook.tsx (1 hunks)
  • src/components/Modals/Send/AddressInput/AddressInput.tsx (3 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/error-handling.mdc)

**/*.{ts,tsx}: ALWAYS use Result<T, E> pattern for error handling in swappers and APIs
ALWAYS use Ok() and Err() from @sniptt/monads for monadic error handling
ALWAYS use custom error classes from @shapeshiftoss/errors
ALWAYS provide meaningful error codes for internationalization
ALWAYS include relevant details in error objects
ALWAYS wrap async operations in try-catch blocks
ALWAYS use AsyncResultOf utility for converting promises to Results
ALWAYS provide fallback error handling
ALWAYS use timeoutMonadic for API calls
ALWAYS provide appropriate timeout values for API calls
ALWAYS handle timeout errors gracefully
ALWAYS validate inputs before processing
ALWAYS provide clear validation error messages
ALWAYS use early returns for validation failures
ALWAYS log errors for debugging
ALWAYS use structured logging for errors
ALWAYS include relevant context in error logs
Throwing errors instead of using monadic patterns is an anti-pattern
Missing try-catch blocks for async operations is an anti-pattern
Generic error messages without context are an anti-pattern
Not handling specific error types is an anti-pattern
Missing timeout handling is an anti-pattern
No input validation is an anti-pattern
Poor error logging is an anti-pattern
Using any for error types is an anti-pattern
Missing error codes for internationalization is an anti-pattern
No fallback error handling is an anti-pattern
Console.error without structured logging is an anti-pattern

**/*.{ts,tsx}: ALWAYS use camelCase for variables, functions, and methods
ALWAYS use descriptive names that explain the purpose for variables and functions
ALWAYS use verb prefixes for functions that perform actions
ALWAYS use PascalCase for types, interfaces, and enums
ALWAYS use descriptive names that indicate the structure for types, interfaces, and enums
ALWAYS use suffixes like Props, State, Config, Type when appropriate for types and interfaces
ALWAYS use UPPER_SNAKE_CASE for constants and configuration values
ALWAYS use d...

Files:

  • src/components/Modals/Send/AddressInput/AddressInput.tsx
  • src/components/Modals/Send/AddressBook/AddressBook.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/error-handling.mdc)

**/*.tsx: ALWAYS wrap components in error boundaries
ALWAYS provide user-friendly fallback components in error boundaries
ALWAYS log errors for debugging in error boundaries
ALWAYS use useErrorToast hook for displaying errors
ALWAYS provide translated error messages in error toasts
ALWAYS handle different error types appropriately in error toasts
Missing error boundaries in React components is an anti-pattern

**/*.tsx: ALWAYS use PascalCase for React component names
ALWAYS use descriptive names that indicate the component's purpose
ALWAYS match the component name to the file name
Flag components without PascalCase
Flag default exports for components

Files:

  • src/components/Modals/Send/AddressInput/AddressInput.tsx
  • src/components/Modals/Send/AddressBook/AddressBook.tsx
**/*

📄 CodeRabbit inference engine (.cursor/rules/naming-conventions.mdc)

**/*: ALWAYS use appropriate file extensions
Flag files without kebab-case

Files:

  • src/components/Modals/Send/AddressInput/AddressInput.tsx
  • src/components/Modals/Send/AddressBook/AddressBook.tsx
**/*.{tsx,jsx}

📄 CodeRabbit inference engine (.cursor/rules/react-best-practices.mdc)

**/*.{tsx,jsx}: ALWAYS use useMemo for expensive computations, object/array creations, and filtered data
ALWAYS use useMemo for derived values and computed properties
ALWAYS use useMemo for conditional values and simple transformations
ALWAYS use useCallback for event handlers and functions passed as props
ALWAYS use useCallback for any function that could be passed as a prop or dependency
ALWAYS include all dependencies in useEffect, useMemo, useCallback dependency arrays
NEVER use // eslint-disable-next-line react-hooks/exhaustive-deps unless absolutely necessary
ALWAYS explain why dependencies are excluded if using eslint disable
ALWAYS use named exports for components
NEVER use default exports for components
KEEP component files under 200 lines when possible
BREAK DOWN large components into smaller, reusable pieces
EXTRACT complex logic into custom hooks
USE local state for component-level state
LIFT state up when needed across multiple components
USE Context for avoiding prop drilling
ALWAYS wrap components in error boundaries for production
ALWAYS handle async errors properly
ALWAYS provide user-friendly error messages
ALWAYS use virtualization for lists with 100+ items
ALWAYS implement proper key props for list items
ALWAYS lazy load heavy components
ALWAYS use React.lazy for code splitting
Components receiving props are wrapped with memo

Files:

  • src/components/Modals/Send/AddressInput/AddressInput.tsx
  • src/components/Modals/Send/AddressBook/AddressBook.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/react-best-practices.mdc)

USE Redux only for global state shared across multiple places

Files:

  • src/components/Modals/Send/AddressInput/AddressInput.tsx
  • src/components/Modals/Send/AddressBook/AddressBook.tsx
🧠 Learnings (1)
📓 Common learnings
Learnt from: NeOMakinG
PR: shapeshift/web#10323
File: src/pages/RFOX/components/Stake/components/StakeSummary.tsx:112-114
Timestamp: 2025-08-22T13:00:44.879Z
Learning: NeOMakinG prefers to keep PR changes minimal and focused on the core objectives, avoiding cosmetic or defensive code improvements that aren't directly related to the PR scope, even when they would improve robustness.
🧬 Code graph analysis (2)
src/components/Modals/Send/AddressInput/AddressInput.tsx (6)
src/components/Modals/Send/Form.tsx (1)
  • SendInput (46-64)
src/state/store.ts (1)
  • useAppSelector (143-143)
src/state/slices/addressBookSlice/selectors.ts (2)
  • selectExternalAddressBookEntryByAddress (128-143)
  • selectInternalAccountIdByAddress (89-122)
src/state/slices/portfolioSlice/selectors.ts (1)
  • selectPortfolioAccountMetadata (109-117)
src/lib/utils/utxo.ts (1)
  • isUtxoAccountId (24-25)
src/state/slices/portfolioSlice/utils/index.ts (1)
  • accountIdToLabel (80-119)
src/components/Modals/Send/AddressBook/AddressBook.tsx (6)
src/state/store.ts (2)
  • useAppDispatch (151-151)
  • useAppSelector (143-143)
src/state/slices/addressBookSlice/types.ts (1)
  • AddressBookEntry (3-17)
src/state/slices/addressBookSlice/selectors.ts (2)
  • selectAddressBookEntriesByChainId (20-28)
  • selectAddressBookEntriesBySearchQuery (30-46)
src/state/slices/addressBookSlice/addressBookSlice.ts (1)
  • addressBookSlice (28-97)
src/components/Text/Text.tsx (1)
  • Text (19-83)
src/components/Modals/Send/AddressBook/ConfirmDelete.tsx (1)
  • ConfirmDelete (19-51)
🔇 Additional comments (4)
src/components/Modals/Send/AddressBook/AddressBook.tsx (1)

95-155: LGTM!

The avatar generation, button rendering, and JSX structure are well-implemented with proper memoization and clean patterns.

src/components/Modals/Send/AddressInput/AddressInput.tsx (3)

82-126: LGTM!

The address book integration is well-structured with proper memoization, correct selector usage, and thoughtful fallback handling for internal account labels.


139-184: LGTM!

The focus management logic and event handlers are well-implemented with proper memoization and clear intent. The setTimeout trick in handlePaste is appropriately documented.


186-210: LGTM!

The ENS/vanity address rendering logic is clean and properly memoized with correct dependencies.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
src/components/Modals/Send/views/Address.tsx (1)

108-117: Align save-button predicates with AddressInput.

This includes internalAccountId in the condition, while AddressInput suppresses Save for internal accounts. Drop internalAccountId here for consistency.

-        (!isInAddressBook || internalAccountId) &&
+        (!isInAddressBook) &&

Please confirm if internal accounts should be savable and update both places accordingly.

🧹 Nitpick comments (9)
src/components/CryptoFiatInput/CryptoFiatInput.tsx (2)

137-137: Consider making padding explicit for clarity.

The combination of p={1} and px={2} works (resulting in py={1} and px={2}), but using py={1} px={2} would make the intent more explicit and improve maintainability.

Apply this diff to clarify the padding intent:

-        <Button variant='ghost' size='sm' p={1} minW='auto' h='auto' px={2}>
+        <Button variant='ghost' size='sm' py={1} px={2} minW='auto' h='auto'>

137-146: LGTM! Consider adding an accessibility label.

The refactor successfully consolidates the toggle UI into a single Button component, improving the visual cohesion and click target area. The implementation is clean and aligns with the PR's UI consolidation objectives.

Optional: For improved accessibility, consider adding an aria-label to help screen reader users understand the button's purpose:

-        <Button variant='ghost' size='sm' p={1} minW='auto' h='auto' px={2}>
+        <Button 
+          variant='ghost' 
+          size='sm' 
+          p={1} 
+          minW='auto' 
+          h='auto' 
+          px={2}
+          aria-label={isFiat ? 'Switch to crypto amount' : 'Switch to fiat amount'}
+        >
src/assets/translations/en/main.json (1)

1449-1460: Minor copy: consider plural “addresses”.

Change “No address found.” to “No addresses found.” for empty states. Non-blocking.

Based on learnings

src/components/Modals/Send/views/SendAmountDetails.tsx (1)

140-171: DRY up duplicated address validation.

The validateAddress logic duplicates Address.tsx. Extract to a shared hook/helper (e.g., useValidateSendAddress) to keep behavior in one place.

src/components/Modals/Send/AddressBook/AddressBook.tsx (2)

60-63: Normalize EVM address comparison.

Use case-insensitive comparison for EVM addresses to avoid false negatives when matching the input against entries.

-  return addressBookEntries?.find(entry => entry.isExternal and entry.address === input)
+  return addressBookEntries?.find(entry =>
+    entry.isExternal && entry.address.toLowerCase() === input.toLowerCase()
+  )

146-153: Clear delete selection on modal close.

Avoid retaining stale selectedDeleteEntry after closing ConfirmDelete.

-      {selectedDeleteEntry && (
+      {selectedDeleteEntry && (
         <ConfirmDelete
           isOpen={isOpen}
-          onDelete={handleDelete(selectedDeleteEntry)}
-          onClose={onClose}
+          onDelete={handleDelete(selectedDeleteEntry)}
+          onClose={() => { setSelectedDeleteEntry(null); onClose() }}
           entryName={selectedDeleteEntry.label}
         />
       )}
src/components/Modals/Send/views/Address.tsx (1)

140-176: Deduplicate address validation logic.

Same validateAddress implementation exists in SendAmountDetails.tsx. Extract to a shared utility/hook to avoid divergence.

src/components/Modals/Send/AddressInput/AddressInput.tsx (2)

83-88: Replace unsafe cast with typed useWatch.

Avoid as Partial<SendInput>. Watch specific fields with generics for type safety.

-  const { vanityAddress, input: value, to, assetId } = useWatch() as Partial<SendInput>
+  const vanityAddress = useWatch<SendInput, SendFormFields.VanityAddress>({ name: SendFormFields.VanityAddress })
+  const value = useWatch<SendInput, SendFormFields.Input>({ name: SendFormFields.Input })
+  const to = useWatch<SendInput, SendFormFields.To>({ name: SendFormFields.To })
+  const assetId = useWatch<SendInput, SendFormFields.AssetId>({ name: SendFormFields.AssetId })

As per coding guidelines.


46-51: Remove or use the unused ‘pe’ prop.

pe is declared but not used. Either forward it to a container (e.g., InputGroup) or drop it.

-  pe?: SpaceProps['pe']
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between c58d3e8 and 07894f5.

📒 Files selected for processing (15)
  • .env.development (1 hunks)
  • src/assets/translations/en/main.json (3 hunks)
  • src/components/CryptoFiatInput/CryptoFiatInput.tsx (1 hunks)
  • src/components/Modals/QrCode/QrCode.tsx (1 hunks)
  • src/components/Modals/Send/AddressBook/AddressBook.tsx (1 hunks)
  • src/components/Modals/Send/AddressBookSaveModal.tsx (1 hunks)
  • src/components/Modals/Send/AddressInput/AddressInput.tsx (3 hunks)
  • src/components/Modals/Send/Form.tsx (2 hunks)
  • src/components/Modals/Send/views/Address.tsx (9 hunks)
  • src/components/Modals/Send/views/SendAmountDetails.tsx (4 hunks)
  • src/config.ts (1 hunks)
  • src/state/migrations/index.ts (2 hunks)
  • src/state/slices/common-selectors.ts (1 hunks)
  • src/state/slices/preferencesSlice/preferencesSlice.ts (2 hunks)
  • src/test/mocks/store.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/components/Modals/Send/AddressBookSaveModal.tsx
  • src/state/slices/common-selectors.ts
  • src/test/mocks/store.ts
  • src/state/migrations/index.ts
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/error-handling.mdc)

**/*.{ts,tsx}: ALWAYS use Result<T, E> pattern for error handling in swappers and APIs
ALWAYS use Ok() and Err() from @sniptt/monads for monadic error handling
ALWAYS use custom error classes from @shapeshiftoss/errors
ALWAYS provide meaningful error codes for internationalization
ALWAYS include relevant details in error objects
ALWAYS wrap async operations in try-catch blocks
ALWAYS use AsyncResultOf utility for converting promises to Results
ALWAYS provide fallback error handling
ALWAYS use timeoutMonadic for API calls
ALWAYS provide appropriate timeout values for API calls
ALWAYS handle timeout errors gracefully
ALWAYS validate inputs before processing
ALWAYS provide clear validation error messages
ALWAYS use early returns for validation failures
ALWAYS log errors for debugging
ALWAYS use structured logging for errors
ALWAYS include relevant context in error logs
Throwing errors instead of using monadic patterns is an anti-pattern
Missing try-catch blocks for async operations is an anti-pattern
Generic error messages without context are an anti-pattern
Not handling specific error types is an anti-pattern
Missing timeout handling is an anti-pattern
No input validation is an anti-pattern
Poor error logging is an anti-pattern
Using any for error types is an anti-pattern
Missing error codes for internationalization is an anti-pattern
No fallback error handling is an anti-pattern
Console.error without structured logging is an anti-pattern

**/*.{ts,tsx}: ALWAYS use camelCase for variables, functions, and methods
ALWAYS use descriptive names that explain the purpose for variables and functions
ALWAYS use verb prefixes for functions that perform actions
ALWAYS use PascalCase for types, interfaces, and enums
ALWAYS use descriptive names that indicate the structure for types, interfaces, and enums
ALWAYS use suffixes like Props, State, Config, Type when appropriate for types and interfaces
ALWAYS use UPPER_SNAKE_CASE for constants and configuration values
ALWAYS use d...

Files:

  • src/components/Modals/Send/Form.tsx
  • src/components/Modals/QrCode/QrCode.tsx
  • src/state/slices/preferencesSlice/preferencesSlice.ts
  • src/components/Modals/Send/views/Address.tsx
  • src/components/Modals/Send/AddressBook/AddressBook.tsx
  • src/config.ts
  • src/components/Modals/Send/AddressInput/AddressInput.tsx
  • src/components/CryptoFiatInput/CryptoFiatInput.tsx
  • src/components/Modals/Send/views/SendAmountDetails.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/error-handling.mdc)

**/*.tsx: ALWAYS wrap components in error boundaries
ALWAYS provide user-friendly fallback components in error boundaries
ALWAYS log errors for debugging in error boundaries
ALWAYS use useErrorToast hook for displaying errors
ALWAYS provide translated error messages in error toasts
ALWAYS handle different error types appropriately in error toasts
Missing error boundaries in React components is an anti-pattern

**/*.tsx: ALWAYS use PascalCase for React component names
ALWAYS use descriptive names that indicate the component's purpose
ALWAYS match the component name to the file name
Flag components without PascalCase
Flag default exports for components

Files:

  • src/components/Modals/Send/Form.tsx
  • src/components/Modals/QrCode/QrCode.tsx
  • src/components/Modals/Send/views/Address.tsx
  • src/components/Modals/Send/AddressBook/AddressBook.tsx
  • src/components/Modals/Send/AddressInput/AddressInput.tsx
  • src/components/CryptoFiatInput/CryptoFiatInput.tsx
  • src/components/Modals/Send/views/SendAmountDetails.tsx
**/*

📄 CodeRabbit inference engine (.cursor/rules/naming-conventions.mdc)

**/*: ALWAYS use appropriate file extensions
Flag files without kebab-case

Files:

  • src/components/Modals/Send/Form.tsx
  • src/components/Modals/QrCode/QrCode.tsx
  • src/state/slices/preferencesSlice/preferencesSlice.ts
  • src/components/Modals/Send/views/Address.tsx
  • src/components/Modals/Send/AddressBook/AddressBook.tsx
  • src/assets/translations/en/main.json
  • src/config.ts
  • src/components/Modals/Send/AddressInput/AddressInput.tsx
  • src/components/CryptoFiatInput/CryptoFiatInput.tsx
  • src/components/Modals/Send/views/SendAmountDetails.tsx
**/*.{tsx,jsx}

📄 CodeRabbit inference engine (.cursor/rules/react-best-practices.mdc)

**/*.{tsx,jsx}: ALWAYS use useMemo for expensive computations, object/array creations, and filtered data
ALWAYS use useMemo for derived values and computed properties
ALWAYS use useMemo for conditional values and simple transformations
ALWAYS use useCallback for event handlers and functions passed as props
ALWAYS use useCallback for any function that could be passed as a prop or dependency
ALWAYS include all dependencies in useEffect, useMemo, useCallback dependency arrays
NEVER use // eslint-disable-next-line react-hooks/exhaustive-deps unless absolutely necessary
ALWAYS explain why dependencies are excluded if using eslint disable
ALWAYS use named exports for components
NEVER use default exports for components
KEEP component files under 200 lines when possible
BREAK DOWN large components into smaller, reusable pieces
EXTRACT complex logic into custom hooks
USE local state for component-level state
LIFT state up when needed across multiple components
USE Context for avoiding prop drilling
ALWAYS wrap components in error boundaries for production
ALWAYS handle async errors properly
ALWAYS provide user-friendly error messages
ALWAYS use virtualization for lists with 100+ items
ALWAYS implement proper key props for list items
ALWAYS lazy load heavy components
ALWAYS use React.lazy for code splitting
Components receiving props are wrapped with memo

Files:

  • src/components/Modals/Send/Form.tsx
  • src/components/Modals/QrCode/QrCode.tsx
  • src/components/Modals/Send/views/Address.tsx
  • src/components/Modals/Send/AddressBook/AddressBook.tsx
  • src/components/Modals/Send/AddressInput/AddressInput.tsx
  • src/components/CryptoFiatInput/CryptoFiatInput.tsx
  • src/components/Modals/Send/views/SendAmountDetails.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/react-best-practices.mdc)

USE Redux only for global state shared across multiple places

Files:

  • src/components/Modals/Send/Form.tsx
  • src/components/Modals/QrCode/QrCode.tsx
  • src/state/slices/preferencesSlice/preferencesSlice.ts
  • src/components/Modals/Send/views/Address.tsx
  • src/components/Modals/Send/AddressBook/AddressBook.tsx
  • src/config.ts
  • src/components/Modals/Send/AddressInput/AddressInput.tsx
  • src/components/CryptoFiatInput/CryptoFiatInput.tsx
  • src/components/Modals/Send/views/SendAmountDetails.tsx
🧠 Learnings (12)
📓 Common learnings
Learnt from: NeOMakinG
PR: shapeshift/web#10323
File: src/pages/RFOX/components/Stake/components/StakeSummary.tsx:112-114
Timestamp: 2025-08-22T13:00:44.879Z
Learning: NeOMakinG prefers to keep PR changes minimal and focused on the core objectives, avoiding cosmetic or defensive code improvements that aren't directly related to the PR scope, even when they would improve robustness.
📚 Learning: 2025-09-08T15:53:09.362Z
Learnt from: gomesalexandre
PR: shapeshift/web#10442
File: src/components/TradeAssetSearch/components/GroupedAssetList/GroupedAssetList.tsx:34-35
Timestamp: 2025-09-08T15:53:09.362Z
Learning: In DefaultAssetList.tsx, the GroupedAssetList component already receives the activeChainId prop correctly on line ~58, contrary to automated analysis that may flag it as missing.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-10-21T17:11:18.087Z
Learnt from: gomesalexandre
PR: shapeshift/web#10871
File: src/components/Modals/Send/hooks/useSendDetails/useSendDetails.tsx:426-428
Timestamp: 2025-10-21T17:11:18.087Z
Learning: In src/components/Modals/Send/hooks/useSendDetails/useSendDetails.tsx, within the handleInputChange function, use .toFixed() without arguments (not .toString()) when converting BigNumber amounts for input field synchronization. This avoids exponential notation in the input while preserving precision for presentational components like <Amount.Crypto /> and <Amount.Fiat /> to format appropriately.

Applied to files:

  • src/components/Modals/Send/views/SendAmountDetails.tsx
📚 Learning: 2025-08-08T15:00:49.887Z
Learnt from: NeOMakinG
PR: shapeshift/web#10231
File: src/components/AssetSearch/components/AssetList.tsx:2-2
Timestamp: 2025-08-08T15:00:49.887Z
Learning: Project shapeshift/web: NeOMakinG prefers avoiding minor a11y/UI nitpicks (e.g., adding aria-hidden to decorative icons in empty states like src/components/AssetSearch/components/AssetList.tsx) within feature PRs; defer such suggestions to a follow-up instead of blocking the PR.

Applied to files:

  • .env.development
📚 Learning: 2025-08-27T09:47:06.275Z
Learnt from: NeOMakinG
PR: shapeshift/web#10323
File: src/components/ButtonWalletPredicate/ButtonWalletPredicate.tsx:7-7
Timestamp: 2025-08-27T09:47:06.275Z
Learning: In shapeshift/web project, NeOMakinG consistently prefers to defer UI/UX improvements and refactoring work (like the Drawer.Close hack fix in ButtonWalletPredicate.tsx) to follow-up PRs rather than expanding the scope of feature PRs, even when the improvements would enhance robustness.

Applied to files:

  • .env.development
📚 Learning: 2025-08-22T13:00:44.879Z
Learnt from: NeOMakinG
PR: shapeshift/web#10323
File: src/pages/RFOX/components/Stake/components/StakeSummary.tsx:112-114
Timestamp: 2025-08-22T13:00:44.879Z
Learning: NeOMakinG prefers to keep PR changes minimal and focused on the core objectives, avoiding cosmetic or defensive code improvements that aren't directly related to the PR scope, even when they would improve robustness.

Applied to files:

  • .env.development
📚 Learning: 2025-08-08T11:41:22.794Z
Learnt from: NeOMakinG
PR: shapeshift/web#10234
File: src/components/MultiHopTrade/hooks/useGetTradeQuotes/hooks/useTrackTradeQuotes.ts:42-86
Timestamp: 2025-08-08T11:41:22.794Z
Learning: NeOMakinG prefers not to include refactors in move-only PRs; such suggestions should be deferred to follow-up issues instead of being applied within the same PR.

Applied to files:

  • .env.development
📚 Learning: 2025-08-25T12:59:43.842Z
Learnt from: NeOMakinG
PR: shapeshift/web#10323
File: src/pages/Fox/components/RFOXSection.tsx:234-248
Timestamp: 2025-08-25T12:59:43.842Z
Learning: NeOMakinG considers suggestions to refactor working modal state management from useState to useDisclosure as too nitpicky, preferring to keep such cosmetic code improvements out of feature PRs unless they address actual issues.

Applied to files:

  • .env.development
📚 Learning: 2025-08-08T15:00:22.321Z
Learnt from: NeOMakinG
PR: shapeshift/web#10231
File: src/components/MultiHopTrade/components/TradeInput/components/HighlightedTokens.tsx:14-14
Timestamp: 2025-08-08T15:00:22.321Z
Learning: In shapeshift/web reviews for NeOMakinG, avoid nitpicks to change deep-relative imports to '@/…' alias paths within feature/non-refactor PRs; defer such style-only changes to a dedicated follow-up refactor unless they fix an issue.

Applied to files:

  • .env.development
📚 Learning: 2025-08-27T09:51:36.901Z
Learnt from: NeOMakinG
PR: shapeshift/web#10376
File: packages/contracts/src/viemClient.ts:39-40
Timestamp: 2025-08-27T09:51:36.901Z
Learning: NeOMakinG confirmed that issues flagged by CodeRabbit that predate the PR changes and are unrelated to the PR's core objective should be addressed separately, not within the current PR scope.

Applied to files:

  • .env.development
📚 Learning: 2025-09-02T12:38:46.940Z
Learnt from: NeOMakinG
PR: shapeshift/web#10380
File: src/components/Table/Table.theme.ts:177-180
Timestamp: 2025-09-02T12:38:46.940Z
Learning: NeOMakinG prefers to defer technical debt and CSS correctness issues (like improper hover selectors) to follow-up PRs when the current PR is already large and focused on major feature implementation, even when the issues are valid from a usability/technical perspective.

Applied to files:

  • .env.development
📚 Learning: 2025-09-17T22:40:30.149Z
Learnt from: gomesalexandre
PR: shapeshift/web#10569
File: src/plugins/walletConnectToDapps/components/WalletConnectSigningModal/WalletConnectModalSigningFooter.tsx:121-129
Timestamp: 2025-09-17T22:40:30.149Z
Learning: gomesalexandre maintains strict scope discipline even for style/UI PRs in shapeshift/web, declining functionally correct UX improvements (like keeping Cancel button enabled during gas simulation loading) when they fall outside the PR's stated styling objectives, demonstrating his consistent pattern of deferring valid but tangential improvements to separate efforts.

Applied to files:

  • .env.development
🧬 Code graph analysis (6)
src/state/slices/preferencesSlice/preferencesSlice.ts (1)
src/config.ts (1)
  • getConfig (210-212)
src/components/Modals/Send/views/Address.tsx (4)
src/state/store.ts (1)
  • useAppSelector (143-143)
src/state/slices/addressBookSlice/selectors.ts (2)
  • selectIsAddressInAddressBook (52-82)
  • selectInternalAccountIdByAddress (89-122)
packages/caip/src/constants.ts (1)
  • ethChainId (60-60)
src/components/Modals/Send/AddressBook/AddressBook.tsx (1)
  • AddressBook (34-156)
src/components/Modals/Send/AddressBook/AddressBook.tsx (7)
src/state/store.ts (2)
  • useAppDispatch (151-151)
  • useAppSelector (143-143)
src/state/slices/addressBookSlice/types.ts (1)
  • AddressBookEntry (3-17)
src/state/slices/addressBookSlice/selectors.ts (2)
  • selectAddressBookEntriesByChainId (20-28)
  • selectAddressBookEntriesBySearchQuery (30-46)
src/state/slices/addressBookSlice/addressBookSlice.ts (1)
  • addressBookSlice (28-97)
src/components/Text/Text.tsx (1)
  • Text (19-83)
src/components/Modals/Send/AddressBook/AddressBookEntryButton.tsx (1)
  • AddressBookEntryButton (85-85)
src/components/Modals/Send/AddressBook/ConfirmDelete.tsx (1)
  • ConfirmDelete (19-51)
src/components/Modals/Send/AddressInput/AddressInput.tsx (8)
src/components/Modals/Send/Form.tsx (1)
  • SendInput (50-68)
src/hooks/useModal/useModal.tsx (1)
  • useModal (6-24)
src/state/store.ts (1)
  • useAppSelector (143-143)
src/state/slices/assetsSlice/selectors.ts (1)
  • selectAssetById (16-20)
src/state/slices/addressBookSlice/selectors.ts (2)
  • selectExternalAddressBookEntryByAddress (128-143)
  • selectInternalAccountIdByAddress (89-122)
src/state/slices/portfolioSlice/selectors.ts (1)
  • selectPortfolioAccountMetadata (109-117)
src/lib/utils/utxo.ts (1)
  • isUtxoAccountId (24-25)
src/state/slices/portfolioSlice/utils/index.ts (1)
  • accountIdToLabel (80-119)
src/components/CryptoFiatInput/CryptoFiatInput.tsx (2)
src/components/Text/Text.tsx (1)
  • Text (19-83)
src/components/Amount/Amount.tsx (1)
  • Amount (21-42)
src/components/Modals/Send/views/SendAmountDetails.tsx (1)
src/components/Modals/Send/AddressInput/AddressInput.tsx (1)
  • AddressInput (61-390)
🪛 dotenv-linter (4.0.0)
.env.development

[warning] 9-9: [UnorderedKey] The VITE_FEATURE_ADDRESS_BOOK key should go before the VITE_FEATURE_THORCHAIN_TCY_ACTIVITY key

(UnorderedKey)

⏰ 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)
  • GitHub Check: Call / Static
🔇 Additional comments (7)
src/components/Modals/Send/Form.tsx (2)

215-236: LGTM! Navigation flow simplified correctly.

The removal of viewport-based conditional navigation in favor of always routing through SendRoutes.Address aligns with the PR objectives to unify desktop and mobile send flows. The dependency array has been correctly updated to remove isSmallerThanMd, and the use of requestAnimationFrame ensures navigation occurs after state updates complete.


248-298: LGTM! QR success handler navigation simplified correctly.

The navigation after successful QR code scanning now consistently routes to SendRoutes.Address regardless of viewport size, which aligns with the unified navigation flow. The dependency array has been correctly updated to remove isSmallerThanMd.

src/components/Modals/QrCode/QrCode.tsx (2)

4-4: LGTM! Routing simplification is clean.

The import of initialEntries from SendCommon successfully removes the responsive routing complexity while maintaining functionality. This aligns with the PR's goal of unifying the routing approach.


21-22: No defensive refactor needed—SendRoutes.Scan is guaranteed to exist in initialEntries.

SendRoutes.Scan is statically defined at index 2 in the initialEntries array, so indexOf() will always return a valid index. The code is correct as written.

src/assets/translations/en/main.json (1)

29-31: LGTM — common actions added.

The new common.save/common.delete keys align with new UI.

src/state/slices/preferencesSlice/preferencesSlice.ts (1)

95-96: Feature flag wiring looks good.

FeatureFlags.AddressBook and initial state sourcing from config are correct.

Also applies to: 221-222

src/config.ts (1)

197-198: Config validator added correctly.

VITE_FEATURE_ADDRESS_BOOK boolean with default false is properly validated.

Copy link
Contributor

@gomesalexandre gomesalexandre left a comment

Choose a reason for hiding this comment

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

Tested locally, see below - generally super happy with this however, just a bunch of smol things that make the experience meh (some of them to be fixed here, some of them TBD as follow-ups, and many which are really product oversights tbh, not really a you thing ser!)

https://jam.dev/c/b23464db-c9a0-4893-b01f-38f699570a2b

Going to stamp for the sake of progression since this is a large boi already.
Feel free to tackle whatever from the reviewsies at your convenience, then she's mergy /gnomes

  • First time pasting an "internal" addy, it's displayed as "..." for a few secs which seems borked

https://jam.dev/c/201ddea7-a8e0-4859-8b3f-1ff62abe1fa7

  • As previously mentioned, address book should merely be a mapping of CAIP-10 to label - as such, feels a bit weird I can see this guy labeled as "Account #0", but not rename it - feature would feel more complete if we already allowed renaming portfolio accounts, including in wallet page but happy to do this in a follow-up obviously!
Image

i.e here, should really be quite an easy one to follow-up with making this guy a rename-able text field

Image
  • Feels weird having to click out of input for ENS, feels like it should just automagically work without me having to click out?

https://jam.dev/c/3aafb9a6-843a-461a-84be-36b75a631e89

  • Feels like ENS should be pre-populated when saving?

https://jam.dev/c/68f3632e-69be-4929-bb03-215dc251632e

Also, feels weird to see ENS on initial new but not after clicking Save

Image
  • Can't see my portfolio addresses suggested anywhere here for cross-addies sends, assuming this is for a follow-up?
Image
  • Obviously not for this PR and not a you issue - from a product perspective just feels super hacked, like the data is here but isn't presented in the right/correct way. Not really sure I have enough brain cells to propose a better solution, but failed the jetlag litmus test fosho, doesn't feel nice or intuitive at all
Image
  • This guy would probably benefit from relevant chain icon or at the very least somin like "Account #0 - THORChain"
Image

@NeOMakinG NeOMakinG enabled auto-merge (squash) October 30, 2025 11:59
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/components/Modals/Send/views/Address.tsx (1)

88-102: Optional: Combine duplicate filter objects.

Both isInAddressBookFilter and internalAccountIdFilter compute the same object. You can extract it to a single memoized value to reduce duplication:

+ const addressLookupFilter = useMemo(
+   () => ({ accountAddress: address, chainId: asset?.chainId }),
+   [address, asset?.chainId],
+ )
- const isInAddressBookFilter = useMemo(
-   () => ({ accountAddress: address, chainId: asset?.chainId }),
-   [address, asset?.chainId],
- )
  const isInAddressBook = useAppSelector(state =>
-   selectIsAddressInAddressBook(state, isInAddressBookFilter),
+   selectIsAddressInAddressBook(state, addressLookupFilter),
  )

- const internalAccountIdFilter = useMemo(
-   () => ({ accountAddress: address, chainId: asset?.chainId }),
-   [address, asset?.chainId],
- )
  const internalAccountId = useAppSelector(state =>
-   selectInternalAccountIdByAddress(state, internalAccountIdFilter),
+   selectInternalAccountIdByAddress(state, addressLookupFilter),
  )
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 757a7dd and 07c3424.

📒 Files selected for processing (1)
  • src/components/Modals/Send/views/Address.tsx (9 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/error-handling.mdc)

**/*.{ts,tsx}: ALWAYS use Result<T, E> pattern for error handling in swappers and APIs
ALWAYS use Ok() and Err() from @sniptt/monads for monadic error handling
ALWAYS use custom error classes from @shapeshiftoss/errors
ALWAYS provide meaningful error codes for internationalization
ALWAYS include relevant details in error objects
ALWAYS wrap async operations in try-catch blocks
ALWAYS use AsyncResultOf utility for converting promises to Results
ALWAYS provide fallback error handling
ALWAYS use timeoutMonadic for API calls
ALWAYS provide appropriate timeout values for API calls
ALWAYS handle timeout errors gracefully
ALWAYS validate inputs before processing
ALWAYS provide clear validation error messages
ALWAYS use early returns for validation failures
ALWAYS log errors for debugging
ALWAYS use structured logging for errors
ALWAYS include relevant context in error logs
Throwing errors instead of using monadic patterns is an anti-pattern
Missing try-catch blocks for async operations is an anti-pattern
Generic error messages without context are an anti-pattern
Not handling specific error types is an anti-pattern
Missing timeout handling is an anti-pattern
No input validation is an anti-pattern
Poor error logging is an anti-pattern
Using any for error types is an anti-pattern
Missing error codes for internationalization is an anti-pattern
No fallback error handling is an anti-pattern
Console.error without structured logging is an anti-pattern

**/*.{ts,tsx}: ALWAYS use camelCase for variables, functions, and methods
ALWAYS use descriptive names that explain the purpose for variables and functions
ALWAYS use verb prefixes for functions that perform actions
ALWAYS use PascalCase for types, interfaces, and enums
ALWAYS use descriptive names that indicate the structure for types, interfaces, and enums
ALWAYS use suffixes like Props, State, Config, Type when appropriate for types and interfaces
ALWAYS use UPPER_SNAKE_CASE for constants and configuration values
ALWAYS use d...

Files:

  • src/components/Modals/Send/views/Address.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/error-handling.mdc)

**/*.tsx: ALWAYS wrap components in error boundaries
ALWAYS provide user-friendly fallback components in error boundaries
ALWAYS log errors for debugging in error boundaries
ALWAYS use useErrorToast hook for displaying errors
ALWAYS provide translated error messages in error toasts
ALWAYS handle different error types appropriately in error toasts
Missing error boundaries in React components is an anti-pattern

**/*.tsx: ALWAYS use PascalCase for React component names
ALWAYS use descriptive names that indicate the component's purpose
ALWAYS match the component name to the file name
Flag components without PascalCase
Flag default exports for components

Files:

  • src/components/Modals/Send/views/Address.tsx
**/*

📄 CodeRabbit inference engine (.cursor/rules/naming-conventions.mdc)

**/*: ALWAYS use appropriate file extensions
Flag files without kebab-case

Files:

  • src/components/Modals/Send/views/Address.tsx
**/*.{tsx,jsx}

📄 CodeRabbit inference engine (.cursor/rules/react-best-practices.mdc)

**/*.{tsx,jsx}: ALWAYS use useMemo for expensive computations, object/array creations, and filtered data
ALWAYS use useMemo for derived values and computed properties
ALWAYS use useMemo for conditional values and simple transformations
ALWAYS use useCallback for event handlers and functions passed as props
ALWAYS use useCallback for any function that could be passed as a prop or dependency
ALWAYS include all dependencies in useEffect, useMemo, useCallback dependency arrays
NEVER use // eslint-disable-next-line react-hooks/exhaustive-deps unless absolutely necessary
ALWAYS explain why dependencies are excluded if using eslint disable
ALWAYS use named exports for components
NEVER use default exports for components
KEEP component files under 200 lines when possible
BREAK DOWN large components into smaller, reusable pieces
EXTRACT complex logic into custom hooks
USE local state for component-level state
LIFT state up when needed across multiple components
USE Context for avoiding prop drilling
ALWAYS wrap components in error boundaries for production
ALWAYS handle async errors properly
ALWAYS provide user-friendly error messages
ALWAYS use virtualization for lists with 100+ items
ALWAYS implement proper key props for list items
ALWAYS lazy load heavy components
ALWAYS use React.lazy for code splitting
Components receiving props are wrapped with memo

Files:

  • src/components/Modals/Send/views/Address.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/react-best-practices.mdc)

USE Redux only for global state shared across multiple places

Files:

  • src/components/Modals/Send/views/Address.tsx
🧠 Learnings (38)
📓 Common learnings
Learnt from: NeOMakinG
PR: shapeshift/web#10231
File: src/components/AssetSearch/components/AssetList.tsx:2-2
Timestamp: 2025-08-08T15:00:49.887Z
Learning: Project shapeshift/web: NeOMakinG prefers avoiding minor a11y/UI nitpicks (e.g., adding aria-hidden to decorative icons in empty states like src/components/AssetSearch/components/AssetList.tsx) within feature PRs; defer such suggestions to a follow-up instead of blocking the PR.
Learnt from: NeOMakinG
PR: shapeshift/web#10323
File: src/components/ButtonWalletPredicate/ButtonWalletPredicate.tsx:7-7
Timestamp: 2025-08-27T09:47:06.275Z
Learning: In shapeshift/web project, NeOMakinG consistently prefers to defer UI/UX improvements and refactoring work (like the Drawer.Close hack fix in ButtonWalletPredicate.tsx) to follow-up PRs rather than expanding the scope of feature PRs, even when the improvements would enhance robustness.
Learnt from: premiumjibles
PR: shapeshift/web#10386
File: src/components/MultiHopTrade/components/VerifyAddresses/VerifyAddresses.tsx:272-294
Timestamp: 2025-08-29T07:07:49.332Z
Learning: In UTXO sell address verification flow in VerifyAddresses.tsx, the user wants address verification to be marked as "verified/complete" before starting the change address fetch, not after. The verification step and change address fetch should be treated as separate sequential operations in the UI flow.
Learnt from: NeOMakinG
PR: shapeshift/web#10323
File: src/pages/RFOX/components/Stake/components/StakeSummary.tsx:112-114
Timestamp: 2025-08-22T13:00:44.879Z
Learning: NeOMakinG prefers to keep PR changes minimal and focused on the core objectives, avoiding cosmetic or defensive code improvements that aren't directly related to the PR scope, even when they would improve robustness.
Learnt from: NeOMakinG
PR: shapeshift/web#10128
File: .cursor/rules/error-handling.mdc:266-274
Timestamp: 2025-07-29T10:35:22.059Z
Learning: NeOMakinG prefers less nitpicky suggestions on documentation and best practices files, finding overly detailed suggestions on minor implementation details (like console.error vs logger.error) too granular for cursor rules documentation.
Learnt from: NeOMakinG
PR: shapeshift/web#10380
File: src/pages/Dashboard/components/AccountList/AccountTable.tsx:60-0
Timestamp: 2025-09-02T08:34:08.157Z
Learning: NeOMakinG prefers code review comments to focus only on actual PR changes, not pre-existing code issues, unless there are critical security or correctness concerns directly related to the new functionality.
Learnt from: NeOMakinG
PR: shapeshift/web#10234
File: src/components/MultiHopTrade/hooks/useGetTradeQuotes/hooks/useTrackTradeQuotes.ts:42-86
Timestamp: 2025-08-08T11:41:22.794Z
Learning: NeOMakinG prefers not to include refactors in move-only PRs; such suggestions should be deferred to follow-up issues instead of being applied within the same PR.
Learnt from: NeOMakinG
PR: shapeshift/web#10380
File: src/components/Table/Table.theme.ts:177-180
Timestamp: 2025-09-02T12:38:46.940Z
Learning: NeOMakinG prefers to defer technical debt and CSS correctness issues (like improper hover selectors) to follow-up PRs when the current PR is already large and focused on major feature implementation, even when the issues are valid from a usability/technical perspective.
📚 Learning: 2025-10-21T17:11:18.087Z
Learnt from: gomesalexandre
PR: shapeshift/web#10871
File: src/components/Modals/Send/hooks/useSendDetails/useSendDetails.tsx:426-428
Timestamp: 2025-10-21T17:11:18.087Z
Learning: In src/components/Modals/Send/hooks/useSendDetails/useSendDetails.tsx, within the handleInputChange function, use .toFixed() without arguments (not .toString()) when converting BigNumber amounts for input field synchronization. This avoids exponential notation in the input while preserving precision for presentational components like <Amount.Crypto /> and <Amount.Fiat /> to format appropriately.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-08-29T07:07:49.332Z
Learnt from: premiumjibles
PR: shapeshift/web#10386
File: src/components/MultiHopTrade/components/VerifyAddresses/VerifyAddresses.tsx:272-294
Timestamp: 2025-08-29T07:07:49.332Z
Learning: In UTXO sell address verification flow in VerifyAddresses.tsx, the user wants address verification to be marked as "verified/complete" before starting the change address fetch, not after. The verification step and change address fetch should be treated as separate sequential operations in the UI flow.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-08-08T15:00:49.887Z
Learnt from: NeOMakinG
PR: shapeshift/web#10231
File: src/components/AssetSearch/components/AssetList.tsx:2-2
Timestamp: 2025-08-08T15:00:49.887Z
Learning: Project shapeshift/web: NeOMakinG prefers avoiding minor a11y/UI nitpicks (e.g., adding aria-hidden to decorative icons in empty states like src/components/AssetSearch/components/AssetList.tsx) within feature PRs; defer such suggestions to a follow-up instead of blocking the PR.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-08-03T22:09:37.542Z
Learnt from: CR
PR: shapeshift/web#0
File: .cursor/rules/error-handling.mdc:0-0
Timestamp: 2025-08-03T22:09:37.542Z
Learning: Applies to **/*.{ts,tsx} : ALWAYS validate inputs before processing

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-09-12T10:44:46.723Z
Learnt from: gomesalexandre
PR: shapeshift/web#10461
File: src/plugins/walletConnectToDapps/components/WalletConnectSigningModal/content/SendTransactionContent.tsx:0-0
Timestamp: 2025-09-12T10:44:46.723Z
Learning: gomesalexandre dismissed a clipboard error handling suggestion in PR #10461 for SendTransactionContent.tsx, demonstrating that the current navigator.clipboard.writeText implementation works as expected and preferring to keep it simple without additional try/catch error handling.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-08-03T22:09:37.542Z
Learnt from: CR
PR: shapeshift/web#0
File: .cursor/rules/error-handling.mdc:0-0
Timestamp: 2025-08-03T22:09:37.542Z
Learning: Applies to **/*.{ts,tsx} : No input validation is an anti-pattern

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-08-08T11:41:36.971Z
Learnt from: NeOMakinG
PR: shapeshift/web#10234
File: src/components/MultiHopTrade/hooks/useGetTradeQuotes/hooks/useTrackTradeQuotes.ts:88-109
Timestamp: 2025-08-08T11:41:36.971Z
Learning: In MultiHopTrade Confirm flow (src/components/MultiHopTrade/components/TradeConfirm/TradeConfirm.tsx), the Confirm route does not remount; navigating away goes to the swapper input page. Therefore, persistent deduplication across remounts for quote tracking is unnecessary; a ref-based single-mount dedupe is sufficient.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-09-08T15:53:09.362Z
Learnt from: gomesalexandre
PR: shapeshift/web#10442
File: src/components/TradeAssetSearch/components/GroupedAssetList/GroupedAssetList.tsx:34-35
Timestamp: 2025-09-08T15:53:09.362Z
Learning: In DefaultAssetList.tsx, the GroupedAssetList component already receives the activeChainId prop correctly on line ~58, contrary to automated analysis that may flag it as missing.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-08-15T07:51:16.374Z
Learnt from: gomesalexandre
PR: shapeshift/web#10278
File: src/components/AssetHeader/hooks/useQuickBuy.ts:97-99
Timestamp: 2025-08-15T07:51:16.374Z
Learning: The selectPortfolioUserCurrencyBalanceByAssetId selector in src/state/slices/portfolioSlice/selectors.ts expects a filter object with an assetId property, not a raw AssetId string. The selector signature is (state: ReduxState, filter) where filter should have an assetId property. This pattern is consistent across portfolio selectors that use selectAssetIdParamFromFilter. Passing a filter object like { assetId: someAssetId } is the correct usage pattern.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-07-24T09:43:11.699Z
Learnt from: CR
PR: shapeshift/web#0
File: .cursor/rules/swapper.mdc:0-0
Timestamp: 2025-07-24T09:43:11.699Z
Learning: Applies to packages/swapper/src/swappers/*/{*.ts,endpoints.ts} : Verify chain ID filtering in filterAssetIdsBySellable and filterBuyAssetsBySellAssetId methods.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-08-10T21:09:25.643Z
Learnt from: premiumjibles
PR: shapeshift/web#10215
File: src/components/MultiHopTrade/hooks/useGetTradeRateInput.ts:65-67
Timestamp: 2025-08-10T21:09:25.643Z
Learning: In the MultiHopTrade components, `selectInputBuyAsset` and `selectInputSellAsset` selectors from `tradeInputSlice` always return defined values because they have default values in the initial state (BTC for buyAsset, ETH for sellAsset, with fallback to defaultAsset). Null checks for these assets are unnecessary when using these selectors.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-08-15T07:51:16.374Z
Learnt from: gomesalexandre
PR: shapeshift/web#10278
File: src/components/AssetHeader/hooks/useQuickBuy.ts:97-99
Timestamp: 2025-08-15T07:51:16.374Z
Learning: The selectPortfolioUserCurrencyBalanceByAssetId selector in src/state/slices/portfolioSlice/selectors.ts accepts a filter object with an assetId property, not a raw AssetId string. The selector signature is (state: ReduxState, filter) where filter is expected to have an assetId property. Passing a filter object like { assetId: someAssetId } is the correct usage pattern.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-08-15T07:51:16.374Z
Learnt from: gomesalexandre
PR: shapeshift/web#10278
File: src/components/AssetHeader/hooks/useQuickBuy.ts:97-99
Timestamp: 2025-08-15T07:51:16.374Z
Learning: The selectPortfolioUserCurrencyBalanceByAssetId selector in src/state/slices/portfolioSlice/selectors.ts accepts a filter object with an assetId property (signature: (state, { assetId })), not a raw AssetId string. Passing a filter object like { assetId: someAssetId } is the correct usage pattern.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-08-22T12:58:36.070Z
Learnt from: NeOMakinG
PR: shapeshift/web#10323
File: src/components/Layout/Header/ActionCenter/components/Notifications/RewardDistributionNotification.tsx:33-55
Timestamp: 2025-08-22T12:58:36.070Z
Learning: In RewardDistributionNotification component (src/components/Layout/Header/ActionCenter/components/Notifications/RewardDistributionNotification.tsx), NeOMakinG confirmed that runeAsset is expected to always be defined when the component renders, so defensive guards against undefined runeAsset are not needed.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-08-26T19:04:38.672Z
Learnt from: kaladinlight
PR: shapeshift/web#10369
File: packages/chain-adapters/src/cosmossdk/CosmosSdkBaseAdapter.ts:167-176
Timestamp: 2025-08-26T19:04:38.672Z
Learning: In packages/chain-adapters/src/cosmossdk/CosmosSdkBaseAdapter.ts, when processing assets from data.assets.reduce(), the team prefers using empty catch blocks to gracefully skip any assets that fail processing, rather than specific error type handling, to avoid useless noise and ensure robust asset filtering.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-09-04T17:29:59.479Z
Learnt from: NeOMakinG
PR: shapeshift/web#10380
File: src/components/TradeAssetSearch/hooks/useGetPopularAssetsQuery.tsx:28-33
Timestamp: 2025-09-04T17:29:59.479Z
Learning: In shapeshift/web, the useGetPopularAssetsQuery function in src/components/TradeAssetSearch/hooks/useGetPopularAssetsQuery.tsx intentionally uses primaryAssets[assetId] instead of falling back to assets[assetId]. The design distributes primary assets across chains by iterating through their related assets and adding the primary asset to each related asset's chain. This ensures primary assets appear in all chains where they have related assets, supporting the grouped asset system.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-10-23T14:27:19.073Z
Learnt from: gomesalexandre
PR: shapeshift/web#10857
File: src/plugins/walletConnectToDapps/eventsManager/useWalletConnectEventsHandler.ts:101-104
Timestamp: 2025-10-23T14:27:19.073Z
Learning: In WalletConnect wallet_switchEthereumChain and wallet_addEthereumChain requests, the chainId parameter is always present as per the protocol spec. Type guards checking for missing chainId in these handlers (like `if (!evmNetworkIdHex) return`) are solely for TypeScript compiler satisfaction, not real runtime edge cases.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-08-27T09:51:36.901Z
Learnt from: NeOMakinG
PR: shapeshift/web#10376
File: packages/contracts/src/viemClient.ts:39-40
Timestamp: 2025-08-27T09:51:36.901Z
Learning: NeOMakinG confirmed that issues flagged by CodeRabbit that predate the PR changes and are unrelated to the PR's core objective should be addressed separately, not within the current PR scope.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-08-27T09:47:06.275Z
Learnt from: NeOMakinG
PR: shapeshift/web#10323
File: src/components/ButtonWalletPredicate/ButtonWalletPredicate.tsx:7-7
Timestamp: 2025-08-27T09:47:06.275Z
Learning: In shapeshift/web project, NeOMakinG consistently prefers to defer UI/UX improvements and refactoring work (like the Drawer.Close hack fix in ButtonWalletPredicate.tsx) to follow-up PRs rather than expanding the scope of feature PRs, even when the improvements would enhance robustness.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-09-17T22:40:30.149Z
Learnt from: gomesalexandre
PR: shapeshift/web#10569
File: src/plugins/walletConnectToDapps/components/WalletConnectSigningModal/WalletConnectModalSigningFooter.tsx:121-129
Timestamp: 2025-09-17T22:40:30.149Z
Learning: gomesalexandre maintains strict scope discipline even for style/UI PRs in shapeshift/web, declining functionally correct UX improvements (like keeping Cancel button enabled during gas simulation loading) when they fall outside the PR's stated styling objectives, demonstrating his consistent pattern of deferring valid but tangential improvements to separate efforts.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-08-25T12:59:43.842Z
Learnt from: NeOMakinG
PR: shapeshift/web#10323
File: src/pages/Fox/components/RFOXSection.tsx:234-248
Timestamp: 2025-08-25T12:59:43.842Z
Learning: NeOMakinG considers suggestions to refactor working modal state management from useState to useDisclosure as too nitpicky, preferring to keep such cosmetic code improvements out of feature PRs unless they address actual issues.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-08-22T13:00:44.879Z
Learnt from: NeOMakinG
PR: shapeshift/web#10323
File: src/pages/RFOX/components/Stake/components/StakeSummary.tsx:112-114
Timestamp: 2025-08-22T13:00:44.879Z
Learning: NeOMakinG prefers to keep PR changes minimal and focused on the core objectives, avoiding cosmetic or defensive code improvements that aren't directly related to the PR scope, even when they would improve robustness.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-09-02T12:38:46.940Z
Learnt from: NeOMakinG
PR: shapeshift/web#10380
File: src/components/Table/Table.theme.ts:177-180
Timestamp: 2025-09-02T12:38:46.940Z
Learning: NeOMakinG prefers to defer technical debt and CSS correctness issues (like improper hover selectors) to follow-up PRs when the current PR is already large and focused on major feature implementation, even when the issues are valid from a usability/technical perspective.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-08-08T15:00:22.321Z
Learnt from: NeOMakinG
PR: shapeshift/web#10231
File: src/components/MultiHopTrade/components/TradeInput/components/HighlightedTokens.tsx:14-14
Timestamp: 2025-08-08T15:00:22.321Z
Learning: In shapeshift/web reviews for NeOMakinG, avoid nitpicks to change deep-relative imports to '@/…' alias paths within feature/non-refactor PRs; defer such style-only changes to a dedicated follow-up refactor unless they fix an issue.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-09-02T08:34:08.157Z
Learnt from: NeOMakinG
PR: shapeshift/web#10380
File: src/pages/Dashboard/components/AccountList/AccountTable.tsx:60-0
Timestamp: 2025-09-02T08:34:08.157Z
Learning: NeOMakinG prefers code review comments to focus only on actual PR changes, not pre-existing code issues, unless there are critical security or correctness concerns directly related to the new functionality.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-08-22T12:58:26.590Z
Learnt from: NeOMakinG
PR: shapeshift/web#10323
File: src/components/Layout/Header/ActionCenter/components/GenericTransactionActionCard.tsx:108-111
Timestamp: 2025-08-22T12:58:26.590Z
Learning: In the RFOX GenericTransactionDisplayType flow in src/components/Layout/Header/ActionCenter/components/GenericTransactionActionCard.tsx, the txHash is always guaranteed to be present according to NeOMakinG, so defensive null checks for txLink are not needed in this context.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-09-13T16:45:17.166Z
Learnt from: gomesalexandre
PR: shapeshift/web#10461
File: src/plugins/walletConnectToDapps/components/WalletConnectSigningModal/StructuredMessage/StructuredMessage.tsx:0-0
Timestamp: 2025-09-13T16:45:17.166Z
Learning: gomesalexandre appreciates safety-focused suggestions for UI rendering in WalletConnect components, specifically defensive programming approaches that prevent null/undefined values from displaying as literal "null"/"undefined" strings in the user interface.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-08-25T23:32:13.876Z
Learnt from: premiumjibles
PR: shapeshift/web#10361
File: src/pages/Markets/components/CardWithSparkline.tsx:83-92
Timestamp: 2025-08-25T23:32:13.876Z
Learning: In shapeshift/web PR #10361, premiumjibles considered the nested button accessibility issue (ChartErrorFallback retry Button inside Card rendered as Button in CardWithSparkline.tsx) out of scope for the error boundaries feature PR, consistent with deferring minor a11y improvements to follow-up PRs.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-09-12T11:56:19.437Z
Learnt from: gomesalexandre
PR: shapeshift/web#10461
File: src/plugins/walletConnectToDapps/utils/tenderly/index.ts:0-0
Timestamp: 2025-09-12T11:56:19.437Z
Learning: gomesalexandre rejected verbose try/catch error handling for address validation in Tenderly integration (PR #10461), calling the approach "ugly" but still implemented safety measures in commit ad7e424b89, preferring cleaner safety implementations over defensive programming patterns.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-09-10T15:35:46.223Z
Learnt from: gomesalexandre
PR: shapeshift/web#10458
File: src/plugins/walletConnectToDapps/components/modals/EIP155SignTypedDataConfirmation.tsx:55-55
Timestamp: 2025-09-10T15:35:46.223Z
Learning: gomesalexandre prefers fail-fast early returns over graceful degradation when critical data is missing in WalletConnect flows (like peer metadata in EIP155SignTypedDataConfirmation.tsx). He favors "safety first, always double-wrap" approach and believes missing peer metadata indicates bigger problems that should be surfaced explicitly rather than masked with partial UI rendering.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-09-02T00:02:44.889Z
Learnt from: premiumjibles
PR: shapeshift/web#10380
File: src/components/AssetSearch/components/GroupedAssetRow.tsx:116-0
Timestamp: 2025-09-02T00:02:44.889Z
Learning: premiumjibles prefers avoiding store.getState() calls in favor of useAppSelector where reactivity is needed, to prevent accidentally breaking reactive updates in components.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-10-17T07:51:58.374Z
Learnt from: NeOMakinG
PR: shapeshift/web#10783
File: src/context/WalletProvider/NewWalletViews/NewWalletViewsSwitch.tsx:344-349
Timestamp: 2025-10-17T07:51:58.374Z
Learning: In the shapeshift/web codebase, Chakra UI's ModalContent component supports the containerProps prop. When using the useModalRegistration hook from ModalStackProvider, spreading {...modalContentProps} directly onto ModalContent is correct and properly applies z-index and pointer-events through containerProps.sx. Do not suggest extracting sx from modalContentProps.containerProps.sx.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-10-07T03:44:27.350Z
Learnt from: 0xApotheosis
PR: shapeshift/web#10760
File: src/components/ManageHiddenAssets/ManageHiddenAssetsList.tsx:78-84
Timestamp: 2025-10-07T03:44:27.350Z
Learning: In the ShapeShift web codebase, the following are stable references and do not need to be included in useCallback/useMemo dependency arrays:
- `navigate` from `useBrowserRouter()` hook
- Modal control objects (like `walletDrawer`) from `useModal()` hook (including their `isOpen`, `close`, and `open` methods)
- These are backed by stable context providers

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-09-04T10:18:34.140Z
Learnt from: gomesalexandre
PR: shapeshift/web#10427
File: src/hooks/useActionCenterSubscribers/useSwapActionSubscriber.tsx:40-40
Timestamp: 2025-09-04T10:18:34.140Z
Learning: In the shapeshift/web codebase, src/state/slices/selectors.ts uses wildcard exports (`export * from './[sliceName]/selectors'`) to re-export all selectors from individual slice selector files, making them available through the barrel import. This means selectors like selectTxByFilter from txHistorySlice/selectors are properly accessible via '@/state/slices/selectors' even though they don't appear in explicit named exports.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-08-05T22:41:35.473Z
Learnt from: premiumjibles
PR: shapeshift/web#10187
File: src/pages/Assets/Asset.tsx:1-1
Timestamp: 2025-08-05T22:41:35.473Z
Learning: In the shapeshift/web codebase, component imports use direct file paths like '@/components/ComponentName/ComponentName' rather than barrel exports. The AssetAccountDetails component should be imported as '@/components/AssetAccountDetails/AssetAccountDetails', not from a directory index.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-08-14T17:54:32.563Z
Learnt from: gomesalexandre
PR: shapeshift/web#10276
File: src/pages/ThorChainLP/components/ReusableLpStatus/ReusableLpStatus.tsx:97-108
Timestamp: 2025-08-14T17:54:32.563Z
Learning: In ReusableLpStatus component (src/pages/ThorChainLP/components/ReusableLpStatus/ReusableLpStatus.tsx), the txAssets dependency is stable from first render because poolAsset, baseAsset, actionSide, and action are all defined first render, making the current txAssetsStatuses initialization pattern safe without needing useEffect synchronization.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
📚 Learning: 2025-08-27T09:47:59.394Z
Learnt from: NeOMakinG
PR: shapeshift/web#10323
File: src/components/Modal/components/DialogCloseButton.tsx:29-37
Timestamp: 2025-08-27T09:47:59.394Z
Learning: In shapeshift/web, for DialogCloseButton.tsx, the onClick handler must remain on Drawer.Close (not moved to IconButton with asChild) because stopPropagation() needs to be called at the Drawer.Close level to properly handle drawer closing behavior; moving event handling to the child IconButton would break the event flow that Drawer.Close expects.

Applied to files:

  • src/components/Modals/Send/views/Address.tsx
🧬 Code graph analysis (1)
src/components/Modals/Send/views/Address.tsx (4)
src/state/store.ts (1)
  • useAppSelector (143-143)
src/state/slices/addressBookSlice/selectors.ts (2)
  • selectIsAddressInAddressBook (64-97)
  • selectInternalAccountIdByAddress (104-131)
packages/caip/src/constants.ts (1)
  • ethChainId (60-60)
src/components/Modals/Send/AddressBook/AddressBook.tsx (1)
  • AddressBook (34-154)
⏰ 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)
  • GitHub Check: Install and Cache
🔇 Additional comments (4)
src/components/Modals/Send/views/Address.tsx (4)

107-111: LGTM: Save button logic correctly prevents saving internal accounts.

The boolean conditions properly gate the save button to only show for:

  • Addresses not already in the address book
  • External addresses (not internal accounts)
  • Valid addresses without errors
  • When the feature flag is enabled

190-194: LGTM: Properly clears all address-related fields.

The callback correctly resets Input, To, and VanityAddress when an address book entry is cleared, ensuring form state remains consistent.


214-217: LGTM: AddressInput integration props are well-structured.

The new props correctly wire up the address book functionality:

  • resolvedAddress provides the validated address for display
  • chainId scopes the address book to the asset's chain
  • onEmptied handles clearing when the user deselects an entry
  • shouldShowSaveButton controls the save CTA visibility

242-244: LGTM: AddressBook feature properly gated and wired.

The AddressBook component is correctly:

  • Feature-flagged to enable gradual rollout
  • Given the asset's chainId for namespace-scoped filtering
  • Wired to populate the input and navigate on selection

@NeOMakinG NeOMakinG merged commit 4a234f5 into develop Oct 30, 2025
4 checks passed
@NeOMakinG NeOMakinG deleted the address-book branch October 30, 2025 12:31
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.

Redesign The Send Flow on desktop

2 participants