Skip to content

prod changes to staging#1148

Closed
jjramirezn wants to merge 12 commits intopeanut-wallet-devfrom
peanut-wallet
Closed

prod changes to staging#1148
jjramirezn wants to merge 12 commits intopeanut-wallet-devfrom
peanut-wallet

Conversation

@jjramirezn
Copy link
Contributor

No description provided.

Zishan-7 and others added 12 commits August 14, 2025 18:11
* Fix: Iban country detection and incorrect bank acc details

* Fix: update IBAN country validation to use correct locale string comparison

* add validations for US and mexican bank accounts

* fix typo

* fix claim flow and create a reusable function for getting 3 letter code

* fix country code mismatch

* fix: show error below input field

* remove unnecessary checks

* remove unnecessary CLABE check
* feat: lpv2.1

* fix: gigaclouds, font and exchange widget

* fixes and improvements

* remove duplicate export

* remove unused component
* fix: hero section responsiveness issue

* fix: stars position

* fix height on desktop

* remove unused code
* feat: handle send link claims to bank account for peanut users (#1078)

* reafactor: create reusable country list component and use it for all the flows

* feat: reusable user accounts components

* feat: handle different cases based on kyc status for bank claim

* fix: account creation

* chore: add docstring to hooks

* chore: better comments for bank flow manager

* fix: kyc modal closing after tos acceptance issue

* fix: remove bank acc caching from withdraw flow

* fix: update confirm claim modal copy

* fix: remove bank acc caching from claim flow

* fix: navheader title

* remove duplicate debounce code and use `useDebounce` hook instead (#1079)

* Landing page v2.1 (#1089)

* lpv2.1 part 1

* Add exchange widget

* add and integrate exchange API

* add yourMoney component bg

* update landing countries svg

* integrate frankfurter API

* fixes and improvements

* decrease hero section height

* allow max 2 decimal places

* Add `/exchange` route

* fix: overlay

* make destination amount editable and bugg fixes

* some fixes & currency improvements

* crucial commit

* fix checkmark, font size and weight

---------

Co-authored-by: Hugo Montenegro <h@hugo0.com>

* [TASK-13186] refactor: use networkName instead of axelarChainName (#1095)

* refactor: use networkName instead of axelarChainName

* fix: types

* fix: onramp currency (#1096)

* fix: stretched favicon (#1099)

* [TASK-13971] fix: scientific notation in eip681 parsing (#1097)

* fix: scientific notation in eip681 parsing

* fix: qr handling tests

* fix: peanut sdk mock

* pull iban hotfix (#1100)

* fix: claim flow bugs (#1102)

* fix: cross chain claim

* fix: full name issue on confirm bank claim view

* fix: back navigation on desktop views

* Fix back button not working on /profile (#1101)

* Fix back button not working

* fix public profile page

* extract internal navigation logic to utility function

* fix: send link claims to us bank accounts (#1108)

* fix: usa bank account claims

* fix: show bank account details in confirm claim view

* Sync Landing page changes (#1111)

* reduce clouds size and update font

* fix: hero section responsiveness issue

* fix: formatting errors

* add currency animation

* fix: us bank claims after kyc for logged in users (#1112)

* fix: trim account form inputs for spaces (#1114)

* [TASK-14107] fix: don't allow claiming on xChain if route is not found (#1115)

* fix: don't allow claiming on xChain if route is not found

* fix(claim): use correct decimals for min receive amount

* feat: handle redirect uri when on unsupported browsers (#1117)

* feat: handle redirect uri when on unsupported browsers

* fix: confirm bank claim ui rows for iban guest claim

* remove animation (#1118)

* fix: formatting

---------

Co-authored-by: Kushagra Sarathe <76868364+kushagrasarathe@users.noreply.github.com>
Co-authored-by: Mohd Zishan <72738005+Zishan-7@users.noreply.github.com>
Co-authored-by: Hugo Montenegro <h@hugo0.com>
* fix: bank claim flow runtime error

* fix: dont have iban as optional

* fix: merge conflicts
* fix: add missing countries

* remove duplicate comment
@vercel
Copy link

vercel bot commented Aug 28, 2025

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

Project Deployment Preview Comments Updated (UTC)
peanut-wallet Ready Ready Preview Comment Aug 28, 2025 6:15pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 28, 2025

Walkthrough

Large refactor introducing a bank-claim flow (ClaimBankFlow), updating types and contexts, adding new country/currency selection UI, implementing an exchange-rate API and hook, migrating chain display names to networkName, revising landing page composition, expanding IBridgeAccount, and multiple supporting hook/utility additions, deletions, and favicon/icon/asset updates.

Changes

Cohort / File(s) Summary
Bank-claim flow refactor
src/context/ClaimBankFlowContext.tsx, src/components/Claim/Link/views/BankFlowManager.view.tsx, src/components/Claim/Link/Initial.view.tsx, src/components/Claim/Link/Onchain/Confirm.view.tsx, src/components/Claim/Link/Onchain/Success.view.tsx, src/components/Common/ActionList.tsx, src/components/Common/CountryListRouter.tsx, src/components/Common/SavedAccountsView.tsx, src/hooks/useDetermineBankClaimType.ts, src/context/contextProvider.tsx, src/hooks/useSavedAccounts.tsx, src/components/Claim/Link/views/Confirm.bank-claim.view.tsx
Replaces GuestFlow with ClaimBankFlow (typed enum steps), adds BankFlowManager orchestrating guest/user/KYC paths, new ActionList/CountryListRouter/SavedAccountsView, integrates saved accounts and KYC, updates claim link views and onchain confirm gating.
Country/currency selection UI
src/components/Common/CountryList.tsx, src/components/Common/CountryListSkeleton.tsx, src/components/AddWithdraw/AddWithdrawRouterView.tsx, src/components/AddWithdraw/AddWithdrawCountriesList.tsx, src/components/AddWithdraw/DynamicBankAccountForm.tsx, src/constants/countryCurrencyMapping.ts, src/components/LandingPage/CurrencySelect.tsx, src/hooks/useGeoLocaion.ts, src/utils/withdraw.utils.ts
Adds geo-aware CountryList with skeleton, router for claim country, refactors add/withdraw countries and form (validation, IBAN/BIC, country code handling), introduces currency mappings and selector, geolocation hook, and withdraw utilities.
Exchange-rate API and tooling
src/app/api/exchange-rate/route.ts, src/hooks/useExchangeRate.ts, scripts/compare-rates.mjs, src/app/exchange/page.tsx
New Next.js GET route for rates (Bridge + Frankfurter fallback), debounced conversion hook, Node script to compare rates, and an Exchange page rendering NoFees.
Landing page recompose
src/app/page.tsx, src/components/LandingPage/hero.tsx, src/components/LandingPage/dropLink.tsx, src/components/LandingPage/RegulatedRails.tsx, src/components/LandingPage/Footer.tsx, src/components/LandingPage/yourMoney.tsx, src/components/LandingPage/noFees.tsx, src/components/LandingPage/index.ts, src/components/LandingPage/imageAssets.tsx, src/components/LandingPage/securityBuiltIn.tsx, src/components/LandingPage/businessIntegrate.tsx (deleted)
Updates Hero API/layout, replaces BusinessIntegrate with DropLink, adds RegulatedRails and Footer, reworks NoFees to interactive converter, restructures YourMoney section, adjusts assets; updates exports.
Chain name migration to networkName
src/app/actions/squid.ts, src/components/Global/TokenSelector/TokenSelector.tsx, src/components/Global/TokenSelector/Components/NetworkListView.tsx, src/components/Global/TokenSelector/Components/TokenListItem.tsx, src/components/AddMoney/views/NetworkSelection.view.tsx, src/components/Withdraw/views/Confirm.withdraw.view.tsx, src/components/Withdraw/views/Initial.withdraw.view.tsx, src/context/tokenSelector.context.tsx, src/context/WithdrawFlowContext.tsx, src/components/Claim/Claim.utils.ts, src/lib/validation/token.test.ts, src/hooks/useTokenChainIcons.ts
Replaces axelarChainName with networkName in types, displays, and contexts; updates return types and props to include networkName; adjusts tests and icon resolution.
Interfaces and actions updates
src/interfaces/interfaces.ts, src/app/actions/external-accounts.ts
Major IBridgeAccount schema overhaul (nested fields, more types), and action return type updated to IBridgeAccount.
QR parsing and tests
src/components/Global/DirectSendQR/utils.ts, src/components/Global/DirectSendQR/__tests__/parseEip681.test.ts
Parses ETH/ERC-20 amounts via formatUnits with token decimals; updates tests to fixed-decimal expectations.
Global utilities and UI tweaks
src/components/Global/ValidatedInput/index.tsx, src/hooks/useUserSearch.ts, src/components/Global/UnsupportedBrowserModal/index.tsx, src/utils/general.utils.ts, src/utils/bridge.utils.ts, src/components/Global/PeanutActionDetailsCard/index.tsx, src/components/0_Bruddle/Button.tsx, src/components/Profile/index.tsx, src/components/Profile/components/PublicProfile.tsx
Debounce hooks adoption, Suspense refactor for modal with redirect_uri copy, add checkIfInternalNavigation and use in back navigation, extend US/USA handling, broaden currency symbol display, mark Button as client.
Icons and assets
src/assets/icons/index.ts, src/components/Global/Icons/Icon.tsx, src/components/Global/Icons/chevron-down.tsx, src/assets/illustrations/index.ts, src/styles/globals.css
Adds multiple bank/payment/social icons, introduces chevron-down icon and export, exports LandingCountries illustration, adds .font-roboto-flex CSS class.
Favicon metadata updates
src/app/(mobile-ui)/claim/page.tsx, src/app/(mobile-ui)/request/pay/page.tsx, src/app/[...recipient]/page.tsx
Changes generateMetadata icons.icon to /favicon.ico.
Guest flow removals
src/components/GuestActions/MethodList.tsx (deleted), src/components/Claim/Link/views/ClaimCountryList.view.tsx (deleted)
Removes legacy guest action and claim country list components.
Miscellaneous
src/app/(mobile-ui)/home/page.tsx, public/game/peanut-game.html, src/utils/__mocks__/peanut-sdk.ts
Home uses ClaimBankFlow reset; HTML paren balancing change; mock TOKEN_DETAILS reshaped to array form.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120+ minutes

Possibly related PRs

Suggested labels

Being worked on

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch peanut-wallet

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbit in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbit 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:
    • @coderabbit gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbit read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbit help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbit ignore or @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbit summary or @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbit or @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

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • 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.

@coderabbitai coderabbitai bot added the Being worked on Being worked on label Aug 28, 2025
@jjramirezn jjramirezn closed this Aug 28, 2025
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: 59

Caution

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

⚠️ Outside diff range comments (24)
public/game/peanut-game.html (1)

1004-1106: Avoid comma-wrapped grouped assignment; revert to simple statements.

The added extra parenthesis turns two top-level assignments into a grouped comma expression. It’s valid JS but reduces readability and can confuse tooling/minifiers. Use plain statements.

-;((Obstacle.MAX_OBSTACLE_LENGTH = 3),
-(Obstacle.prototype = {
+Obstacle.MAX_OBSTACLE_LENGTH = 3
+Obstacle.prototype = {
   /**
    * Initialise the DOM for the obstacle.
    * @param {number} speed
    */
   init: function (speed) {
     …
   },
   …
-}))
+}
src/components/Global/ValidatedInput/index.tsx (1)

95-99: Spinner can get stuck when clearing input

If a validation is in-flight and debouncedValue becomes '', this effect returns early and the previous effect’s finally won’t clear isValidating (guarded by isStale). Result: spinner may remain true until next keystroke.

Apply:

 useEffect(() => {
-        if (debouncedValue === '') {
-            return
-        }
+        if (debouncedValue === '') {
+            // reset state when cleared
+            setIsValid(false)
+            setIsValidating(false)
+            return
+        }
src/hooks/useUserSearch.ts (1)

22-26: Avoid stale searches toggling isSearching

A slow earlier request can flip isSearching to false after a newer one starts. Also, clearing the box should stop the spinner immediately.

-        if (!debouncedValue) {
-            setSearchResults([])
-            return
-        }
+        if (!debouncedValue) {
+            setSearchResults([])
+            setIsSearching(false)
+            return
+        }
...
-            } finally {
-                setIsSearching(false)
-            }
+            } finally {
+                if (currentValueRef.current === debouncedValue) {
+                    setIsSearching(false)
+                }
+            }

Also applies to: 44-46

src/components/Global/UnsupportedBrowserModal/index.tsx (1)

114-131: Hard-coded visible={true} makes the modal unclosable when props.visible is true.
If a parent forces visible=true and allowClose=true, the close button appears but the modal never closes. Derive visibility from state/prop.

Apply this diff:

-    return (
-        <ActionModal
-            visible={true}
+    const isOpen = visible || showInAppBrowserModalViaDetection
+    return (
+        <ActionModal
+            visible={isOpen}
             onClose={handleModalClose}
-            title="Open this link in your browser"
+            title="Unsupported browser"
             description={
-                "Peanut doesn't work inside this app because of browser limitations. To continue, please open this link in your default browser."
+                "Peanut doesn't work inside this app because of browser limitations. To continue, please open this link in your default browser."
             }

Note: The title change aligns with the previously agreed copy for unsupported scenarios.

src/components/LandingPage/imageAssets.tsx (4)

24-29: Fix initial/animate mismatch and include startXOffset in travel distance to avoid jumpy starts and speed drift.

Currently, initial.x and the first keyframe disagree, causing a visible teleport on start, and totalDistance ignores startXOffset, making speed inconsistent across instances.

Apply:

-    // Total travel distance is screen width + image width + offset
-    const totalDistance = vpWidth + imageWidth
+    // Compute start/end so first keyframe equals initial; include offset in distance
+    const startX = side === 'left' ? vpWidth + startXOffset : -imageWidth - startXOffset
+    const endX = side === 'left' ? -imageWidth - startXOffset : vpWidth + startXOffset
+    const distance = Math.abs(endX - startX)
...
-            initial={{
-                x: side === 'left' ? -imageWidth : vpWidth + startXOffset,
-            }}
+            initial={{ x: startX }}
...
-            animate={{
-                x: side === 'left' ? [vpWidth, -imageWidth] : [-imageWidth, vpWidth],
-            }}
+            animate={{ x: [startX, endX] }}
...
-            transition={{
-                ease: 'linear',
-                duration: totalDistance / speed,
-                repeat: Infinity,
-            }}
+            transition={{
+                ease: 'linear',
+                duration: distance / speed,
+                repeat: Infinity,
+            }}

Also applies to: 36-45


17-17: Remove unused duration prop from the type and call sites.

duration is required by the prop type but unused, forcing callers to pass dead data.

-    duration: number
+    // duration removed — duration is derived from distance/speed
-                duration={10}
-                duration={12}
-                duration={15}
-                duration={18}

Also applies to: 58-58, 67-67, 88-88, 98-98


32-35: Decorative cloud images should be hidden from AT and optimized for animation.

Use empty alt text and hide from assistive tech; add willChange to hint the compositor.

-            alt={`Floating Cloud ${side}`}
+            alt=""
+            aria-hidden="true"
...
-            style={{ top, width: imageWidth }}
+            style={{ top, width: imageWidth, willChange: 'transform' }}

121-134: Add alt="" and aria-hidden to decorative star images.

Missing alt attributes cause noisy announcements in screen readers.

             <motion.img
                 initial={{ opacity: 0, translateY: 20, translateX: 5 }}
                 whileInView={{ opacity: 1, translateY: 0, translateX: 0 }}
                 transition={{ type: 'spring', damping: 5 }}
                 src={Star.src}
+                alt=""
+                aria-hidden="true"
                 className="absolute bottom-[-4%] left-[1%] w-8 sm:bottom-[11%] sm:left-[12%] md:bottom-[18%] md:left-[5%] md:w-12"
             />
             <motion.img
                 initial={{ opacity: 0, translateY: 28, translateX: -5 }}
                 whileInView={{ opacity: 1, translateY: 0, translateX: 0 }}
                 transition={{ type: 'spring', damping: 5 }}
                 src={Star.src}
+                alt=""
+                aria-hidden="true"
                 className="absolute right-[1.5%] top-[-12%] w-8 sm:right-[6%] sm:top-[8%] md:right-[5%] md:top-[8%] md:w-12 lg:right-[10%]"
             />
src/components/0_Bruddle/Button.tsx (2)

209-218: Tailwind class bug: dynamic translate-y can purge and also emits “undefinedpx”.
active:translate-y-[${shadowSize}px] won’t be seen by Tailwind JIT and yields invalid class when shadowSize is undefined. Use a static safelisted map.

Apply:

@@
-        const buttonClasses = twMerge(
-            `btn w-full flex items-center gap-2 transition-all duration-100 active:translate-x-[3px] active:translate-y-[${shadowSize}px] active:shadow-none notranslate`,
+        const activeTranslateByShadow: Record<ShadowSize, string> = {
+            '4': 'active:translate-y-[4px]',
+            '6': 'active:translate-y-[6px]',
+            '8': 'active:translate-y-[8px]',
+        }
+
+        const buttonClasses = twMerge(
+            `btn w-full flex items-center gap-2 transition-all duration-100 active:translate-x-[3px] active:shadow-none notranslate`,
             buttonVariants[variant],
             variant === 'transparent' && props.disabled && 'disabled:bg-transparent disabled:border-transparent',
             size && buttonSizes[size],
             shape === 'square' && 'btn-square',
             shadowSize && buttonShadows[shadowType || 'primary'][shadowSize],
+            shadowSize && activeTranslateByShadow[shadowSize],
 
             className
         )

98-101: Ref handling can break with function refs.
Casting ref to RefObject is unsafe; use useImperativeHandle.

-import React, { forwardRef, useEffect, useRef, useState, useCallback } from 'react'
+import React, { forwardRef, useEffect, useRef, useState, useCallback, useImperativeHandle } from 'react'
@@
-        const localRef = useRef<HTMLButtonElement>(null)
-        const buttonRef = (ref as React.RefObject<HTMLButtonElement>) || localRef
+        const localRef = useRef<HTMLButtonElement>(null)
+        useImperativeHandle(ref, () => localRef.current)
+        const buttonRef = localRef
src/utils/general.utils.ts (2)

195-196: Fix early return/breaks that prematurely stop iterating localStorage.

These control-flow statements exit the whole function or loop early, skipping valid entries.

Use continue instead:

-            if (key === `${address} - created links` || key === `${address} - claimed links`) return
+            if (key === `${address} - created links` || key === `${address} - claimed links`) continue
-                        if (startIndex === -1) {
-                            return
-                        }
+                        if (startIndex === -1) {
+                            continue
+                        }
-                    if (!item) break
+                    if (!item) continue
-                    if (!item) break
+                    if (!item) continue
-                    if (!item) break
+                    if (!item) continue

Also applies to: 218-224, 534-536, 600-601, 666-667


855-858: Sorting by timestamp strings yields NaN; parse to Date (or epoch) before comparing.

b.mostRecentInteraction - a.mostRecentInteraction subtracts strings.

Apply:

-        .sort((a, b) => b.mostRecentInteraction - a.mostRecentInteraction)
+        .sort(
+            (a, b) =>
+                new Date(b.mostRecentInteraction).getTime() -
+                new Date(a.mostRecentInteraction).getTime()
+        )
src/utils/bridge.utils.ts (1)

14-18: Normalize countryId once to handle case/variants ('us', 'Usa', etc.).

Prevents subtle bugs and aligns with prior learning that maps use uppercase keys.

-export const getCurrencyConfig = (countryId: string, operationType: BridgeOperationType): CurrencyConfig => {
-    if (countryId === 'US' || countryId === 'USA') {
+export const getCurrencyConfig = (countryId: string, operationType: BridgeOperationType): CurrencyConfig => {
+    const id = countryId.toUpperCase()
+    if (id === 'US' || id === 'USA') {
         return {
             currency: 'usd',
             paymentRail: operationType === 'onramp' ? 'ach_push' : 'ach',
         }
     }
 
-    if (countryId === 'MX') {
+    if (id === 'MX') {
         return {
             currency: 'mxn',
             paymentRail: 'spei', // SPEI works for both onramp and offramp in Mexico
         }
     }

Also update minimums:

-export const getMinimumAmount = (countryId: string): number => {
-    if (countryId === 'MX') {
+export const getMinimumAmount = (countryId: string): number => {
+    const id = countryId.toUpperCase()
+    if (id === 'MX') {
         return 50
     }
     return 1
 }
src/components/AddMoney/components/DepositMethodList.tsx (1)

76-85: Include flagcdn.com in next.config.js images.remotePatterns
Add an entry under images.remotePatterns in next.config.js to allow loading flags from https://flagcdn.com (e.g. { protocol: 'https', hostname: 'flagcdn.com', port: '', pathname: '/w160/**' }) to prevent runtime image failures.

src/interfaces/interfaces.ts (1)

72-79: Duplicate interface name IToken with conflicting shape.

You declare IToken twice; later declaration shadows the earlier (and drops logoURI), causing type conflicts.

 export interface IToken {
     address: string
     name: string
     symbol: string
     decimals: number
     logoURI: string
 }
-
-export interface IToken {
-    chainId: string
-    address: string
-    name: string
-    symbol: string
-    decimals: number
-}
+// If needed, introduce a distinct type instead of shadowing:
+// export interface BasicToken { chainId: string; address: string; name: string; symbol: string; decimals: number }

Also applies to: 113-118

src/components/Claim/Claim.utils.ts (1)

42-51: Populate token decimals from source; 0 breaks formatting/amount math.

                 combinedObject.tokens.push({
                     address: token.address || '',
                     name: token.name || '',
                     symbol: token.symbol || '',
-                    decimals: 0,
+                    decimals: typeof token.decimals === 'number' ? token.decimals : 18,
                     logoURI: token.logoURI || '',
                     chainId: chain.chainId || '',
                 })
src/components/AddMoney/views/NetworkSelection.view.tsx (1)

46-47: Fix “Coming Soon” logic to reflect the configured list

Current logic marks every network except the Peanut Wallet chain as “coming soon,” ignoring TOKEN_SELECTOR_COMING_SOON_NETWORKS. Use the configured list to determine coming soon status.

-                        const isComingSoon = network.chainId !== PEANUT_WALLET_CHAIN.id.toString()
+                        const comingSoonIds = new Set(
+                            TOKEN_SELECTOR_COMING_SOON_NETWORKS.map((n) => String(n.chainId))
+                        )
+                        const isComingSoon = comingSoonIds.has(String(network.chainId))
src/app/actions/external-accounts.ts (2)

13-31: Don’t return unvalidated API responses; parse to IBridgeAccount (and surface coherent errors).

Returning raw data risks shape drift. Parse minimally and fail closed.

 export async function createBridgeExternalAccountForGuest(
   customerId: string,
   accountDetails: AddBankAccountPayload
-): Promise<IBridgeAccount | { error: string }> {
+): Promise<IBridgeAccount | { error: string }> {
   try {
     const response = await fetchWithSentry(`${API_URL}/bridge/customers/${customerId}/external-accounts`, {
       method: 'POST',
       headers: {
         'Content-Type': 'application/json',
         'api-key': API_KEY,
       },
       body: JSON.stringify({ ...accountDetails, reuseOnError: true }), // note: reuseOnError is used to avoid showing errors for duplicate accounts on guest flow
     })
 
-    const data = await response.json()
+    const data = await response.json()
 
     if (!response.ok) {
-      return { error: data.error || 'Failed to create external account.' }
+      return { error: (data && (data.error || data.message)) || 'Failed to create external account.' }
     }
 
-    return data
+    // Narrow the response to the expected shape
+    const parsed = BridgeAccountSchema.safeParse(data)
+    if (!parsed.success) {
+      return { error: 'Invalid response from server.' }
+    }
+    return parsed.data

Add this lightweight schema near the top of the file (outside the diffed range):

import { z } from 'zod'

const BridgeAccountSchema = z
  .object({
    id: z.string(),
    customer_id: z.string(),
    account_owner_name: z.string(),
    account_type: z.enum(['iban', 'us', 'clabe']),
    beneficiary_address_valid: z.boolean(),
  })
  .passthrough()

21-22: Add an idempotency key for the POST to prevent duplicate external-account creation.

Use a stable hash of inputs to guard retries/timeouts.

       headers: {
         'Content-Type': 'application/json',
         'api-key': API_KEY,
+        'Idempotency-Key': `${customerId}:${hashAccount(accountDetails)}`,
       },

Outside this range, define:

import crypto from 'crypto'
function hashAccount(a: AddBankAccountPayload): string {
  return crypto.createHash('sha256').update(JSON.stringify(a)).digest('hex')
}
src/components/Claim/Link/views/Confirm.bank-claim.view.tsx (1)

67-69: formatUnits requires decimals; add a safe fallback to avoid runtime throw.

-                    amount={formatUnits(claimLinkData?.amount ?? 0, claimLinkData?.tokenDecimals) || '0.00'}
+                    amount={formatUnits(
+                        (claimLinkData?.amount as any) ?? 0,
+                        (claimLinkData?.tokenDecimals as number | undefined) ?? 6
+                    ) || '0.00'}

If decimals are always present upstream, assert that and remove the fallback; otherwise this prevents crashes.

src/hooks/useKycFlow.ts (2)

56-66: Fix unsafe optional chaining on username.

user?.user.username will throw if user.user is undefined. Use user?.user?.username.

Apply:

-        username: user?.user.username ?? undefined,
+        username: user?.user?.username ?? undefined,

101-110: Fix missing bracket in fields[iqt_token] query parameter
In src/utils/bridge-accounts.utils.ts, the convertPersonaUrl return string is missing the closing bracket for fields[iqt_token], resulting in an invalid Persona widget URL. Apply this patch:

@@ src/utils/bridge-accounts.utils.ts
-  return `https://bridge.withpersona.com/widget?environment=production&inquiry-template-id=${templateId}&fields[iqt_token=${iqtToken}&iframe-origin=${origin}&redirect-uri=${origin}&fields[developer_id]=${developerId}&reference-id=${referenceId}`
+  return `https://bridge.withpersona.com/widget?environment=production&inquiry-template-id=${templateId}&fields[iqt_token]=${iqtToken}&iframe-origin=${origin}&redirect-uri=${origin}&fields[developer_id]=${developerId}&reference-id=${referenceId}`
src/components/Claim/Link/Onchain/Confirm.view.tsx (1)

87-95: Use route-derived destination chain/token to avoid mismatches.

When executing x-chain claim, use the selected route’s toChain/toToken instead of the selector’s current values.

-                claimTxHash = await claimLinkXchain({
-                    address: recipient ? recipient.address : (address ?? ''),
-                    link: claimLinkData.link,
-                    destinationChainId: selectedChainID,
-                    destinationToken: selectedTokenAddress,
-                })
+                claimTxHash = await claimLinkXchain({
+                    address: recipient ? recipient.address : (address ?? ''),
+                    link: claimLinkData.link,
+                    destinationChainId: selectedRoute.rawResponse.route.params.toChain,
+                    destinationToken: selectedRoute.rawResponse.route.estimate.toToken.address,
+                })
src/components/AddWithdraw/DynamicBankAccountForm.tsx (1)

123-131: Country detection bug for MX/US codes.

Support both 2- and 3-letter forms. Also pass normalized code to isIBANCountry.

-                const isUs = country.toUpperCase() === 'USA'
-                const isMx = country.toUpperCase() === 'MX'
-                const isIban = isUs || isMx ? false : isIBANCountry(country)
+                const countryCode = country.toUpperCase()
+                const isUs = countryCode === 'USA' || countryCode === 'US'
+                const isMx = countryCode === 'MEX' || countryCode === 'MX'
+                const isIban = isUs || isMx ? false : isIBANCountry(countryCode)
🧹 Nitpick comments (61)
src/components/Global/ValidatedInput/index.tsx (2)

48-48: Remove unused previousValueRef

previousValueRef is written but never read; safe to delete.

-    const previousValueRef = useRef(value)
...
-        previousValueRef.current = debouncedValue

Also applies to: 101-101


50-50: Drop unused listId ref

listId is never referenced.

-    const listId = useRef(`datalist-${Math.random().toString(36).substr(2, 9)}`)
src/hooks/useUserSearch.ts (2)

27-49: Optional: add cancellation to save bandwidth

If usersApi.search supports AbortController/Signal, cancel previous requests when debouncedValue changes or on unmount.


39-43: Brittle error check

Matching on '3 characters' couples to server message text. Prefer a typed error shape or status code (e.g., 400 with code 'MIN_LENGTH').

src/utils/__mocks__/peanut-sdk.ts (1)

31-31: Nit: make the mock readonly to catch accidental mutations

Freezing the literal helps TS flag inadvertent writes in tests.

Apply this diff:

-]
+] as const
src/components/0_Bruddle/Button.tsx (2)

103-106: Timer types: prefer env-agnostic ReturnType.
Prevents TS mismatches between Node and DOM.

-        const [pressTimer, setPressTimer] = useState<NodeJS.Timeout | null>(null)
+        const [pressTimer, setPressTimer] = useState<ReturnType<typeof setTimeout> | null>(null)
@@
-        const [progressInterval, setProgressInterval] = useState<NodeJS.Timeout | null>(null)
+        const [progressInterval, setProgressInterval] = useState<ReturnType<typeof setInterval> | null>(null)

242-247: Unify input events with Pointer Events to avoid duplicate mouse/touch flows.
Simplifies logic and avoids double timers on some devices.

-                onMouseDown={longPress ? handlePressStart : undefined}
-                onMouseUp={longPress ? handlePressEnd : undefined}
-                onMouseLeave={longPress ? handlePressCancel : undefined}
-                onTouchStart={longPress ? handlePressStart : undefined}
-                onTouchEnd={longPress ? handlePressEnd : undefined}
-                onTouchCancel={longPress ? handlePressCancel : undefined}
+                onPointerDown={longPress ? handlePressStart : undefined}
+                onPointerUp={longPress ? handlePressEnd : undefined}
+                onPointerCancel={longPress ? handlePressCancel : undefined}
src/components/Global/PeanutActionDetailsCard/index.tsx (1)

159-165: Ensure flagcdn URL uses lowercase ISO alpha-2.

flagcdn expects lowercase codes; uppercased inputs will 404.

-                            src={`https://flagcdn.com/w320/${countryCodeForFlag}.png`}
+                            src={`https://flagcdn.com/w320/${countryCodeForFlag.toLowerCase()}.png`}
src/components/Global/Icons/chevron-down.tsx (1)

3-10: Optional a11y: default to decorative.

Most chevrons are decorative; set aria-hidden by default (overridable via props).

-export const ChevronDownIcon: FC<SVGProps<SVGSVGElement>> = (props) => (
-    <svg width="13" height="8" viewBox="0 0 13 8" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
+export const ChevronDownIcon: FC<SVGProps<SVGSVGElement>> = ({ 'aria-hidden': ariaHidden = true, ...props }) => (
+    <svg width="13" height="8" viewBox="0 0 13 8" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden={ariaHidden} {...props}>
src/components/AddMoney/components/DepositMethodList.tsx (1)

59-60: Minor: remove redundant nullish coalescing

twoLetterCountryCode is always a string; toLowerCase() never returns null/undefined. Drop ?? ''.

-const countryCodeForFlag = twoLetterCountryCode.toLowerCase() ?? ''
+const countryCodeForFlag = twoLetterCountryCode.toLowerCase()
src/hooks/useGeoLocaion.ts (1)

1-7: Optional: expose a reload function

Returning reload() can help callers retry without remounting.

const reload = () => { /* call fetchCountry() */ }
return { countryCode, isLoading, error, reload }

Consider feature-flagging geolocation (env toggle) and adding a fallback (e.g., server-provided country) to reduce dependency on a third-party endpoint.

src/constants/countryCurrencyMapping.ts (1)

9-35: Make the list immutable and type-safe

Prevent accidental mutation and ensure entries satisfy the interface.

-export const countryCurrencyMappings: CountryCurrencyMapping[] = [
+export const countryCurrencyMappings = [
   // ...
-]
-export default countryCurrencyMappings
+] as const satisfies ReadonlyArray<CountryCurrencyMapping>
+export default countryCurrencyMappings
src/components/LandingPage/RegulatedRails.tsx (1)

47-60: Remove unused parameter in createCloudAnimation.

The top parameter is unused; drop it and update call sites.

-    const createCloudAnimation = (side: 'left' | 'right', top: string, width: number, speed: number) => {
+    const createCloudAnimation = (side: 'left' | 'right', width: number, speed: number) => {
...
-                    {...createCloudAnimation('left', '20%', 200, 35)}
+                    {...createCloudAnimation('left', 200, 35)}
...
-                    {...createCloudAnimation('right', '60%', 220, 40)}
+                    {...createCloudAnimation('right', 220, 40)}

Also applies to: 70-71, 77-78

src/hooks/useExchangeRate.ts (1)

131-135: Reset edit state on currency change to avoid stale destination input.

When currencies change, resetting edit state prevents confusing displays.

-    useEffect(() => {
-        setSourceAmount(initialSourceAmount)
-    }, [initialSourceAmount])
+    useEffect(() => {
+        setSourceAmount(initialSourceAmount)
+        setLastEditedField('source')
+        clearDestinationFields()
+    }, [initialSourceAmount, sourceCurrency, destinationCurrency])
scripts/compare-rates.mjs (2)

57-61: Support --arg=value style flags.

Small QoL: accept both “--pairs value” and “--pairs=value”.

 function getArg(name) {
-    const i = params.indexOf(name)
-    if (i === -1) return null
-    return params[i + 1] || null
+    const i = params.indexOf(name)
+    if (i !== -1) return params[i + 1] || null
+    const withEq = params.find((p) => p.startsWith(name + '='))
+    return withEq ? withEq.slice(name.length + 1) : null
 }

133-139: Be forgiving with Local API rate type.

If local API returns a numeric string, it currently shows “-”.

-        const localRate = typeof local?.rate === 'number' ? local.rate : undefined
+        const localRate = typeof local?.rate === 'number' ? local.rate : (local?.rate != null ? Number(local.rate) : undefined)
src/components/LandingPage/hero.tsx (4)

47-49: Questionable positioning on secondary CTA.

Using right:[calc(...)] on a relatively positioned flex item is brittle. Prefer consistent centering via flex utilities.

-const getButtonContainerClasses = (variant: 'primary' | 'secondary') =>
-    `relative z-20 mt-8 md:mt-12 flex flex-col items-center justify-center ${variant === 'primary' ? 'mx-auto w-fit' : 'right-[calc(50%-120px)]'}`
+const getButtonContainerClasses = (variant: 'primary' | 'secondary') =>
+    `relative z-20 mt-8 md:mt-12 flex flex-col items-center justify-center ${variant === 'primary' ? 'mx-auto w-fit' : 'mx-auto w-fit'}`

30-37: Simplify redundant translateX logic.

-    translateX: buttonVisible ? (variant === 'primary' ? 0 : 0) : 20,
+    translateX: buttonVisible ? 0 : 20,

50-52: Remove dead code or use it.

getButtonClasses isn’t used; either wire it into or drop it.

-const getButtonClasses = (variant: 'primary' | 'secondary') =>
-    `${variant === 'primary' ? 'btn bg-white fill-n-1 text-n-1 hover:bg-white/90' : 'btn-yellow'} px-7 md:px-9 py-3 md:py-8 text-base md:text-xl btn-shadow-primary-4`
+// removed unused helper

161-163: Duplicate decorative stars (HeroImages) rendered twice.

Confirm if this duplication is intentional; otherwise remove one to avoid visual clutter and extra paints.

-                <HeroImages />
+                {/* <HeroImages /> */}
@@
-                <HeroImages />
+                <HeroImages />

Also applies to: 181-181

src/interfaces/interfaces.ts (1)

314-341: Potential PII exposure risk.

Account numbers/routing numbers are sensitive. Ensure logging/analytics never include these fields.

src/components/Claim/Claim.utils.ts (3)

4-7: Parameter is non-optional; remove redundant null-guard.

The early return is now dead code.

 export function mapToIPeanutChainDetailsArray(
     data: (_interfaces.SquidChainWithTokens & { networkName: string })[]
 ): _interfaces.CombinedType[] {
-    if (!data) return []

10-35: Prefer actual chain attributes over placeholders.

Hardcoding mainnet: true and empty nativeCurrency may mislead consumers.

-            nativeCurrency: {
-                name: '',
-                symbol: '',
-                decimals: 0,
-            },
+            nativeCurrency: {
+                name: chain.nativeCurrency?.name || '',
+                symbol: chain.nativeCurrency?.symbol || '',
+                decimals: typeof chain.nativeCurrency?.decimals === 'number' ? chain.nativeCurrency.decimals : 18,
+            },
@@
-            mainnet: true,
+            mainnet: chain.isTestnet ? false : true,

61-74: Type sortCrossChainDetails to reduce any[].

-export const sortCrossChainDetails = (details: any[], order: any[], sourceChainId: string) => {
+export const sortCrossChainDetails = <T extends { chainId: string }>(
+  details: T[],
+  order: Array<{ chainId: string }>,
+  sourceChainId: string
+) => {
src/hooks/useTokenChainIcons.ts (1)

47-48: Strengthen fallback chain name to prevent generic 'CHAIN' label.

If networkName is undefined, fall back to other known fields (e.g., axelarChainName, name) before defaulting.

Apply:

-// use networkName if available, otherwise fallback to simple CHAIN
-const resolvedChainName = chainInfo.networkName ? chainInfo.networkName : `CHAIN`
+// Prefer networkName; fallback to other known fields before generic label
+const resolvedChainName =
+  chainInfo.networkName ??
+  (chainInfo as any).axelarChainName ??
+  (chainInfo as any).name ??
+  'CHAIN'
src/components/AddMoney/views/NetworkSelection.view.tsx (2)

51-53: Normalize chainId to a consistent type (string) to avoid subtle mismatches

If supportedSquidChainsAndTokens keys are strings but squidChainDetails.chainId is a number, props/callbacks can drift. Normalize once.

-                                chainId={squidChainDetails?.chainId ?? network.chainId}
+                                chainId={String(squidChainDetails?.chainId ?? network.chainId)}
@@
-                                        chainId: squidChainDetails?.chainId ?? network.chainId,
+                                        chainId: String(squidChainDetails?.chainId ?? network.chainId),

Also applies to: 62-65


60-66: Prevent clicks on “Coming Soon” items

Guard the onClick to no-op when isComingSoon, unless NetworkListItem itself disables interaction.

-                                onClick={() =>
-                                    onNetworkSelect({
-                                        chainId: squidChainDetails?.chainId ?? network.chainId,
-                                        name: squidChainDetails?.networkName ?? network.name,
-                                        iconUrl: squidChainDetails?.chainIconURI ?? network.iconUrl,
-                                    })
-                                }
+                                onClick={() => {
+                                    if (isComingSoon) return
+                                    onNetworkSelect({
+                                        chainId: String(squidChainDetails?.chainId ?? network.chainId),
+                                        name: squidChainDetails?.networkName ?? network.name,
+                                        iconUrl: squidChainDetails?.chainIconURI ?? network.iconUrl,
+                                    })
+                                }}

If NetworkListItem already disables interaction when isComingSoon, ignore this.

src/app/exchange/page.tsx (2)

1-1: Avoid page-level "use client" unless necessary

Let this page be a Server Component and render Client children (Layout/NoFees/Footer) as needed. This improves SSR/streaming and reduces bundle size.

-'use client'
+

6-13: Optional: Add page metadata for SEO

Consider exporting generateMetadata or a static metadata export for title/description of the Exchange page.

src/hooks/useSavedAccounts.tsx (1)

13-20: Tighten filter with includes to reduce branching and ease future extension

Cleaner and less error-prone than multiple ORs.

-    const savedAccounts = useMemo(() => {
-        return (
-            user?.accounts.filter(
-                (acc) => acc.type === AccountType.IBAN || acc.type === AccountType.US || acc.type === AccountType.CLABE
-            ) ?? []
-        )
-    }, [user])
+    const savedAccounts = useMemo(() => {
+        const allowed = [AccountType.IBAN, AccountType.US, AccountType.CLABE] as const
+        return user?.accounts?.filter((acc) => allowed.includes(acc.type)) ?? []
+    }, [user])
src/components/Common/CountryListSkeleton.tsx (1)

14-21: Disable interaction on skeleton items

Prevents hover/click affordances on loading placeholders.

                     <SearchResultCard
                         key={index}
                         title={<div className="h-4 w-24 animate-pulse rounded bg-gray-200" />}
                         position={position}
-                        onClick={() => {}}
+                        onClick={() => {}}
+                        isDisabled
                         leftIcon={<div className="h-8 w-8 animate-pulse rounded-full bg-gray-200" />}
                     />
src/components/Withdraw/views/Initial.withdraw.view.tsx (1)

51-66: Consider capturing review-prep failures in telemetry.

Replace console.error with your centralized logger (Sentry is used elsewhere) so missing details in withdraw review attempts are observable.

-            console.error('Token, chain, or address not selected/entered')
+            Sentry.captureMessage('Withdraw review missing data', {
+              level: 'warning',
+              extra: { selectedTokenData: !!selectedTokenData, selectedChainData: !!selectedChainData, hasRecipient: !!recipient.address }
+            })
src/components/Withdraw/views/Confirm.withdraw.view.tsx (1)

141-141: Harden chain name fallback to avoid blank labels.

If resolvedChainName and networkName are unexpectedly missing, fall back to chainId to prevent empty UI.

-                                    <span className="capitalize">{resolvedChainName || chain.networkName}</span>
+                                    <span className="capitalize">{resolvedChainName || chain.networkName || String(chain.chainId)}</span>
src/context/WithdrawFlowContext.tsx (1)

119-135: Trim unnecessary setter from useMemo deps.

State setters from useState are stable; including them in deps is noise.

         ],
-        [
+        [
             amountToWithdraw,
             currentView,
             withdrawData,
             showCompatibilityModal,
             isPreparingReview,
             paymentError,
             usdAmount,
             isValidRecipient,
             inputChanging,
             recipient,
             error,
             selectedBankAccount,
             showAllWithdrawMethods,
-            setShowAllWithdrawMethods,
             resetWithdrawFlow,
         ]
src/context/tokenSelector.context.tsx (2)

165-166: Use strict equality for chainId checks.

Avoid implicit coercion; normalize both sides to strings.

-                if (!supportedMobulaChains.some((chain) => chain.chainId == chainId)) {
+                if (!supportedMobulaChains.some((chain) => String(chain.chainId) === String(chainId))) {

175-183: Optional: Consider price=0 fallback on fetch miss (parity with Pay flow).

If desired to keep UI flows active when price APIs miss, set selectedTokenPrice to 0 instead of undefined. Confirm this won’t enable flows that should be blocked without a price.

src/app/(mobile-ui)/home/page.tsx (1)

137-159: Deduplicate the two balance-warning effects.

They’re nearly identical but with slightly different conditions; keeping one source reduces drift.

-    // effect for showing balance warning modal
-    useEffect(() => {
-        if (isFetchingBalance || balance === undefined) return
-        ...
-    }, [balance, isFetchingBalance, showIOSPWAInstallModal, showAddMoneyPromptModal])
-
-    // effect for showing balance warning modal
-    useEffect(() => {
-        if (isFetchingBalance || balance === undefined) return
-        ...
-    }, [balance, isFetchingBalance, showIOSPWAInstallModal, showAddMoneyPromptModal])
+    // effect for showing balance warning modal
+    useEffect(() => {
+        if (isFetchingBalance || balance === undefined) return
+        if (typeof window === 'undefined') return
+        const hasSeenBalanceWarning = getFromLocalStorage(`${user!.user.userId}-hasSeenBalanceWarning`)
+        const balanceInUsd = Number(formatUnits(balance, PEANUT_WALLET_TOKEN_DECIMALS))
+        if (
+          balanceInUsd > BALANCE_WARNING_THRESHOLD &&
+          !hasSeenBalanceWarning &&
+          !showIOSPWAInstallModal &&
+          !showAddMoneyPromptModal &&
+          !isPostSignupActionModalVisible
+        ) {
+          setShowBalanceWarningModal(true)
+        }
+    }, [balance, isFetchingBalance, showIOSPWAInstallModal, showAddMoneyPromptModal, isPostSignupActionModalVisible])

Also applies to: 160-182

src/app/actions/external-accounts.ts (3)

5-5: Use a type-only import to avoid unintentionally bundling types at runtime.

-import { IBridgeAccount } from '@/interfaces'
+import type { IBridgeAccount } from '@/interfaces'

7-9: Fail fast if required env vars are missing (avoid non-null assertions).

-const API_KEY = process.env.PEANUT_API_KEY!
-const API_URL = process.env.PEANUT_API_URL!
+const API_KEY = process.env.PEANUT_API_KEY
+const API_URL = process.env.PEANUT_API_URL
+if (!API_KEY || !API_URL) {
+  throw new Error('PEANUT_API_KEY and PEANUT_API_URL must be set on the server.')
+}

32-36: Avoid logging PII in server logs. Mask customer identifiers.

- console.error(`Error creating external account for ${customerId}:`, error)
+ console.error(`Error creating external account for ****${customerId.slice(-4)}:`, error)
src/components/Global/TokenSelector/TokenSelector.tsx (2)

575-577: Handle Drawer open/close correctly and only clear search on close.

Current handler always calls close logic. Respect the next open state.

-            <Drawer open={isDrawerOpen} onOpenChange={closeDrawer}>
+            <Drawer
+                open={isDrawerOpen}
+                onOpenChange={(open) => {
+                    setIsDrawerOpen(open)
+                    if (!open) setTimeout(() => setSearchValue(''), 200)
+                }}
+            >

525-536: Reset token image error when the logo URI changes.

Without reset, a prior error masks new valid icons.

Add this effect nearby:

useEffect(() => {
  setButtonImageError(false)
}, [buttonLogoURI])

Also applies to: 548-548

src/components/Claim/Link/views/Confirm.bank-claim.view.tsx (1)

74-82: Enable copy-to-clipboard for bank identifiers (IBAN/CLABE/account/routing).

Improves UX and reduces retyping errors.

-                    {bankDetails.iban && <PaymentInfoRow label="IBAN" value={bankDetails.iban.toUpperCase()} />}
-                    {bankDetails.bic && <PaymentInfoRow label="BIC" value={bankDetails.bic.toUpperCase()} />}
-                    {bankDetails.clabe && <PaymentInfoRow label="CLABE" value={bankDetails.clabe.toUpperCase()} />}
+                    {bankDetails.iban && (
+                        <PaymentInfoRow label="IBAN" value={bankDetails.iban.toUpperCase()} allowCopy />
+                    )}
+                    {bankDetails.bic && (
+                        <PaymentInfoRow label="BIC" value={bankDetails.bic.toUpperCase()} allowCopy />
+                    )}
+                    {bankDetails.clabe && (
+                        <PaymentInfoRow label="CLABE" value={bankDetails.clabe.toUpperCase()} allowCopy />
+                    )}
                     {bankDetails.accountNumber && (
-                        <PaymentInfoRow label="Account Number" value={bankDetails.accountNumber.toUpperCase()} />
+                        <PaymentInfoRow
+                            label="Account Number"
+                            value={bankDetails.accountNumber.toUpperCase()}
+                            allowCopy
+                        />
                     )}
                     {bankDetails.routingNumber && (
-                        <PaymentInfoRow label="Routing Number" value={bankDetails.routingNumber.toUpperCase()} />
+                        <PaymentInfoRow
+                            label="Routing Number"
+                            value={bankDetails.routingNumber.toUpperCase()}
+                            allowCopy
+                        />
                     )}
src/components/Profile/index.tsx (1)

9-11: Remove unused import after the back-logic change.

-import { checkIfInternalNavigation } from '@/utils'
src/hooks/useKycFlow.ts (2)

129-141: Tighten ToS-accepted -> KYC transition per known invariant.

Given our invariant that a KYC link is always present after ToS acceptance (see Learnings), we can drop the defensive apiResponse?.kycLink check and assert non-null, keeping the “only when ToS is currently shown” guard.

Apply:

-            if (source === 'tos_accepted') {
-                if (wasShowingTos && apiResponse?.kycLink) {
-                    const kycUrl = convertPersonaUrl(apiResponse.kycLink)
+            if (source === 'tos_accepted') {
+                if (wasShowingTos) {
+                    const kycUrl = convertPersonaUrl(apiResponse!.kycLink)
                     setIframeOptions({
                         src: kycUrl,
                         visible: true,
                         closeConfirmMessage: 'Are you sure? Your KYC progress will be lost.',
                     })
                 }
                 // ignore late ToS events when KYC is already open
                 return
             }

150-157: Manual close behavior: confirm intended redirect only for add-flow.

Behavior is sound. If other flows later need custom handling, consider passing an enum for source+context to avoid sprinkling flow checks.

src/components/LandingPage/Footer.tsx (1)

105-115: Use the imported SVG object with next/image (avoid .src).

Passing the StaticImport directly lets Next optimize consistently.

Apply:

-                <Image src={handThumbsUp.src} alt="Hand thumbs up" width={20} height={20} />
+                <Image src={handThumbsUp} alt="Hand thumbs up" width={20} height={20} />
@@
-                    <Image src={handMiddleFinger.src} alt="Hand Middle finger" width={20} height={20} />
+                    <Image src={handMiddleFinger} alt="Hand Middle finger" width={20} height={20} />
@@
-                <Image src={handWaving.src} alt="Hand waving" width={25} height={25} />
+                <Image src={handWaving} alt="Hand waving" width={25} height={25} />
src/components/LandingPage/securityBuiltIn.tsx (1)

12-16: Remove unused titleSvg from Feature.

titleSvg is defined but unused; drop it to reduce noise and prevent dead imports.

Apply:

 interface Feature {
     id: number
     title: string
-    titleSvg: any
     description: string
     iconSrc: any
     iconAlt: string
 }
@@
-        titleSvg: biometricProtection,
@@
-        titleSvg: selfCustodialDesign,
@@
-        titleSvg: kycOnlyWhenRequired,

And remove the now-unused imports:

-import biometricProtection from '@/assets/illustrations/biometric-protection.svg'
-import selfCustodialDesign from '@/assets/illustrations/self-custodial-design.svg'
-import kycOnlyWhenRequired from '@/assets/illustrations/kyc-only-when-required.svg'

Also applies to: 20-46

src/components/LandingPage/yourMoney.tsx (2)

71-71: Improve image semantics and LCP.

Add a descriptive alt and mark this as priority; include responsive sizes.

-                    <Image src={LandingCountries} alt="countries" />
+                    <Image src={LandingCountries} alt="Global availability map" priority sizes="(min-width: 768px) 50vw, 100vw" />

20-48: Remove unused features data (dead code).

The features array isn’t rendered anymore; delete or move to a constants file to avoid drift.

src/components/Common/CountryList.tsx (1)

105-106: Optional: avoid invalid flag URLs when no mapping exists.

If no 2-letter mapping, skip the flag image instead of falling back to country.id.toLowerCase().

Also ensure flagcdn.com is in next.config.js images.domains.

src/utils/withdraw.utils.ts (2)

8-27: Add basic IBAN structure guard.

Avoid false positives by ensuring IBAN starts with two letters; return null otherwise.

 export const getCountryFromIban = (iban: string): string | null => {
     // Remove spaces and convert to uppercase
     const cleanIban = iban.replace(/\s/g, '').toUpperCase()
 
+    // Require at least 2 leading letters
+    if (!/^[A-Z]{2}/.test(cleanIban)) return null
+
     // Extract the first 2 characters as country code
     const countryCode = cleanIban.substring(0, 2)

82-134: Nit: correct function name casing.

Consider renaming to validateMXClabeAccount or validateMXCLABEAccount for consistency with CLABE acronym.

src/components/Claim/Link/views/BankFlowManager.view.tsx (1)

411-420: Name parsing is brittle.

Splitting fullName by space fails for middle/compound names. Consider storing first/last separately in account details or using a safer split with a fallback.

src/components/LandingPage/noFees.tsx (2)

241-251: Optional: flag image fallback.

If a currency has no flag mapping, the remote image 404s. Render the flag only when code exists (or add an onError fallback) to avoid layout shifts.

-                                        <Image
-                                            src={`https://flagcdn.com/w320/${sourceCurrencyFlag}.png`}
-                                            alt={`${sourceCurrencyFlag} flag`}
-                                            width={160}
-                                            height={160}
-                                            className="size-4 rounded-full object-cover"
-                                        />
+                                        {sourceCurrencyFlag && (
+                                            <Image
+                                                src={`https://flagcdn.com/w320/${sourceCurrencyFlag}.png`}
+                                                alt={`${sourceCurrencyFlag} flag`}
+                                                width={160}
+                                                height={160}
+                                                className="size-4 rounded-full object-cover"
+                                            />
+                                        )}

285-296: Apply same optional flag fallback for destination.

-                                        <Image
-                                            src={`https://flagcdn.com/w320/${destinationCurrencyFlag}.png`}
-                                            alt={`${destinationCurrencyFlag} flag`}
-                                            width={160}
-                                            height={160}
-                                            className="size-4 rounded-full object-cover"
-                                        />
+                                        {destinationCurrencyFlag && (
+                                            <Image
+                                                src={`https://flagcdn.com/w320/${destinationCurrencyFlag}.png`}
+                                                alt={`${destinationCurrencyFlag} flag`}
+                                                width={160}
+                                                height={160}
+                                                className="size-4 rounded-full object-cover"
+                                            />
+                                        )}
src/components/Common/ActionList.tsx (3)

100-105: Remove dead switch branch.

'crypto' is not present in ACTION_METHODS; the case can be dropped.

-            case 'crypto':
             case 'exchange-or-wallet':
                 setClaimToExternalWallet(true)
                 break

24-30: Type the icons field precisely.

Avoid any[]. Use a union to match actual usage.

-interface Method {
+interface Method {
     id: string
     title: string
     description: string
-    icons: any[]
+    icons: (string | StaticImageData)[]
     soon: boolean
 }

Add (outside this hunk):

import type { StaticImageData } from 'next/image'

32-58: Move ACTION_METHODS to a constants module to prevent circular deps.

Follow our prior learning for ActionList to centralize constants.

-const ACTION_METHODS: Method[] = [
-  /* ... */
-]

New file suggestion (outside this hunk):

// src/constants/actionlist.consts.ts
import mercadoPagoIcon from '@/assets/payment-apps/mercado-pago.svg'
import binanceIcon from '@/assets/exchanges/binance.svg'
import { METAMASK_LOGO, TRUST_WALLET_SMALL_LOGO } from '@/assets/wallets'

export interface ActionMethod {
  id: string
  title: string
  description: string
  icons: (string | StaticImageData)[]
  soon: boolean
}

export const ACTION_METHODS: ActionMethod[] = [
  {
    id: 'bank',
    title: 'Bank',
    description: 'EUR, USD, ARS (more coming soon)',
    icons: ['https://flagcdn.com/w160/ar.png','https://flagcdn.com/w160/de.png','https://flagcdn.com/w160/us.png'],
    soon: false,
  },
  { id: 'mercadopago', title: 'Mercado Pago', description: 'Instant transfers', icons: [mercadoPagoIcon], soon: true },
  { id: 'exchange-or-wallet', title: 'Exchange or Wallet', description: 'Binance, Coinbase, Metamask and more', icons: [binanceIcon, TRUST_WALLET_SMALL_LOGO, METAMASK_LOGO], soon: false },
]

Then import it here:

import { ACTION_METHODS } from '@/constants/actionlist.consts'
src/components/Claim/Link/Initial.view.tsx (2)

93-94: Remove unused resetClaimBankFlow destructure.

It’s not used; drop to avoid confusion.

-        resetFlow: resetClaimBankFlow,

773-784: DRY: centralize setup redirect logic.

Same pattern exists here and in ActionList; extract to a helper.

Example helper:

// utils/navigation.ts
export function pushSetupWithRedirect(router: ReturnType<typeof useRouter>) {
  const redirectUri = encodeURIComponent(`${window.location.pathname}${window.location.search}${window.location.hash}`)
  saveRedirectUrl()
  router.push(`/setup?redirect_uri=${redirectUri}`)
}

Then replace the inline block with:

pushSetupWithRedirect(router)

Comment on lines +55 to +61
const BRIDGE_API_KEY = process.env.BRIDGE_API_KEY

function getArg(name) {
const i = params.indexOf(name)
if (i === -1) return null
return params[i + 1] || null
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add a global timeout for network calls to avoid hanging CLI runs.

Long-running fetches can stall the script indefinitely. Introduce a configurable timeout.

 const BRIDGE_API_KEY = process.env.BRIDGE_API_KEY
+const TIMEOUT_MS = Number(process.env.RATE_FETCH_TIMEOUT_MS) || 8000
+
+// helper
+async function fetchWithTimeout(input, init = {}, timeoutMs = TIMEOUT_MS) {
+  const controller = new AbortController()
+  const id = setTimeout(() => controller.abort(new Error('Timeout')), timeoutMs)
+  try {
+    return await fetch(input, { ...init, signal: controller.signal })
+  } finally {
+    clearTimeout(id)
+  }
+}
📝 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 BRIDGE_API_KEY = process.env.BRIDGE_API_KEY
function getArg(name) {
const i = params.indexOf(name)
if (i === -1) return null
return params[i + 1] || null
}
const BRIDGE_API_KEY = process.env.BRIDGE_API_KEY
const TIMEOUT_MS = Number(process.env.RATE_FETCH_TIMEOUT_MS) || 8000
// helper
async function fetchWithTimeout(input, init = {}, timeoutMs = TIMEOUT_MS) {
const controller = new AbortController()
const id = setTimeout(() => controller.abort(new Error('Timeout')), timeoutMs)
try {
return await fetch(input, { ...init, signal: controller.signal })
} finally {
clearTimeout(id)
}
}
function getArg(name) {
const i = params.indexOf(name)
if (i === -1) return null
return params[i + 1] || null
}
🤖 Prompt for AI Agents
In scripts/compare-rates.mjs around lines 55 to 61, the script currently
performs network fetches without any timeout which can hang the CLI; add a
configurable global timeout mechanism: read a timeout value from an env var
(e.g., FETCH_TIMEOUT_MS) with a sensible default (e.g., 15000 ms), implement a
helper that wraps fetch using AbortController and a setTimeout to abort the
request when the timeout elapses (clear the timer on success/failure), and
replace direct fetch calls with this fetchWithTimeout helper so all network
calls respect the timeout and the process won't hang indefinitely.

Comment on lines +63 to +78
async function fetchBridge(from, to) {
if (!BRIDGE_API_KEY) {
return { error: 'Missing BRIDGE_API_KEY' }
}
const url = `https://api.bridge.xyz/v0/exchange_rates?from=${from.toLowerCase()}&to=${to.toLowerCase()}`
const res = await fetch(url, {
method: 'GET',
headers: { 'Api-Key': BRIDGE_API_KEY },
})
if (!res.ok) {
return { error: `${res.status} ${res.statusText}` }
}
const data = await res.json()
const { midmarket_rate, buy_rate, sell_rate } = data || {}
return { midmarket_rate, buy_rate, sell_rate }
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Use the timeout helper and tighten error handling for Bridge calls.

Also send Accept to be explicit.

-    const res = await fetch(url, {
+    const res = await fetchWithTimeout(url, {
         method: 'GET',
-        headers: { 'Api-Key': BRIDGE_API_KEY },
+        headers: { 'Api-Key': BRIDGE_API_KEY, 'Accept': 'application/json' },
     })
📝 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
async function fetchBridge(from, to) {
if (!BRIDGE_API_KEY) {
return { error: 'Missing BRIDGE_API_KEY' }
}
const url = `https://api.bridge.xyz/v0/exchange_rates?from=${from.toLowerCase()}&to=${to.toLowerCase()}`
const res = await fetch(url, {
method: 'GET',
headers: { 'Api-Key': BRIDGE_API_KEY },
})
if (!res.ok) {
return { error: `${res.status} ${res.statusText}` }
}
const data = await res.json()
const { midmarket_rate, buy_rate, sell_rate } = data || {}
return { midmarket_rate, buy_rate, sell_rate }
}
async function fetchBridge(from, to) {
if (!BRIDGE_API_KEY) {
return { error: 'Missing BRIDGE_API_KEY' }
}
const url = `https://api.bridge.xyz/v0/exchange_rates?from=${from.toLowerCase()}&to=${to.toLowerCase()}`
const res = await fetchWithTimeout(url, {
method: 'GET',
headers: { 'Api-Key': BRIDGE_API_KEY, 'Accept': 'application/json' },
})
if (!res.ok) {
return { error: `${res.status} ${res.statusText}` }
}
const data = await res.json()
const { midmarket_rate, buy_rate, sell_rate } = data || {}
return { midmarket_rate, buy_rate, sell_rate }
}
🤖 Prompt for AI Agents
In scripts/compare-rates.mjs around lines 63 to 78, the Bridge fetch lacks a
timeout wrapper, explicit Accept header, and robust error handling; update
fetchBridge to use the project timeout helper (wrap the fetch call so it rejects
after the configured timeout), add headers 'Api-Key' and 'Accept:
application/json', and wrap the whole operation in try/catch so network or
timeout errors produce a clear { error: ... } return; additionally validate the
parsed JSON before destructuring and return an error if expected fields are
missing.

Comment on lines +80 to +89
async function fetchFrankfurter(from, to) {
const url = `https://api.frankfurter.app/latest?from=${from}&to=${to}`
const res = await fetch(url, { method: 'GET' })
if (!res.ok) {
return { error: `${res.status} ${res.statusText}` }
}
const data = await res.json()
const rate = data?.rates?.[to]
return { rate, rate_995: typeof rate === 'number' ? rate * 0.995 : undefined }
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Apply timeout and explicit Accept for Frankfurter.

-    const res = await fetch(url, { method: 'GET' })
+    const res = await fetchWithTimeout(url, { method: 'GET', headers: { 'Accept': 'application/json' } })

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

🤖 Prompt for AI Agents
In scripts/compare-rates.mjs around lines 80 to 89, the Frankfurter fetch call
needs a request timeout and an explicit Accept header; update fetch to use an
AbortController with a short timeout (e.g., 3–5s) so the request is aborted on
timeout, include headers: { Accept: 'application/json' }, and handle
abort/errors by returning a descriptive { error: 'timeout' } or the fetch error
message; ensure the timeout timer is cleared after response to avoid leaks.

Comment on lines +91 to +104
async function fetchLocalApi(from, to) {
if (!apiArg) return {}
try {
const url = `${apiArg}?from=${from}&to=${to}`
const res = await fetch(url, { method: 'GET' })
if (!res.ok) {
return { error: `${res.status} ${res.statusText}` }
}
const data = await res.json()
return { rate: data?.rate }
} catch (error) {
return { error: `Connection failed: ${error.message}` }
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Apply timeout and coerce local API rate to a number.

Prevent silent undefined when rate comes back as a string.

-        const res = await fetch(url, { method: 'GET' })
+        const res = await fetchWithTimeout(url, { method: 'GET', headers: { 'Accept': 'application/json' } })
@@
-        const data = await res.json()
-        return { rate: data?.rate }
+        const data = await res.json()
+        const n = typeof data?.rate === 'number' ? data.rate : Number(data?.rate)
+        return { rate: Number.isFinite(n) ? n : undefined }
📝 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
async function fetchLocalApi(from, to) {
if (!apiArg) return {}
try {
const url = `${apiArg}?from=${from}&to=${to}`
const res = await fetch(url, { method: 'GET' })
if (!res.ok) {
return { error: `${res.status} ${res.statusText}` }
}
const data = await res.json()
return { rate: data?.rate }
} catch (error) {
return { error: `Connection failed: ${error.message}` }
}
}
async function fetchLocalApi(from, to) {
if (!apiArg) return {}
try {
const url = `${apiArg}?from=${from}&to=${to}`
const res = await fetchWithTimeout(url, {
method: 'GET',
headers: { 'Accept': 'application/json' }
})
if (!res.ok) {
return { error: `${res.status} ${res.statusText}` }
}
const data = await res.json()
const n = typeof data?.rate === 'number'
? data.rate
: Number(data?.rate)
return { rate: Number.isFinite(n) ? n : undefined }
} catch (error) {
return { error: `Connection failed: ${error.message}` }
}
}
🤖 Prompt for AI Agents
In scripts/compare-rates.mjs around lines 91 to 104, the local API call needs a
request timeout and must coerce the returned rate to a number to avoid silently
returning undefined when the API sends a string; add an AbortController with a
short timeout (e.g. 3-5s) and pass its signal to fetch, clear the timeout on
success, await res.json(), then coerce data.rate using Number(data.rate) (or
parseFloat) and return { rate: Number(...) } only if it is a finite number
otherwise return an { error: 'Invalid rate' } (preserve non-OK HTTP handling and
include error details on catch).

...(siteUrl ? { metadataBase: new URL(siteUrl) } : {}),
icons: {
icon: '/logo-favicon.png',
icon: '/favicon.ico',
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Switched favicon to /favicon.ico—confirm file exists and cross-page consistency.

Change is fine; ensure public/favicon.ico exists and update other pages still referencing legacy /logo-favicon.png.


🏁 Script executed:

#!/bin/bash
fd -a 'favicon.ico' public
rg -n "/logo-favicon.png" -g '!node_modules'

Length of output: 214


Update QR sheet script to reference new favicon

  • In scripts/generate-qr-sheet.ts line 10, replace path.join(projectRoot, 'public/logo-favicon.png') with path.join(projectRoot, 'public/favicon.ico'). Verified public/favicon.ico exists.
🤖 Prompt for AI Agents
In src/app/(mobile-ui)/claim/page.tsx around line 100 the QR sheet script
reference must be updated: modify scripts/generate-qr-sheet.ts (line 10) to
replace path.join(projectRoot, 'public/logo-favicon.png') with
path.join(projectRoot, 'public/favicon.ico') so the script points to the
verified existing favicon; ensure the import/path variable names remain correct
and run the script to confirm the new path resolves.

Comment on lines +8 to +9
export const useGeoLocaion = () => {
const [countryCode, setCountryCode] = useState<string | null>(null)
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Typo in hook name: rename to useGeoLocation (file and export)

This will avoid import confusion and align with standard spelling.

  • Rename file to useGeoLocation.ts.
  • Update all imports project-wide:

Apply within this file:

-export const useGeoLocaion = () => {
+export const useGeoLocation = () => {

🏁 Script executed:

#!/bin/bash
rg -n "useGeoLocaion" -g "!node_modules"

Length of output: 338


Rename hook and file to correct spelling

  • Rename file src/hooks/useGeoLocaion.tssrc/hooks/useGeoLocation.ts and update its export to:
    - export const useGeoLocaion = () => {
    + export const useGeoLocation = () => {
  • Update all usages in src/components/Common/CountryList.tsx:
    • Line 9: change import { useGeoLocaion } from '@/hooks/useGeoLocaion'import { useGeoLocation } from '@/hooks/useGeoLocation'
    • Line 35: change useGeoLocaion()useGeoLocation()
📝 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
export const useGeoLocaion = () => {
const [countryCode, setCountryCode] = useState<string | null>(null)
// File: src/hooks/useGeoLocation.ts
import { useState } from 'react'
export const useGeoLocation = () => {
const [countryCode, setCountryCode] = useState<string | null>(null)
// …rest of hook…
}
🤖 Prompt for AI Agents
In src/hooks/useGeoLocaion.ts around lines 8-9, the hook and filename are
misspelled as "useGeoLocaion"; rename the file to src/hooks/useGeoLocation.ts,
update the exported function name to useGeoLocation, and update all
imports/usages accordingly—specifically change the import in
src/components/Common/CountryList.tsx line 9 from "@/hooks/useGeoLocaion" to
"@/hooks/useGeoLocation" and replace the call at line 35 from useGeoLocaion() to
useGeoLocation().

Comment on lines +15 to +27
const fetchCountry = async () => {
try {
const response = await fetch('https://ipapi.co/country')
if (!response.ok) {
throw new Error('Failed to fetch country')
}
const countryCode = await response.text()
setCountryCode(countryCode)
} catch (err: any) {
setError(err.message)
} finally {
setIsLoading(false)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Harden fetch with timeout, abort, and normalization

Prevent hanging requests, normalize the code, and improve error safety.

-        const fetchCountry = async () => {
+        const fetchCountry = async () => {
             try {
-                const response = await fetch('https://ipapi.co/country')
+                const controller = new AbortController()
+                const timeout = setTimeout(() => controller.abort(), 5000)
+                const response = await fetch('https://ipapi.co/country', {
+                    signal: controller.signal,
+                    headers: { Accept: 'text/plain' },
+                    cache: 'no-store',
+                })
                 if (!response.ok) {
                     throw new Error('Failed to fetch country')
                 }
-                const countryCode = await response.text()
-                setCountryCode(countryCode)
-            } catch (err: any) {
-                setError(err.message)
+                const text = await response.text()
+                const code = text.trim().toUpperCase()
+                setCountryCode(code)
+            } catch (err: unknown) {
+                setError(err instanceof Error ? err.message : 'Unknown error')
             } finally {
+                // @ts-ignore - clear even if controller aborted
+                if (typeof timeout !== 'undefined') clearTimeout(timeout)
                 setIsLoading(false)
             }
         }

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

🤖 Prompt for AI Agents
In src/hooks/useGeoLocaion.ts around lines 15 to 27, the fetch call should be
hardened: wrap fetch in an AbortController with a timeout (e.g., 3–5s) that
calls controller.abort(), pass controller.signal to fetch, clear the timeout on
success, and handle AbortError separately in the catch; also normalize the
returned country code by trimming and converting to uppercase before calling
setCountryCode, and ensure error messages are safe (use a default string if
err.message is missing) while retaining the existing finally block to
setIsLoading(false).

Comment on lines 182 to 218
export interface IBridgeAccount {
id: string
customer_id: string
created_at: string
updated_at: string
bank_name: string | null
account_name: string | null
account_owner_name: string
active: boolean
currency: string
account_owner_type: string | null
account_type: 'iban' | 'us'
first_name: string | null
last_name: string | null
business_name: string | null
beneficiary_address_valid?: boolean // Optional, only present in US account
last_4: string

// Use a union type for the account-specific details
account_details: IBridgeIbanDetails | IBridgeUsAccountDetails
}

export interface IBridgeIbanDetails {
type: 'iban'
last_4: string
bic: string
country: string
}

interface IBridgeUsAccountDetails {
type: 'us'
last_4: string
routing_number: string
}

interface IBridgeDepositInstructions {
payment_rail: string
amount: string
currency: string
from_address: string
to_address: string
}

interface IBridgeSource {
payment_rail: string
currency: string
from_address: string
}

interface IBridgeDestination {
payment_rail: string
currency: string
external_account_id: string
}

interface IBridgeReceipt {
initial_amount: string
developer_fee: string
exchange_fee: string
subtotal_amount: string
gas_fee: string
final_amount: string
exchange_rate?: string
}

interface IBridgeTransaction {
id: string
client_reference_id: string | null
state: string
on_behalf_of: string
source_deposit_instructions: IBridgeDepositInstructions
currency: string
amount: string
developer_fee: string
source: IBridgeSource
destination: IBridgeDestination
receipt: IBridgeReceipt
created_at: string
updated_at: string
currency?: 'usd' | 'eur' | 'mxn'
bank_name?: string
account_owner_name: string
account_number?: string
routing_number?: string
account_type: 'iban' | 'us' | 'clabe'
iban?: {
account_number: string
bic?: string
country: string
}
clabe?: {
account_number: string
}
account?: {
account_number: string
routing_number: string
checking_or_savings?: string
}
account_owner_type: 'individual' | 'business'
first_name?: string
last_name?: string
business_name?: string
address?: {
street_line_1: string
street_line_2?: string
city: string
country: string
state?: string
postal_code?: string
}
beneficiary_address_valid: boolean
}
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Model Bridge accounts as a discriminated union and relax currency.

  • Top-level account_number/routing_number duplicate nested fields; make the shape mutually exclusive via account_type.
  • Bridge may add currencies beyond 'usd' | 'eur' | 'mxn'; use a string or a broader union to avoid future breaks.
  • Consider making last_4 optional (varies by account type).
 export interface IBridgeAccount {
     id: string
     customer_id: string
-    last_4: string
-    currency?: 'usd' | 'eur' | 'mxn'
+    last_4?: string
+    currency?: string
-    bank_name?: string
-    account_owner_name: string
-    account_number?: string
-    routing_number?: string
-    account_type: 'iban' | 'us' | 'clabe'
-    iban?: {
+    bank_name?: string
+    account_owner_name: string
+    account_type: 'iban' | 'us' | 'clabe'
+    // IBAN details
+    iban?: {
         account_number: string
         bic?: string
         country: string
     }
-    clabe?: {
+    // Mexico CLABE
+    clabe?: {
         account_number: string
     }
-    account?: {
+    // US account details
+    account?: {
         account_number: string
         routing_number: string
-        checking_or_savings?: string
+        checking_or_savings?: 'checking' | 'savings'
     }
     account_owner_type: 'individual' | 'business'
@@
     beneficiary_address_valid: boolean
 }

To confirm optionality and currency list, search call sites:


🏁 Script executed:

#!/bin/bash
rg -n "IBridgeAccount" -C2
rg -n "\blast_4\b" -C2

Length of output: 1406


🏁 Script executed:

#!/bin/bash
# Search for usage of account_number outside the interface definition
rg -n "account_number" -C2 | grep -v 'src/interfaces/interfaces.ts'
# Search for usage of routing_number outside the interface definition
rg -n "routing_number" -C2 | grep -v 'src/interfaces/interfaces.ts'

Length of output: 6834


Update call sites or preserve top-level fields
Removing account_number/routing_number from the top level breaks existing consumers. In
src/components/Claim/Link/views/BankFlowManager.view.tsx (around lines 332 and 334), change:

accountNumber: externalAccountResponse.account_number
routingNumber: externalAccountResponse.account?.routing_number

to use the nested properties (externalAccountResponse.account.account_number and .account.routing_number), or keep the top-level aliases on IBridgeAccount. Also verify any other direct uses of account_number/routing_number.

🤖 Prompt for AI Agents
In src/interfaces/interfaces.ts around lines 182 to 218, the interface removed
top-level account_number and routing_number which breaks consumers expecting
those fields; restore compatibility by either reintroducing top-level
account_number?: string and routing_number?: string as aliases that mirror
account?.account_number and account?.routing_number, or update all call sites
(e.g., src/components/Claim/Link/views/BankFlowManager.view.tsx lines ~332-334)
to read nested values externalAccountResponse.account.account_number and
externalAccountResponse.account.routing_number; ensure both patterns are handled
or add a migration note and update tests.

},
export const TOKEN_DETAILS = [
{
chainId: '1',
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Blocker: chainId should be a number, not a string

Downstream lookups typically use numeric chainIds (and CHAIN_DETAILS here uses numbers). Using a string risks mismatches (e.g., strict equality or map keys).

Apply this diff:

-        chainId: '1',
+        chainId: 1,
📝 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
chainId: '1',
chainId: 1,
🤖 Prompt for AI Agents
In src/utils/__mocks__/peanut-sdk.ts around line 19, the mock sets chainId as
the string '1' which must be a number to match downstream lookups and
CHAIN_DETAILS; change chainId from the string '1' to the numeric literal 1
(ensure any related tests or mock consumers expect a number and update them if
they rely on a string).

Comment on lines +1221 to +1223
export function checkIfInternalNavigation(): boolean {
return !!document.referrer && new URL(document.referrer).origin === window.location.origin
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Guard against SSR and invalid referrers in checkIfInternalNavigation.

Accessing window/document will crash on the server; also URL() can throw on malformed referrers.

Apply:

-export function checkIfInternalNavigation(): boolean {
-    return !!document.referrer && new URL(document.referrer).origin === window.location.origin
-}
+export function checkIfInternalNavigation(): boolean {
+    if (typeof window === 'undefined' || typeof document === 'undefined') return false
+    const ref = document.referrer
+    if (!ref) return false
+    try {
+        return new URL(ref).origin === window.location.origin
+    } catch {
+        return false
+    }
+}
📝 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
export function checkIfInternalNavigation(): boolean {
return !!document.referrer && new URL(document.referrer).origin === window.location.origin
}
export function checkIfInternalNavigation(): boolean {
if (typeof window === 'undefined' || typeof document === 'undefined') return false
const ref = document.referrer
if (!ref) return false
try {
return new URL(ref).origin === window.location.origin
} catch {
return false
}
}
🤖 Prompt for AI Agents
In src/utils/general.utils.ts around lines 1221 to 1223, the function accesses
document/window (breaking SSR) and calls new URL() directly (which can throw for
malformed referrers); update the function to first guard for SSR by checking
typeof window !== 'undefined' and typeof document !== 'undefined', ensure
document.referrer is a non-empty string, then try to construct a URL in a
try/catch (or otherwise safely parse) and return false on error; finally compare
the parsed referrer.origin to window.location.origin and return the boolean
result.

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.

Review continued from previous batch...

Comment on lines +46 to 47
error: string | null
}
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Prop contract change: ensure all call sites pass error.

error is now required; verify usages.


🏁 Script executed:

#!/bin/bash
rg -nP "(<|\\()\s*DynamicBankAccountForm\\b" -C3 --type=ts --type=tsx

Length of output: 97


🏁 Script executed:

#!/bin/bash
rg -nP '(<|\()\s*DynamicBankAccountForm\b' -C3 -g '*.tsx' -g '*.ts'

Length of output: 1502


🏁 Script executed:

#!/bin/bash
find . -type f -name 'AddWithdrawCountriesList.tsx'
find . -type f -name 'BankFlowManager.view.tsx'

Length of output: 216


🏁 Script executed:

sed -n '1,200p' src/components/AddWithdraw/DynamicBankAccountForm.tsx

Length of output: 8171


Pass the error prop to all DynamicBankAccountForm instances
DynamicBankAccountFormProps now requires error: string | null; add error={…} to each call site (e.g. in src/components/AddWithdraw/AddWithdrawCountriesList.tsx and src/components/Claim/Link/views/BankFlowManager.view.tsx).

🤖 Prompt for AI Agents
In src/components/AddWithdraw/DynamicBankAccountForm.tsx around lines 46-47, the
prop type now requires error: string | null but several call sites don’t pass
it; update every DynamicBankAccountForm usage (at minimum in
src/components/AddWithdraw/AddWithdrawCountriesList.tsx and
src/components/Claim/Link/views/BankFlowManager.view.tsx) to include an
error={someError} prop — where possible forward the existing error state/prop
from the parent, or pass error={null} if none is available, ensuring the value
is string|null and matches the expected prop type.

Comment on lines +66 to +72
const { country: countryNameParams } = useParams()
const { amountToWithdraw } = useWithdrawFlow()
const [firstName, ...lastNameParts] = (user?.user.fullName ?? '').split(' ')
const lastName = lastNameParts.join(' ')

let selectedCountry = (countryNameFromProps ?? (countryNameParams as string)).toLowerCase()

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Handle catch-all route param and preserve title-casing.

useParams for [...country] can be string[]. Also avoid lowercasing when persisting countryName.

-        const { country: countryNameParams } = useParams()
+        const { country: countryNameParams } = useParams()
@@
-        let selectedCountry = (countryNameFromProps ?? (countryNameParams as string)).toLowerCase()
+        const rawCountryParam = Array.isArray(countryNameParams)
+            ? countryNameParams[0]
+            : (countryNameParams as string | undefined)
+        const selectedCountryNameLC = (countryNameFromProps ?? rawCountryParam ?? '').toLowerCase()
🤖 Prompt for AI Agents
In src/components/AddWithdraw/DynamicBankAccountForm.tsx around lines 66 to 72,
handle the catch‑all route param which may be a string[] and stop lowercasing
the country name when persisting it: read useParams() as { country?: string |
string[] }, normalize countryNameParams by selecting the first element when it's
an array (e.g. Array.isArray(countryNameParams) ? countryNameParams[0] :
countryNameParams) and then compute selectedCountry from countryNameFromProps ??
normalizedCountryName without calling toLowerCase() so the original title‑casing
is preserved.

Comment on lines +175 to 176
countryName: selectedCountry,
accountOwnerType: BridgeAccountOwnerType.INDIVIDUAL,
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Don’t lowercase countryName in payload.

Send a proper title-cased name for downstream UX/logging.

-                    countryName: selectedCountry,
+                    countryName: countryNameFromProps ?? (rawCountryParam || ''),
📝 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
countryName: selectedCountry,
accountOwnerType: BridgeAccountOwnerType.INDIVIDUAL,
countryName: countryNameFromProps ?? (rawCountryParam || ''),
accountOwnerType: BridgeAccountOwnerType.INDIVIDUAL,
🤖 Prompt for AI Agents
In src/components/AddWithdraw/DynamicBankAccountForm.tsx around lines 175-176,
the payload currently sends countryName in lowercase; change it to a title-cased
country name instead. Before building the payload, derive a titleCasedCountry by
trimming selectedCountry and capitalizing the first letter of each word (or
reuse an existing util if available), handle empty/null safely, and assign
countryName: titleCasedCountry in the payload while leaving accountOwnerType as
BridgeAccountOwnerType.INDIVIDUAL.

Comment on lines 215 to 218
const isMx = country.toUpperCase() === 'MX'
const isUs = country.toUpperCase() === 'USA'
const isIban = isUs || isMx ? false : isIBANCountry(country)

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Mirror MX/US detection outside submit.

Keep logic consistent across render and submit.

-        const isMx = country.toUpperCase() === 'MX'
-        const isUs = country.toUpperCase() === 'USA'
-        const isIban = isUs || isMx ? false : isIBANCountry(country)
+        const cc = country.toUpperCase()
+        const isMx = cc === 'MEX' || cc === 'MX'
+        const isUs = cc === 'USA' || cc === 'US'
+        const isIban = isUs || isMx ? false : isIBANCountry(cc)
📝 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 isMx = country.toUpperCase() === 'MX'
const isUs = country.toUpperCase() === 'USA'
const isIban = isUs || isMx ? false : isIBANCountry(country)
const cc = country.toUpperCase()
const isMx = cc === 'MEX' || cc === 'MX'
const isUs = cc === 'USA' || cc === 'US'
const isIban = isUs || isMx ? false : isIBANCountry(cc)
🤖 Prompt for AI Agents
In src/components/AddWithdraw/DynamicBankAccountForm.tsx around lines 215 to
218, the MX/US/IBAN detection logic is only defined in render which causes
inconsistent behavior when submitting; refactor by extracting the country
normalization and detection into a single helper (or a shared variable at
component scope) used both in render and in the submit handler so isMx, isUs and
isIban are computed consistently (e.g., normalize country.toUpperCase() once,
then set isMx = === 'MX', isUs = === 'USA', isIban = !(isMx || isUs) &&
isIBANCountry(country) and use that same logic in submit).

Comment on lines +328 to +331
if (getCountryFromIban(val)?.toLowerCase() !== selectedCountry) {
return 'IBAN does not match the selected country'
}

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Use normalized country-name var in IBAN-country check.

-                                            if (getCountryFromIban(val)?.toLowerCase() !== selectedCountry) {
+                                            if (getCountryFromIban(val)?.toLowerCase() !== selectedCountryNameLC) {
📝 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 (getCountryFromIban(val)?.toLowerCase() !== selectedCountry) {
return 'IBAN does not match the selected country'
}
@@ src/components/AddWithdraw/DynamicBankAccountForm.tsx:328
if (getCountryFromIban(val)?.toLowerCase() !== selectedCountryNameLC) {
return 'IBAN does not match the selected country'
}
🤖 Prompt for AI Agents
In src/components/AddWithdraw/DynamicBankAccountForm.tsx around lines 328 to
331, the IBAN country check uses selectedCountry directly; change it to use the
normalized country-name variable used elsewhere (e.g. normalizedCountryName) so
comparison is consistent: call getCountryFromIban(val)?.toLowerCase() and
compare it to the normalized country variable (already lowercased) instead of
selectedCountry, keeping the same error message when they differ.

Comment on lines +15 to 16
import countryCurrencyMappings from '@/constants/countryCurrencyMapping'

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix incorrect import (named vs default).

countryCurrencyMappings is a named export.

-import countryCurrencyMappings from '@/constants/countryCurrencyMapping'
+import { countryCurrencyMappings } from '@/constants/countryCurrencyMapping'
📝 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
import countryCurrencyMappings from '@/constants/countryCurrencyMapping'
import { countryCurrencyMappings } from '@/constants/countryCurrencyMapping'
🤖 Prompt for AI Agents
In src/components/LandingPage/noFees.tsx around lines 15-16 the module is
imported as a default export but countryCurrencyMappings is a named export;
change the import to a named import (e.g. import { countryCurrencyMappings }
from '@/constants/countryCurrencyMapping') and update any local references if
necessary to match the named identifier.

Comment on lines +76 to 81
if (typeof debouncedSourceAmount === 'number' && debouncedSourceAmount !== urlSourceAmount) {
updateUrlParams({ amount: debouncedSourceAmount })
}
}, [debouncedSourceAmount, urlSourceAmount, updateUrlParams])

useEffect(() => {
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Guard against NaN in URL sync.

Avoid writing “amount=NaN” when the input is invalid.

-    if (typeof debouncedSourceAmount === 'number' && debouncedSourceAmount !== urlSourceAmount) {
+    if (
+        typeof debouncedSourceAmount === 'number' &&
+        Number.isFinite(debouncedSourceAmount) &&
+        debouncedSourceAmount !== urlSourceAmount
+    ) {
         updateUrlParams({ amount: debouncedSourceAmount })
     }
📝 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
useEffect(() => {
if (typeof debouncedSourceAmount === 'number' && debouncedSourceAmount !== urlSourceAmount) {
updateUrlParams({ amount: debouncedSourceAmount })
}
}, [debouncedSourceAmount, urlSourceAmount, updateUrlParams])
useEffect(() => {
if (
typeof debouncedSourceAmount === 'number' &&
Number.isFinite(debouncedSourceAmount) &&
debouncedSourceAmount !== urlSourceAmount
) {
updateUrlParams({ amount: debouncedSourceAmount })
}
}, [debouncedSourceAmount, urlSourceAmount, updateUrlParams])
🤖 Prompt for AI Agents
In src/components/LandingPage/noFees.tsx around lines 76 to 81, the effect may
write amount=NaN into the URL when the input is invalid; guard the update by
ensuring debouncedSourceAmount is a finite number (e.g.
Number.isFinite(debouncedSourceAmount)) before comparing to urlSourceAmount and
calling updateUrlParams, so only valid numeric amounts are written to the URL.

Comment on lines +39 to +43
if (user?.user.userId) {
setClaimType(BankClaimType.ReceiverKycNeeded)
} else {
setClaimType(BankClaimType.GuestKycNeeded)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix optional chaining to avoid runtime errors when user.user is undefined.

Use user?.user?.userId consistently.

-                if (user?.user.userId) {
+                if (user?.user?.userId) {
...
-                    if (user?.user.userId) {
+                    if (user?.user?.userId) {
...
-                if (user?.user.userId) {
+                if (user?.user?.userId) {

Also applies to: 57-61, 64-68

🤖 Prompt for AI Agents
In src/hooks/useDetermineBankClaimType.ts around lines 39-43 (and similarly at
57-61 and 64-68), the code uses user?.user.userId which will throw if user.user
is undefined; change these checks to use optional chaining for the nested
property (user?.user?.userId) and update the corresponding condition branches to
rely on that safe check so the setClaimType calls only run when the nested
userId exists.

Comment on lines +110 to +129
// Validate CLABE check digit using the official algorithm
const digits = cleanAccountNumber.split('').map(Number)
const weights = [3, 7, 1, 3, 7, 1, 3, 7, 1, 3, 7, 1, 3, 7, 1, 3, 7]

let sum = 0
for (let i = 0; i < 17; i++) {
sum += digits[i] * weights[i]
}

const remainder = sum % 10
const calculatedCheckDigit = remainder === 0 ? 0 : 10 - remainder
const providedCheckDigit = digits[17]

if (calculatedCheckDigit !== providedCheckDigit) {
return {
isValid: false,
error: 'CLABE check digit is invalid',
}
}

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix CLABE check-digit algorithm (per-digit mod 10 is required).

CLABE validation must sum (digit × weight) % 10 for the first 17 digits, then compute check = (10 - (sum % 10)) % 10. Current logic misses the per-digit modulo, causing false negatives/positives.

Apply:

-    const digits = cleanAccountNumber.split('').map(Number)
-    const weights = [3, 7, 1, 3, 7, 1, 3, 7, 1, 3, 7, 1, 3, 7, 1, 3, 7]
-
-    let sum = 0
-    for (let i = 0; i < 17; i++) {
-        sum += digits[i] * weights[i]
-    }
-
-    const remainder = sum % 10
-    const calculatedCheckDigit = remainder === 0 ? 0 : 10 - remainder
+    const digits = cleanAccountNumber.split('').map(Number)
+    const weights = [3, 7, 1, 3, 7, 1, 3, 7, 1, 3, 7, 1, 3, 7, 1, 3, 7]
+
+    let sum = 0
+    for (let i = 0; i < 17; i++) {
+        sum += (digits[i] * weights[i]) % 10
+    }
+
+    const calculatedCheckDigit = (10 - (sum % 10)) % 10
📝 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
// Validate CLABE check digit using the official algorithm
const digits = cleanAccountNumber.split('').map(Number)
const weights = [3, 7, 1, 3, 7, 1, 3, 7, 1, 3, 7, 1, 3, 7, 1, 3, 7]
let sum = 0
for (let i = 0; i < 17; i++) {
sum += digits[i] * weights[i]
}
const remainder = sum % 10
const calculatedCheckDigit = remainder === 0 ? 0 : 10 - remainder
const providedCheckDigit = digits[17]
if (calculatedCheckDigit !== providedCheckDigit) {
return {
isValid: false,
error: 'CLABE check digit is invalid',
}
}
// Validate CLABE check digit using the official algorithm
const digits = cleanAccountNumber.split('').map(Number)
const weights = [3, 7, 1, 3, 7, 1, 3, 7, 1, 3, 7, 1, 3, 7, 1, 3, 7]
let sum = 0
for (let i = 0; i < 17; i++) {
sum += (digits[i] * weights[i]) % 10
}
const calculatedCheckDigit = (10 - (sum % 10)) % 10
const providedCheckDigit = digits[17]
if (calculatedCheckDigit !== providedCheckDigit) {
return {
isValid: false,
error: 'CLABE check digit is invalid',
}
}
🤖 Prompt for AI Agents
In src/utils/withdraw.utils.ts around lines 110 to 129, the CLABE check-digit
computation incorrectly sums raw digit×weight values instead of applying
per-digit modulo 10 before accumulating, and then computes the check digit;
update the algorithm to for each of the first 17 digits compute (digit * weight)
% 10 and add that to the running sum, then compute the check digit as (10 - (sum
% 10)) % 10 and compare against the provided 18th digit, returning the same
error object if they differ.

Comment on lines +136 to +147
// Returns the 3-letter country code for the given country code
export const getCountryCodeForWithdraw = (country: string) => {
// If the input is already a 3-digit code and exists in the map, return it
if (countryCodeMap[country]) {
return country
}

// If the input is a 2-digit code, find the corresponding 3-digit code
const threeDigitCode = Object.keys(countryCodeMap).find((key) => countryCodeMap[key] === country)

return threeDigitCode || country
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Normalize input to uppercase and fix doc wording (“3-letter” not “3-digit”).

countryCodeMap keys are uppercase 3-letter codes; normalize before lookup (matches prior learning).

-// Returns the 3-letter country code for the given country code
-export const getCountryCodeForWithdraw = (country: string) => {
-    // If the input is already a 3-digit code and exists in the map, return it
-    if (countryCodeMap[country]) {
-        return country
-    }
-
-    // If the input is a 2-digit code, find the corresponding 3-digit code
-    const threeDigitCode = Object.keys(countryCodeMap).find((key) => countryCodeMap[key] === country)
-
-    return threeDigitCode || country
-}
+// Returns the 3-letter country code (e.g., 'DEU') for the given country code
+export const getCountryCodeForWithdraw = (country: string) => {
+    const code = country.toUpperCase()
+    // If input is already a 3-letter code and exists in the map, return it
+    if (countryCodeMap[code]) return code
+    // If input is a 2-letter code, find the corresponding 3-letter code
+    const threeLetter = Object.keys(countryCodeMap).find((key) => countryCodeMap[key] === code)
+    return (threeLetter ?? code)
+}

Used learning: “countryCodeMap uses uppercase 3-letter codes as keys; normalize inputs to uppercase.”

📝 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
// Returns the 3-letter country code for the given country code
export const getCountryCodeForWithdraw = (country: string) => {
// If the input is already a 3-digit code and exists in the map, return it
if (countryCodeMap[country]) {
return country
}
// If the input is a 2-digit code, find the corresponding 3-digit code
const threeDigitCode = Object.keys(countryCodeMap).find((key) => countryCodeMap[key] === country)
return threeDigitCode || country
}
// Returns the 3-letter country code (e.g., 'DEU') for the given country code
export const getCountryCodeForWithdraw = (country: string) => {
const code = country.toUpperCase()
// If input is already a 3-letter code and exists in the map, return it
if (countryCodeMap[code]) return code
// If input is a 2-letter code, find the corresponding 3-letter code
const threeLetter = Object.keys(countryCodeMap).find((key) => countryCodeMap[key] === code)
return (threeLetter ?? code)
}
🤖 Prompt for AI Agents
In src/utils/withdraw.utils.ts around lines 136 to 147, update the doc comment
to say "3-letter country code" and normalize the input to uppercase before
lookups; specifically, convert the incoming country to country =
country.toUpperCase() (or a new const) before checking countryCodeMap keys and
before searching Object.keys(countryCodeMap).find(...), so that existing
uppercase 3-letter keys are matched correctly and 2-letter inputs are compared
in uppercase as well; finally return the found 3-letter code or the original
input.

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

Labels

Being worked on Being worked on

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants