Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(trading): move buy trading part to suite-common #16866

Draft
wants to merge 7 commits into
base: develop
Choose a base branch
from

Conversation

adderpositive
Copy link
Contributor

Description

Duplicated the functionality of the buy trading section.

Info

  • use reduxjs-toolkit
  • move the reducer and middleware to suite-common
  • move actions of the buy section
  • create thunks (before there were in actions)
  • move reducer test and also refactor
  • add test for thunks
  • add test for middleware (maybe some part of middleware will be a little bit different for native and desktop - router)

Related Issue

Resolve #16518

@adderpositive adderpositive added +Invity Related to Invity project chore labels Feb 6, 2025
@adderpositive adderpositive self-assigned this Feb 6, 2025
Copy link

coderabbitai bot commented Feb 6, 2025

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

This pull request introduces a Redux-based implementation for managing trading functionality in the suite-common module, effectively duplicating the existing context-based approach. Changes include updates to import paths in hooks and components, the addition of new trading constants and utility functions, and enhancements to the trading middleware to handle new conditions using updated constants. New Redux action creators, reducers, thunks, and fixtures have been added for both trading and buying functionalities. Comprehensive test suites and configurations (including a new Jest config) have been introduced to ensure robust coverage. Additionally, package and TypeScript configuration files have been updated to incorporate new dependencies and references. Public exports in the trading module have been expanded to integrate the new Redux state management without affecting the original Suite code.

Assessment against linked issues

Objective Addressed Explanation
Duplicate trade contexts using Redux state instead of context (#16518)
Increased test coverage for trading functionality (#16518) Comprehensive tests and fixtures have been added.

Possibly related PRs

Suggested labels

code

Suggested reviewers

  • tomasklim
  • MiroslavProchazka
  • FreeWall

🪧 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. (Beta)
  • @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.

@adderpositive
Copy link
Contributor Author

@coderabbitai review

Copy link

coderabbitai bot commented Feb 6, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@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

🔭 Outside diff range comments (1)
packages/suite/src/middlewares/wallet/tradingMiddleware.ts (1)

84-120: Consider implementing retry logic for failed API calls.

The middleware loads data from multiple APIs but doesn't handle retries for failed requests.

Implement retry logic for API calls:

const retryWithBackoff = async (fn: () => Promise<void>, maxRetries = 3) => {
  for (let i = 0; i < maxRetries; i++) {
    try {
      await fn();
      return;
    } catch (error) {
      if (i === maxRetries - 1) throw error;
      await new Promise(resolve => setTimeout(resolve, Math.pow(2, i) * 1000));
    }
  }
};

// Usage in loadPromises
loadPromises.push(
  retryWithBackoff(() =>
    invityAPI.getInfo().then(info => {
      api.dispatch(tradingInfoAction.saveInfo(info));
    })
  )
);
🧹 Nitpick comments (17)
suite-common/trading/src/selectors/tradingSelectors.ts (2)

8-30: Consider reducing coupling with Suite state.

While the type structure is well-defined, being a partial copy of Suite state creates tight coupling. This could lead to maintenance challenges if the Suite state structure changes.

Consider:

  1. Creating an interface that defines only the essential properties needed for trading functionality.
  2. Using type composition to extend the base trading interface when Suite-specific properties are needed.
  3. Adding documentation to explain the relationship between this type and the Suite state.

This would make the trading module more independent and easier to maintain.


32-32: Enhance selector performance with memoization.

The current selector returns the entire state, which could cause unnecessary re-renders. Consider using createSelector from @reduxjs/toolkit to create specific, memoized selectors for better performance.

Example implementation:

import { createSelector } from '@reduxjs/toolkit';

export const selectTradingState = (state: TradingRootState) => state.wallet.trading;
export const selectSelectedAccount = (state: TradingRootState) => state.wallet.selectedAccount;

export const selectTradingWithAccount = createSelector(
  [selectTradingState, selectSelectedAccount],
  (trading, selectedAccount) => ({
    trading,
    selectedAccount,
  })
);
suite-common/trading/src/reducers/buyReducer.ts (3)

24-33: Add JSDoc comments to document state properties.

The interface would benefit from documentation explaining the purpose and lifecycle of each state property.

Add JSDoc comments to improve maintainability:

 export interface TradingBuyState {
+    /** Buy information including supported currencies and provider details */
     buyInfo?: BuyInfo;
+    /** Flag indicating if the current state resulted from a redirect */
     isFromRedirect: boolean;
+    /** Current quote request parameters */
     quotesRequest?: BuyTradeQuoteRequest;
+    /** List of available quotes matching the current request */
     quotes: BuyTrade[] | undefined;
+    /** Currently selected quote for processing */
     selectedQuote: BuyTrade | undefined;
+    /** Address that has been verified for the current transaction */
     addressVerified: string | undefined;
+    /** Unique identifier for the current transaction */
     transactionId?: string;
 }

35-43: Consider using null instead of undefined for optional fields.

Redux serialization works better with null than undefined. Using null also makes it easier to distinguish between "not set" and "intentionally empty" states.

 export const buyInitialState: TradingBuyState = {
-    transactionId: undefined,
+    transactionId: null,
     isFromRedirect: false,
-    buyInfo: undefined,
+    buyInfo: null,
-    quotesRequest: undefined,
+    quotesRequest: null,
-    selectedQuote: undefined,
+    selectedQuote: null,
     quotes: [],
-    addressVerified: undefined,
+    addressVerified: null,
 };

45-74: Consider adding error handling for edge cases.

The reducer handles the happy path well but could benefit from additional error handling for edge cases.

Consider adding validation and error handling:

 export const prepareBuyReducer = createReducerWithExtraDeps(buyInitialState, builder => {
     builder
         .addCase(tradingBuyActions.saveBuyInfo, (state, { payload }) => {
+            if (!payload) {
+                return;
+            }
             state.buyInfo = payload;
         })
         .addCase(tradingBuyActions.saveQuotes, (state, { payload }) => {
+            if (!Array.isArray(payload)) {
+                state.quotes = [];
+                return;
+            }
             state.quotes = payload;
         })
         // ... other cases
 });
suite-common/trading/src/thunks/tradingThunks.ts (1)

70-72: Use action creators instead of dispatching raw action objects

For consistency and to leverage TypeScript type safety, consider using action creators when dispatching actions. This improves code maintainability and readability.

Apply this diff to use the action creator:

- dispatch({
-     type: tradingAction,
-     payload: address,
- });
+ dispatch(tradingBuyActions.verifyAddress(address));
suite-common/trading/src/reducers/tradingReducer.ts (1)

79-123: Remove commented-out code to improve readability

The reducer contains large blocks of commented-out code, which can clutter the file and reduce readability. If this code is no longer needed, consider removing it. If it's needed for future development, move it to a separate branch or document it elsewhere.

Apply this diff to remove commented-out sections:

         buy: buyInitialState,
-        /*
-        exchange: {
-            exchangeInfo: undefined,
-            transactionId: undefined,
-            quotesRequest: undefined,
-            quotes: [],
-            addressVerified: undefined,
-            tradingAccountKey: undefined,
-            selectedQuote: undefined,
-            isFromRedirect: false,
-        },
-        sell: {
-            sellInfo: undefined,
-            quotesRequest: undefined,
-            quotes: [],
-            selectedQuote: undefined,
-            transactionId: undefined,
-            isFromRedirect: false,
-            tradingAccountKey: undefined,
-        },
-        */
suite-common/trading/src/actions/buyActions.ts (1)

19-42: Type-safe action creators with proper payload handling.

Good use of Redux Toolkit's createAction with explicit typing of payloads. Consider adding JSDoc comments to document the purpose of each action creator.

Example for one action:

+/**
+ * Saves buy information including provider details and supported currencies
+ * @param payload Buy information containing provider and currency details
+ */
 const saveBuyInfo = createAction(SAVE_BUY_INFO, (payload: BuyInfo) => ({ payload }));
suite-common/trading/src/thunks/buyThunks.ts (1)

31-39: Optimize data transformation for better readability and performance.

The current implementation using multiple array operations could be simplified.

Consider using Set operations directly:

-const tradedFiatCurrencies: string[] = [];
-const tradedCoins: CryptoId[] = [];
-buyInfo.providers.forEach(p => {
-    tradedFiatCurrencies.push(...p.tradedFiatCurrencies.map(c => c.toLowerCase()));
-    tradedCoins.push(...p.tradedCoins);
-});
-const supportedFiatCurrencies = new Set(tradedFiatCurrencies);
-const supportedCryptoCurrencies = new Set(tradedCoins);
+const supportedFiatCurrencies = new Set(
+    buyInfo.providers.flatMap(p => p.tradedFiatCurrencies.map(c => c.toLowerCase()))
+);
+const supportedCryptoCurrencies = new Set(
+    buyInfo.providers.flatMap(p => p.tradedCoins)
+);
suite-common/trading/src/thunks/__tests__/tradingThunk.test.ts (1)

73-73: Address TODO comment for additional test coverage.

The TODO comment indicates missing test coverage. I can help implement additional test cases.

Would you like me to generate additional test cases for:

  1. Error scenarios
  2. Edge cases
  3. Different device states?
suite-common/trading/src/thunks/__tests__/buyThunk.test.ts (1)

14-85: Consider adding more test cases for comprehensive coverage.

The test file currently only covers the loadInfoThunk. Consider adding test cases for other thunks that might exist in the buy trading functionality.

suite-common/trading/src/utils.ts (1)

160-174: Improve robustness of route type determination.

The function could be more explicit and handle edge cases better:

  1. Add explicit return type annotation
  2. Add explicit return undefined for unmatched routes
  3. Consider handling invalid route names

Consider this implementation:

 export const getTradeTypeByRoute = (
     routeName: Route['name'] | undefined,
-): TradingType | undefined => {
+): TradingType | undefined => {
+    if (!routeName) {
+        return undefined;
+    }
+
     if (routeName?.startsWith('wallet-trading-buy')) {
         return 'buy';
     }
 
     if (routeName?.startsWith('wallet-trading-sell')) {
         return 'sell';
     }
 
     if (routeName?.startsWith('wallet-trading-exchange')) {
         return 'exchange';
     }
+
+    return undefined;
 };
suite-common/trading/src/reducers/__fixtures__/tradingReducer.ts (3)

13-30: Initial state should be frozen to prevent accidental mutations.

The initial state object is mutable, which could lead to unintended side effects in tests if the object is accidentally modified.

Apply this diff to freeze the initial state:

-const initialState: TradingState = {
+const initialState: TradingState = Object.freeze({
   info: {
     platforms: undefined,
     coins: undefined,
     paymentMethods: [],
   },
   buy: buyInitialState,
   // TODO: sell:
   // TODO: exchange:
   composedTransactionInfo: {},
   trades: [],
   isLoading: false,
   modalAccountKey: undefined,
   modalCryptoId: undefined,
   lastLoadedTimestamp: 0,
   activeSection: 'buy',
   prefilledFromCryptoId: undefined,
-};
+});

32-56: Consider using BigInt for cryptocurrency amounts.

The amounts in the trade fixtures are using strings and floating-point numbers, which can lead to precision issues when dealing with cryptocurrency values.

Consider using BigInt for amounts to ensure precise calculations:

-        fiatStringAmount: '47.12',
+        fiatStringAmount: '4712', // Store as minor units (cents)
-        receiveStringAmount: '0.004705020432603938',
+        receiveStringAmount: '470502043260', // Store as satoshis
-        rate: 10014.834297738,
+        rate: '10014834297738', // Store as minor units

126-335: Enhance test coverage with edge cases.

The test cases cover basic functionality but could be enhanced with edge cases:

  1. Test behavior with invalid/malformed data
  2. Test state transitions with concurrent actions
  3. Test error handling scenarios

Add the following test cases to the tradingFixtures array:

{
  description: 'test handling malformed trade data',
  initialState,
  actions: [{
    type: TRADING_ACTIONS.SAVE_TRADE,
    payload: { ...tradeBuy, data: undefined },
  }],
  result: {
    ...initialState,
    trades: [],
  },
},
{
  description: 'test concurrent loading states',
  initialState: { ...initialState, isLoading: true },
  actions: [
    {
      type: TRADING_ACTIONS.SET_LOADING,
      payload: { isLoading: true, lastLoadedTimestamp: 2 },
    },
    {
      type: TRADING_ACTIONS.SET_LOADING,
      payload: { isLoading: false, lastLoadedTimestamp: 1 },
    },
  ],
  result: {
    ...initialState,
    isLoading: true,
    lastLoadedTimestamp: 2,
  },
}
packages/suite/src/middlewares/wallet/tradingMiddleware.ts (1)

64-69: Consider adding a buffer time to prevent rapid reloading.

The condition for reloading data might trigger too frequently if the timestamp is close to the threshold.

Add a small buffer time to prevent rapid reloading:

-                    lastLoadedTimestamp + INVITY_API_RELOAD_DATA_AFTER_MS < Date.now())
+                    lastLoadedTimestamp + INVITY_API_RELOAD_DATA_AFTER_MS + 1000 < Date.now())
suite-common/trading/src/reducers/__fixtures__/account.ts (1)

3-182: Consider using a factory function for generating account fixtures.

The account fixtures contain a lot of repeated data that could be generated using a factory function.

Create a factory function to generate account fixtures:

const createAccountFixture = (
  accountType: 'normal' | 'taproot',
  path: string,
  descriptor: string,
  key: string
): Account => ({
  deviceState: 'mvbu1Gdy8SUjTenqerxUaZyYjmveZvt33q@C906E19794613145E3DF45F4:0',
  index: 0,
  path,
  descriptor,
  key,
  accountType,
  symbol: 'btc',
  empty: false,
  visible: true,
  balance: '0',
  availableBalance: '0',
  formattedBalance: '0',
  tokens: [],
  addresses: {
    change: [],
    used: [],
    unused: [],
  },
  // ... other common properties
});
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 0a1177d and c82dffc.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (25)
  • packages/suite/src/hooks/wallet/trading/form/common/useTradingInitializer.ts (1 hunks)
  • packages/suite/src/hooks/wallet/trading/form/common/useTradingPreviousRoute.ts (1 hunks)
  • packages/suite/src/middlewares/wallet/tradingMiddleware.ts (1 hunks)
  • packages/suite/src/views/wallet/trading/common/TradingHeader/TradingHeader.tsx (1 hunks)
  • suite-common/trading/jest.config.js (1 hunks)
  • suite-common/trading/package.json (1 hunks)
  • suite-common/trading/src/actions/buyActions.ts (1 hunks)
  • suite-common/trading/src/actions/tradingActions.ts (1 hunks)
  • suite-common/trading/src/constants.ts (1 hunks)
  • suite-common/trading/src/index.ts (1 hunks)
  • suite-common/trading/src/middlewares/tradingMiddleware.ts (1 hunks)
  • suite-common/trading/src/reducers/__fixtures__/account.ts (1 hunks)
  • suite-common/trading/src/reducers/__fixtures__/buyTradingReducer.ts (1 hunks)
  • suite-common/trading/src/reducers/__fixtures__/tradingReducer.ts (1 hunks)
  • suite-common/trading/src/reducers/__tests__/buyReducer.test.ts (1 hunks)
  • suite-common/trading/src/reducers/__tests__/tradingReducer.test.ts (1 hunks)
  • suite-common/trading/src/reducers/buyReducer.ts (1 hunks)
  • suite-common/trading/src/reducers/tradingReducer.ts (1 hunks)
  • suite-common/trading/src/selectors/tradingSelectors.ts (1 hunks)
  • suite-common/trading/src/thunks/__tests__/buyThunk.test.ts (1 hunks)
  • suite-common/trading/src/thunks/__tests__/tradingThunk.test.ts (1 hunks)
  • suite-common/trading/src/thunks/buyThunks.ts (1 hunks)
  • suite-common/trading/src/thunks/tradingThunks.ts (1 hunks)
  • suite-common/trading/src/utils.ts (2 hunks)
  • suite-common/trading/tsconfig.json (1 hunks)
✅ Files skipped from review due to trivial changes (3)
  • suite-common/trading/jest.config.js
  • packages/suite/src/hooks/wallet/trading/form/common/useTradingPreviousRoute.ts
  • suite-common/trading/src/constants.ts
⏰ Context from checks skipped due to timeout of 90000ms (15)
  • GitHub Check: PR-check / node-authorizeCoinjoin cancelCoinjoinAuthorization passphrase unlockPath setBusy checkFirmwareAuthenticity keepSession cancel.test info.test resetDevice-api
  • GitHub Check: PR-check / node-override init-api-flaky
  • GitHub Check: PR-check / web-authorizeCoinjoin cancelCoinjoinAuthorization passphrase unlockPath setBusy checkFirmwareAuthenticity keepSession cancel.test info.test resetDevice-api
  • GitHub Check: PR-check / web-override init-api-flaky
  • GitHub Check: build-deploy
  • GitHub Check: run-desktop-tests (@group=wallet, trezor-user-env-unix bitcoin-regtest)
  • GitHub Check: run-desktop-tests (@group=settings, trezor-user-env-unix bitcoin-regtest)
  • GitHub Check: run-desktop-tests (@group=device-management, trezor-user-env-unix)
  • GitHub Check: build-web
  • GitHub Check: run-desktop-tests (@group=suite, trezor-user-env-unix)
  • GitHub Check: build-web
  • GitHub Check: prepare_android_test_app
  • GitHub Check: Analyze with CodeQL (javascript)
  • GitHub Check: test
  • GitHub Check: Socket Security: Pull Request Alerts
🔇 Additional comments (26)
suite-common/trading/src/selectors/tradingSelectors.ts (1)

1-6: LGTM! Well-organized imports.

The imports are properly scoped and align with the PR objective of moving trading functionality to suite-common.

suite-common/trading/package.json (6)

10-11: Script Updates for Type-Check and Unit Testing:
The addition of "test:unit": "yarn g:jest" alongside the "type-check": "yarn g:tsc --build" command enhances the developer workflow by providing a dedicated unit testing shortcut. Please verify that these commands match your project’s build and test environment.


14-14: Addition of Redux Toolkit Dependency:
Including "@reduxjs/toolkit": "1.9.5" aligns well with the PR’s objective to adopt a Redux-based approach. Ensure that this version meets your project's compatibility and security requirements.


15-15: Integration of Suite-Common Redux Utils:
The new dependency "@suite-common/redux-utils": "workspace:*" is appropriately added to support shared Redux utilities. Make sure that the workspace resolution works as expected within your environment.


17-17: Addition of Wallet Core Dependency:
Adding "@suite-common/wallet-core": "workspace:*" supplements the trading module functions. Confirm that this dependency does not conflict with "@suite-common/wallet-config" and that its integration is properly scoped.


19-19: Inclusion of Trezor Connect Dependency:
Incorporating "@trezor/connect": "workspace:*" supports the necessary interfacing with Trezor functionalities. Please ensure that this workspace dependency delivers the expected API and is consistent with your integration tests.


25-25: Addition of Suite-Common Test Utilities:
The newly added dev dependency "@suite-common/test-utils": "workspace:*" is intended to bolster test coverage for the trading module. Verify its compatibility with your Jest configuration and overall testing strategy.

suite-common/trading/src/reducers/buyReducer.ts (1)

1-13: LGTM! Well-organized imports.

The imports are logically grouped and follow a clear structure, separating external dependencies, internal utilities, and local module imports.

suite-common/trading/src/reducers/tradingReducer.ts (1)

119-123: Handle missing reducers for 'sell' and 'exchange' actions

The default case delegates actions to prepareBuyReducer, but prepareSellReducer and prepareExchangeReducer are commented out. Ensure that reducers for 'sell' and 'exchange' are implemented to handle those actions properly.

Do you plan to implement prepareSellReducer and prepareExchangeReducer? If so, consider adding a timeline or creating tasks to track their development.

suite-common/trading/src/index.ts (1)

6-11: LGTM! Verify that all exported modules are properly implemented.

The new exports follow a logical structure and align with the PR objectives of moving buy trading functionality to suite-common.

Run the following script to verify that all exported modules exist and are properly implemented:

✅ Verification successful

🏁 Script executed:

#!/bin/bash
# Description: Verify that all exported modules exist and are properly implemented.

# Test: Check if all exported modules exist
for module in "reducers/tradingReducer" "middlewares/tradingMiddleware" "actions/tradingActions" "actions/buyActions" "thunks/buyThunks" "thunks/tradingThunks"; do
  if ! fd -t f "$module\.(ts|tsx)$" suite-common/trading/src/; then
    echo "Missing module: $module"
    exit 1
  fi
done

Length of output: 1399


🏁 Script executed:

#!/bin/bash
# Description: Verify that all exported modules exist, are non-empty, and contain at least one 'export' statement, confirming they are properly implemented.

modules=("reducers/tradingReducer" "middlewares/tradingMiddleware" "actions/tradingActions" "actions/buyActions" "thunks/buyThunks" "thunks/tradingThunks")
errors=0

for module in "${modules[@]}"; do
  # Find the file with .ts or .tsx extension for the module
  file=$(fd -t f "$module\.(ts|tsx)$" suite-common/trading/src/ | head -n 1)
  
  if [ -z "$file" ]; then
    echo "Error: Missing file for module '$module'"
    errors=1
    continue
  fi
  
  # Check that the file is non-empty
  if [ ! -s "$file" ]; then
    echo "Error: File '$file' is empty"
    errors=1
    continue
  fi
  
  # Verify the file contains an 'export' statement to ensure proper implementation
  if ! grep -q "export" "$file"; then
    echo "Error: File '$file' does not contain any export statement"
    errors=1
    continue
  fi
  
  echo "Module '$module' found in file '$file' and passes implementation check."
done

if [ $errors -ne 0 ]; then
  exit 1
fi

echo "All modules are properly implemented."

Length of output: 1929


🏁 Script executed:

#!/bin/bash
# Description: Search for exported module files using their basename.

modules=("reducers/tradingReducer" "middlewares/tradingMiddleware" "actions/tradingActions" "actions/buyActions" "thunks/buyThunks" "thunks/tradingThunks")

for module in "${modules[@]}"; do
    name=$(basename "$module")
    echo "Searching for module '$module' using filename pattern '$name.(ts|tsx)$'"
    fd -t f "$name\.(ts|tsx)$" suite-common/trading/src/
    echo "-----------------------------"
done

Length of output: 3082


🏁 Script executed:

#!/bin/bash
# Description: Verify that each exported module (excluding __fixtures__ directories) exists,
# is non-empty, and contains at least one 'export' statement.

modules=("reducers/tradingReducer" "middlewares/tradingMiddleware" "actions/tradingActions" "actions/buyActions" "thunks/buyThunks" "thunks/tradingThunks")
errors=0

for module in "${modules[@]}"; do
  name=$(basename "$module")
  # Exclude __fixtures__ directories to ensure we pick the actual implementation file.
  file=$(fd -t f --exclude __fixtures__ "$name\.(ts|tsx)$" suite-common/trading/src/ | head -n 1)
  
  if [ -z "$file" ]; then
    echo "Error: Missing file for module '$module' (excluding __fixtures__ directories)"
    errors=1
    continue
  fi
  
  if [ ! -s "$file" ]; then
    echo "Error: File '$file' is empty"
    errors=1
    continue
  fi
  
  if ! grep -q "export" "$file"; then
    echo "Error: File '$file' does not contain any 'export' statement"
    errors=1
    continue
  fi
  
  echo "Module '$module' found in file '$file' and passes implementation check."
done

if [ $errors -ne 0 ]; then
  exit 1
fi

echo "All modules are properly implemented."

Length of output: 4619


All exported modules are properly implemented.

  • suite-common/trading/src/reducers/tradingReducer.ts exists and contains export statements.
  • suite-common/trading/src/middlewares/tradingMiddleware.ts exists and is correctly implemented.
  • suite-common/trading/src/actions/tradingActions.ts exists and is properly implemented.
  • suite-common/trading/src/actions/buyActions.ts exists and is correctly implemented.
  • suite-common/trading/src/thunks/buyThunks.ts exists and contains export statements.
  • suite-common/trading/src/thunks/tradingThunks.ts exists and is correctly implemented.
suite-common/trading/src/reducers/__tests__/tradingReducer.test.ts (1)

1-28: LGTM! Consider verifying test coverage.

The test implementation follows Redux testing best practices and uses fixtures for comprehensive test cases.

Run the following script to verify test coverage:

✅ Verification successful

🏁 Script executed:

#!/bin/bash
# Description: Check test coverage for trading reducer.

# Test: Search for all possible action types in the reducer
rg -t ts "case '.*':|createAction\(" suite-common/trading/src/actions/tradingActions.ts suite-common/trading/src/reducers/tradingReducer.ts

# Test: Compare with test fixtures to ensure all actions are covered
rg -t ts "description.*:.*" suite-common/trading/src/reducers/__fixtures__/tradingReducer.ts

Length of output: 2152


Test coverage verification complete:
The shell script outputs confirm that each trading action declared in the reducer (via createAction in tradingActions.ts) has a corresponding fixture test in the test suite. The test fixture descriptions cover actions related to loading symbols info, saving trade details, setting modal and crypto parameters, and more. This indicates comprehensive test coverage.

suite-common/trading/src/reducers/__tests__/buyReducer.test.ts (1)

1-36: LGTM! Consider verifying test coverage.

The test implementation follows Redux testing best practices and uses fixtures for comprehensive test cases.

Run the following script to verify test coverage:

✅ Verification successful

🏁 Script executed:

#!/bin/bash
# Description: Check test coverage for buy trading reducer.

# Test: Search for all possible action types in the reducer
rg -t ts "case '.*':|createAction\(" suite-common/trading/src/actions/buyActions.ts suite-common/trading/src/reducers/buyReducer.ts

# Test: Compare with test fixtures to ensure all actions are covered
rg -t ts "description.*:.*" suite-common/trading/src/reducers/__fixtures__/buyTradingReducer.ts

Length of output: 1866


Test coverage verification confirmed

  • All action types in suite-common/trading/src/actions/buyActions.ts are present in the test fixtures.
  • The fixture file (suite-common/trading/src/reducers/__fixtures__/buyTradingReducer.ts) includes tests for saving buy info, saving quote request, saving transaction detail id, saving quotes, clearing quotes, verifying address, disposing verifying address, saving selected quote, and setting redirect status.
packages/suite/src/hooks/wallet/trading/form/common/useTradingInitializer.ts (1)

3-3: LGTM! Verify the constant value after the move.

The import path change aligns with the PR objectives of moving trading functionality to suite-common.

Run the following script to verify that the constant value remains the same after the move:

✅ Verification successful

🏁 Script executed:

#!/bin/bash
# Description: Compare constant values between old and new locations.

# Test: Search for the constant value in both locations
echo "New location:"
rg -t ts "INVITY_API_RELOAD_QUOTES_AFTER_SECONDS.*=.*" suite-common/trading/src/

echo "Old location:"
rg -t ts "INVITY_API_RELOAD_QUOTES_AFTER_SECONDS.*=.*" packages/suite/src/constants/wallet/trading/metadata

Length of output: 455


Constant preservation verified.
The constant is now defined in suite-common/trading/src/constants.ts with the value of 30, and the old location is no longer present, confirming the proper move as intended.

suite-common/trading/src/actions/buyActions.ts (1)

1-18: Well-structured action types with consistent naming pattern.

The action types follow Redux best practices with:

  • Common prefix for related actions
  • Clear, descriptive names
  • Proper string literal type definitions
suite-common/trading/src/thunks/buyThunks.ts (1)

14-25: Robust error handling with proper fallback values.

Good defensive programming by handling null/undefined cases with sensible defaults.

suite-common/trading/src/thunks/__tests__/tradingThunk.test.ts (1)

29-71: 🛠️ Refactor suggestion

Add test cases for error scenarios.

While the happy path is tested, consider adding tests for:

  • Device not connected
  • Address verification failure
  • Invalid account/address
packages/suite/src/views/wallet/trading/common/TradingHeader/TradingHeader.tsx (1)

3-3: LGTM! Import path updated correctly.

The import path change aligns with the PR objective of moving trading functionality to suite-common.

suite-common/trading/src/thunks/__tests__/buyThunk.test.ts (2)

38-67: LGTM! Test case for successful response is well structured.

The test properly validates the transformation of API response data, especially the conversion of defaultAmountsOfFiatCurrencies from object to Map.


69-84: LGTM! Test case for unsuccessful response handles edge case appropriately.

The test ensures proper fallback behavior when the API returns undefined.

suite-common/trading/src/actions/tradingActions.ts (3)

7-9: Address TODO comments about prefix unification.

The TODO comments suggest technical debt regarding prefix unification. Consider addressing this before the code becomes more established.

Would you like me to help create an issue to track this task?


23-82: LGTM! Action creators are well-structured.

The action creators are properly typed and follow Redux Toolkit best practices.


14-14: Clarify the status of SET_LOADING action.

The comment suggests this action might be deprecated. Either remove if unused or document why it's still needed.

✅ Verification successful

🏁 Script executed:

#!/bin/bash
# Search for usages of SET_LOADING action
rg "SET_LOADING|setLoading" -A 3

Length of output: 27814


Action is actively used within trading state management—do not remove the SET_LOADING constant.
The grep search confirms that the action is used in multiple places (middleware, reducers, and tests) in the trading module. Instead of deprecating it, the code should update the comment to document its role clearly.

suite-common/trading/src/reducers/__fixtures__/buyTradingReducer.ts (1)

6-196: LGTM! Comprehensive test fixtures with realistic data.

The fixtures cover all major actions with clear descriptions and realistic mock data. The test cases are well-structured and maintainable.

packages/suite/src/middlewares/wallet/tradingMiddleware.ts (1)

3-7: LGTM! Imports are correctly organized.

The imports are well-organized, with related trading utilities grouped together.

suite-common/trading/src/reducers/__fixtures__/account.ts (1)

184-424: Consider adding validation for transaction amounts.

The transaction fixtures contain amounts that should be validated to ensure they're within reasonable ranges.

✅ Verification successful

🏁 Script executed:

#!/bin/bash
# Description: Check if transaction amounts are validated in the codebase.

# Test: Search for amount validation logic
rg -A 5 'amount.*validate|validate.*amount' --type ts

Length of output: 2963


Validation for transaction amounts is already handled in the relevant processing logic.

The fixture file is used to supply static sample data for tests. We’ve confirmed that validation of transaction amounts is performed in other parts of the codebase—for example, in the API logic in packages/connect/src/api/bitcoin/outputs.ts as well as in the UI components like in CoinControl (packages/suite/src/views/wallet/send/Options/BitcoinOptions/CoinControl/CoinControl.tsx). Since the fixtures are well-defined test inputs and the runtime validation occurs during transaction processing, there is no need to add additional validation logic directly within these fixtures.

suite-common/trading/tsconfig.json (1)

5-12: LGTM! Dependencies are properly organized.

The references are correctly ordered and include all necessary dependencies for the trading module.

Comment on lines +14 to +22
// TODO: refactor Map and Set https://redux.js.org/style-guide/#do-not-put-non-serializable-values-in-state-or-actions
export interface BuyInfo {
buyInfo: Omit<BuyListResponse, 'defaultAmountsOfFiatCurrencies'> & {
defaultAmountsOfFiatCurrencies: TradingFiatCurrenciesProps;
};
providerInfos: { [name: string]: BuyProviderInfo };
supportedFiatCurrencies: Set<string>;
supportedCryptoCurrencies: Set<CryptoId>;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Address TODO: Replace Set with serializable alternatives.

The use of Set for currency collections violates Redux's serialization requirements. This could cause issues with state persistence and time-travel debugging.

Replace Set with arrays and consider using a selector for Set-like operations:

 export interface BuyInfo {
     buyInfo: Omit<BuyListResponse, 'defaultAmountsOfFiatCurrencies'> & {
         defaultAmountsOfFiatCurrencies: TradingFiatCurrenciesProps;
     };
     providerInfos: { [name: string]: BuyProviderInfo };
-    supportedFiatCurrencies: Set<string>;
-    supportedCryptoCurrencies: Set<CryptoId>;
+    supportedFiatCurrencies: string[];
+    supportedCryptoCurrencies: CryptoId[];
 }

Add a selector for Set-like operations:

// selectors.ts
export const selectSupportedFiatCurrencies = (state: RootState) => 
  new Set(state.trading.buy.buyInfo?.supportedFiatCurrencies);

});
} else {
// special case: device no-backup permissions not granted
if (response.payload.code === 'Method_PermissionsNotGranted') return;
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Safely access response.payload.code to prevent undefined errors

To avoid potential runtime errors when response.payload is undefined, safely access response.payload.code using optional chaining.

Apply this diff to ensure safe access:

- if (response.payload.code === 'Method_PermissionsNotGranted') return;
+ if (response.payload?.code === 'Method_PermissionsNotGranted') return;
📝 Committable suggestion

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

Suggested change
if (response.payload.code === 'Method_PermissionsNotGranted') return;
if (response.payload?.code === 'Method_PermissionsNotGranted') return;

Comment on lines +35 to +37
address = address ?? accountAddress.address;
path = path ?? accountAddress.path;
if (!path || !address) return;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Check for undefined accountAddress before accessing its properties

If getUnusedAddressFromAccount(account) returns undefined, accessing accountAddress.address or accountAddress.path may cause a runtime error. Ensure that accountAddress is defined before accessing its properties.

Apply this diff to prevent potential runtime errors:

 const accountAddress = getUnusedAddressFromAccount(account);
+if (!accountAddress) return;
 address = address ?? accountAddress.address;
 path = path ?? accountAddress.path;
📝 Committable suggestion

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

Suggested change
address = address ?? accountAddress.address;
path = path ?? accountAddress.path;
if (!path || !address) return;
const accountAddress = getUnusedAddressFromAccount(account);
if (!accountAddress) return;
address = address ?? accountAddress.address;
path = path ?? accountAddress.path;
if (!path || !address) return;

Comment on lines +81 to +85
const routeName = newState.router.route?.name;
const isBuy = routeName === 'wallet-trading-buy';
const isSell = routeName === 'wallet-trading-sell';
const isExchange = routeName === 'wallet-trading-exchange';

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Ensure that route and route.name are defined before accessing them

Accessing newState.router.route?.name and state.router.route?.name without checking if router.route is defined may lead to runtime errors if route is undefined. Add checks to handle cases where route may not be available.

Apply this diff to ensure safe access:

 const routeName = newState.router.route?.name;
+if (!routeName) return;
 const isBuy = routeName === 'wallet-trading-buy';
 const isSell = routeName === 'wallet-trading-sell';
 const isExchange = routeName === 'wallet-trading-exchange';
📝 Committable suggestion

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

Suggested change
const routeName = newState.router.route?.name;
const isBuy = routeName === 'wallet-trading-buy';
const isSell = routeName === 'wallet-trading-sell';
const isExchange = routeName === 'wallet-trading-exchange';
const routeName = newState.router.route?.name;
if (!routeName) return;
const isBuy = routeName === 'wallet-trading-buy';
const isSell = routeName === 'wallet-trading-sell';
const isExchange = routeName === 'wallet-trading-exchange';

Comment on lines +25 to +27
export const tradingMiddleware = createMiddleware(async (action, { dispatch, next, getState }) => {
const state = selectState(getState());
const { isLoading, lastLoadedTimestamp } = state.wallet.trading;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Correct the import or usage of tradingActions to avoid confusion

The usage of tradingActions.tradingActions can be confusing and may lead to errors. Ensure that the import and usage of tradingActions are consistent.

Apply this diff to correct the import and usage:

- import * as tradingActions from '../actions/tradingActions';
+ import { tradingActions } from '../actions/tradingActions';

Now, you can use tradingActions.setLoading(true) directly without redundancy.

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

Comment on lines +101 to +110
const paymentMethods: TradingPaymentMethodListProps[] = [
{
value: '',
label: '',
},
{
value: 'creditCard',
label: 'Credit Card',
},
];
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Add validation for payment method values.

The payment methods array includes an empty value/label pair which could cause issues in the UI.


🏁 Script executed:

#!/bin/bash
# Description: Check if empty payment methods are handled correctly in the UI.

# Test: Search for payment method handling in the UI components
rg -A 5 'paymentMethods.*map' --type ts

Length of output: 41


I'll generate a new shell script to search for all references to paymentMethods across the repository (with context) so we can better understand how and where these values (including the empty ones) are used.


🏁 Script executed:

#!/bin/bash
# Searching for all occurrences of paymentMethods with context to see its usage across the codebase:
rg -i -C 5 'paymentMethods'

Length of output: 64889


Action: Remove or Validate Empty Payment Method Entry

Our investigation confirms that the fixture file

suite-common/trading/src/reducers/__fixtures__/tradingReducer.ts

includes an entry with an empty value/label pair. When this fixture is used, no additional validation prevents an empty payment method from propagating into the UI. Although some UI logic (e.g., in the dropdown’s label formatter) conditionally skips rendering components for empty values, there is no explicit check preventing empty values from ever being set or used, which could lead to unexpected UI behavior.

Recommendations:

  • Filter or Remove Empty Entries: Update the fixture (and any similar logic producing payment method lists) to filter out entries with empty strings for both value and label.
  • Add Validation Logic: Enhance the business logic (for example, in the hook or the component that builds the payment methods list) to validate that each payment method is a non-empty, valid option before it reaches the UI.

Copy link

github-actions bot commented Feb 6, 2025

🚀 Expo preview is ready!

  • Project → trezor-suite-preview
  • Platforms → android, ios
  • Scheme → trezorsuitelite
  • Runtime Version → 24
  • More info

Learn more about 𝝠 Expo Github Action

@adderpositive adderpositive requested a review from izmy February 7, 2025 13:03
@adderpositive adderpositive force-pushed the chore/move-buy-trading-state-in-common branch from fffe3d1 to d69d585 Compare February 7, 2025 13:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
chore +Invity Related to Invity project
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Duplicate trade contexts with redux state in suite-common
1 participant