Conversation
* 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
* 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>
* fix: scientific notation in eip681 parsing * fix: qr handling tests * fix: peanut sdk mock
* fix: cross chain claim * fix: full name issue on confirm bank claim view * fix: back navigation on desktop views
* Fix back button not working * fix public profile page * extract internal navigation logic to utility function
* fix: usa bank account claims * fix: show bank account details in confirm claim view
* reduce clouds size and update font * fix: hero section responsiveness issue * fix: formatting errors * add currency animation
#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 * fix: confirm bank claim ui rows for iban guest claim
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughAdds an exchange-rate API and comparison script, overhauls claim-to-bank flow via a new ClaimBankFlow context and views, replaces axelarChainName with networkName across chain data, revamps landing pages (DropLink, RegulatedRails, Footer, NoFees with live rates, CurrencySelect), introduces new hooks/utilities, updates icons/assets, and adjusts related components/tests. Changes
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Suggested labels
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 unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 28
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (18)
src/components/Global/UnsupportedBrowserModal/index.tsx (1)
114-131: Modal becomes non-closable becausevisibleis hard-coded totrue.Currently,
ActionModalalways receivesvisible={true}. Even ifhandleModalCloseruns, the modal stays open when the parent hasn’t setvisibletofalse, making the close button ineffective and trapping users in the modal. Pass the computed visibility instead.Apply this diff:
return ( <ActionModal - visible={true} + visible={showInAppBrowserModalViaDetection || visible} onClose={handleModalClose} title="Open this link in your browser"src/utils/bridge.utils.ts (2)
14-33: Case sensitivity and 3-letter aliases can misroute to EUR/SEPA — normalize countryIdAs-is, inputs like 'usa' or 'mex' fall through to EUR/SEPA. Given upstream sources may provide mixed-case or 3-letter codes (see repository learnings), normalize once and map common 3→2 aliases.
Apply:
-export const getCurrencyConfig = (countryId: string, operationType: BridgeOperationType): CurrencyConfig => { - if (countryId === 'US' || countryId === 'USA') { +// Normalize to uppercase and convert common 3-letter aliases to 2-letter +const normalizeCountryId = (id: string): string => { + const v = (id || '').toUpperCase().trim() + const threeToTwo: Record<string, string> = { USA: 'US', MEX: 'MX' } + return threeToTwo[v] || v +} + +export const getCurrencyConfig = (countryId: string, operationType: BridgeOperationType): CurrencyConfig => { + const id = normalizeCountryId(countryId) + if (id === 'US') { 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 } } // All other countries use EUR/SEPA return { currency: 'eur', paymentRail: 'sepa', // SEPA works for both onramp and offramp in Europe } }Follow-up: apply the same normalization for getMinimumAmount below. See next comment.
65-72: Use the same normalization in getMinimumAmount to avoid MX lower-case edge casesLower-case 'mx' would incorrectly return 1 today.
Apply:
-export const getMinimumAmount = (countryId: string): number => { - if (countryId === 'MX') { +export const getMinimumAmount = (countryId: string): number => { + const id = normalizeCountryId(countryId) + if (id === 'MX') { return 50 } // Default minimum for all other countries (including US and EU) return 1 }src/components/Global/PeanutActionDetailsCard/index.tsx (1)
196-214: Fix amount suffix for ADD_MONEY_BANK_ACCOUNT to avoid mixed fiat+token displayWhen transactionType is ADD_MONEY_BANK_ACCOUNT, you now prefix with a fiat symbol, but the suffix logic still appends the token symbol (since only ADD_MONEY is excluded). This can produce outputs like “$ 100 USDC”.
Exclude ADD_MONEY_BANK_ACCOUNT from the suffix:
- {tokenSymbol.toLowerCase() !== PEANUT_WALLET_TOKEN_SYMBOL.toLowerCase() && - transactionType !== 'ADD_MONEY' && + {tokenSymbol.toLowerCase() !== PEANUT_WALLET_TOKEN_SYMBOL.toLowerCase() && + transactionType !== 'ADD_MONEY' && + transactionType !== 'ADD_MONEY_BANK_ACCOUNT' && !(transactionType === 'CLAIM_LINK_BANK_ACCOUNT' && viewType === 'SUCCESS') && ` ${ tokenSymbol.toLowerCase() === 'pnt' ? Number(amount) === 1 ? 'Beer' : 'Beers' : tokenSymbol }`}src/components/AddMoney/components/DepositMethodList.tsx (1)
77-84: Add flagcdn.com to Next.js image remotePatterns configurationIt looks like your
next.config.jsdefines animages.remotePatternsarray (around line 28) but doesn’t include an entry forflagcdn.com. Without this,<Image>requests to flagcdn.com will error at runtime. Please update your config as follows:// next.config.js module.exports = { images: { remotePatterns: [ // …existing patterns… + { + protocol: 'https', + hostname: 'flagcdn.com', + port: '', + pathname: '/w160/**', + }, ], }, // …other config… };Alternatively, if you prefer using the older
domainsapproach, you can add:images: { - domains: [/* … */], + domains: [ + /* …existing domains… */, + 'flagcdn.com', + ], },Either approach will ensure Next.js allows loading the flag images from flagcdn.com.
src/components/0_Bruddle/Button.tsx (2)
98-101: Forwarded ref is broken for function refs. Use useImperativeHandle.Casting
reftoRefObjectdrops support for callback refs and can silently fail. Bind an internal ref, then expose it viauseImperativeHandle.- 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 innerRef = useRef<HTMLButtonElement>(null) + useImperativeHandle(ref, () => innerRef.current as HTMLButtonElement | null, []) @@ - if (!buttonRef.current) return - buttonRef.current.setAttribute('translate', 'no') - buttonRef.current.classList.add('notranslate') + if (!innerRef.current) return + innerRef.current.setAttribute('translate', 'no') + innerRef.current.classList.add('notranslate') @@ - ref={buttonRef} + ref={innerRef}Also applies to: 108-111, 239-239, 2-2
209-216: Tailwind JIT cannot see dynamic class “active:translate-y-[${shadowSize}px]”.Dynamic arbitrary values won’t be generated by Tailwind. Use a static mapping based on the finite
shadowSizeunion.- 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 activeYTranslate = + shadowSize === '4' + ? 'active:translate-y-[4px]' + : shadowSize === '6' + ? 'active:translate-y-[6px]' + : shadowSize === '8' + ? 'active:translate-y-[8px]' + : undefined + + 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], + activeYTranslate, className )src/app/actions/squid.ts (1)
8-14: Align chainId types in squid filtersThe
supportedPeanutChainsarray is built fromIPeanutChainDetails, whosechainIdproperty is declared as a string (src/interfaces/interfaces.ts). However, in the Peanut SDK’s API you passchainIdas a number (e.g.peanut.createLink({ chainId: 5, … })), sointerfaces.ISquidChain.chainIdis a number (github.com). Comparing a string to a number with===will always fail, causing valid chains or tokens to be filtered out.Locations to update:
- src/app/actions/squid.ts (lines 8–14)
Suggested diff:
const supportedByPeanut = (chain: interfaces.ISquidChain): boolean => 'evm' === chain.chainType && - supportedPeanutChains.some((supportedChain) => supportedChain.chainId === chain.chainId) + supportedPeanutChains.some((supportedChain) => supportedChain.chainId === chain.chainId.toString()) const tokensSupportedByPeanut = (token: interfaces.ISquidToken): boolean => - supportedPeanutChains.some((supportedChain) => supportedChain.chainId === token.chainId) + supportedPeanutChains.some((supportedChain) => supportedChain.chainId === token.chainId.toString())src/components/Claim/Link/views/Confirm.bank-claim.view.tsx (1)
67-69: formatUnits default can throw; use bigint and a safe decimals fallback.formatUnits expects a bigint and a number. The current fallback 0 is a number and decimals may be undefined; either can cause runtime errors.
Apply:
- amount={formatUnits(claimLinkData?.amount ?? 0, claimLinkData?.tokenDecimals) || '0.00'} + amount={ + formatUnits( + (claimLinkData?.amount as bigint | undefined) ?? 0n, + (claimLinkData?.tokenDecimals as number | undefined) ?? 18 + ) || '0.00' + }src/lib/validation/token.test.ts (1)
9-15: Remove all remaining axelarChainName references to enforce networkName-only contractTests, types, and utility code still include
axelarChainName. Please drop the field so that any accidental usage will immediately surface as a compilation or test error.• src/lib/validation/token.test.ts (lines 9–15, 39–45, 67–73)
Apply the original diff to removeaxelarChainNamein each mock entry:'1': { chainId: '1', - axelarChainName: 'Ethereum', networkName: 'Ethereum', … }, '10': { chainId: '10', - axelarChainName: 'optimism', networkName: 'optimism', … }, '8453': { chainId: '8453', - axelarChainName: 'base', networkName: 'base', … },• src/utils/tests/url-parser.test.ts (line 62)
- axelarChainName: 'base',• src/components/utils/utils.ts (around line 10)
Remove theaxelarChainName: stringproperty from theChainData(or equivalent) type/interface so it no longer appears in parsed or serialized chain objects.• src/components/Claim/Claim.interfaces.ts (line 5)
- axelarChainName?: string;After these removals, rerun your grep command to confirm zero matches for
axelarChainName. This ensures no hidden usages remain and enforces the new networkName-only contract.src/app/(mobile-ui)/home/page.tsx (2)
137-159: Guard against null user and consolidate duplicate “balance warning” effectsThis effect dereferences user! and has a twin below (Lines 160–182). The non-null assertion can crash on initial renders; duplication risks double state updates.
Apply this diff to guard user and keep a single effect:
- // effect for showing balance warning modal - useEffect(() => { - if (isFetchingBalance || balance === undefined) return - - if (typeof window !== 'undefined') { - const hasSeenBalanceWarning = getFromLocalStorage(`${user!.user.userId}-hasSeenBalanceWarning`) + // effect for showing balance warning modal + useEffect(() => { + if (isFetchingBalance || balance === undefined || !user) return + + if (typeof window !== 'undefined') { + const hasSeenBalanceWarning = getFromLocalStorage(`${user.user.userId}-hasSeenBalanceWarning`) const balanceInUsd = Number(formatUnits(balance, PEANUT_WALLET_TOKEN_DECIMALS)) // show if: // 1. balance is above the threshold // 2. user hasn't seen this warning in the current session // 3. no other modals are currently active if ( balanceInUsd > BALANCE_WARNING_THRESHOLD && !hasSeenBalanceWarning && !showIOSPWAInstallModal && - !showAddMoneyPromptModal && - !isPostSignupActionModalVisible + !showAddMoneyPromptModal && + !isPostSignupActionModalVisible ) { setShowBalanceWarningModal(true) } } - }, [balance, isFetchingBalance, showIOSPWAInstallModal, showAddMoneyPromptModal]) + }, [user, balance, isFetchingBalance, showIOSPWAInstallModal, showAddMoneyPromptModal, isPostSignupActionModalVisible])
160-182: Remove duplicate “balance warning” effectThis second copy overlaps the first but with slightly different conditions; keep one source of truth to avoid surprises.
Apply this diff:
- // effect for showing balance warning modal - useEffect(() => { - if (isFetchingBalance || balance === undefined) return - - if (typeof window !== 'undefined') { - const hasSeenBalanceWarning = getFromLocalStorage(`${user!.user.userId}-hasSeenBalanceWarning`) - const balanceInUsd = Number(formatUnits(balance, PEANUT_WALLET_TOKEN_DECIMALS)) - - // show if: - // 1. balance is above the threshold - // 2. user hasn't seen this warning in the current session - // 3. no other modals are currently active - if ( - balanceInUsd > BALANCE_WARNING_THRESHOLD && - !hasSeenBalanceWarning && - !showIOSPWAInstallModal && - !showAddMoneyPromptModal - ) { - setShowBalanceWarningModal(true) - } - } - }, [balance, isFetchingBalance, showIOSPWAInstallModal, showAddMoneyPromptModal])src/hooks/useKycFlow.ts (1)
104-110: Fix malformedfields[iqt_token]parameter inconvertPersonaUrlThe
convertPersonaUrlhelper builds thefields[iqt_token]query parameter incorrectly—it's missing the closing bracket before the=and thus emitsfields[iqt_token=…instead offields[iqt_token]=…. This will break the Persona widget URL.• File:
src/utils/bridge-accounts.utils.ts
• Return statement (around line 31)Suggested patch:
-export const convertPersonaUrl = (url: string) => { - // … previous code … - 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}` -} +export const convertPersonaUrl = (url: string) => { + // … previous code … + 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}` +}This correction closes the bracket for
fields[iqt_token]and aligns it with the other query parameters.src/components/Claim/Link/Onchain/Confirm.view.tsx (1)
87-93: Use route-derived chain/token to avoid parameter driftTo ensure that the on-chain claim call always matches the computed route, pull the destination chain and token directly from
selectedRoute.rawResponse.routeinstead of the context’sselectedChainID/selectedTokenAddress, which may diverge if the user changes their selection after routing.• File:
src/components/Claim/Link/Onchain/Confirm.view.tsx
Lines: 87–93Suggested change:
- 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 (2)
95-103: Normalize country detection (USA/MX) using alpha-2 codes; current checks are inconsistent.You compare
'USA'(alpha-3) for US but'MX'(alpha-2) for Mexico. This can misclassify depending on whatcountrycontains.-const isUs = country.toUpperCase() === 'USA' -const isMx = country.toUpperCase() === 'MX' -const isIban = isUs || isMx ? false : isIBANCountry(country) +// Normalize to alpha-3 and alpha-2 +const alpha3 = country.length === 2 + ? (Object.keys(countryCodeMap).find((k) => countryCodeMap[k] === country.toUpperCase()) ?? country.toUpperCase()) + : country.toUpperCase() +const alpha2 = countryCodeMap[alpha3] ?? (country.length === 2 ? country.toUpperCase() : '') +const isUs = alpha2 === 'US' +const isMx = alpha2 === 'MX' +const isIban = !(isUs || isMx) && isIBANCountry(alpha3)-const isMx = country.toUpperCase() === 'MX' -const isUs = country.toUpperCase() === 'USA' -const isIban = isUs || isMx ? false : isIBANCountry(country) +const isMx = alpha2 === 'MX' +const isUs = alpha2 === 'US' +const isIban = !(isUs || isMx) && isIBANCountry(alpha3)Also applies to: 172-175
127-145: Use explicit ISO-3 and ISO-2 country codes in the payloadBefore you build the payload, derive and reuse two standardized codes:
// inside onSubmit, after determining country param const iso3 = country.toUpperCase(); // e.g. "USA", "GBR", "CZE" const iso2 = countryCodeMap[iso3] ?? iso3; // maps to "US", "GB", "CZ"Then update the payload to use those:
const payload: Partial<AddBankAccountPayload> = { accountType, accountNumber: accountNumber.replace(/\s/g, ''), - countryCode: isUs ? 'USA' : country.toUpperCase(), + // API expects the 3-letter ISO code + countryCode: iso3, countryName: selectedCountry, accountOwnerType: BridgeAccountOwnerType.INDIVIDUAL, accountOwnerName: { firstName: firstName.trim(), lastName: lastName.trim(), }, address: { street: data.street ?? '', city: data.city ?? '', state: data.state ?? '', postalCode: data.postalCode ?? '', - country: isUs ? 'USA' : country.toUpperCase(), + // address.country should be the canonical 2-letter ISO code + country: iso2, }, ...(bic && { bic }), }This change ensures
payload.countryCodeis always a valid ISO 3166-1 alpha-3 codepayload.address.countryis the corresponding alpha-2 code for downstream address handling and flag displayssrc/context/ClaimBankFlowContext.tsx (2)
17-45: Make setter types React.Dispatch and remove optional+null duplication to restore type-safetyRight now the context interface uses custom setter signatures and mixes
?with| null. This forces an unsafeas ClaimBankFlowContextTypecast later and weakens type guarantees for consumers.
- Use
React.Dispatch<React.SetStateAction<...>>for setters so they match React’s actual types.- Make properties always-present and encode absence via
nullorundefined(not both plus?).- Keep
senderKycStatusasKYCStatus | undefined(present but possiblyundefined) for consistency.Apply:
interface ClaimBankFlowContextType { - claimToExternalWallet: boolean - setClaimToExternalWallet: (claimToExternalWallet: boolean) => void + claimToExternalWallet: boolean + setClaimToExternalWallet: React.Dispatch<React.SetStateAction<boolean>> flowStep: ClaimBankFlowStep | null - setFlowStep: (step: ClaimBankFlowStep | null) => void + setFlowStep: React.Dispatch<React.SetStateAction<ClaimBankFlowStep | null>> selectedCountry: CountryData | null - setSelectedCountry: (country: CountryData | null) => void + setSelectedCountry: React.Dispatch<React.SetStateAction<CountryData | null>> resetFlow: () => void - offrampDetails?: TCreateOfframpResponse | null - setOfframpDetails: (details: TCreateOfframpResponse | null) => void - claimError?: string | null - setClaimError: (error: string | null) => void - claimType?: 'claim-bank' | 'claim' | 'claimxchain' | null - setClaimType: (type: 'claim-bank' | 'claim' | 'claimxchain' | null) => void + offrampDetails: TCreateOfframpResponse | null + setOfframpDetails: React.Dispatch<React.SetStateAction<TCreateOfframpResponse | null>> + claimError: string | null + setClaimError: React.Dispatch<React.SetStateAction<string | null>> + claimType: 'claim-bank' | 'claim' | 'claimxchain' | null + setClaimType: React.Dispatch<React.SetStateAction<'claim-bank' | 'claim' | 'claimxchain' | null>> senderDetails: User | null - setSenderDetails: (details: User | null) => void + setSenderDetails: React.Dispatch<React.SetStateAction<User | null>> showVerificationModal: boolean - setShowVerificationModal: (show: boolean) => void + setShowVerificationModal: React.Dispatch<React.SetStateAction<boolean>> bankDetails: IBankAccountDetails | null - setBankDetails: (details: IBankAccountDetails | null) => void + setBankDetails: React.Dispatch<React.SetStateAction<IBankAccountDetails | null>> - savedAccounts: Account[] - setSavedAccounts: (accounts: Account[]) => void + savedAccounts: Account[] + setSavedAccounts: React.Dispatch<React.SetStateAction<Account[]>> selectedBankAccount: Account | null - setSelectedBankAccount: (account: Account | null) => void - senderKycStatus?: KYCStatus - setSenderKycStatus: (status?: KYCStatus) => void + setSelectedBankAccount: React.Dispatch<React.SetStateAction<Account | null>> + senderKycStatus: KYCStatus | undefined + setSenderKycStatus: React.Dispatch<React.SetStateAction<KYCStatus | undefined>> justCompletedKyc: boolean - setJustCompletedKyc: (status: boolean) => void + setJustCompletedKyc: React.Dispatch<React.SetStateAction<boolean>> }
80-109: Eliminate unsafeascast by typing useMemo result directlyOnce the interface is corrected, you can avoid the forced cast on the Provider. Type
useMemowith the context type and passvaluedirectly.- const value = useMemo( + const value = useMemo<ClaimBankFlowContextType>( () => ({ claimToExternalWallet, setClaimToExternalWallet, flowStep, setFlowStep, selectedCountry, setSelectedCountry, resetFlow, offrampDetails, setOfframpDetails, claimError, setClaimError, claimType, setClaimType, senderDetails, setSenderDetails, showVerificationModal, setShowVerificationModal, bankDetails, setBankDetails, savedAccounts, setSavedAccounts, selectedBankAccount, setSelectedBankAccount, senderKycStatus, setSenderKycStatus, justCompletedKyc, setJustCompletedKyc, }), [ claimToExternalWallet, flowStep, selectedCountry, resetFlow, offrampDetails, claimError, claimType, senderDetails, showVerificationModal, bankDetails, savedAccounts, selectedBankAccount, senderKycStatus, justCompletedKyc, ] ) - return ( - <ClaimBankFlowContext.Provider value={value as ClaimBankFlowContextType}> - {children} - </ClaimBankFlowContext.Provider> - ) + return ( + <ClaimBankFlowContext.Provider value={value}> + {children} + </ClaimBankFlowContext.Provider> + )Also applies to: 128-132
| async (): Promise< | ||
| Record<string, interfaces.ISquidChain & { networkName: string; tokens: interfaces.ISquidToken[] }> | ||
| > => { | ||
| const [chains, tokens] = await Promise.all([getSquidChainsCache(), getSquidTokensCache()]) | ||
|
|
||
| const chainsById = chains.reduce<Record<string, interfaces.ISquidChain & { tokens: interfaces.ISquidToken[] }>>( | ||
| (acc, chain) => { | ||
| acc[chain.chainId] = { ...chain, tokens: [] } | ||
| return acc | ||
| }, | ||
| {} | ||
| ) | ||
| const chainsById = chains.reduce< | ||
| Record<string, interfaces.ISquidChain & { networkName: string; tokens: interfaces.ISquidToken[] }> | ||
| >((acc, chain) => { | ||
| acc[chain.chainId] = { ...(chain as interfaces.ISquidChain & { networkName: string }), tokens: [] } | ||
| return acc | ||
| }, {}) | ||
|
|
There was a problem hiding this comment.
Unsound cast for networkName risks undefined at runtime — derive and set explicitly
{ ...(chain as ISquidChain & { networkName: string }), tokens: [] } trusts that SDK chains already include networkName. If the SDK doesn’t yet ship this field, downstream UI reading chain.networkName will render undefined.
Set networkName explicitly with safe fallbacks:
export const getSquidChainsAndTokens = unstable_cache(
async (): Promise<
Record<string, interfaces.ISquidChain & { networkName: string; tokens: interfaces.ISquidToken[] }>
> => {
const [chains, tokens] = await Promise.all([getSquidChainsCache(), getSquidTokensCache()])
- const chainsById = chains.reduce<
- Record<string, interfaces.ISquidChain & { networkName: string; tokens: interfaces.ISquidToken[] }>
- >((acc, chain) => {
- acc[chain.chainId] = { ...(chain as interfaces.ISquidChain & { networkName: string }), tokens: [] }
- return acc
- }, {})
+ const chainsById = chains.reduce<
+ Record<string, interfaces.ISquidChain & { networkName: string; tokens: interfaces.ISquidToken[] }>
+ >((acc, chain) => {
+ const anyChain = chain as any
+ const networkName: string =
+ anyChain.networkName ??
+ anyChain.axelarChainName ?? // legacy field seen in prior codebase
+ anyChain.name ??
+ String(chain.chainId)
+ acc[chain.chainId] = { ...(chain as interfaces.ISquidChain), networkName, tokens: [] }
+ return acc
+ }, {})📝 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.
| async (): Promise< | |
| Record<string, interfaces.ISquidChain & { networkName: string; tokens: interfaces.ISquidToken[] }> | |
| > => { | |
| const [chains, tokens] = await Promise.all([getSquidChainsCache(), getSquidTokensCache()]) | |
| const chainsById = chains.reduce<Record<string, interfaces.ISquidChain & { tokens: interfaces.ISquidToken[] }>>( | |
| (acc, chain) => { | |
| acc[chain.chainId] = { ...chain, tokens: [] } | |
| return acc | |
| }, | |
| {} | |
| ) | |
| const chainsById = chains.reduce< | |
| Record<string, interfaces.ISquidChain & { networkName: string; tokens: interfaces.ISquidToken[] }> | |
| >((acc, chain) => { | |
| acc[chain.chainId] = { ...(chain as interfaces.ISquidChain & { networkName: string }), tokens: [] } | |
| return acc | |
| }, {}) | |
| const chainsById = chains.reduce< | |
| Record<string, interfaces.ISquidChain & { networkName: string; tokens: interfaces.ISquidToken[] }> | |
| >((acc, chain) => { | |
| const anyChain = chain as any | |
| const networkName: string = | |
| anyChain.networkName ?? | |
| anyChain.axelarChainName ?? // legacy field seen in prior codebase | |
| anyChain.name ?? | |
| String(chain.chainId) | |
| acc[chain.chainId] = { ...(chain as interfaces.ISquidChain), networkName, tokens: [] } | |
| return acc | |
| }, {}) |
🤖 Prompt for AI Agents
In src/app/actions/squid.ts around lines 37 to 48, the code unsafely casts chain
to include networkName which may be missing at runtime; instead explicitly set
networkName with a safe fallback when building chainsById — e.g. avoid the cast
and construct the object by spreading the known chain properties and adding
networkName: chain.networkName ?? chain.name ?? String(chain.chainId) (or a
lookup map if you have canonical names), and keep tokens: [] so downstream
consumers never see undefined.
| interface ExchangeRateResponse { | ||
| rate: number | ||
| } |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Confirm response shape across the app; prior code used midmarket/buy/sell fields.
This route returns { rate: number }, which differs from the older ExchangeRateResponse shape found in src/app/actions/exchange-rate.ts. Ensure all consumers of /api/exchange-rate expect the new shape.
If any consumer expects mid/buy/sell, either adapt them or extend this route to include those fields.
Also applies to: 120-128, 151-159
🏁 Script executed:
#!/bin/bash
# Where do we call /api/exchange-rate?
rg -nP -C2 '/api/exchange-rate'
# Who imports/defines ExchangeRateResponse?
rg -nP -C2 '\binterface\s+ExchangeRateResponse\b|type\s+ExchangeRateResponse\b'
# Look for 'midmarket_rate' | 'buy_rate' | 'sell_rate' assumptions
rg -nP -C2 'midmarket_rate|buy_rate|sell_rate'Length of output: 4967
Mismatch in ExchangeRateResponse shape – update API or consumers
The /api/exchange-rate route now returns only:
interface ExchangeRateResponse {
rate: number
}whereas the rest of the codebase (and scripts) still expect the legacy fields: midmarket_rate, buy_rate, and sell_rate. You must either extend the route’s response to include those properties or update all consumers to use the new rate field.
Consumers to address:
- src/components/ExchangeRate/index.tsx (line 25):
setExchangeRate(data.sell_rate) - src/hooks/useExchangeRate.ts:
returnsres.json()but components expect{ buy_rate, sell_rate, ... } - scripts/compare-rates.mjs (around lines 76–78):
const { midmarket_rate, buy_rate, sell_rate } = data || {} - src/app/actions/exchange-rate.ts:
definesExchangeRateResponsewithmidmarket_rate,buy_rate,sell_rate,updated_at, but this no longer matches the API route
Next steps:
- Option A: Modify
src/app/api/exchange-rate/route.tsto return{ rate: number; midmarket_rate: string; buy_rate: string; sell_rate: string; // (plus any other legacy fields) }
- Option B: Refactor all consumers to read from
rateand remove legacy fields
🤖 Prompt for AI Agents
In src/app/api/exchange-rate/route.ts around lines 3 to 5, the route's response
type was reduced to { rate: number } but other modules still expect legacy
fields (midmarket_rate, buy_rate, sell_rate, updated_at); either restore the
legacy shape in the route or update all consumers to use rate. Preferred quick
fix: extend the route response to include midmarket_rate, buy_rate, sell_rate
(and updated_at if used) populated from rate (or computed/formatted as strings
if existing consumers expect strings) so the returned object contains both rate
and the legacy properties; alternatively, if you choose refactor, change these
consumers: src/components/ExchangeRate/index.tsx (line ~25) to use data.rate,
update src/hooks/useExchangeRate.ts to return { rate } shape and update typing,
change scripts/compare-rates.mjs to read rate instead of
midmarket_rate/buy_rate/sell_rate, and update src/app/actions/exchange-rate.ts
to match the new type; ensure types/returns and tests are updated accordingly.
| let rate = parseFloat(bridgeData[rateType]) | ||
|
|
||
| // If we fetched the reverse pair (e.g., fetched USD→EUR for EUR→USD request), | ||
| // we need to invert the rate | ||
| if (shouldInvert) { | ||
| rate = 1 / rate | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Guard against invalid or zero Bridge rates before inversion.
If Bridge returns 0/NaN/negative, 1 / rate will crash or produce nonsense. Fail fast and fall back.
- let rate = parseFloat(bridgeData[rateType])
+ let rate = parseFloat(bridgeData[rateType])
+ if (!Number.isFinite(rate) || rate <= 0) {
+ console.error(`Bridge returned invalid ${rateType}: ${bridgeData[rateType]}`)
+ return null
+ }
@@
- if (shouldInvert) {
- rate = 1 / rate
- }
+ if (shouldInvert) {
+ rate = 1 / rate
+ }📝 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.
| let rate = parseFloat(bridgeData[rateType]) | |
| // If we fetched the reverse pair (e.g., fetched USD→EUR for EUR→USD request), | |
| // we need to invert the rate | |
| if (shouldInvert) { | |
| rate = 1 / rate | |
| } | |
| let rate = parseFloat(bridgeData[rateType]) | |
| if (!Number.isFinite(rate) || rate <= 0) { | |
| console.error(`Bridge returned invalid ${rateType}: ${bridgeData[rateType]}`) | |
| return null | |
| } | |
| // If we fetched the reverse pair (e.g., fetched USD→EUR for EUR→USD request), | |
| // we need to invert the rate | |
| if (shouldInvert) { | |
| rate = 1 / rate | |
| } |
🤖 Prompt for AI Agents
In src/app/api/exchange-rate/route.ts around lines 112 to 118, the code inverts
the Bridge rate without validating it first; before doing rate = 1 / rate, check
that parsed rate is a finite, positive number (e.g., Number.isFinite(rate) &&
rate > 0). If the check fails, handle it by logging the invalid value and
failing fast to trigger the fallback path (return an error or throw a specific
exception that the caller uses to try the next provider) instead of performing
the inversion; ensure this validation happens both before and after any
parseFloat and only invert when the check passes.
| const data = await response.json() | ||
|
|
||
| const exchangeRate: ExchangeRateResponse = { | ||
| rate: data.rates[to] * 0.995, // Subtract 50bps | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Validate Frankfurter payload shape and avoid NaN when rates[to] is undefined.
Add a guard and clear error message. Also consider making the bps adjustment configurable.
- const data = await response.json()
-
- const exchangeRate: ExchangeRateResponse = {
- rate: data.rates[to] * 0.995, // Subtract 50bps
- }
+ const data = await response.json()
+ const baseRate = data?.rates?.[to]
+ if (!Number.isFinite(baseRate)) {
+ console.error(`Frankfurter: missing/invalid rate for ${to}`, data)
+ return NextResponse.json({ error: `Rate not available for ${to}` }, { status: 502 })
+ }
+ const exchangeRate: ExchangeRateResponse = {
+ rate: Number(baseRate) * 0.995, // Subtract 50bps
+ }📝 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.
| const data = await response.json() | |
| const exchangeRate: ExchangeRateResponse = { | |
| rate: data.rates[to] * 0.995, // Subtract 50bps | |
| } | |
| const data = await response.json() | |
| const baseRate = data?.rates?.[to] | |
| if (!Number.isFinite(baseRate)) { | |
| console.error(`Frankfurter: missing/invalid rate for ${to}`, data) | |
| return NextResponse.json({ error: `Rate not available for ${to}` }, { status: 502 }) | |
| } | |
| const exchangeRate: ExchangeRateResponse = { | |
| rate: Number(baseRate) * 0.995, // Subtract 50bps | |
| } |
🤖 Prompt for AI Agents
In src/app/api/exchange-rate/route.ts around lines 149 to 153, the code assumes
response.json() contains data.rates[to] and multiplies it, which can produce NaN
if rates or rates[to] is missing; add a runtime guard that checks data && typeof
data.rates === "object" && typeof data.rates[to] === "number" and if the check
fails throw or return a clear 400/500 error message indicating the Frankfurter
payload is malformed or the target currency is unsupported; calculate the
adjustment factor from a configurable source (e.g., an environment variable or a
function parameter with a safe default of 0.995) instead of hardcoding 0.995,
and use that factor when computing the rate to ensure no NaN is produced.
| const destination = getOfframpCurrencyConfig(account.country ?? selectedCountry!.id) | ||
|
|
||
| // handle offramp request creation |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Normalize destination country code before fetching currency config.
Saved accounts may store country as either 2- or 3-letter codes. Passing a normalized 3-letter code avoids mismatches in getOfframpCurrencyConfig.
Apply:
- const destination = getOfframpCurrencyConfig(account.country ?? selectedCountry!.id)
+ const destination = getOfframpCurrencyConfig(
+ getCountryCodeForWithdraw((account.country ?? selectedCountry!.id) as string)
+ )📝 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.
| const destination = getOfframpCurrencyConfig(account.country ?? selectedCountry!.id) | |
| // handle offramp request creation | |
| const destination = getOfframpCurrencyConfig( | |
| getCountryCodeForWithdraw((account.country ?? selectedCountry!.id) as string) | |
| ) | |
| // handle offramp request creation |
🤖 Prompt for AI Agents
In the src/components/Claim/Link/views/BankFlowManager.view.tsx file, around
lines 157 to 159, the code is using the `getOfframpCurrencyConfig` function to
fetch the currency configuration based on the account's country. However, the
comment suggests that the country code stored in the account may be in either
2-letter or 3-letter format, which can lead to mismatches when fetching the
currency configuration. To fix this, normalize the country code to a consistent
3-letter format before passing it to the `getOfframpCurrencyConfig` function.
This will ensure that the correct currency configuration is retrieved,
regardless of the format of the stored country code.
| useEffect(() => { | ||
| // use ipapi.co to get the user's country code | ||
| 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) | ||
| } | ||
| } | ||
|
|
||
| fetchCountry() | ||
| }, []) |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Abort in-flight request on unmount to prevent state updates on unmounted component.
Without cancellation, fast navigations can trigger React warnings and memory leaks.
Use AbortController and guard abort errors:
- useEffect(() => {
+ useEffect(() => {
+ const controller = new AbortController()
// use ipapi.co to get the user's country code
const fetchCountry = async () => {
try {
- const response = await fetch('https://ipapi.co/country')
+ const response = await fetch('https://ipapi.co/country', { signal: controller.signal })
if (!response.ok) {
throw new Error('Failed to fetch country')
}
- const countryCode = await response.text()
- setCountryCode(countryCode)
+ const code = (await response.text()).trim()
+ setCountryCode(code || null)
} catch (err: any) {
- setError(err.message)
+ if ((err as any)?.name !== 'AbortError') {
+ setError((err as Error)?.message ?? 'Unknown error')
+ }
} finally {
setIsLoading(false)
}
}
fetchCountry()
- }, [])
+ return () => controller.abort()
+ }, [])📝 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.
| useEffect(() => { | |
| // use ipapi.co to get the user's country code | |
| 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) | |
| } | |
| } | |
| fetchCountry() | |
| }, []) | |
| useEffect(() => { | |
| const controller = new AbortController() | |
| // use ipapi.co to get the user's country code | |
| const fetchCountry = async () => { | |
| try { | |
| const response = await fetch('https://ipapi.co/country', { signal: controller.signal }) | |
| if (!response.ok) { | |
| throw new Error('Failed to fetch country') | |
| } | |
| const code = (await response.text()).trim() | |
| setCountryCode(code || null) | |
| } catch (err: any) { | |
| if ((err as any)?.name !== 'AbortError') { | |
| setError((err as Error)?.message ?? 'Unknown error') | |
| } | |
| } finally { | |
| setIsLoading(false) | |
| } | |
| } | |
| fetchCountry() | |
| return () => controller.abort() | |
| }, []) |
🤖 Prompt for AI Agents
In src/hooks/useGeoLocaion.ts around lines 13 to 31, the fetch call should be
cancellable to avoid state updates after unmount: create an AbortController
before calling fetch, pass controller.signal into fetch, and return a cleanup
function that calls controller.abort(); in the try/catch handle AbortError
specially (do not call setError for aborts) and ensure setCountryCode and
setIsLoading are only invoked when the request was not aborted (e.g., check
error.name === 'AbortError' and skip state updates), so no state is updated
after unmount.
| const countryCode = await response.text() | ||
| setCountryCode(countryCode) | ||
| } catch (err: any) { |
There was a problem hiding this comment.
Trim response text to avoid subtle bugs; also avoid shadowing state variable.
ipapi’s /country endpoint often returns a trailing newline. Not trimming can break equality checks and map lookups. Also, the local countryCode const shadows state.
Apply:
- const countryCode = await response.text()
- setCountryCode(countryCode)
+ const code = (await response.text()).trim()
+ setCountryCode(code || null)📝 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.
| const countryCode = await response.text() | |
| setCountryCode(countryCode) | |
| } catch (err: any) { | |
| const code = (await response.text()).trim() | |
| setCountryCode(code || null) |
🤖 Prompt for AI Agents
In src/hooks/useGeoLocaion.ts around lines 21 to 23, the code reads
response.text() into a const named countryCode which both may include trailing
whitespace/newlines from ipapi and shadows the state variable; change the local
binding to a different name (e.g., rawCountryCode), call .trim() on the response
text to remove trailing/newline/whitespace, then pass the trimmed value to
setCountryCode(trimmedCountryCode).
| user?.accounts.filter( | ||
| (acc) => acc.type === AccountType.IBAN || acc.type === AccountType.US || acc.type === AccountType.CLABE | ||
| ) ?? [] |
There was a problem hiding this comment.
Fix optional chaining to avoid runtime crash when user.accounts is undefined
user?.accounts.filter will throw if user exists but accounts is undefined/null. Chain the filter call too.
Apply this diff:
- return (
- user?.accounts.filter(
- (acc) => acc.type === AccountType.IBAN || acc.type === AccountType.US || acc.type === AccountType.CLABE
- ) ?? []
- )
+ return (
+ user?.accounts?.filter(
+ (acc) => acc.type === AccountType.IBAN || acc.type === AccountType.US || acc.type === AccountType.CLABE
+ ) ?? []
+ )📝 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.
| user?.accounts.filter( | |
| (acc) => acc.type === AccountType.IBAN || acc.type === AccountType.US || acc.type === AccountType.CLABE | |
| ) ?? [] | |
| return ( | |
| user?.accounts?.filter( | |
| (acc) => | |
| acc.type === AccountType.IBAN || | |
| acc.type === AccountType.US || | |
| acc.type === AccountType.CLABE | |
| ) ?? [] | |
| ) |
🤖 Prompt for AI Agents
In src/hooks/useSavedAccounts.tsx around lines 16 to 18, the expression uses
user?.accounts.filter(...) which will throw if user exists but accounts is
null/undefined; change the access to safely chain into accounts (e.g.
user?.accounts?.filter(...) ?? []) so the filter call only executes when
accounts is defined, or otherwise return the empty array fallback.
| export const TOKEN_DETAILS = [ | ||
| { | ||
| chainId: '1', | ||
| name: 'Ethereum', | ||
| tokens: [ | ||
| { | ||
| address: '0x0000000000000000000000000000000000000000', | ||
| name: 'Ether', | ||
| symbol: 'ETH', | ||
| decimals: 18, | ||
| logoURI: 'https://raw.githubusercontent.com/spothq/cryptocurrency-icons/master/svg/color/eth.svg', | ||
| }, | ||
| ], | ||
| }, | ||
| } | ||
| ] |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Unify chainId types across mocks (string vs number).
In this mock, TOKEN_DETAILS uses chainId: '1' (string) while CHAIN_DETAILS above uses numeric IDs. Downstream utilities frequently compare chainIds by strict equality; mixing types will cause lookups like find(chain.chainId === chainId) to fail silently.
Apply this diff to standardize on a number here (or standardize the whole codebase on strings—whichever the prod SDK expects):
- chainId: '1',
+ chainId: 1,Follow-up: if the real SDK now exposes string chainIds, flip CHAIN_DETAILS to strings instead and adjust tests. I can help generate a migration diff once you confirm the canonical type.
Run this to see what your code expects:
🏁 Script executed:
#!/bin/bash
# Inspect usages comparing chainId strictly to string/number
rg -nP -C2 "\bchainId\s*===\s*['\"][0-9]+['\"]|\bchainId\s*===\s*\d+" --type=tsLength of output: 472
🏁 Script executed:
#!/bin/bash
# Locate peanut-sdk.ts in the repository
echo "Finding peanut-sdk.ts..."
fd peanut-sdk.ts
# Display CHAIN_DETAILS context in the mock file
echo "Inspecting CHAIN_DETAILS in src/utils/__mocks__/peanut-sdk.ts..."
rg -n "CHAIN_DETAILS" -C5 src/utils/__mocks__/peanut-sdk.tsLength of output: 518
🏁 Script executed:
#!/bin/bash
# Check which version of peanut-sdk is installed to infer its chainId type
rg -n "\"peanut-sdk\"" -C1 package.json || echo "peanut-sdk not found in dependencies"Length of output: 122
Standardize chainId types across mocks and code
We’ve confirmed that in src/utils/__mocks__/peanut-sdk.ts, CHAIN_DETAILS uses a numeric chainId (e.g. chainId: 1) while TOKEN_DETAILS uses a string (chainId: '1'). Meanwhile, production code strictly compares against a string literal (from.chainId === '1' in src/services/swap.ts), so these mismatches will lead to silent failures or broken tests.
Please apply the following refactors:
• In src/utils/__mocks__/peanut-sdk.ts
- Change all
TOKEN_DETAILS[n].chainIdvalues from strings to numbers. - Ensure the
chainIdfield on each entry inCHAIN_DETAILSremains the same type.
• In src/services/swap.ts (and any other strict comparisons)
- Update comparisons against string literals to numeric comparisons, e.g.:
- const isUsdtInMainnet = from.chainId === '1' && … + const isUsdtInMainnet = from.chainId === 1 && …
• After unifying on numbers, run the full test suite to catch any remaining chainId type mismatches.
These changes will eliminate inconsistent types between mocks and production code and prevent silent lookup failures.
📝 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.
| export const TOKEN_DETAILS = [ | |
| { | |
| chainId: '1', | |
| name: 'Ethereum', | |
| tokens: [ | |
| { | |
| address: '0x0000000000000000000000000000000000000000', | |
| name: 'Ether', | |
| symbol: 'ETH', | |
| decimals: 18, | |
| logoURI: 'https://raw.githubusercontent.com/spothq/cryptocurrency-icons/master/svg/color/eth.svg', | |
| }, | |
| ], | |
| }, | |
| } | |
| ] | |
| export const TOKEN_DETAILS = [ | |
| { | |
| chainId: 1, | |
| name: 'Ethereum', | |
| tokens: [ | |
| { | |
| address: '0x0000000000000000000000000000000000000000', | |
| name: 'Ether', | |
| symbol: 'ETH', | |
| decimals: 18, | |
| logoURI: 'https://raw.githubusercontent.com/spothq/cryptocurrency-icons/master/svg/color/eth.svg', | |
| }, | |
| ], | |
| }, | |
| ] |
🤖 Prompt for AI Agents
In src/utils/__mocks__/peanut-sdk.ts around lines 17 to 31, TOKEN_DETAILS
currently uses string chainId values; change all TOKEN_DETAILS[n].chainId
entries from strings (e.g. '1') to numbers (e.g. 1) to match CHAIN_DETAILS
numeric types and tests; then search src/services/swap.ts (and other code that
does strict comparisons like from.chainId === '1') and update those comparisons
to numeric form (e.g. from.chainId === 1) so production code and mocks use the
same numeric type; run the full test suite to detect and fix any remaining
chainId type mismatches.
| // 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 | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Normalize input and fix “digits” vs “letters” in docs; honor uppercase 3-letter keys.
Per the retrieved learning, countryCodeMap keys are uppercase 3-letter codes; values are uppercase 2-letter. Uppercasing input prevents subtle misses (e.g., 'usa', 'Us') and the function should return the 3-letter code when possible.
Apply:
-// Returns the 3-letter country code for the given country code
+// Returns the 3-letter country code for the given country code (accepts 2- or 3-letter; case-insensitive)
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
+ const normalized = (country || '').toUpperCase()
+ // If the input is already a 3-letter code and exists in the map, return it
+ if (countryCodeMap[normalized]) {
+ return normalized
+ }
+ // If the input is a 2-letter code, find the corresponding 3-letter code
+ const threeLetterCode = Object.keys(countryCodeMap).find((key) => countryCodeMap[key] === normalized)
+ return threeLetterCode || normalized
}This directly addresses the uppercase requirement noted in your prior PRs.
📝 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.
| // 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 for the given country code (accepts 2- or 3-letter; case-insensitive) | |
| export const getCountryCodeForWithdraw = (country: string) => { | |
| const normalized = (country || '').toUpperCase() | |
| // If the input is already a 3-letter code and exists in the map, return it | |
| if (countryCodeMap[normalized]) { | |
| return normalized | |
| } | |
| // If the input is a 2-letter code, find the corresponding 3-letter code | |
| const threeLetterCode = Object.keys(countryCodeMap).find((key) => countryCodeMap[key] === normalized) | |
| return threeLetterCode || normalized | |
| } |
🤖 Prompt for AI Agents
In src/utils/withdraw.utils.ts around lines 136 to 147, the comment refers to
"digits" but country codes are letters and the function doesn't normalize input
casing; change the docs to say "3-letter/2-letter country code", uppercase the
incoming country string, treat a 3-character input by uppercasing and checking
against the uppercase 3-letter keys in countryCodeMap (return the key if
present), and for 2-character inputs uppercase and find the corresponding
3-letter key by comparing against the map values; return the found 3-letter code
(or the original input if no mapping exists).
No description provided.