Skip to content

Conversation

@limitofzero
Copy link
Contributor

@limitofzero limitofzero commented Dec 1, 2025

Summary

Updated supported network list for bff (regarding migration to alchemy) and added disabling retries for "Unsupported network Id error"

To Test

  1. Switch on the flag bffBalanceEnabledPercentage in launchdarkly for dev env

pls make sure that the value for the flag is 100, otherwise you have a chance no to enable the api:

image
  • Check that balances updates correctly
  • Check that balances also updates in time (without cache)
  • Check that custom tokens are also supported by the API

Summary by CodeRabbit

  • New Features

    • Added a guide banner in token search with instructions on adding custom tokens.
    • Added dynamic tracking of unsupported blockchain networks to stop futile requests.
  • Bug Fixes

    • Improved error handling for balance fetches to avoid unnecessary retries on unsupported chains.
    • Stopped background requests for chains marked unsupported to reduce noise and failures.

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

@vercel
Copy link

vercel bot commented Dec 1, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Updated (UTC)
cowfi Ready Ready Preview Dec 11, 2025 8:12pm
explorer-dev Ready Ready Preview Dec 11, 2025 8:12pm
swap-dev Ready Ready Preview Dec 11, 2025 8:12pm
widget-configurator Ready Ready Preview Dec 11, 2025 8:12pm
2 Skipped Deployments
Project Deployment Preview Updated (UTC)
cosmos Ignored Ignored Dec 11, 2025 8:12pm
sdk-tools Ignored Ignored Preview Dec 11, 2025 8:12pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 1, 2025

Walkthrough

Adds explicit unsupported-chain handling for BFF balance fetching: new UnsupportedChainError type, atom-based tracking of unsupported chain IDs, a runtime hook to check BFF support, SWR retry suppression for unsupported-chain errors, and updated fetch hook/updater and tests to use the new flow.

Changes

Cohort / File(s) Summary
Unsupported Chain Error Infrastructure
libs/balances-and-allowances/src/utils/UnsupportedChainError.ts
New UnsupportedChainError class and isUnsupportedChainError type guard exported.
BFF Balance State Management
libs/balances-and-allowances/src/state/isBffFailedAtom.ts
Added bffUnsupportedChainsAtom: atom(new Set<SupportedChainId>()) and useAddUnsupportedChainId() hook to record unsupported chain IDs.
BFF Network Support Checks
libs/balances-and-allowances/src/utils/isBffSupportedNetwork.ts
Added useIsBffSupportedNetwork(chainId) hook that combines static unsupported-network list (now excludes LENS) with dynamic bffUnsupportedChainsAtom overrides.
SWR Retry Behavior
libs/balances-and-allowances/src/constants/bff-balances-swr-config.ts
onErrorRetry signature updated to accept error; early return if error indicates unsupported chain (via isUnsupportedChainMessage), otherwise retains exponential backoff retry.
BFF Balance Fetching Hook
libs/balances-and-allowances/src/hooks/usePersistBalancesFromBff.ts
Replaced static check with useIsBffSupportedNetwork; added parsing helpers and handleBffError; detect/throw UnsupportedChainError and call useAddUnsupportedChainId when detected; adjusted fetch logic and error propagation.
Updater Rendering Guard
libs/balances-and-allowances/src/updaters/BalancesAndAllowancesUpdater.tsx
Added isBffSupported = useIsBffSupportedNetwork(chainId) and require both isBffEnabled and isBffSupported before rendering BalancesBffUpdater.
Tests
libs/balances-and-allowances/src/hooks/usePersistBalancesFromBff.test.tsx
Tests updated to initialize bffUnsupportedChainsAtom, use UnsupportedChainError, add tests for unsupported-chain detection, atom updates, and request suppression when chain is marked unsupported.
Localization
apps/cowswap-frontend/src/locales/en-US.po
Updated translation references to TokenSearchContent/useSearchRows.ts and added new GuideBanner translation entry.

Sequence Diagram(s)

sequenceDiagram
    participant Updater as BalancesAndAllowancesUpdater
    participant Hook as usePersistBalancesFromBff
    participant SWR as SWR (bff-balances config)
    participant BFF as BFF API
    participant State as bffUnsupportedChainsAtom

    Updater->>Hook: mount/use with chainId
    Hook->>Hook: call useIsBffSupportedNetwork(chainId)
    alt chain marked unsupported (static or atom)
        Hook->>SWR: supply null key (skip request)
    else supported
        Hook->>SWR: start fetch (key -> getBffBalances)
        SWR->>BFF: HTTP request
        BFF-->>SWR: response (OK or error)
        SWR-->>Hook: deliver response/error
        alt UnsupportedChainError detected
            Hook->>State: useAddUnsupportedChainId(chainId)
            SWR->>SWR: onErrorRetry detects unsupported message -> early return (no retry)
        else other error
            SWR->>SWR: onErrorRetry schedules exponential backoff retry
        else success
            Hook->>Hook: parse and persist balances
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Review error-class/type-guard correctness in UnsupportedChainError.ts.
  • Validate useAddUnsupportedChainId Set immutability and hook dependencies in isBffFailedAtom.ts.
  • Verify useIsBffSupportedNetwork combines static checks and atom state correctly.
  • Confirm onErrorRetry early exit only suppresses retries for unsupported-chain messages and preserves backoff for other errors.
  • Check tests for correct initialization of atom state and reliable assertions using waitFor.

Possibly related PRs

Suggested reviewers

  • shoom3301
  • elena-zh

Poem

🐰 I hopped through code to find the snare,

Unsupported chains caught in my care.
I plant them in atoms, a tidy little bed,
No noisy retries, just sleep instead.
Hooray — balances fetch with fewer dread.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
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
Title check ✅ Passed The title accurately describes the two main changes: handling unsupported chain ID BFF errors and updating the supported chain list, matching the core objectives of the pull request.
Description check ✅ Passed The description includes a summary and testing instructions with checkboxes as required by the template, though it lacks the optional background section and issue reference.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/handle-usupported-chain-id-bff

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7e5eaa6 and ee6908d.

📒 Files selected for processing (1)
  • apps/cowswap-frontend/src/locales/en-US.po (7 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/cowswap-frontend/src/locales/en-US.po
⏰ 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). (2)
  • GitHub Check: Setup
  • GitHub Check: Cypress

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.

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)
libs/balances-and-allowances/src/utils/isBffSupportedNetwork.ts (1)

7-8: Consider updating or removing the TODO comment.

The TODO references checking before Plasma launch (2025/10/20), but this PR is implementing dynamic chain support tracking. Consider whether this TODO is still relevant or should be updated to reflect the new dynamic tracking approach.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ce4d7e3 and bf0c235.

📒 Files selected for processing (5)
  • libs/balances-and-allowances/src/constants/bff-balances-swr-config.ts (1 hunks)
  • libs/balances-and-allowances/src/hooks/usePersistBalancesFromBff.ts (4 hunks)
  • libs/balances-and-allowances/src/state/isBffFailedAtom.ts (2 hunks)
  • libs/balances-and-allowances/src/updaters/BalancesAndAllowancesUpdater.tsx (3 hunks)
  • libs/balances-and-allowances/src/utils/isBffSupportedNetwork.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (5)
📚 Learning: 2025-08-05T14:27:05.023Z
Learnt from: alfetopito
Repo: cowprotocol/cowswap PR: 5992
File: libs/wallet/src/web3-react/utils/switchChain.ts:36-38
Timestamp: 2025-08-05T14:27:05.023Z
Learning: In libs/wallet/src/web3-react/utils/switchChain.ts, the team prefers using Record<SupportedChainId, string | null> over Partial<Record<SupportedChainId, string>> for WALLET_RPC_SUGGESTION to enforce that all supported chain IDs have explicit values set, even if some might be null. This ensures compile-time completeness checking.

Applied to files:

  • libs/balances-and-allowances/src/state/isBffFailedAtom.ts
  • libs/balances-and-allowances/src/utils/isBffSupportedNetwork.ts
  • libs/balances-and-allowances/src/hooks/usePersistBalancesFromBff.ts
📚 Learning: 2025-08-08T13:55:17.528Z
Learnt from: shoom3301
Repo: cowprotocol/cowswap PR: 6125
File: libs/tokens/src/state/tokens/allTokensAtom.ts:78-78
Timestamp: 2025-08-08T13:55:17.528Z
Learning: In libs/tokens/src/state/tokens/allTokensAtom.ts (TypeScript/Jotai), the team prefers to wait for token lists to initialize (listsStatesListAtom non-empty) before returning tokens. No fallback to favorites/user-added/native tokens should be used when listsStatesList is empty.

Applied to files:

  • libs/balances-and-allowances/src/state/isBffFailedAtom.ts
  • libs/balances-and-allowances/src/utils/isBffSupportedNetwork.ts
📚 Learning: 2025-08-08T13:56:18.009Z
Learnt from: shoom3301
Repo: cowprotocol/cowswap PR: 6125
File: libs/tokens/src/updaters/TokensListsUpdater/index.tsx:29-31
Timestamp: 2025-08-08T13:56:18.009Z
Learning: In libs/tokens/src/updaters/TokensListsUpdater/index.tsx, the project’s current Jotai version requires using `unstable_getOnInit` (not `getOnInit`) in atomWithStorage options; keep `{ unstable_getOnInit: true }` until Jotai is upgraded.

Applied to files:

  • libs/balances-and-allowances/src/state/isBffFailedAtom.ts
  • libs/balances-and-allowances/src/utils/isBffSupportedNetwork.ts
📚 Learning: 2025-08-12T05:57:08.021Z
Learnt from: shoom3301
Repo: cowprotocol/cowswap PR: 6138
File: libs/hook-dapp-lib/src/hookDappsRegistry.ts:1-1
Timestamp: 2025-08-12T05:57:08.021Z
Learning: The matchHooksToDapps function in libs/hook-dapp-lib/src/utils.ts provides backward compatibility for permit hooks through function selector detection (EIP_2612_PERMIT_SELECTOR and DAI_PERMIT_SELECTOR) rather than dappId matching, making it robust against dappId changes.

Applied to files:

  • libs/balances-and-allowances/src/utils/isBffSupportedNetwork.ts
  • libs/balances-and-allowances/src/hooks/usePersistBalancesFromBff.ts
📚 Learning: 2025-10-13T19:41:31.440Z
Learnt from: limitofzero
Repo: cowprotocol/cowswap PR: 6351
File: apps/cowswap-frontend/src/modules/erc20Approve/containers/TradeApproveModal/useTradeApproveCallback.ts:87-121
Timestamp: 2025-10-13T19:41:31.440Z
Learning: In apps/cowswap-frontend/src/modules/erc20Approve, useApproveCallback returns Promise<TransactionResponse | undefined> and is distinct from useApproveCurrency, which can return Promise<TransactionReceipt | SafeMultisigTransactionResponse>. When reviewing approval flows, verify which hook is actually being used before flagging Safe wallet concerns.

Applied to files:

  • libs/balances-and-allowances/src/updaters/BalancesAndAllowancesUpdater.tsx
  • libs/balances-and-allowances/src/hooks/usePersistBalancesFromBff.ts
🧬 Code graph analysis (4)
libs/balances-and-allowances/src/state/isBffFailedAtom.ts (1)
libs/widget-lib/src/types.ts (1)
  • SupportedChainId (4-4)
libs/balances-and-allowances/src/utils/isBffSupportedNetwork.ts (2)
libs/widget-lib/src/types.ts (1)
  • SupportedChainId (4-4)
libs/balances-and-allowances/src/state/isBffFailedAtom.ts (1)
  • bffUnsupportedChainsAtom (16-16)
libs/balances-and-allowances/src/updaters/BalancesAndAllowancesUpdater.tsx (1)
libs/balances-and-allowances/src/utils/isBffSupportedNetwork.ts (1)
  • useIsBffSupportedNetwork (14-17)
libs/balances-and-allowances/src/hooks/usePersistBalancesFromBff.ts (2)
libs/balances-and-allowances/src/utils/isBffSupportedNetwork.ts (1)
  • useIsBffSupportedNetwork (14-17)
libs/balances-and-allowances/src/state/isBffFailedAtom.ts (2)
  • useSetIsBffFailed (12-14)
  • useAddUnsupportedChainId (18-28)
⏰ 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). (2)
  • GitHub Check: Setup
  • GitHub Check: Cypress
🔇 Additional comments (8)
libs/balances-and-allowances/src/updaters/BalancesAndAllowancesUpdater.tsx (1)

19-19: LGTM! Clean integration of dynamic BFF support check.

The changes correctly integrate the new useIsBffSupportedNetwork hook to add a runtime check before rendering the BFF updater. This ensures that the BFF updater only runs when both the feature is enabled and the network is supported (both statically and dynamically).

Also applies to: 44-44, 76-83

libs/balances-and-allowances/src/state/isBffFailedAtom.ts (1)

4-4: LGTM! Correct immutable Set updates.

The new atom and hook follow proper Jotai patterns. The useAddUnsupportedChainId hook correctly performs immutable updates by creating a new Set only when adding a chain ID that doesn't already exist, which is an efficient optimization.

Also applies to: 16-28

libs/balances-and-allowances/src/constants/bff-balances-swr-config.ts (1)

49-58: LGTM! Proper error-based retry control.

The addition of early-return logic for "unsupported chain" errors is well-implemented:

  • Defensively checks error instanceof Error before accessing .message
  • Uses case-insensitive matching with .toLowerCase()
  • Preserves existing exponential backoff for other error types

This prevents unnecessary retries when the chain is fundamentally unsupported by the BFF.

libs/balances-and-allowances/src/utils/isBffSupportedNetwork.ts (1)

1-2: LGTM! Clean separation of static and dynamic BFF support checks.

The new useIsBffSupportedNetwork hook correctly combines:

  1. Static allowlist checking via isBffSupportedNetwork(chainId) (now only PLASMA)
  2. Dynamic runtime exclusions via the bffUnsupportedChainsAtom

This allows the system to discover and remember unsupported chains at runtime while maintaining a static baseline.

Also applies to: 5-6, 14-17

libs/balances-and-allowances/src/hooks/usePersistBalancesFromBff.ts (4)

29-56: Well-structured error parsing helpers.

The error handling utilities are cleanly separated and work together effectively:

  • isUnsupportedChainError provides centralized detection logic
  • parseErrorResponse safely extracts error messages with type guards
  • parseBffResponse gracefully handles JSON parsing failures
  • handleBffError (typed as never) ensures proper error propagation

64-64: LGTM! Correct hook integration.

Both hooks are properly called at the top level:

  • useIsBffSupportedNetwork replaces the static check with dynamic tracking
  • useAddUnsupportedChainId provides the callback for recording unsupported chains

Also applies to: 67-67


92-101: LGTM! Proper error tracking with complete dependencies.

The effect correctly:

  • Detects unsupported chain errors using the helper function
  • Records the chain ID in the atom for future checks
  • Updates the general failure flag
  • Includes all necessary dependencies (error, setIsBffFailed, addUnsupportedChainId, targetChainId)

The dependency array is complete and the effect will properly track unsupported chains.


144-172: Robust error handling with proper type narrowing.

The getBffBalances updates improve error handling significantly:

  1. Line 155: parseBffResponse gracefully handles JSON parsing failures
  2. Line 158: handleBffError centralizes error logic and ensures consistent error messages
  3. Line 161: The 'balances' in data type guard properly narrows the union type before accessing data.balances
  4. Lines 167-169: Specific re-throw of unsupported chain errors preserves error semantics for the SWR retry logic

The error flow is well-coordinated with the SWR config's retry behavior.

@limitofzero limitofzero changed the title [DRAFT] fix: handle unsupported chain id bff error feat: handle unsupported chain id bff error + update supported chain list Dec 1, 2025
@limitofzero limitofzero requested a review from a team December 1, 2025 16:27
@limitofzero limitofzero self-assigned this Dec 1, 2025
@limitofzero limitofzero requested a review from a team December 1, 2025 16:27
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 (3)
libs/balances-and-allowances/src/hooks/usePersistBalancesFromBff.test.tsx (3)

187-211: Tighten expectations on SWR calls for unsupported chains (optional)

The test correctly verifies that the SWR key is null for an unsupported chain. To guard against regressions where the hook might accidentally call SWR multiple times (e.g. once with a real key and once with null), you could also assert the call count:

-      expect(mockUseSWR).toHaveBeenCalledWith(
+      expect(mockUseSWR).toHaveBeenCalledWith(
         null,
         expect.any(Function),
         BFF_BALANCES_SWR_CONFIG,
       )
+      expect(mockUseSWR).toHaveBeenCalledTimes(1)

Not mandatory, but it would make the intent and constraints of this behavior clearer.


213-239: Consider aligning the hard-coded error message with the actual BFF error string

The test logic and use of waitFor to observe bffUnsupportedChainsAtom updates look good. The only concern is the hard-coded new Error('Unsupported chain'). The PR description mentions an "Unsupported network Id" error from BFF, while this and BFF_BALANCES_SWR_CONFIG.onErrorRetry look for "unsupported chain".

To avoid drift between tests, frontend error handling, and the backend contract, consider:

  • Using a shared constant (e.g. exported from the hook or SWR config) for the substring you match on, and referencing it here, or
  • Updating the message in the test to exactly match the real BFF error text used in production.

This will keep the tests aligned if the backend message changes or was slightly different to begin with.


241-285: Wrapper duplication for unsupported-chain pre-hydration is fine but could be DRYed up

The custom wrapperWithUnsupportedChain correctly pre-hydrates bffUnsupportedChainsAtom with MAINNET and verifies that SWR is called with a null key, which matches the intended behavior.

If you want to reduce duplication with the main wrapper, you could extract a small helper that takes an optional unsupportedChains: Set<SupportedChainId> and returns a wrapper, e.g.:

-  const wrapper = ({ children }: { children: ReactNode }): ReactNode => {
+  const createWrapper =
+    (unsupportedChains: Set<SupportedChainId> = new Set()) =>
+    ({ children }: { children: ReactNode }): ReactNode => {
       const HydrateAtoms = ({ children }: { children: ReactNode }): ReactNode => {
         useHydrateAtoms([
           [balancesAtom, { ... } as BalancesState],
           [balancesUpdateAtom, mockBalancesUpdate],
-          [bffUnsupportedChainsAtom, new Set<SupportedChainId>()],
+          [bffUnsupportedChainsAtom, unsupportedChains],
         ])
         return <>{children}</>
       }
       return (
         <Provider>
           <HydrateAtoms>{children}</HydrateAtoms>
         </Provider>
       )
     }
+
+  const wrapper = createWrapper()

Then this test can use wrapper: createWrapper(new Set([SupportedChainId.MAINNET])). Purely a readability/maintenance improvement; current code is correct.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4a3f1d5 and aad27de.

📒 Files selected for processing (1)
  • libs/balances-and-allowances/src/hooks/usePersistBalancesFromBff.test.tsx (4 hunks)
🧰 Additional context used
🧠 Learnings (6)
📓 Common learnings
Learnt from: alfetopito
Repo: cowprotocol/cowswap PR: 5992
File: libs/wallet/src/web3-react/utils/switchChain.ts:36-38
Timestamp: 2025-08-05T14:27:05.023Z
Learning: In libs/wallet/src/web3-react/utils/switchChain.ts, the team prefers using Record<SupportedChainId, string | null> over Partial<Record<SupportedChainId, string>> for WALLET_RPC_SUGGESTION to enforce that all supported chain IDs have explicit values set, even if some might be null. This ensures compile-time completeness checking.
📚 Learning: 2025-09-25T08:49:32.256Z
Learnt from: shoom3301
Repo: cowprotocol/cowswap PR: 6299
File: apps/cowswap-frontend/src/entities/bridgeProvider/useBridgeSupportedNetworks.test.tsx:62-67
Timestamp: 2025-09-25T08:49:32.256Z
Learning: In the cowswap-frontend codebase, when testing hooks that use multiple bridge providers, both providers are always properly mocked as complete jest.Mocked<BridgeProvider<BridgeQuoteResult>> objects with all required methods stubbed, ensuring no undefined returns that could break the hook logic.

Applied to files:

  • libs/balances-and-allowances/src/hooks/usePersistBalancesFromBff.test.tsx
📚 Learning: 2025-08-08T13:56:18.009Z
Learnt from: shoom3301
Repo: cowprotocol/cowswap PR: 6125
File: libs/tokens/src/updaters/TokensListsUpdater/index.tsx:29-31
Timestamp: 2025-08-08T13:56:18.009Z
Learning: In libs/tokens/src/updaters/TokensListsUpdater/index.tsx, the project’s current Jotai version requires using `unstable_getOnInit` (not `getOnInit`) in atomWithStorage options; keep `{ unstable_getOnInit: true }` until Jotai is upgraded.

Applied to files:

  • libs/balances-and-allowances/src/hooks/usePersistBalancesFromBff.test.tsx
📚 Learning: 2025-09-25T08:48:53.495Z
Learnt from: shoom3301
Repo: cowprotocol/cowswap PR: 6299
File: apps/cowswap-frontend/src/entities/bridgeProvider/useBridgeSupportedNetworks.test.tsx:58-60
Timestamp: 2025-09-25T08:48:53.495Z
Learning: In the cowswap-frontend codebase, when writing SWR tests, the team prefers maximum test isolation by using `provider: () => new Map()` in SWRConfig wrappers, even if it recreates cache on every render, to ensure tests don't share any state.

Applied to files:

  • libs/balances-and-allowances/src/hooks/usePersistBalancesFromBff.test.tsx
📚 Learning: 2025-07-28T16:26:08.051Z
Learnt from: cowdan
Repo: cowprotocol/cowswap PR: 6034
File: apps/cowswap-frontend-e2e/src/e2e/fiat-amounts.test.ts:44-47
Timestamp: 2025-07-28T16:26:08.051Z
Learning: In the cowswap codebase, using trivial placeholder tests like `it('should be true', () => { expect(true).to.be.true })` in e2e test files is an intentional pattern when disabling broken tests to keep CI green while maintaining build efficiency.

Applied to files:

  • libs/balances-and-allowances/src/hooks/usePersistBalancesFromBff.test.tsx
📚 Learning: 2025-08-05T14:27:05.023Z
Learnt from: alfetopito
Repo: cowprotocol/cowswap PR: 5992
File: libs/wallet/src/web3-react/utils/switchChain.ts:36-38
Timestamp: 2025-08-05T14:27:05.023Z
Learning: In libs/wallet/src/web3-react/utils/switchChain.ts, the team prefers using Record<SupportedChainId, string | null> over Partial<Record<SupportedChainId, string>> for WALLET_RPC_SUGGESTION to enforce that all supported chain IDs have explicit values set, even if some might be null. This ensures compile-time completeness checking.

Applied to files:

  • libs/balances-and-allowances/src/hooks/usePersistBalancesFromBff.test.tsx
🧬 Code graph analysis (1)
libs/balances-and-allowances/src/hooks/usePersistBalancesFromBff.test.tsx (5)
libs/balances-and-allowances/src/state/isBffFailedAtom.ts (1)
  • bffUnsupportedChainsAtom (16-16)
libs/widget-lib/src/types.ts (1)
  • SupportedChainId (4-4)
libs/balances-and-allowances/src/hooks/usePersistBalancesFromBff.ts (2)
  • PersistBalancesFromBffParams (21-27)
  • usePersistBalancesFromBff (58-142)
libs/balances-and-allowances/src/constants/bff-balances-swr-config.ts (1)
  • BFF_BALANCES_SWR_CONFIG (27-59)
libs/balances-and-allowances/src/state/balancesAtom.ts (2)
  • balancesAtom (33-33)
  • balancesUpdateAtom (35-37)
⏰ 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). (2)
  • GitHub Check: Cypress
  • GitHub Check: Setup
🔇 Additional comments (1)
libs/balances-and-allowances/src/hooks/usePersistBalancesFromBff.test.tsx (1)

1-1: bffUnsupportedChainsAtom wiring in the test wrapper looks solid

Importing useAtomValue/bffUnsupportedChainsAtom and hydrating the atom to an empty Set in the shared wrapper gives the new tests a clean, predictable starting state without affecting existing cases. No changes needed here.

Also applies to: 17-17, 53-76

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)
apps/cowswap-frontend/src/modules/trade/containers/TradeBasicConfirmDetails/useTradeBasicConfirmDetailsData.ts (1)

20-22: Align isInvertedState type with useState’s readonly tuple

React 18’s useState returns a readonly tuple, while the result interface currently declares a mutable tuple type. To avoid potential TS incompatibilities and better reflect the actual type, consider aligning the interface with useState’s return type:

 interface UseTradeBasicConfirmDetailsDataResult {
-  isInvertedState: [boolean, Dispatch<SetStateAction<boolean>>]
+  isInvertedState: ReturnType<typeof useState<boolean>>
   amountAfterFees: CurrencyAmount<Currency>
   amountAfterSlippage: CurrencyAmount<Currency>

This keeps the contract accurate without changing runtime behavior.

Also applies to: 36-36

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b8eba80 and 3d39709.

📒 Files selected for processing (3)
  • apps/cowswap-frontend/src/modules/trade/containers/TradeBasicConfirmDetails/useTradeBasicConfirmDetailsData.ts (1 hunks)
  • libs/balances-and-allowances/src/hooks/usePersistBalancesFromBff.test.tsx (4 hunks)
  • libs/balances-and-allowances/src/hooks/usePersistBalancesFromBff.ts (4 hunks)
🧰 Additional context used
🧠 Learnings (10)
📓 Common learnings
Learnt from: alfetopito
Repo: cowprotocol/cowswap PR: 5992
File: libs/wallet/src/web3-react/utils/switchChain.ts:36-38
Timestamp: 2025-08-05T14:27:05.023Z
Learning: In libs/wallet/src/web3-react/utils/switchChain.ts, the team prefers using Record<SupportedChainId, string | null> over Partial<Record<SupportedChainId, string>> for WALLET_RPC_SUGGESTION to enforce that all supported chain IDs have explicit values set, even if some might be null. This ensures compile-time completeness checking.
📚 Learning: 2025-08-05T14:27:05.023Z
Learnt from: alfetopito
Repo: cowprotocol/cowswap PR: 5992
File: libs/wallet/src/web3-react/utils/switchChain.ts:36-38
Timestamp: 2025-08-05T14:27:05.023Z
Learning: In libs/wallet/src/web3-react/utils/switchChain.ts, the team prefers using Record<SupportedChainId, string | null> over Partial<Record<SupportedChainId, string>> for WALLET_RPC_SUGGESTION to enforce that all supported chain IDs have explicit values set, even if some might be null. This ensures compile-time completeness checking.

Applied to files:

  • libs/balances-and-allowances/src/hooks/usePersistBalancesFromBff.ts
  • libs/balances-and-allowances/src/hooks/usePersistBalancesFromBff.test.tsx
📚 Learning: 2025-10-13T19:41:31.440Z
Learnt from: limitofzero
Repo: cowprotocol/cowswap PR: 6351
File: apps/cowswap-frontend/src/modules/erc20Approve/containers/TradeApproveModal/useTradeApproveCallback.ts:87-121
Timestamp: 2025-10-13T19:41:31.440Z
Learning: In apps/cowswap-frontend/src/modules/erc20Approve, useApproveCallback returns Promise<TransactionResponse | undefined> and is distinct from useApproveCurrency, which can return Promise<TransactionReceipt | SafeMultisigTransactionResponse>. When reviewing approval flows, verify which hook is actually being used before flagging Safe wallet concerns.

Applied to files:

  • libs/balances-and-allowances/src/hooks/usePersistBalancesFromBff.ts
  • apps/cowswap-frontend/src/modules/trade/containers/TradeBasicConfirmDetails/useTradeBasicConfirmDetailsData.ts
📚 Learning: 2025-08-12T05:57:08.021Z
Learnt from: shoom3301
Repo: cowprotocol/cowswap PR: 6138
File: libs/hook-dapp-lib/src/hookDappsRegistry.ts:1-1
Timestamp: 2025-08-12T05:57:08.021Z
Learning: The matchHooksToDapps function in libs/hook-dapp-lib/src/utils.ts provides backward compatibility for permit hooks through function selector detection (EIP_2612_PERMIT_SELECTOR and DAI_PERMIT_SELECTOR) rather than dappId matching, making it robust against dappId changes.

Applied to files:

  • libs/balances-and-allowances/src/hooks/usePersistBalancesFromBff.ts
📚 Learning: 2025-10-10T20:28:16.565Z
Learnt from: fairlighteth
Repo: cowprotocol/cowswap PR: 6347
File: apps/cowswap-frontend/src/modules/trade/pure/TradeConfirmation/index.tsx:49-49
Timestamp: 2025-10-10T20:28:16.565Z
Learning: In apps/cowswap-frontend/src/modules/trade, TradeConfirmation follows a two-layer architecture: TradeConfirmationView (pure/stateless) in pure/TradeConfirmation/index.tsx renders the UI, while TradeConfirmation (container) in containers/TradeConfirmation/index.tsx wraps it to freeze props during pending trades (via useStableTradeConfirmationProps), wire in signing state (useSigningStep), and inject trade confirmation state (useTradeConfirmState). Consuming modules should import the container TradeConfirmation from 'modules/trade' to preserve this stateful behavior.
<!-- [add_learning]
When reviewing component refactoring in apps/cowswap-frontend/src/modules/trade, recognize the pattern where a pure view component (e.g., TradeConfirmationView) is separated from a stateful container (e.g., TradeConfirmation) that wraps it. The container adds runtime state management (prop stabilization, signing state, etc.) while the view remains testable and composable. Do not flag usages that import th...

Applied to files:

  • apps/cowswap-frontend/src/modules/trade/containers/TradeBasicConfirmDetails/useTradeBasicConfirmDetailsData.ts
📚 Learning: 2025-06-23T07:03:50.760Z
Learnt from: shoom3301
Repo: cowprotocol/cowswap PR: 5859
File: apps/cowswap-frontend/src/modules/tradeQuote/hooks/useTradeQuotePolling.ts:76-82
Timestamp: 2025-06-23T07:03:50.760Z
Learning: In the useTradeQuotePolling hook, there are two useLayoutEffect hooks that work together: one resets the counter to 0 when the confirmation modal closes, and another automatically triggers pollQuote(false, true) whenever the counter reaches 0. This creates an intentional chain reaction for immediate quote updates.

Applied to files:

  • apps/cowswap-frontend/src/modules/trade/containers/TradeBasicConfirmDetails/useTradeBasicConfirmDetailsData.ts
📚 Learning: 2025-08-08T13:56:18.009Z
Learnt from: shoom3301
Repo: cowprotocol/cowswap PR: 6125
File: libs/tokens/src/updaters/TokensListsUpdater/index.tsx:29-31
Timestamp: 2025-08-08T13:56:18.009Z
Learning: In libs/tokens/src/updaters/TokensListsUpdater/index.tsx, the project’s current Jotai version requires using `unstable_getOnInit` (not `getOnInit`) in atomWithStorage options; keep `{ unstable_getOnInit: true }` until Jotai is upgraded.

Applied to files:

  • libs/balances-and-allowances/src/hooks/usePersistBalancesFromBff.test.tsx
📚 Learning: 2025-09-25T08:49:32.256Z
Learnt from: shoom3301
Repo: cowprotocol/cowswap PR: 6299
File: apps/cowswap-frontend/src/entities/bridgeProvider/useBridgeSupportedNetworks.test.tsx:62-67
Timestamp: 2025-09-25T08:49:32.256Z
Learning: In the cowswap-frontend codebase, when testing hooks that use multiple bridge providers, both providers are always properly mocked as complete jest.Mocked<BridgeProvider<BridgeQuoteResult>> objects with all required methods stubbed, ensuring no undefined returns that could break the hook logic.

Applied to files:

  • libs/balances-and-allowances/src/hooks/usePersistBalancesFromBff.test.tsx
📚 Learning: 2025-09-25T08:48:53.495Z
Learnt from: shoom3301
Repo: cowprotocol/cowswap PR: 6299
File: apps/cowswap-frontend/src/entities/bridgeProvider/useBridgeSupportedNetworks.test.tsx:58-60
Timestamp: 2025-09-25T08:48:53.495Z
Learning: In the cowswap-frontend codebase, when writing SWR tests, the team prefers maximum test isolation by using `provider: () => new Map()` in SWRConfig wrappers, even if it recreates cache on every render, to ensure tests don't share any state.

Applied to files:

  • libs/balances-and-allowances/src/hooks/usePersistBalancesFromBff.test.tsx
📚 Learning: 2025-07-28T16:26:08.051Z
Learnt from: cowdan
Repo: cowprotocol/cowswap PR: 6034
File: apps/cowswap-frontend-e2e/src/e2e/fiat-amounts.test.ts:44-47
Timestamp: 2025-07-28T16:26:08.051Z
Learning: In the cowswap codebase, using trivial placeholder tests like `it('should be true', () => { expect(true).to.be.true })` in e2e test files is an intentional pattern when disabling broken tests to keep CI green while maintaining build efficiency.

Applied to files:

  • libs/balances-and-allowances/src/hooks/usePersistBalancesFromBff.test.tsx
🔇 Additional comments (14)
libs/balances-and-allowances/src/hooks/usePersistBalancesFromBff.ts (8)

13-15: LGTM!

Clean imports for the new unsupported chain handling utilities. The modular approach with separate hooks and error types improves maintainability.


17-20: LGTM!

The BalanceResponse type extension with optional message field properly supports the new error response parsing.


30-47: LGTM!

Well-structured helper functions:

  • isUnsupportedChainMessage uses case-insensitive matching for robustness
  • parseErrorResponse safely extracts message with proper null checks
  • parseBffResponse gracefully handles JSON parse failures

This addresses the past review comment about extracting 'Unsupported chain' into a dedicated check.


49-57: LGTM!

The handleBffError function cleanly centralizes error handling and throws UnsupportedChainError when appropriate, addressing the past review feedback about using a custom error class.


65-68: LGTM!

Good integration of the new useIsBffSupportedNetwork hook and useAddUnsupportedChainId callback for tracking unsupported chains at runtime.


93-101: LGTM!

The effect correctly handles unsupported chain errors by:

  1. Checking if error is UnsupportedChainError via type guard
  2. Adding the chain to the unsupported list when detected
  3. Setting the BFF failed state

The useCallback wrapping of addUnsupportedChainId was addressed per past review feedback in the atom hook implementation.


160-164: LGTM!

Proper null safety check for the balances field before returning. The logic correctly returns null when balances are missing rather than throwing, allowing the caller to handle this case gracefully.


153-164: Network errors from fetch propagate uncaught but are properly handled by SWR retry logic.

The fetch call has no try-catch wrapper, so network errors (e.g., TypeError: Failed to fetch) propagate directly to SWR's onErrorRetry handler, which retries with exponential backoff (3 retries × 30s). This is intentional and correct—the onErrorRetry handler specifically checks for "Unsupported chain" errors to prevent retrying them, but network errors have different message text and will retry normally, which is the expected behavior.

libs/balances-and-allowances/src/hooks/usePersistBalancesFromBff.test.tsx (5)

1-18: LGTM!

Clean imports for the new testing requirements. The addition of Provider and useAtomValue from jotai supports the new atom-based unsupported chain tracking tests.


54-77: LGTM!

Good test setup with proper hydration of all relevant atoms including the new bffUnsupportedChainsAtom. The use of Provider with useHydrateAtoms pattern ensures maximum test isolation, which aligns with team preferences.


188-212: LGTM!

Good test case verifying that unsupported networks (SEPOLIA) result in a null SWR key, preventing unnecessary network requests.


214-240: LGTM!

Well-structured test that:

  1. Creates an UnsupportedChainError instance
  2. Mocks SWR to return this error
  3. Uses a helper hook to access both the hook under test and the atom value
  4. Verifies the chain is added to the unsupported set via waitFor

The 3000ms timeout is reasonable for async effect propagation.


242-286: LGTM!

Comprehensive test that pre-populates the unsupported chains atom with SupportedChainId.MAINNET and verifies that subsequent requests for that chain result in a null SWR key. This validates the "circuit breaker" behavior for known unsupported chains.

apps/cowswap-frontend/src/modules/trade/containers/TradeBasicConfirmDetails/useTradeBasicConfirmDetailsData.ts (1)

31-53: Hook implementation looks solid

The hook cleanly derives post-fee/slippage amounts, conditionally computes USD values without breaking hook rules, memoizes limitPrice with appropriate dependencies, and passes through the network cost suffix props. Aside from the minor isInvertedState typing nit above, this looks good to ship.

networkCostsTooltipSuffix?: ReactNode
}

export function useTradeBasicConfirmDetailsData(
Copy link
Collaborator

Choose a reason for hiding this comment

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

The file is probably should be in this PR. I don't see any places where it is used

Copy link
Contributor Author

Choose a reason for hiding this comment

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

lol, it's from another pr, thank you for catch

onErrorRetry: (_: unknown, __key, config, revalidate, { retryCount }) => {
onErrorRetry: (error: unknown, _key, config, revalidate, { retryCount }) => {
// Don't retry if error is "Unsupported chain"
if (error instanceof Error && error.message.toLowerCase().includes('unsupported chain')) {
Copy link
Collaborator

@shoom3301 shoom3301 Dec 11, 2025

Choose a reason for hiding this comment

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

Should isUnsupportedChainMessage be used here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

thanks, fixed

@shoom3301
Copy link
Collaborator

@limitofzero unfortunately, it doesn't fetch balances from BFF even when bffBalanceEnabledPercentage is enabled.
Tested in https://swap-dev-git-fix-handle-usupported-chain-id-bff-cowswap-dev.vercel.app/

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 (2)
libs/balances-and-allowances/src/hooks/usePersistBalancesFromBff.ts (2)

13-24: Centralized BFF error parsing/handling looks good; consider exposing body message and hardening message extraction

The new flow around BalanceResponse.message, parseBffResponse, handleBffError, and UnsupportedChainError is a nice improvement and keeps unsupported-chain logic contained. A couple of small tweaks could make it more robust and easier to debug:

  • In handleBffError, you compute a richer errorMessage but then drop it for the generic case. Using the parsed message in the thrown error would improve observability without changing behavior for callers:
function handleBffError(res: Response, data: BalanceResponse | { message?: string }): never {
  const errorMessage = parseErrorResponse(data, res.statusText)

  if (isUnsupportedChainMessage(errorMessage)) {
    throw new UnsupportedChainError()
  }

-  throw new Error(`BFF error: ${res.status} ${res.statusText}`)
+  throw new Error(`BFF error: ${res.status} ${errorMessage}`)
}
  • parseErrorResponse currently only recognizes an object with a message field; any other JSON shapes (e.g. { error: 'Unsupported network id' }) or a string body will fall back to statusText, which means isUnsupportedChainMessage may never see the real message. You could make this more resilient without affecting existing behavior:
function parseErrorResponse(data: unknown, statusText: string): string {
-  if (typeof data === 'object' && data !== null && 'message' in data) {
-    return String((data as { message: unknown }).message)
-  }
-  return statusText
+  if (typeof data === 'string') {
+    return data
+  }
+
+  if (typeof data === 'object' && data !== null) {
+    const anyData = data as any
+    if (anyData.message) return String(anyData.message)
+    if (anyData.error) return String(anyData.error)
+  }
+
+  return statusText
}
  • Given the PR description mentions an "Unsupported network Id" error from BFF while isUnsupportedChainMessage (in UnsupportedChainError.ts) currently matches 'unsupported chain', it’s worth double‑checking the actual BFF payload. If the phrase doesn’t include 'unsupported chain', UnsupportedChainError will never be thrown and the unsupported-chains tracking + SWR retry suppression won’t trigger. Consider broadening the matcher (e.g. to also cover 'unsupported network' / 'unsupported network id') once you confirm the BFF response text.

Also applies to: 34-57, 153-165


65-69: Unsupported-chain effect wiring works; consider guarding chainId and narrowing isBffFailed semantics

The integration of useIsBffSupportedNetwork and useAddUnsupportedChainId with isUnsupportedChainError(error) nicely closes the loop between runtime BFF responses and future fetch gating.

Two optional refinements you might consider:

  • Be defensive about targetChainId when adding to the unsupported set. Today types say it’s always a SupportedChainId, but at runtime it’s still derived via chainId ?? activeChainId. A simple guard avoids ever storing an undefined in bffUnsupportedChainsAtom if this hook is misused in the future:
  useEffect(() => {
    const hasUnsupportedChainError = isUnsupportedChainError(error)

-    if (hasUnsupportedChainError) {
-      addUnsupportedChainId(targetChainId)
-    }
-
-    setIsBffFailed(!!error)
+    if (hasUnsupportedChainError && targetChainId) {
+      addUnsupportedChainId(targetChainId)
+    }
+
+    setIsBffFailed(!!error && !hasUnsupportedChainError)
   }, [error, setIsBffFailed, addUnsupportedChainId, targetChainId])
  • Related: if the intended meaning of isBffFailedAtom is “BFF infra is down/misbehaving”, you may not want to flag it for a clean “unsupported chain” response (which is more of a capability/configuration outcome). The adjustment in the diff above (!!error && !hasUnsupportedChainError) keeps failure semantics focused on genuine errors while unsupported networks are tracked exclusively via bffUnsupportedChainsAtom.

These are UX/semantics tweaks rather than correctness issues, so feel free to skip if the current behavior matches existing UI expectations.

Also applies to: 94-101

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3d39709 and 7e5eaa6.

📒 Files selected for processing (3)
  • libs/balances-and-allowances/src/constants/bff-balances-swr-config.ts (2 hunks)
  • libs/balances-and-allowances/src/hooks/usePersistBalancesFromBff.ts (4 hunks)
  • libs/balances-and-allowances/src/utils/UnsupportedChainError.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • libs/balances-and-allowances/src/constants/bff-balances-swr-config.ts
  • libs/balances-and-allowances/src/utils/UnsupportedChainError.ts
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-05T14:27:05.023Z
Learnt from: alfetopito
Repo: cowprotocol/cowswap PR: 5992
File: libs/wallet/src/web3-react/utils/switchChain.ts:36-38
Timestamp: 2025-08-05T14:27:05.023Z
Learning: In libs/wallet/src/web3-react/utils/switchChain.ts, the team prefers using Record<SupportedChainId, string | null> over Partial<Record<SupportedChainId, string>> for WALLET_RPC_SUGGESTION to enforce that all supported chain IDs have explicit values set, even if some might be null. This ensures compile-time completeness checking.

Applied to files:

  • libs/balances-and-allowances/src/hooks/usePersistBalancesFromBff.ts
📚 Learning: 2025-10-13T19:41:31.440Z
Learnt from: limitofzero
Repo: cowprotocol/cowswap PR: 6351
File: apps/cowswap-frontend/src/modules/erc20Approve/containers/TradeApproveModal/useTradeApproveCallback.ts:87-121
Timestamp: 2025-10-13T19:41:31.440Z
Learning: In apps/cowswap-frontend/src/modules/erc20Approve, useApproveCallback returns Promise<TransactionResponse | undefined> and is distinct from useApproveCurrency, which can return Promise<TransactionReceipt | SafeMultisigTransactionResponse>. When reviewing approval flows, verify which hook is actually being used before flagging Safe wallet concerns.

Applied to files:

  • libs/balances-and-allowances/src/hooks/usePersistBalancesFromBff.ts
🧬 Code graph analysis (1)
libs/balances-and-allowances/src/hooks/usePersistBalancesFromBff.ts (3)
libs/balances-and-allowances/src/utils/UnsupportedChainError.ts (3)
  • isUnsupportedChainMessage (12-14)
  • UnsupportedChainError (1-6)
  • isUnsupportedChainError (8-10)
libs/balances-and-allowances/src/utils/isBffSupportedNetwork.ts (1)
  • useIsBffSupportedNetwork (14-17)
libs/balances-and-allowances/src/state/isBffFailedAtom.ts (2)
  • useSetIsBffFailed (13-15)
  • useAddUnsupportedChainId (19-32)
⏰ 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). (2)
  • GitHub Check: Setup
  • GitHub Check: Cypress

@limitofzero
Copy link
Contributor Author

@limitofzero unfortunately, it doesn't fetch balances from BFF even when bffBalanceEnabledPercentage is enabled. Tested in https://swap-dev-git-fix-handle-usupported-chain-id-bff-cowswap-dev.vercel.app/

image I think you wasn't able to check it due to setting for 10% users only

Copy link
Contributor

@elena-zh elena-zh left a comment

Choose a reason for hiding this comment

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

Hey @limitofzero , thank you, works like a charm besides some issues:

  1. could you please check whether it is possible to support Plasma here?
  2. Could you please check the rules for rate-limiting? I've started my tests, checked balances in all networks, and got blocked :(
image
  1. I noticed that balances are not reflected correctly/missing in all networks. The most vivd example is Base:
balances don't match not 0
  1. It might be related to the previous case: after my swap and bridge trade the token's balance has not been updated in the dst chain
received to base

Could you please take a look at these issues?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants