Skip to content

feat/PRO-3162/The-Exchange-changes#289

Merged
RanaBug merged 2 commits intostagingfrom
feat/PRO-3162/The-Exchange-changes
Apr 23, 2025
Merged

feat/PRO-3162/The-Exchange-changes#289
RanaBug merged 2 commits intostagingfrom
feat/PRO-3162/The-Exchange-changes

Conversation

@RanaBug
Copy link
Collaborator

@RanaBug RanaBug commented Apr 17, 2025

Description

  • Wallet portfolio Mobula's endpoint implementation to fetch token balances
  • UI updates and improvement

How Has This Been Tested?

  • Existing Unit Tests and Manual Testing

Screenshots (if appropriate):

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Summary by CodeRabbit

  • New Features

    • Integrated wallet portfolio fetching, allowing users to view their token balances and USD values directly within the swap interface.
    • Enhanced token selection dropdown to display tokens from the connected wallet and show a message if no tokens are found.
    • Improved token search with local fuzzy search for wallet tokens and dynamic filtering by blockchain.
    • Token details now include balance and price information where available.
  • Improvements

    • Updated UI elements for better clarity, including improved error handling, loading indicators, and updated placeholders.
    • Swap and receive cards now flexibly display token information and amounts based on available data.
  • Bug Fixes

    • The swap button is now conditionally shown based on a feature flag.
  • Tests

    • Updated and refactored tests to support new token object props and wallet portfolio features.
  • Chores

    • Added new type definitions and API integrations to support wallet portfolio data.

@RanaBug RanaBug requested a review from IAmKio April 17, 2025 16:05
@RanaBug RanaBug self-assigned this Apr 17, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Apr 17, 2025

Walkthrough

This update introduces wallet portfolio integration into the token swap UI. It adds a new API service and types for fetching and modeling wallet portfolio data, and enhances token selection components to display tokens and balances from the user's wallet. The Redux state is extended to store wallet portfolio data, and new actions are added for managing it. Token search and selection logic is refactored to conditionally use wallet portfolio tokens or search results based on UI state. Components now support and display token balances and prices. Several component prop types are updated to accommodate these changes, and related tests are refactored accordingly.

Changes

File(s) Change Summary
src/services/pillarXApiWalletPortfolio.ts, src/types/api.ts Introduced a new Redux Toolkit Query API service for fetching wallet portfolio data, added comprehensive Mobula portfolio and asset TypeScript types, and a utility to convert API responses to tokens.
src/services/tokensData.ts Extended the Token type to include optional balance and price properties.
src/apps/the-exchange/reducer/theExchangeSlice.ts Extended Redux state with a walletPortfolio property, updated searchToken type, added new actions for managing wallet portfolio data.
src/apps/the-exchange/components/DropdownTokensList/DropdownTokenList.tsx Integrated wallet portfolio fetching and Redux dispatch, overhauled token list rendering to display wallet tokens or search results based on UI state, improved error and loading handling, updated close/reset logic.
src/apps/the-exchange/components/DropdownTokensList/TokenRow.tsx, src/apps/the-exchange/components/TokenListItem/TokenListItem.tsx Refactored to pass a single token object prop; TokenListItem now displays balance, price, and chain-specific logos using unified token data.
src/apps/the-exchange/components/TokenListItem/test/TokenListItem.test.tsx Updated tests to use a single token object prop, adjusted assertions for new display logic and formatting.
src/apps/the-exchange/components/TokenSearchInput/TokenSearchInput.tsx Refactored token search to use local fuzzy search via Fuse.js on wallet portfolio tokens when swap UI is open, API search when receive UI is open; adjusted filtering and result dispatch logic.
src/apps/the-exchange/components/EnterAmount/EnterAmount.tsx Removed internal wallet balance fetching, added optional tokenBalance prop, refactored balance validation and error messaging, updated effect dependencies and display logic.
src/apps/the-exchange/components/SelectToken/SelectToken.tsx Added optional tokenBalance and tokenPrice props, displays formatted balance and USD value if provided, updated styling.
src/apps/the-exchange/components/SwapReceiveCard/SwapReceiveCard.tsx Passes tokenBalance and tokenPrice to child components for swap tokens, adjusted root container height styling.
src/apps/the-exchange/components/CardsSwap/CardsSwap.tsx Added feature flag check via environment variable for rendering the SwitchCardsButton.
src/apps/the-exchange/components/CardsSwap/test/CardSwap.test.tsx Set environment variable in tests, removed an assertion related to token search prompt.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant DropdownTokensList
    participant ReduxStore
    participant pillarXApiWalletPortfolio
    participant TokenSearchInput

    User->>DropdownTokensList: Opens token dropdown
    DropdownTokensList->>pillarXApiWalletPortfolio: Fetch wallet portfolio (if wallet connected)
    pillarXApiWalletPortfolio-->>DropdownTokensList: Returns portfolio data
    DropdownTokensList->>ReduxStore: Dispatch setWalletPortfolio(portfolio)
    User->>TokenSearchInput: Types in search box
    TokenSearchInput->>ReduxStore: Checks isReceiveOpen / isSwapOpen, walletPortfolio
    alt Receive UI open
        TokenSearchInput->>pillarXApiWalletPortfolio: API search by receive chain
        pillarXApiWalletPortfolio-->>TokenSearchInput: Search results
        TokenSearchInput->>ReduxStore: Dispatch search results
    else Swap UI open and walletPortfolio available
        TokenSearchInput->>TokenSearchInput: Local fuzzy search via Fuse.js
        TokenSearchInput->>ReduxStore: Dispatch search results
    end
    DropdownTokensList->>User: Renders tokens from wallet or search results
Loading

Possibly related PRs

Suggested reviewers

  • IAmKio

Poem

In the garden of code where tokens bloom,
Now wallets show their treasures soon!
Balances sparkle, prices gleam,
With portfolio magic in every stream.
Search is smarter, swaps are neat—
A rabbit’s work is never beat!
🐰✨

✨ Finishing Touches
  • 📝 Generate Docstrings

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Apr 17, 2025

Deploying x with  Cloudflare Pages  Cloudflare Pages

Latest commit: b119a50
Status: ✅  Deploy successful!
Preview URL: https://bc36693f.x-e62.pages.dev
Branch Preview URL: https://feat-pro-3162-the-exchange-c.x-e62.pages.dev

View logs

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: 6

🧹 Nitpick comments (7)
src/apps/the-exchange/components/SelectToken/SelectToken.tsx (1)

46-55: Effective conditional rendering of token balance and USD value.

The implementation correctly handles the display of token balances and their USD value when available. Consider adding handling for very small balances or potential edge cases.

 {tokenBalance && (
   <Body className="font-normal">
-    {limitDigits(tokenBalance)}{' '}
+    {tokenBalance > 0 ? limitDigits(tokenBalance) : '0.00'}{' '}
     {tokenPrice && (
       <span className="text-black_grey/[.4]">
-        ${(tokenPrice * tokenBalance).toFixed(4)}
+        ${isFinite(tokenPrice * tokenBalance) ? (tokenPrice * tokenBalance).toFixed(4) : '0.00'}
       </span>
     )}
   </Body>
 )}
src/services/pillarXApiWalletPortfolio.ts (1)

57-61: Consider adding a comment about the chain IDs array.

The conditional chain IDs selection isn't immediately obvious without context.

Consider adding a brief comment about why different chain IDs are used for testnet vs mainnet:

+        // For testnet, only use Sepolia testnet (11155111)
+        // For mainnet, use all supported production chains
        const chainIds = isTestnet
          ? [11155111]
          : CompatibleChains.map((chain) => chain.chainId);
src/apps/the-exchange/components/TokenSearchInput/TokenSearchInput.tsx (1)

2-2: Add fuse.js to project dependencies

Fuse is imported but I don’t see it declared in package.json. A missing dependency will break CI builds.

# package.json
   "dependencies": {
+    "fuse.js": "^7.0.0",
     ...
   }
src/apps/the-exchange/components/EnterAmount/EnterAmount.tsx (1)

212-218: Avoid double invocation of tokenBalanceLimit

tokenBalanceLimit is executed twice per render – once to test truthiness and once to render the message. Compute once for minor perf/readability gain:

- {tokenBalanceLimit(Number(inputValue), tokenBalance) && (
-   <BodySmall ...>
-     {tokenBalanceLimit(Number(inputValue), tokenBalance)}
-   </BodySmall>
- )}
+ {(() => {
+   const limitMsg = tokenBalanceLimit(Number(inputValue), tokenBalance);
+   return (
+     limitMsg && (
+       <BodySmall id="token-balance-limit-exchange" data-testid="error-max-limit">
+         {limitMsg}
+       </BodySmall>
+     )
+   );
+ })()}
src/types/api.ts (3)

605-611: Inconsistent type naming: use singular ContractBalanceMobula
The new type is named ContractsBalanceMobula (plural), but we already have a legacy ContractBalance (singular) for non‑Mobula data. For consistency and clearer symmetry, consider renaming this to ContractBalanceMobula and updating all references accordingly.

-export type ContractsBalanceMobula = {
+export type ContractBalanceMobula = {
   address: string;
   balance: number;
   balanceRaw: string;
   chainId: string;
   decimals: number;
};

678-687: Consider suffixing PortfolioData with Mobula for clarity
We have an earlier WalletPortfolioData; this new type could be confused with it. To maintain clear boundaries between legacy and Mobula models, you might rename this to PortfolioDataMobula.


693-696: Remove commented-out stub code
The leftover WalletBalances stub is commented out and unused. Please delete these lines to keep the codebase clean.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between debacac and 488c22a.

⛔ Files ignored due to path filters (5)
  • src/apps/pillarx-app/components/PointsTile/test/__snapshots__/PointsTile.test.tsx.snap is excluded by !**/*.snap
  • src/apps/the-exchange/components/CardsSwap/test/__snapshots__/CardSwap.test.tsx.snap is excluded by !**/*.snap
  • src/apps/the-exchange/components/DropdownTokensList/test/__snapshots__/DropdownTokensList.test.tsx.snap is excluded by !**/*.snap
  • src/apps/the-exchange/components/SelectToken/test/__snapshots__/SelectToken.test.tsx.snap is excluded by !**/*.snap
  • src/apps/the-exchange/components/TokenListItem/test/__snapshots__/TokenListItem.test.tsx.snap is excluded by !**/*.snap
📒 Files selected for processing (14)
  • src/apps/the-exchange/components/CardsSwap/CardsSwap.tsx (1 hunks)
  • src/apps/the-exchange/components/CardsSwap/test/CardSwap.test.tsx (1 hunks)
  • src/apps/the-exchange/components/DropdownTokensList/DropdownTokenList.tsx (7 hunks)
  • src/apps/the-exchange/components/DropdownTokensList/TokenRow.tsx (1 hunks)
  • src/apps/the-exchange/components/EnterAmount/EnterAmount.tsx (5 hunks)
  • src/apps/the-exchange/components/SelectToken/SelectToken.tsx (4 hunks)
  • src/apps/the-exchange/components/SwapReceiveCard/SwapReceiveCard.tsx (3 hunks)
  • src/apps/the-exchange/components/TokenListItem/TokenListItem.tsx (2 hunks)
  • src/apps/the-exchange/components/TokenListItem/test/TokenListItem.test.tsx (2 hunks)
  • src/apps/the-exchange/components/TokenSearchInput/TokenSearchInput.tsx (5 hunks)
  • src/apps/the-exchange/reducer/theExchangeSlice.ts (6 hunks)
  • src/services/pillarXApiWalletPortfolio.ts (1 hunks)
  • src/services/tokensData.ts (1 hunks)
  • src/types/api.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
src/apps/the-exchange/components/TokenSearchInput/TokenSearchInput.tsx (1)
Learnt from: RanaBug
PR: pillarwallet/x#275
File: src/apps/the-exchange/components/DropdownTokensList/DropdownTokenList.tsx:180-195
Timestamp: 2025-03-28T09:22:22.712Z
Learning: In the Exchange app, `swapTokenList` and `receiveTokenList` are derived from `searchTokenResult` when search is active, so including `searchToken` in the useEffect dependency array that uses these lists would be redundant as the lists will update when search results change.
src/apps/the-exchange/components/DropdownTokensList/DropdownTokenList.tsx (1)
Learnt from: RanaBug
PR: pillarwallet/x#275
File: src/apps/the-exchange/components/DropdownTokensList/DropdownTokenList.tsx:180-195
Timestamp: 2025-03-28T09:22:22.712Z
Learning: In the Exchange app, `swapTokenList` and `receiveTokenList` are derived from `searchTokenResult` when search is active, so including `searchToken` in the useEffect dependency array that uses these lists would be redundant as the lists will update when search results change.
🧬 Code Graph Analysis (6)
src/apps/the-exchange/components/SelectToken/SelectToken.tsx (1)
src/apps/token-atlas/utils/converters.ts (1)
  • limitDigits (8-23)
src/services/pillarXApiWalletPortfolio.ts (4)
src/types/api.ts (2)
  • PortfolioData (678-687)
  • WalletPortfolioMobulaResponse (689-691)
src/services/tokensData.ts (2)
  • Token (11-21)
  • chainIdToChainNameTokensData (213-234)
src/utils/blockchain.ts (2)
  • isTestnet (29-35)
  • CompatibleChains (260-289)
src/store.ts (1)
  • addMiddleware (48-58)
src/apps/the-exchange/components/TokenListItem/test/TokenListItem.test.tsx (1)
src/apps/token-atlas/utils/converters.ts (1)
  • limitDigits (8-23)
src/apps/the-exchange/components/TokenListItem/TokenListItem.tsx (3)
src/services/tokensData.ts (3)
  • Token (11-21)
  • chainNameToChainIdTokensData (237-258)
  • chainNameDataCompatibility (46-66)
src/utils/blockchain.ts (1)
  • getLogoForChainId (158-192)
src/apps/token-atlas/utils/converters.ts (1)
  • limitDigits (8-23)
src/apps/the-exchange/components/DropdownTokensList/DropdownTokenList.tsx (6)
src/apps/the-exchange/hooks/useReducerHooks.tsx (1)
  • useAppSelector (6-6)
src/apps/the-exchange/utils/types.tsx (1)
  • ChainType (28-31)
src/types/api.ts (1)
  • PortfolioData (678-687)
src/apps/the-exchange/reducer/theExchangeSlice.ts (6)
  • setWalletPortfolio (107-112)
  • setSwapChain (65-67)
  • setIsReceiveOpen (62-64)
  • setReceiveChain (68-70)
  • setSearchTokenResult (86-88)
  • setSearchToken (98-100)
src/services/pillarXApiWalletPortfolio.ts (1)
  • convertPortfolioAPIResponseToToken (13-33)
src/services/tokensData.ts (1)
  • chainNameToChainIdTokensData (237-258)
src/apps/the-exchange/reducer/theExchangeSlice.ts (1)
src/types/api.ts (1)
  • PortfolioData (678-687)
⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: unit-tests
  • GitHub Check: lint
  • GitHub Check: build
  • GitHub Check: Cloudflare Pages
🔇 Additional comments (46)
src/apps/the-exchange/components/CardsSwap/test/CardSwap.test.tsx (1)

205-205: Good test setup update

Adding the feature flag REACT_APP_SWAP_BUTTON_SWITCH to the test environment properly aligns with the component changes in CardsSwap.tsx, ensuring that tests will continue to pass with the new conditional rendering logic.

src/apps/the-exchange/components/CardsSwap/CardsSwap.tsx (1)

113-115:

Details

✅ Verification successful

Feature flag implementation LGTM

The addition of the REACT_APP_SWAP_BUTTON_SWITCH environment variable check provides more control over the UI by making the switch button's visibility configurable without code changes.

To ensure consistent feature flag naming across the codebase:


🏁 Script executed:

#!/bin/bash
# Check for other feature flags to verify naming convention consistency
rg "process\.env\.REACT_APP_.*_SWITCH" --type ts --type tsx

Length of output: 89


🏁 Script executed:

#!/bin/bash
# Retry searching across .ts and .tsx files for feature-flag naming consistency
rg -E 'process\.env\.REACT_APP_.*_SWITCH' --glob '*.{ts,tsx}'

Length of output: 460


🏁 Script executed:

#!/bin/bash
# Properly search for any feature flags ending with _SWITCH in .ts and .tsx files
rg 'process\.env\.REACT_APP_.*_SWITCH' --glob '*.ts' --glob '*.tsx'

Length of output: 309


Feature flag naming consistency confirmed

A search for all REACT_APP_*_SWITCH flags returned only these two occurrences:

  • src/apps/the-exchange/components/CardsSwap/CardsSwap.tsx
  • src/apps/the-exchange/components/CardsSwap/test/CardSwap.test.tsx

No further instances or naming mismatches found—no changes required.

src/services/tokensData.ts (1)

19-20: Enhancement of the Token data model to support wallet portfolio integration.

The addition of optional balance and price fields to the Token type enables components to display token balances and value calculations. This is a clean, non-breaking change that follows the Open-Closed Principle by extending the existing model without modifying current functionality.

src/apps/the-exchange/components/DropdownTokensList/TokenRow.tsx (1)

26-26: Good simplification by unifying token data props.

Passing the entire token object instead of individual properties reduces prop drilling and simplifies the component interface. This change supports the new balance and price display features while making the code more maintainable.

src/apps/the-exchange/components/SwapReceiveCard/SwapReceiveCard.tsx (3)

108-108: Improved container flexibility with min-height and full-height.

Changing from a fixed height to min-h-[200px] h-full allows the container to adapt to content size while maintaining a minimum size, which provides better responsive behavior.


125-130: Token balance and price data correctly passed to SelectToken component.

The conditional passing of balance and price data only when in SWAP position aligns with the application's intended behavior for displaying user balances.


139-141: Token balance appropriately passed for validation in EnterAmount component.

Passing the balance to EnterAmount enables amount validation against available balance, enhancing the user experience by preventing over-spending.

src/apps/the-exchange/components/SelectToken/SelectToken.tsx (3)

6-6: Appropriate utility import for balance formatting.

The limitDigits utility will ensure consistent numeric formatting across the application.


19-20: Well-designed props extension for token balance and price.

Adding these optional props maintains backward compatibility while enabling new functionality.


37-37: Added margin improves vertical spacing.

The added bottom margin provides better visual separation between the token information and other elements.

src/apps/the-exchange/components/TokenListItem/test/TokenListItem.test.tsx (7)

4-6: Good addition of the limitDigits utility.

The inclusion of the limitDigits utility from token-atlas is appropriate for testing the token balance formatting.


11-21: Well-structured test fixture data.

The tokenExample object provides comprehensive test data including balance and price properties, which aligns with the updated Token interface. This is a good practice for creating reusable test fixtures.


27-27: Updated component prop structure.

The test now correctly passes a single token object instead of separate primitive props, matching the component's new API.


35-44: Thorough verification of rendered token data.

The test now properly verifies that token details, including the formatted balance and computed total value, are correctly rendered in the component.


54-58: Proper type handling for edge case testing.

Using TypeScript comments to silence the type error is appropriate here since you're intentionally testing the component's behavior with incomplete data (undefined logo).


69-70: Updated element selection strategy.

Tests now locate elements by the token's name from the token object, which is more resilient than using fixed strings.


80-81: Consistent element selection approach.

The hover test is also updated to use the token name from the object, maintaining consistency with the click test.

src/apps/the-exchange/components/DropdownTokensList/DropdownTokenList.tsx (11)

15-20: Updated reducer actions to support wallet portfolio.

Added setSearchToken and setWalletPortfolio imports from the reducer to manage the new wallet portfolio state.


23-27: New wallet portfolio API integration.

Added imports for the wallet portfolio API service and conversion utility, which will enable token balance display.


39-41: Added type imports for portfolio data.

The PortfolioData and ChainType imports support the new portfolio functionality.


73-75: Added Redux state selectors for new features.

Properly added selectors for isReceiveOpen, swapChain, and walletPortfolio state, which are needed for the enhanced portfolio and chain-based filtering functionality.

Also applies to: 92-97


102-126: Wallet portfolio data fetching and state management.

The component uses the RTK Query hook to fetch wallet portfolio data and manages related loading states. The useEffect correctly updates Redux state based on the API response.

I notice you've excluded some dependencies from the dependency array in the useEffect. Based on the retrieved learning from PR #275, this seems intentional as you're focusing only on portfolio data changes.


197-219: Smart token list rendering with portfolio integration.

The component now intelligently chooses between wallet portfolio tokens and search results based on UI state. The filtering by chain ID is a nice feature that improves UX when users select specific chains.


233-240: Improved dropdown close behavior.

When closing the dropdown, the component now properly resets the chain filter to "all" and clears the search token. This ensures a clean state when reopening the dropdown.


263-264: Updated search placeholder.

The updated placeholder "Search name / contract" is more informative about what users can search for.


271-282: Enhanced error and empty state handling.

The component now handles both token search errors and wallet portfolio errors, and displays an appropriate message when no tokens are found in the wallet.


286-290: Improved loading state.

The loading indicator now considers both token search loading and wallet portfolio loading/fetching states, providing a better user experience.


291-312: Updated token list rendering with proper filtering.

The List component now uses the updated token list data structure and properly filters out tokens that are already selected in either the swap or receive positions.

src/services/pillarXApiWalletPortfolio.ts (4)

13-33: Well-implemented portfolio data conversion.

The convertPortfolioAPIResponseToToken function effectively converts the portfolio API response into the expected Token structure. It handles null checks and properly maps chain IDs to chain names using the existing utility.


35-45: Robust API query configuration with retry logic.

The use of retry logic with fetchBaseQuery is a good practice for handling network failures. The dynamic base URL based on testnet/mainnet environment is also a good approach.


47-75: Well-structured RTK Query API definition.

The API is properly configured to fetch wallet portfolio data with the correct request structure. The dynamic chainIds handling based on environment is a good approach for supporting different networks.


77-79: Proper Redux store integration.

The service is correctly integrated with the Redux store using addMiddleware and exports the generated hook for component usage.

src/apps/the-exchange/components/TokenListItem/TokenListItem.tsx (7)

2-8: Updated imports to support enhanced token display.

Added imports for Token type, chain utilities, and the limitDigits formatter to support the new balance and price display functionality.


15-19: Simplified prop interface.

The component now accepts a single token object instead of multiple primitive props, which simplifies the API and improves type safety.


21-21: Updated function signature.

The function signature now uses the new prop structure.


30-38: Enhanced token logo display.

The TokenLogo component now receives both the token logo and a chain-specific logo based on the token's blockchain, improving visual identification.


40-45: Updated token detail rendering.

Token name and symbol are now accessed from the token object properties.


48-62: Added conditional rendering for token balance information.

The component now conditionally displays either the token balance and USD value (when available) or the chain compatibility text. The use of limitDigits for formatting the balance and proper calculation of the USD value improve the user experience.


55-55: Verify valid price before calculating value.

Good use of the optional chaining to only attempt calculation if price exists.

src/apps/the-exchange/components/TokenSearchInput/TokenSearchInput.tsx (1)

179-186: Missing dependencies in useEffect

isLoading, isFetching, and error are referenced inside the effect but not listed in the dependency array. The loading/error flags may become stale.

-  }, [
+  }, [
     searchData,
     debouncedSearchText,
     isSwapOpen,
     swapChain.chainId,
     receiveChain.chainId,
     isReceiveOpen,
     walletPortfolio,
+    isLoading,
+    isFetching,
+    error,
   ]);
src/apps/the-exchange/reducer/theExchangeSlice.ts (1)

23-24: State shape change looks correct

searchToken is now optional – good alignment with the UI where an empty search is undefined rather than ''. No issues spotted here.

Also applies to: 49-50

src/types/api.ts (5)

613-618: New CrossChainBalanceMobula type looks good
Fields align with the Mobula API response and mirror the legacy CrossChainBalance. No changes needed here.


624-632: Verify decimals should be string[]
The AssetMobula type declares decimals: string[], but decimals are typically numeric. If the API really returns string‑encoded decimals, this is fine. Otherwise, consider using number[] to reflect numeric precision and simplify downstream calculations.


634-650: New AssetDataMobula type aligned with API spec
This captures balances, price data, PnL metrics, and wallets as expected. References to ContractsBalanceMobula and CrossChainBalances now map correctly (once the mapping type is fixed).


652-657: Clarify PnL history value types
PnLHistory uses string[][], but it’s unclear what each inner string represents (timestamp, value, etc.). Please confirm the API returns nested string arrays and, if possible, introduce a tuple type (e.g., [timestamp: number, value: number][]) for better type safety.


689-692: Wrapper response type is correctly defined
WalletPortfolioMobulaResponse matches the API shape (result.data). No changes needed.

Comment on lines +659 to +676
export type TotalPnLHistory = {
'24h': {
realized: number;
unrealized: number;
};
'7d': {
realized: number;
unrealized: number;
};
'30d': {
realized: number;
unrealized: number;
};
'1y': {
realized: number;
unrealized: number;
};
};
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Naming collision: unify TotalPnLHistory vs. existing TotalPnlHistory
There’s already a TotalPnlHistory defined earlier. The new TotalPnLHistory only differs by capitalization of “L”. This risks confusion and import errors. Consider deprecating one or renaming the new type to TotalPnlHistoryMobula (or vice versa) for clear separation.

@github-actions github-actions bot temporarily deployed to Preview (feat/PRO-3162/The-Exchange-changes) April 22, 2025 13:05 Inactive
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 (3)
src/apps/the-exchange/components/TokenSearchInput/TokenSearchInput.tsx (3)

105-111: ⚠️ Potential issue

Add null-safety check for API response data

The code is missing a null check for searchData?.result?.data before passing it to convertAPIResponseToTokens(). Without this check, the code could throw an error if the API returns a malformed payload or a 204 response.

Apply this fix:

  if (isReceiveOpen) {
    if (!searchData) return;

+   if (!searchData?.result?.data) {
+     dispatch(setSearchTokenResult([]));
+     return;
+   }
+
    const result = convertAPIResponseToTokens(
-     searchData?.result?.data as TokenAssetResponse[],
+     searchData.result.data as TokenAssetResponse[],
      debouncedSearchText
    );

136-176: ⚠️ Potential issue

Fix fuzzy search logic overwriting exact matches

The exact-match branch (for contract addresses) is immediately overwritten by the fuzzy search branch. After dispatching the results for contract address search, the code creates another Fuse instance and overwrites the Redux state, causing users to never see the exact-match results.

Apply this fix:

  if (debouncedSearchText.length > 40) {
    const fuse = new Fuse(tokensWithBalances, {
      keys: ['name', 'symbol', 'contract'], // Fields to search in
      threshold: 0.2, // Allow some fuzziness for queries that are not contract like
      minMatchCharLength: 3,
      useExtendedSearch: true, // Enables exact match using '='
    });

    // Check if query length is above 40 characters have an exact match (likely a contract address)
    const searchQuery =
      debouncedSearchText.length > 40
        ? `="${debouncedSearchText}"`
        : debouncedSearchText;

    const results = fuse.search(searchQuery).map((r) => r.item);
    dispatch(setSearchTokenResult(results));
+   return; // Early return to prevent overwriting exact matches
  }

  const fuse = new Fuse(tokensWithBalances, {

130-158: 🛠️ Refactor suggestion

Optimize Fuse.js instance creation with useMemo

Fuse instances are recreated on every render cycle, which is inefficient for large token lists. Use useMemo to cache these instances and their input data.

Move the Fuse setup outside the effect and use useMemo:

+ // Memoize tokens with balances to avoid unnecessary conversions
+ const tokensWithBalances = useMemo(() => {
+   if (!walletPortfolio) return [];
+   return convertPortfolioAPIResponseToToken(walletPortfolio);
+ }, [walletPortfolio]);
+
+ // Memoize Fuse instance for contract address searches
+ const contractFuse = useMemo(() => {
+   return new Fuse(tokensWithBalances, {
+     keys: ['name', 'symbol', 'contract'],
+     threshold: 0.2,
+     minMatchCharLength: 3,
+     useExtendedSearch: true,
+   });
+ }, [tokensWithBalances]);
+
+ // Memoize Fuse instance for normal searches
+ const normalFuse = useMemo(() => {
+   return new Fuse(tokensWithBalances, {
+     keys: ['name', 'symbol', 'contract'],
+     threshold: 0.3,
+   });
+ }, [tokensWithBalances]);

  useEffect(() => {
    // ...existing code...

    if (isSwapOpen && walletPortfolio) {
-     // This sets the token results list that will be displayed in the UI
-     const tokensWithBalances =
-       convertPortfolioAPIResponseToToken(walletPortfolio);
-
      // Since the list of available tokens with balance is loaded, we just need
      // here a simpler search with Fuse.js with auto select when the contract
      // address is entered
      if (debouncedSearchText.length > 40) {
-       const fuse = new Fuse(tokensWithBalances, {
-         keys: ['name', 'symbol', 'contract'], // Fields to search in
-         threshold: 0.2, // Allow some fuzziness for queries that are not contract like
-         minMatchCharLength: 3,
-         useExtendedSearch: true, // Enables exact match using '='
-       });
-
        // Check if query length is above 40 characters have an exact match (likely a contract address)
        const searchQuery =
          debouncedSearchText.length > 40
            ? `="${debouncedSearchText}"`
            : debouncedSearchText;

-       const results = fuse.search(searchQuery).map((r) => r.item);
+       const results = contractFuse.search(searchQuery).map((r) => r.item);
        dispatch(setSearchTokenResult(results));
+       return;
      }

-     const fuse = new Fuse(tokensWithBalances, {
-       keys: ['name', 'symbol', 'contract'], // Fields to search in
-       threshold: 0.3, // Allow some fuzziness for queries that are not contract like
-     });
-
      const results = fuse
🧹 Nitpick comments (2)
src/apps/the-exchange/components/TokenSearchInput/TokenSearchInput.tsx (2)

159-175: Extract common chain filtering logic

The chain filtering logic is duplicated between the receive and swap cases. Consider extracting this into a helper function.

+ // Helper function to filter tokens by chain ID
+ const filterTokensByChain = (tokens, chainId) => {
+   if (chainId === 0) return tokens;
+   return tokens.filter(
+     (token) => chainNameToChainIdTokensData(token.blockchain) === chainId
+   );
+ };

  // In the receive case:
  dispatch(
    setSearchTokenResult(
-     receiveChain.chainId === 0
-       ? result
-       : result
-           .filter(
-             (tokens) =>
-               chainNameToChainIdTokensData(tokens.blockchain) ===
-               receiveChain.chainId
-           )
-           .map((tokens) => tokens)
+     filterTokensByChain(result, receiveChain.chainId)
    )
  );

  // In the swap case:
  dispatch(
    setSearchTokenResult(
-     swapChain.chainId === 0
-       ? results
-       : results
-           .filter(
-             (tokens) =>
-               chainNameToChainIdTokensData(tokens.blockchain) ===
-               swapChain.chainId
-           )
-           .map((tokens) => tokens)
+     filterTokensByChain(results, swapChain.chainId)
    )
  );

146-148: Simplify contract address search query logic

The searchQuery assignment has a redundant condition since you already checked debouncedSearchText.length > 40 in the outer if statement.

  // Check if query length is above 40 characters have an exact match (likely a contract address)
- const searchQuery =
-   debouncedSearchText.length > 40
-     ? `="${debouncedSearchText}"`
-     : debouncedSearchText;
+ const searchQuery = `="${debouncedSearchText}"`;
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 488c22a and b119a50.

📒 Files selected for processing (2)
  • src/apps/the-exchange/components/TokenSearchInput/TokenSearchInput.tsx (6 hunks)
  • src/types/api.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/types/api.ts
🧰 Additional context used
🧠 Learnings (1)
src/apps/the-exchange/components/TokenSearchInput/TokenSearchInput.tsx (1)
Learnt from: RanaBug
PR: pillarwallet/x#275
File: src/apps/the-exchange/components/DropdownTokensList/DropdownTokenList.tsx:180-195
Timestamp: 2025-03-28T09:22:22.712Z
Learning: In the Exchange app, `swapTokenList` and `receiveTokenList` are derived from `searchTokenResult` when search is active, so including `searchToken` in the useEffect dependency array that uses these lists would be redundant as the lists will update when search results change.
⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: unit-tests
  • GitHub Check: lint
  • GitHub Check: Cloudflare Pages
  • GitHub Check: build
🔇 Additional comments (2)
src/apps/the-exchange/components/TokenSearchInput/TokenSearchInput.tsx (2)

178-186:

Details

✅ Verification successful

Pay attention to useEffect dependencies

The dependencies list now includes walletPortfolio which could be a complex object that causes the effect to run frequently. Consider using a more stable dependency if possible.

Check if walletPortfolio changes frequently:


🏁 Script executed:

#!/bin/bash
# Look for redux actions that modify walletPortfolio
grep -r "walletPortfolio" --include="*.ts" --include="*.tsx" src/apps/the-exchange/reducer

Length of output: 362


🏁 Script executed:

#!/bin/bash
# Search for the definition of PortfolioData to understand its shape
rg "type PortfolioData" -A10 src/apps/the-exchange
rg "interface PortfolioData" -A10 src/apps/the-exchange

Length of output: 110


🏁 Script executed:

#!/bin/bash
# Search for PortfolioData type definition across the repository
rg "PortfolioData" -A5 .

Length of output: 12690


🏁 Script executed:

#!/bin/bash
# Show the useEffect callback in TokenSearchInput to see how walletPortfolio is used
rg -n "useEffect" -A5 -B5 src/apps/the-exchange/components/TokenSearchInput/TokenSearchInput.tsx

Length of output: 1580


🏁 Script executed:

#!/bin/bash
# Print lines 60-200 to inspect useEffect with walletPortfolio dependency
sed -n '60,200p' src/apps/the-exchange/components/TokenSearchInput/TokenSearchInput.tsx

Length of output: 4458


Effect dependencies are appropriate
After reviewing the reducer and selector, walletPortfolio is only updated via explicit setWalletPortfolio dispatches (on initial fetch or error), so its reference doesn’t change on every render. Including it ensures the effect runs once when real portfolio data arrives—there’s no unnecessary re‑execution.


93-96: 🛠️ Refactor suggestion

Always skip query when search text is empty

The API query skips only when debouncedSearchText is empty, but doesn't consider which mode is active. This could still trigger large, empty searches.

  } = useGetSearchTokensQuery(
    {
      searchInput: debouncedSearchText,
      filterBlockchains: chainIdToChainNameTokensData(receiveChain.chainId),
    },
-   { skip: !debouncedSearchText }
+   { skip: !debouncedSearchText || isSwapOpen }
  );

This will skip the query when either:

  1. The search text is empty (existing behavior)
  2. The swap UI is open (since you're using local wallet portfolio data in this case)

Likely an incorrect or invalid review comment.

Copy link
Collaborator

@IAmKio IAmKio left a comment

Choose a reason for hiding this comment

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

LGTM - great work!

@RanaBug RanaBug merged commit d960f41 into staging Apr 23, 2025
7 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Jun 23, 2025
3 tasks
@coderabbitai coderabbitai bot mentioned this pull request Jul 1, 2025
3 tasks
@coderabbitai coderabbitai bot mentioned this pull request Aug 20, 2025
3 tasks
@coderabbitai coderabbitai bot mentioned this pull request Jan 9, 2026
3 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants