Skip to content

Stage manteca-integration#1269

Merged
jjramirezn merged 166 commits intopeanut-wallet-devfrom
feat/manteca-integration
Oct 1, 2025
Merged

Stage manteca-integration#1269
jjramirezn merged 166 commits intopeanut-wallet-devfrom
feat/manteca-integration

Conversation

@jjramirezn
Copy link
Contributor

No description provided.

jjramirezn and others added 30 commits September 1, 2025 16:35
…positCard for improved account details display
@jjramirezn jjramirezn requested a review from Hugo0 October 1, 2025 19:52
@vercel
Copy link

vercel bot commented Oct 1, 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 Oct 1, 2025 7:52pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 1, 2025

Caution

Review failed

Failed to post review comments

Walkthrough

Adds Mantéca regional payments (QR Pay, deposits, withdrawals) across app routes, components, hooks, and contexts. Refactors KYC to bridge/mantéca flows, updates request-fulfillment logic, country-code mappings, currency pricing API (buy/sell), exchange-rate route, and transaction receipt surfaces. Introduces new pages, modals, UI components, and renames several APIs/props.

Changes

Cohort / File(s) Summary
Config & Tests
package.json, src/components/Global/DirectSendQR/__tests__/recognizeQr.test.ts
Jest adds asset transformer; new QR test cases for Argentina QR3.
New QR Pay Flow
src/app/(mobile-ui)/qr-pay/layout.tsx, src/app/(mobile-ui)/qr-pay/page.tsx, src/components/Global/DirectSendQR/*
Adds QR Pay route/layout/page; extends QR types (ARGENTINA_QR3, PIX) and redirects to /qr-pay.
Mantéca Add Money
src/app/(mobile-ui)/add-money/[country]/[regional-method]/page.tsx, src/components/AddMoney/components/*Manteca*, src/components/Global/MantecaDetailsCard/*
New Mantéca deposit flow: input, details/share, supporting card component; route selector for regional methods.
Mantéca Withdraw
src/app/(mobile-ui)/withdraw/manteca/page.tsx, src/constants/manteca.consts.ts
New multi-step Mantéca withdrawal flow with country-specific configs and validations.
Withdraw/Main Flow Updates
src/app/(mobile-ui)/withdraw/page.tsx, src/context/WithdrawFlowContext.tsx, src/components/AddWithdraw/*
Introduces selectedMethod to context; reworks navigation/steps; routes bridge/crypto/mantéca paths; recent methods persistence.
Withdraw (Bank/Crypto) Adjustments
src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx, src/app/(mobile-ui)/withdraw/crypto/page.tsx
Navigation guards and identifier formatting updates; redirect and effect logic tweaks.
Country-Code Mapping Changes
src/components/AddMoney/consts/index.ts, src/components/AddMoney/components/*, src/components/AddWithdraw/DynamicBankAccountForm.tsx, src/components/Common/CountryList.tsx, src/components/Common/SavedAccountsView.tsx
Introduces BRIDGE/MANTECA/ALL alpha3→alpha2 maps; replaces legacy map usages; routes per-country methods; adds icons/isSoon fields.
KYC Refactor (Bridge/Mantéca)
src/hooks/useBridgeKycFlow.ts, src/components/Kyc/*, src/app/(mobile-ui)/history/page.tsx, src/components/Home/HomeHistory.tsx, src/app/(mobile-ui)/add-money/[country]/bank/page.tsx
Renames useKycFlow→useBridgeKycFlow; adds Mantéca KYC modal, status drawer changes, verification rows; replaces modals with InitiateBridgeKYCModal.
KYC Status Surfaces
src/components/Kyc/states/*, src/components/Kyc/CountryFlagAndName.tsx, src/components/Kyc/CountryRegionRow.tsx
Adds country/region displays; expands props and rendering for processing/completed/failed states.
Identity Verification UI
src/components/Profile/views/IdentityVerification.view.tsx, src/components/Profile/*, src/app/(mobile-ui)/home/page.tsx
New country-driven verification with Mantéca/Bridge paths; centralizes verification via useKycStatus.
Claim Flow (Mantéca/Mercado Pago)
src/components/Claim/Link/*, src/components/Common/CountryListRouter.tsx
Adds Mantéca claim manager/details/review steps; toggles claimToMercadoPago; integrates KYC gating.
Request Fulfillment Flow
src/context/RequestFulfillmentFlowContext.tsx, src/components/Payment/PaymentForm/*, src/components/Payment/Views/MantecaFulfillment.view.tsx, src/components/Request/views/*, src/app/[...recipient]/client.tsx, src/components/Common/ActionList*.tsx
Adds fulfillUsingManteca and regional method type; renames fulfil→fulfill; Mantéca fulfillment view; geolocation-based actions; PIX/MercadoPago handling.
Currency & Exchange Rate
src/app/actions/currency.ts, src/hooks/useCurrency.ts, src/app/api/exchange-rate/route.ts, src/app/actions/onramp.ts, src/hooks/useCreateOnramp.ts
getCurrencyPrice returns {buy,sell}; hook updated; exchange-rate route uses currency price/Frankfurter; amount calcs use price.buy.
History/Receipt/Transactions
src/app/actions/history.ts, src/app/receipt/[entryId]/page.tsx, src/components/TransactionDetails/*, src/components/TransactionDetails/transactionTransformer.ts
Adds history fetch; dynamic receipt page/metadata; new types (qr_payment, pay), avatarUrl, Mantéca rows/receipt data.
Contexts & Auth
src/context/ClaimBankFlowContext.tsx, src/context/authContext.tsx, src/context/tokenSelector.context.tsx
Adds claimToMercadoPago/regionalMethodType; clears redirect on logout; token context reset simplification.
General UI Tweaks
src/components/Global/* (IconStack, TokenAmountInput, ValidatedInput, CopyToClipboard, Layout, IframeWrapper), src/components/0_Bruddle/Card.tsx, src/components/Refund/index.tsx, src/constants/*
TokenAmountInput redesign; new props (imageClassName, SelectItem); loading states extended; PIX asset added; Footer removed; small style adjustments.
App Routes & Misc
src/app/not-found.tsx, src/app/kyc/success/page.tsx, src/app/(mobile-ui)/layout.tsx, src/app/actions/bridge/get-customer.ts
Adds not-found and KYC success pages; minor layout refactor; new bridge customer action.

Estimated code review effort

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

Possibly related PRs

Suggested labels

enhancement

Suggested reviewers

  • Hugo0

Pre-merge checks and finishing touches and finishing touches

❌ Failed checks (2 warnings, 1 inconclusive)
Check name Status Explanation Resolution
Description Check ⚠️ Warning No pull request description was provided, leaving reviewers without any context or summary of the extensive changes in this branch, which makes it impossible to understand the purpose and scope of the updates. Please include a descriptive pull request description that outlines the objectives of the Manteca integration, the key areas affected (such as add-money, withdraw, and QR pay flows), and any relevant high-level details to guide the review.
Docstring Coverage ⚠️ Warning Docstring coverage is 24.32% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title Check ❓ Inconclusive The title “Stage manteca-integration” references the Manteca feature but uses ambiguous phrasing that does not clearly convey the primary change or its scope, and it is not structured as a concise, descriptive sentence that a teammate scanning history would immediately understand. Please rephrase the title to a clear, concise sentence that highlights the primary change, for example “Integrate Manteca payment and withdrawal flows,” to improve clarity for reviewers and future readers.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/manteca-integration
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/manteca-integration

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

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai bot added the enhancement New feature or request label Oct 1, 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: 37

Caution

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

⚠️ Outside diff range comments (14)
src/hooks/useDetermineBankClaimType.ts (1)

75-75: Missing dependency in useEffect array.

The effect uses isUserBridgeKycApproved (line 31) but the dependency array doesn't include it. React's exhaustive-deps rule will warn about this omission, and the effect may not re-run when KYC status changes independently of the user object reference.

Apply this diff to add the missing dependency:

-    }, [user, senderUserId, setSenderDetails])
+    }, [user, senderUserId, setSenderDetails, isUserBridgeKycApproved])
src/components/Refund/index.tsx (1)

152-154: Critical: Fix property access in onChange callback.

The callback accesses chainId.chainId, but the Select component now passes a SelectItem object with only id and title properties. This will cause a runtime error.

Apply this diff to fix the property access:

-                                onChange={(chainId: any) => {
-                                    refundForm.setValue('chainId', chainId.chainId)
+                                onChange={(item) => {
+                                    refundForm.setValue('chainId', item.id)
                                 }}
src/hooks/useCurrency.ts (2)

27-57: Prevent stale updates from in‑flight requests (race condition).

If code changes while getCurrencyPrice is pending, the older promise may overwrite newer state. Guard with a cancellation flag and reset state at the start of a fetch.

Apply this diff:

 useEffect(() => {
-        if (!code) {
-            setIsLoading(false)
-            return
-        }
+        let cancelled = false
+        if (!code) {
+            setSymbol(null)
+            setPrice(null)
+            setIsLoading(false)
+            return () => {
+                cancelled = true
+            }
+        }
 
         if (code === 'USD') {
             setSymbol(SYMBOLS_BY_CURRENCY_CODE[code])
             setPrice({ buy: 1, sell: 1 })
             setIsLoading(false)
-            return
+            return () => {
+                cancelled = true
+            }
         }
 
-        if (!Object.keys(SYMBOLS_BY_CURRENCY_CODE).includes(code)) {
-            setCode(null)
-            setIsLoading(false)
-            return
-        }
+        if (!(code in SYMBOLS_BY_CURRENCY_CODE)) {
+            setCode(null)
+            setSymbol(null)
+            setPrice(null)
+            setIsLoading(false)
+            return () => {
+                cancelled = true
+            }
+        }
 
         setIsLoading(true)
+        setSymbol(null)
+        setPrice(null)
         getCurrencyPrice(code)
             .then((price) => {
-                setSymbol(SYMBOLS_BY_CURRENCY_CODE[code])
-                setPrice(price)
-                setIsLoading(false)
+                if (cancelled) return
+                if (!Number.isFinite(price.buy) || !Number.isFinite(price.sell)) {
+                    console.error('Invalid price payload', price)
+                    setIsLoading(false)
+                    return
+                }
+                setSymbol(SYMBOLS_BY_CURRENCY_CODE[code])
+                setPrice(price)
+                setIsLoading(false)
             })
             .catch((err) => {
                 console.error(err)
-                setIsLoading(false)
+                if (cancelled) return
+                setSymbol(null)
+                setPrice(null)
+                setIsLoading(false)
             })
-    }, [code])
+        return () => {
+            cancelled = true
+        }
+    }, [code])

40-44: Use O(1) membership check and clear stale state on invalid codes.

Object.keys(...).includes(...) allocates and you leave previous symbol/price around.

Use the diff in the race-condition comment above; it already swaps to (code in SYMBOLS_BY_CURRENCY_CODE) and clears state.

src/app/api/exchange-rate/route.ts (1)

17-21: Validate currency codes format (A‑Z, length 3).

Avoids hitting providers with invalid inputs and reduces noise.

Apply this diff:

         // Validate required parameters
         if (!from || !to) {
             return NextResponse.json({ error: 'Missing required parameters: from and to' }, { status: 400 })
         }
 
         const fromUc = from.toUpperCase()
         const toUc = to.toUpperCase()
+        if (!/^[A-Z]{3}$/.test(fromUc) || !/^[A-Z]{3}$/.test(toUc)) {
+            return NextResponse.json({ error: 'Invalid currency code format' }, { status: 400 })
+        }
src/components/Claim/useClaimLink.tsx (1)

99-106: Add missing handler for the ‘regional-claim’ step

Verified that the new 'regional-req-fulfill' step is correctly consumed in PaymentForm/index.tsx, but there is no branch handling the 'regional-claim' value anywhere. You must implement routing or state logic for 'regional-claim' (e.g. in the Claim or ClaimLink components) to complete the regional payment flow.

src/components/AddMoney/UserDetailsForm.tsx (1)

14-14: Remove unused isSubmitting prop from interface.

The isSubmitting prop is declared in the interface but not used in the component implementation (line 20). This creates an unnecessary prop that callers must provide but has no effect.

Apply this diff to remove the unused prop:

 interface UserDetailsFormProps {
     onSubmit: (data: UserDetailsFormData) => Promise<{ error?: string }>
-    isSubmitting: boolean
     onValidChange?: (isValid: boolean) => void
     initialData?: Partial<UserDetailsFormData>
 }

Also applies to: 20-20

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

140-185: Remove duplicate balance warning effect

Lines 140-162 and 164-185 contain identical logic for showing the balance warning modal. This duplication will cause the effect to run twice and may lead to unexpected behavior.

Remove one of the duplicate effects:

-    // 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
-            ) {
-                setShowBalanceWarningModal(true)
-            }
-        }
-    }, [balance, isFetchingBalance, showIOSPWAInstallModal, showAddMoneyPromptModal, user])
src/components/TransactionDetails/transactionTransformer.ts (1)

328-334: Add MANTECA flows to bridge status mapping

The new Mantéca on/off-ramp entries still hit the generic status branch, so statuses like PAYMENT_SUBMITTED/PAYMENT_PROCESSED collapse to “pending”. Please include the Mantéca entry types in the bridge-style status block so they keep their processing/completed states.

-    if (
-        entry.type === EHistoryEntryType.BRIDGE_OFFRAMP ||
-        entry.type === EHistoryEntryType.BRIDGE_ONRAMP ||
-        entry.type === EHistoryEntryType.BANK_SEND_LINK_CLAIM ||
-        entry.extraData?.fulfillmentType === 'bridge'
-    ) {
+    if (
+        entry.type === EHistoryEntryType.BRIDGE_OFFRAMP ||
+        entry.type === EHistoryEntryType.BRIDGE_ONRAMP ||
+        entry.type === EHistoryEntryType.MANTECA_OFFRAMP ||
+        entry.type === EHistoryEntryType.MANTECA_ONRAMP ||
+        entry.type === EHistoryEntryType.BANK_SEND_LINK_CLAIM ||
+        entry.extraData?.fulfillmentType === 'bridge'
+    ) {
src/components/Common/CountryListRouter.tsx (1)

63-86: Reset Mantéca flags when selecting non-supported countries

After the first Mantéca selection, setClaimToMercadoPago(true) / setFulfillUsingManteca(true) stay latched. Picking a different country later still drives the Mantéca UI. Please explicitly clear the flags in the non-Mantéca branches (and restore any UI toggles you suppressed) so the flow reverts to the standard bank experience.

         if (flow === 'claim') {
             setSelectedCountry(country)
             if (isMantecaSupportedCountry) {
                 setClaimBankFlowStep(null) // reset the flow step to initial view first
                 setClaimToMercadoPago(true)
             } else {
+                setClaimToMercadoPago(false)
                 setClaimBankFlowStep(ClaimBankFlowStep.BankDetailsForm)
             }
         } else if (flow === 'request') {
             if (isMantecaSupportedCountry) {
                 setShowRequestFulfilmentBankFlowManager(false)
                 setFulfillUsingManteca(true)
+            } else {
+                setFulfillUsingManteca(false)
+                setShowRequestFulfilmentBankFlowManager(true)
             }
             setSelectedCountryForRequest(country)
src/hooks/useBridgeKycFlow.ts (1)

118-157: Include onManualClose in useCallback deps to avoid stale closure

onManualClose is used inside handleIframeClose but omitted from the dependency list, risking no-op callbacks after prop changes.

-  }, [iframeOptions.src, apiResponse, flow, router])
+  }, [iframeOptions.src, apiResponse, flow, router, onManualClose])
src/components/Kyc/KycStatusItem.tsx (1)

79-79: Tailwind class typo breaks subtitle color

Use text-gray-1 (consistent with the codebase), not text-grey-1.

-<p className="text-sm text-grey-1">{subtitle}</p>
+<p className="text-sm text-gray-1">{subtitle}</p>
src/components/TransactionDetails/TransactionDetailsReceipt.tsx (1)

100-161: Include isPublic in the memo dependencies.

rowVisibilityConfig now branches on isPublic, but the memo only depends on [transaction, isPendingBankRequest]. If isPublic flips (drawer reused for a public receipt), this memo will keep the stale private value and expose bank/deposit fields that should stay hidden. Add isPublic to the dependency array so the memo recomputes safely.

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

168-177: Ensure geo-filtered methods react to location changes.
Line 168 memoizes sortedActionMethods only on requiresVerification, so when userGeoLocationCountryCode finishes loading the geolocatedMethods array changes but this memo stays frozen. Result: Brazilian users still see Mercado Pago, and everyone else still sees Pix, because the geo-specific filtering never applies. Include the geo-dependent array (or the underlying country code) in the dependency list so the list updates once location data arrives.

-    const sortedActionMethods = useMemo(() => {
-        return [...geolocatedMethods].sort((a, b) => {
+    const sortedActionMethods = useMemo(() => {
+        return [...geolocatedMethods].sort((a, b) => {
             const aIsUnavailable = a.soon || (a.id === 'bank' && requiresVerification)
             const bIsUnavailable = b.soon || (b.id === 'bank' && requiresVerification)

             if (aIsUnavailable === bIsUnavailable) {
                 return 0
             }
             return aIsUnavailable ? 1 : -1
         })
-    }, [requiresVerification])
+    }, [geolocatedMethods, requiresVerification])
🧹 Nitpick comments (56)
src/components/Kyc/KycVerificationInProgressModal.tsx (1)

52-59: Consider adding aria-hidden to the decorative icon.

The component implementation is clean and well-structured. For a minor accessibility improvement, consider adding aria-hidden="true" to the Icon since it's decorative (the text conveys the full message).

Apply this diff to improve accessibility:

-            <Icon name="info" className="h-3 w-3" />
+            <Icon name="info" className="h-3 w-3" aria-hidden="true" />
src/context/authContext.tsx (1)

162-162: Consider removing debug console.log.

The console.log({ user }) statement appears to be leftover debug code. Consider removing it to avoid cluttering production console logs and potentially exposing user data in browser DevTools.

Apply this diff to remove the debug statement:

-    console.log({ user })
-
     return (
src/components/Refund/index.tsx (1)

147-151: Optimize: Compute mapped items once.

The array is mapped twice—once for items and again for value. This is inefficient and error-prone if the mappings diverge.

Refactor to compute the mapped items once:

+                            const mappedChains = consts.supportedPeanutChains.map((chain) => ({
+                                id: chain.chainId,
+                                title: chain.name,
+                            }))
                             <Select
                                 className="h-8 border border-n-1 p-1 outline-none"
                                 classButton="h-auto px-0 border-none bg-trasparent text-sm !font-normal"
                                 classOptions="-left-4 -right-3 w-auto py-1 overflow-auto max-h-36"
                                 classArrow="ml-1"
-                                items={consts.supportedPeanutChains.map((chain) => ({
-                                    id: chain.chainId,
-                                    title: chain.name,
-                                }))}
+                                items={mappedChains}
                                 value={
-                                    consts.supportedPeanutChains
-                                        .map((c) => ({ id: c.chainId, title: c.name }))
-                                        .find((i) => i.id === refundFormWatch.chainId) ?? null
+                                    mappedChains.find((i) => i.id === refundFormWatch.chainId) ?? null
                                 }
src/context/tokenSelector.context.tsx (1)

56-70: Consider moving constant token data objects outside the component.

peanutWalletTokenData and emptyTokenData are recreated on every render. Since they are constant values, moving them outside the component body would:

  • Avoid unnecessary object creation on each render.
  • Prevent potential stale closure issues in resetTokenContextProvider (lines 104-118), where these objects are referenced inside a useCallback.

Apply this diff to move the constants outside:

+'use client'
+import React, { createContext, useEffect, useState, useCallback } from 'react'
+
+import { getSquidChainsAndTokens } from '@/app/actions/squid'
+// ... other imports
+
+const emptyTokenData = {
+    address: '',
+    chainId: '',
+    decimals: undefined,
+}
+
+const peanutWalletTokenData = {
+    price: 1,
+    decimals: PEANUT_WALLET_TOKEN_DECIMALS,
+    symbol: PEANUT_WALLET_TOKEN_SYMBOL,
+    name: PEANUT_WALLET_TOKEN_NAME,
+    address: PEANUT_WALLET_TOKEN,
+    chainId: PEANUT_WALLET_CHAIN.id.toString(),
+    logoURI: PEANUT_WALLET_TOKEN_IMG_URL,
+} as ITokenPriceData
+
 export const TokenContextProvider = ({ children }: { children: React.ReactNode }) => {
     const { isConnected: isPeanutWallet } = useWallet()
-    const { user } = useAuth()
-
-    const peanutWalletTokenData = {
-        price: 1,
-        decimals: PEANUT_WALLET_TOKEN_DECIMALS,
-        symbol: PEANUT_WALLET_TOKEN_SYMBOL,
-        name: PEANUT_WALLET_TOKEN_NAME,
-        address: PEANUT_WALLET_TOKEN,
-        chainId: PEANUT_WALLET_CHAIN.id.toString(),
-        logoURI: PEANUT_WALLET_TOKEN_IMG_URL,
-    } as ITokenPriceData
-
-    const emptyTokenData = {
-        address: '',
-        chainId: '',
-        decimals: undefined,
-    }
src/components/Profile/views/IdentityVerification.view.tsx (2)

110-123: Consider extracting helper functions outside the component.

isBridgeSupportedCountry and isMantecaSupportedCountry are recreated on every render. Since they only depend on external constants, consider moving them outside the component for better performance.

Apply this diff:

+'use client'
+import { updateUserById } from '@/app/actions/users'
+// ... other imports ...
+
+// Helper functions (add before component)
+const isBridgeSupportedCountry = (code: string) => {
+    const upper = code.toUpperCase()
+    return (
+        upper === 'US' ||
+        upper === 'MX' ||
+        Object.keys(BRIDGE_ALPHA3_TO_ALPHA2).includes(upper) ||
+        Object.values(BRIDGE_ALPHA3_TO_ALPHA2).includes(upper)
+    )
+}
+
+const isMantecaSupportedCountry = (code: string) => {
+    const upper = code.toUpperCase()
+    return Object.prototype.hasOwnProperty.call(MantecaSupportedExchanges, upper)
+}
+
 const IdentityVerificationView = () => {
     const router = useRouter()
     // ... rest of component ...
-
-    // country validation helpers
-    const isBridgeSupportedCountry = (code: string) => {
-        const upper = code.toUpperCase()
-        return (
-            upper === 'US' ||
-            upper === 'MX' ||
-            Object.keys(BRIDGE_ALPHA3_TO_ALPHA2).includes(upper) ||
-            Object.values(BRIDGE_ALPHA3_TO_ALPHA2).includes(upper)
-        )
-    }
-
-    const isMantecaSupportedCountry = (code: string) => {
-        const upper = code.toUpperCase()
-        return Object.prototype.hasOwnProperty.call(MantecaSupportedExchanges, upper)
-    }

39-47: Consider memoizing handleRedirect.

The handleRedirect function is called from multiple useCallback hooks (handleBridgeKycSuccess, handleBack) but is not itself memoized. This could lead to stale closures if those callbacks are not properly updated.

Apply this diff:

-    const handleRedirect = () => {
+    const handleRedirect = useCallback(() => {
         const redirectUrl = getRedirectUrl()
         if (redirectUrl) {
             clearRedirectUrl()
             router.push(redirectUrl)
         } else {
             router.replace('/profile')
         }
-    }
+    }, [router])

Then update the dependent callbacks to include handleRedirect in their dependency arrays instead of router.

src/components/TransactionDetails/transaction-details.utils.ts (1)

21-21: Fix typo in comment.

The comment has a typo: "rder" should be "order".

Apply this diff:

-// rder of the rows in the receipt
+// order of the rows in the receipt
src/app/actions/onramp.ts (1)

77-81: Confirm rate orientation and harden parsing for amount calculation

  • Ensure price.buy is the correct direction for USD → source currency here; inconsistent usage will misquote users.
  • Guard against invalid strings (commas, empty, NaN) before Number(); fail fast with a clear error.

Suggested patch:

-        const amount = (Number(params.amount) * price.buy).toFixed(2)
+        const usd = Number(String(params.amount).replace(/,/g, ''))
+        if (!Number.isFinite(usd) || usd <= 0) {
+            return { error: 'Invalid amount.' }
+        }
+        const amount = (usd * price.buy).toFixed(2)
src/hooks/useCreateOnramp.ts (1)

45-49: Avoid param reassignment; sanitize USD input and validate before computing

Reassigning amount obscures types and risks NaN if usdAmount has commas/invalid chars.

Apply:

-                    amount = (Number(usdAmount) * price.buy).toFixed(2)
+                    const usd = Number(String(usdAmount).replace(/,/g, ''))
+                    if (!Number.isFinite(usd) || usd <= 0) throw new Error('Invalid USD amount')
+                    const calculatedAmount = (usd * price.buy).toFixed(2)

Then use calculatedAmount below when sending the request:

-                        amount,
+                        amount: calculatedAmount ?? amount,
src/components/AddMoney/components/MantecaAddMoney.tsx (1)

25-27: Define deposit limit constants as numbers or document string intent

Using strings works with parseUnits, but for readability consider numbers and convert locally, or add a comment that strings are intentional for parseUnits compatibility.

src/hooks/useCurrency.ts (1)

4-19: Tighten typing of symbol map and expose a CurrencyCode type.

Improves safety and autocompletion across the app.

Apply this diff:

-export const SYMBOLS_BY_CURRENCY_CODE: Record<string, string> = {
+export const SYMBOLS_BY_CURRENCY_CODE = {
     ARS: 'ARS',
     USD: '$',
     EUR: '€',
     MXN: 'MX$',
     BRL: 'R$',
     COP: 'Col$',
     CRC: '₡',
     BOB: '$b',
     PUSD: 'PUSD',
     GTQ: 'Q',
     PHP: '₱',
     GBP: '£',
     JPY: '¥',
     CAD: 'CA$',
-}
+} as const
+export type CurrencyCode = keyof typeof SYMBOLS_BY_CURRENCY_CODE
src/app/api/exchange-rate/route.ts (6)

78-96: Parallelize intermediate USD lookups for non‑USD pairs.

Two independent calls; run them concurrently.

Apply this diff:

-        const fromToUsdRate = await getExchangeRate(fromUc, 'USD')
-        const usdToToRate = await getExchangeRate('USD', toUc)
+        const [fromToUsdRate, usdToToRate] = await Promise.all([
+            getExchangeRate(fromUc, 'USD'),
+            getExchangeRate('USD', toUc),
+        ])

102-120: Name shadowing: getExchangeRate here vs actions/exchange-rate.getExchangeRate.

To reduce confusion during debugging, consider renaming the local helper to resolveExchangeRate.


146-158: Parallelize price fetches for cross currency via USD.

Saves one RTT and lowers latency.

Apply this diff:

-            const fromPrices = await getCurrencyPrice(from)
-            const toPrices = await getCurrencyPrice(to)
+            const [fromPrices, toPrices] = await Promise.all([getCurrencyPrice(from), getCurrencyPrice(to)])

176-185: Parallelize Frankfurter direct hops.

Same rationale: independent requests.

Apply this diff:

-        const fromToUsdRate = await fetchDirectFromFrankfurter(from, 'USD')
-        const usdToToRate = await fetchDirectFromFrankfurter('USD', to)
+        const [fromToUsdRate, usdToToRate] = await Promise.all([
+            fetchDirectFromFrankfurter(from, 'USD'),
+            fetchDirectFromFrankfurter('USD', to),
+        ])

8-10: Factor provider spread into a named constant for clarity.

Improves readability and makes future tweaks safer.

Apply this diff:

-// LATAM currencies that should use Manteca API
-const MANTECA_CURRENCIES = new Set(['ARS', 'BRL', 'COP', 'CRC', 'PUSD', 'GTQ', 'PHP', 'BOB'])
+// LATAM currencies that should use Manteca API
+const MANTECA_CURRENCIES = new Set(['ARS', 'BRL', 'COP', 'CRC', 'PUSD', 'GTQ', 'PHP', 'BOB'])
+const FRANKFURTER_MARKDOWN = 0.005 // 50 bps
 
 ...
-        return data.rates[to] * 0.995 // Subtract 50bps
+        return data.rates[to] * (1 - FRANKFURTER_MARKDOWN)

Also applies to: 191-208


123-131: Reduce log noise and avoid leaking parameters at info level.

Swap console.log to debug (or remove) and keep errors; parameters can remain but avoid verbose logs in hot paths.

-    console.log('Fetching from getCurrencyPrice')
+    // debug: using getCurrencyPrice path

Also applies to: 164-166

src/components/Kyc/InitiateMantecaKYCModal.tsx (2)

55-63: Handle openMantecaKyc result for better UX.

openMantecaKyc returns success/error; consider awaiting and surfacing errors (toast) to avoid silent failures.

-                        onClick: () => openMantecaKyc(country),
+                        onClick: async () => {
+                            const res = await openMantecaKyc(country)
+                            if (!res.success) {
+                                // TODO: surface error via toast/inline message
+                                console.error(res.error)
+                            }
+                        },

72-136: Minor: ensure selectedCountry.title is safe for ReactNode injection.

If title can come from external sources, sanitize or constrain to string to avoid surprises; otherwise ignore.

src/components/AddMoney/components/MantecaDepositShareDetails.tsx (2)

54-67: Consider refactoring data mutation for clarity.

The code mutates usdAmount after extraction (line 62), and uses let declarations that are reassigned. While functional, this approach can be harder to trace. Consider computing the final values directly.

Apply this diff for a more declarative approach:

     const depositAddress = depositDetails.details.depositAddress
     const shortenedAddress = depositAddress.length > 30 ? shortenStringLong(depositAddress, 10) : depositAddress
     const depositAlias = depositDetails.details.depositAlias
     const depositAmount = currencyAmount ?? depositDetails.stages['1'].thresholdAmount
-    let usdAmount = depositDetails.stages['3'].amount
     const currencySymbol = depositDetails.stages['1'].asset
     const exchangeRate = depositDetails.details.price
-    let networkFees = depositDetails.details.withdrawCostInAsset
-    usdAmount = (Number(usdAmount) - Number(networkFees)).toString()
-    if (Number(networkFees) < 0.01) {
-        networkFees = '< 0.01 USD'
-    } else {
-        networkFees = `${formatCurrency(networkFees)} USD`
-    }
+    const rawNetworkFees = depositDetails.details.withdrawCostInAsset
+    const networkFees = Number(rawNetworkFees) < 0.01 
+        ? '< 0.01 USD' 
+        : `${formatCurrency(rawNetworkFees)} USD`
+    const usdAmount = (Number(depositDetails.stages['3'].amount) - Number(rawNetworkFees)).toString()

85-114: Improve image accessibility and error handling.

Lines 94-99: The Image component uses a generic alt text "flag" and relies on an external CDN without error handling. Consider more descriptive alt text and adding error boundaries.

Apply this diff:

                             <Image
                                 src={`https://flagcdn.com/w160/${countryCodeForFlag}.png`}
-                                alt={`flag`}
+                                alt={`${currentCountryDetails?.title || 'Country'} flag`}
                                 width={48}
                                 height={48}
                                 className="h-12 w-12 rounded-full object-cover"
+                                onError={(e) => {
+                                    e.currentTarget.style.display = 'none'
+                                }}
                             />
src/components/Profile/views/ProfileEdit.view.tsx (1)

126-127: Suggest consistent hook usage for KYC checks.

Lines 126 and 134 still use direct user?.user.bridgeKycStatus === 'approved' checks instead of the isUserBridgeKycApproved value from the useKycStatus hook.

Apply this diff for consistency:

                    disabled={user?.user.bridgeKycStatus === 'approved'}
+                    disabled={isUserBridgeKycApproved}

And:

                    disabled={user?.user.bridgeKycStatus === 'approved'}
+                    disabled={isUserBridgeKycApproved}

Also applies to: 134-135

src/components/Claim/Link/Initial.view.tsx (1)

637-651: Consider removing the dead isReward branch.

The isReward constant is hardcoded to false at line 399, so the ternary at lines 643-647 will always take the else branch. You can simplify this to always compute the USD amount.

Apply this diff to simplify the amount calculation:

     if (claimToMercadoPago) {
         return (
             <MantecaFlowManager
                 claimLinkData={claimLinkData}
                 attachment={attachment}
                 amount={
-                    isReward
-                        ? formatTokenAmount(Number(formatUnits(claimLinkData.amount, claimLinkData.tokenDecimals)))!
-                        : (formatTokenAmount(
-                              Number(formatUnits(claimLinkData.amount, claimLinkData.tokenDecimals)) * tokenPrice
-                          ) ?? '')
+                    formatTokenAmount(
+                        Number(formatUnits(claimLinkData.amount, claimLinkData.tokenDecimals)) * (tokenPrice ?? 0)
+                    ) ?? ''
                 }
             />
         )
     }
src/components/Global/MantecaDetailsCard/index.tsx (1)

5-11: Prefer id or rowId instead of key in the data model.

Using a key field in the row type can be confusing since React treats key specially in JSX. Consider renaming to id/rowId and mapping it to the JSX key prop.

src/context/ClaimBankFlowContext.tsx (2)

45-48: Centralize regional method type.

Instead of inline string literals ('mercadopago' | 'pix'), define a shared type/enum (e.g., RegionalMethodType) in a consts/types module to avoid drift across files.


137-140: Clean up useMemo deps (setter inconsistency).

You include setClaimToMercadoPago but not setRegionalMethodType. Setters from useState are stable and can be omitted. Recommend removing the setter from deps for consistency.

Apply this diff:

         [
             claimToExternalWallet,
             flowStep,
             selectedCountry,
             resetFlow,
             offrampDetails,
             claimError,
             claimType,
             senderDetails,
             showVerificationModal,
             bankDetails,
             savedAccounts,
             selectedBankAccount,
             senderKycStatus,
             justCompletedKyc,
             claimToMercadoPago,
-            setClaimToMercadoPago,
             regionalMethodType,
         ]
src/components/Profile/AvatarWithBadge.tsx (2)

7-7: Broaden logo type to support remote URLs.

If callers may pass CDN/remote URLs, widen to string | StaticImport (Next 13+). Update import and prop type.

Apply this diff:

-import Image, { type StaticImageData } from 'next/image'
+import Image, { type StaticImport } from 'next/image'
@@
-    logo?: StaticImageData
+    logo?: string | StaticImport

Also applies to: 24-25


104-112: Tailwind JIT cannot see dynamic size-[${…}]; use fill or w-full h-full.

The class size-[${iconSizeMap[size]}] won't generate CSS. Prefer fill to fully cover the rounded container.

Apply this diff:

-                {logo && (
-                    <Image
-                        src={logo}
-                        alt={''}
-                        width={160}
-                        height={160}
-                        className={`size-[${iconSizeMap[size]}] rounded-full object-cover`}
-                    />
-                )}
+                {logo && (
+                    <div className="relative h-full w-full overflow-hidden rounded-full">
+                        <Image
+                            src={logo}
+                            alt={name ? `${name} logo` : 'logo'}
+                            fill
+                            sizes="(max-width: 768px) 64px, 96px"
+                            className="object-cover"
+                            priority={size !== 'large'}
+                        />
+                    </div>
+                )}
src/components/Global/DirectSendQR/utils.ts (1)

68-73: Avoid relying on object key iteration order for precedence.

Since order matters (MP before QR3), use an ordered tuple list for matching to make intent explicit and resilient to refactors.

You can add:

const ORDERED_REGEXES: ReadonlyArray<[QrType, RegExp]> = [
  [EQrType.MERCADO_PAGO, MP_AR_REGEX],
  [EQrType.ARGENTINA_QR3, ARGENTINA_QR3_REGEX],
  [EQrType.BITCOIN_ONCHAIN, /^(bc1|[13])[a-zA-HJ-NP-Z0-9]{25,39}$/],
  [EQrType.BITCOIN_INVOICE, /^ln(bc|tb|bcrt)([0-9]{1,}[a-z0-9]+){1}$/],
  [EQrType.PIX, PIX_REGEX],
  [EQrType.XRP_ADDRESS, /^r[0-9a-zA-Z]{24,34}$/],
  [EQrType.TRON_ADDRESS, /^T[1-9A-HJ-NP-Za-km-z]{33}$/],
  [EQrType.SOLANA_ADDRESS, /^[1-9A-HJ-NP-Za-km-z]{32,44}$/],
  [EQrType.URL, /^(https?:\/\/)?(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)$/],
]

Then in recognizeQr:

for (const [type, regex] of ORDERED_REGEXES) {
  if (regex.test(data)) return type
}
src/app/(mobile-ui)/history/page.tsx (2)

70-85: Ensure KYC item UUIDs are unique.

'bridge-kyc-status-item' is fine for a single status row, but verification UUID fallback (provider-mantecaGeo) could collide across multiple verifications. Append a timestamp or index to guarantee uniqueness.

Example:

-    uuid: verification.providerUserId ?? `${verification.provider}-${verification.mantecaGeo}`,
+    uuid:
+      verification.providerUserId ??
+      `${verification.provider}-${verification.mantecaGeo}-${verification.approvedAt ?? verification.updatedAt ?? verification.createdAt ?? 'na'}`

162-169: Pass a consistent date to KycStatusItem.

Subtitle uses bridgeKycStartedAt, while grouping uses item.timestamp. To avoid mismatches, pass item.timestamp as a fallback so the visible date aligns with grouping.

Apply this diff:

-    bridgeKycStartedAt={
-        item.bridgeKycStatus ? user?.user.bridgeKycStartedAt : undefined
-    }
+    bridgeKycStartedAt={
+        item.bridgeKycStatus ? (user?.user.bridgeKycStartedAt ?? item.timestamp) : undefined
+    }
src/components/Claim/Link/views/MantecaDetailsStep.view.tsx (1)

20-22: Remove unnecessary async keyword.

The handleOnClick function is marked async but performs no asynchronous operations. This adds unnecessary overhead and can be misleading.

Apply this diff:

-    const handleOnClick = async () => {
+    const handleOnClick = () => {
         setCurrentStep(MercadoPagoStep.REVIEW)
     }
src/app/actions/bridge/get-customer.ts (1)

55-59: Remove the debug console.log.

Shipping with console.log('normalized kushagra', …) is noisy and leaks into server logs. Please drop it before merging.

Apply this diff:

-            const normalized = normalizeCountry(raw)
-            console.log('normalized kushagra', normalized)
-            return { countryCode: normalized, rawCountry: raw }
+            const normalized = normalizeCountry(raw)
+            return { countryCode: normalized, rawCountry: raw }
src/components/Claim/Link/views/MantecaReviewStep.tsx (5)

9-9: Consider renaming MercadoPagoStep to MantecaStep for consistency.

The enum name MercadoPagoStep appears inconsistent with the component name MantecaReviewStep and the broader Manteca integration context. If this enum is specifically for Manteca flows, consider renaming it to MantecaStep or MantecaFlowStep for clarity.

Also applies to: 13-13


51-51: Redundant null check for destinationAddress.

The check if (destinationAddress) on line 51 is redundant since destinationAddress is a required prop (non-optional in the interface). Consider removing this check or making the prop optional if it can legitimately be empty.


55-60: Add validation for claimResponse.

The code extracts txHash from claimResponse without first checking if claimResponse itself is valid. If sendLinksApi.claim returns null or undefined, the optional chaining on line 56 will result in txHash being undefined, which is then caught by the check on line 57. However, it would be clearer to validate claimResponse explicitly before extracting txHash.

Consider this approach:

                 const claimResponse = await sendLinksApi.claim(MANTECA_DEPOSIT_ADDRESS, claimLink)
+                if (!claimResponse) {
+                    setError('Claim failed: no response from server.')
+                    return
+                }
                 const txHash = claimResponse?.claim?.txHash
                 if (!txHash) {
                     setError('Claim failed: missing transaction hash.')
                     return
                 }

61-66: Validate the cleaned amount before sending to API.

While removing commas from amount is correct, there's no validation that the resulting string is a valid number. If amount contains unexpected characters (beyond commas), the API call may fail with an unclear error.

Consider adding validation:

+                const cleanedAmount = amount.replace(/,/g, '')
+                if (isNaN(Number(cleanedAmount)) || Number(cleanedAmount) <= 0) {
+                    setError('Invalid amount format.')
+                    return
+                }
                 const { data, error: withdrawError } = await mantecaApi.withdraw({
-                    amount: amount.replace(/,/g, ''),
+                    amount: cleanedAmount,
                     destinationAddress,
                     txHash,
                     currency,
                 })

81-83: Review coverFullScreen usage in a multi-step flow.

Using coverFullScreen for the loading state may not be ideal if this component is part of a modal or a multi-step flow, as it will cover the entire screen rather than just the step container. Consider whether coverFullScreen={false} would provide a better user experience.

src/app/[...recipient]/client.tsx (1)

395-398: Verify comment placement and logic clarity.

The comment on line 398 ("Send to bank step if its mentioned in the URL and guest KYC is not needed") appears to describe the useEffect below (lines 399-411) rather than the showActionList calculation. Consider moving the comment immediately above the useEffect for clarity.

Additionally, the showActionList logic is correct but could benefit from a brief inline comment explaining the conditions.

     const showActionList =
         (flow !== 'direct_pay' || (flow === 'direct_pay' && !user)) && // Show for direct-pay when user is not logged in
         !fulfillUsingManteca // Show when not fulfilling using Manteca
-    // Send to bank step if its mentioned in the URL and guest KYC is not needed
+
+    // Send to bank step if its mentioned in the URL and guest KYC is not needed
     useEffect(() => {
src/components/Kyc/CountryFlagAndName.tsx (1)

25-32: Add error handling for flag image loading.

The Image component doesn't have an onError handler or fallback. If the flag image fails to load (e.g., invalid country code), the component will show a broken image. Consider adding a fallback icon or placeholder.

             ) : (
                 <Image
                     src={`https://flagcdn.com/w160/${countryCode?.toLowerCase()}.png`}
                     alt={`${countryName} flag`}
                     width={80}
                     height={80}
                     className="h-5 w-5 rounded-full object-cover object-center shadow-sm"
                     loading="lazy"
+                    onError={(e) => {
+                        e.currentTarget.style.display = 'none'
+                    }}
                 />
             )}
src/components/Kyc/states/KycFailed.tsx (1)

50-59: Consider using the loading prop for better UX.

The Button component supports a loading prop (as shown in the relevant snippets from Button.tsx) that renders a loading indicator. Currently, the button is only disabled when isLoading is true, and the loading state is indicated by changing the text to "Loading...". Using the loading prop would provide a more consistent loading experience with a spinner.

Apply this diff:

             <Button
                 icon="retry"
                 variant="purple"
                 className="w-full"
                 shadowSize="4"
                 onClick={onRetry}
                 disabled={isLoading}
+                loading={isLoading}
             >
-                {isLoading ? 'Loading...' : 'Retry verification'}
+                Retry verification
             </Button>
src/components/AddMoney/components/InputAmountStep.tsx (1)

50-58: Non-null assertions are safe here but could be more explicit.

The non-null assertions on lines 53-55 (currencyData.code!, currencyData.symbol!, currencyData.price!.buy) are safe because they're inside a conditional that checks currencyData is truthy. However, the assertions rely on the assumption that when currencyData exists, code, symbol, and price are also defined. Consider making this more explicit with a type guard or additional checks if the useCurrency hook can return partial data.

                     currency={
                         currencyData
                             ? {
-                                  code: currencyData.code!,
-                                  symbol: currencyData.symbol!,
-                                  price: currencyData.price!.buy,
+                                  code: currencyData.code ?? '',
+                                  symbol: currencyData.symbol ?? '',
+                                  price: currencyData.price?.buy ?? 1,
                               }
                             : undefined
                     }
src/components/TransactionDetails/TransactionCard.tsx (2)

105-112: Fix Next/Image sizing to avoid layout shift and distortion

Width/height (30px) conflicts with Tailwind size-12 (48px). Use fill within the sized parent or align dimensions.

-  <Image
-      src={avatarUrl}
-      alt="Icon"
-      className="size-12 object-contain"
-      width={30}
-      height={30}
-  />
+  <Image
+      src={avatarUrl}
+      alt="Transaction avatar"
+      fill
+      className="object-contain"
+      sizes="48px"
+  />

The wrapping div is already relative h-12 w-12, so fill is appropriate.


77-81: Make name-shortening intent clearer

Passing chars=0 relies on internal defaults and can confuse readers. Prefer explicit first/last counts.

-} else if (type === 'pay' && displayName.length > 19) {
-    displayName = shortenStringLong(displayName, 0, 16)
+} else if (type === 'pay' && displayName.length > 19) {
+    displayName = shortenStringLong(displayName, undefined, 16, 0)
}
src/hooks/useBridgeKycFlow.ts (1)

67-76: Remove duplicate prevStatusRef assignment

prevStatusRef.current is set twice each run. Keep a single update at the end of the effect.

-    const prevStatus = prevStatusRef.current
-    prevStatusRef.current = liveKycStatus
+    const prevStatus = prevStatusRef.current
     if (prevStatus !== 'approved' && liveKycStatus === 'approved') {
       setIsVerificationProgressModalOpen(false)
       onKycSuccess?.()
     } else if (prevStatus !== 'rejected' && liveKycStatus === 'rejected') {
       setIsVerificationProgressModalOpen(false)
     }
-    prevStatusRef.current = liveKycStatus
+    prevStatusRef.current = liveKycStatus
src/components/Kyc/KycStatusItem.tsx (1)

47-61: Label subtitle by status (Approved/Rejected/Submitted) for clarity

Currently always "Submitted on". Switch label based on status and include kycStatus in deps.

-  const subtitle = useMemo(() => {
-      const date = verification
-          ? (verification.approvedAt ?? verification.updatedAt ?? verification.createdAt)
-          : bridgeKycStartedAt
-      if (!date) {
-          return 'Verification in progress'
-      }
-      try {
-          return `Submitted on ${formatDate(new Date(date)).split(' - ')[0]}`
-      } catch (error) {
-          console.error('Failed to parse date:', error)
-          return 'Verification in progress'
-      }
-  }, [bridgeKycStartedAt, verification])
+  const subtitle = useMemo(() => {
+      const date =
+        verification
+          ? (verification.approvedAt ?? verification.updatedAt ?? verification.createdAt)
+          : bridgeKycStartedAt
+      if (!date) return 'Verification in progress'
+      try {
+        const day = formatDate(new Date(date)).split(' - ')[0]
+        const label =
+          (verification?.approvedAt || kycStatus === 'approved') ? 'Approved on' :
+          (kycStatus === 'rejected') ? 'Rejected on' : 'Submitted on'
+        return `${label} ${day}`
+      } catch (error) {
+        console.error('Failed to parse date:', error)
+        return 'Verification in progress'
+      }
+  }, [bridgeKycStartedAt, verification, kycStatus])
src/app/(mobile-ui)/withdraw/manteca/page.tsx (1)

200-209: Use error.code to detect user-rejection
In the catch block of src/app/(mobile-ui)/withdraw/manteca/page.tsx, replace or augment the string check with a guard for error.code === 4001 (EIP-1193 “user rejected request”) instead of relying on toString().includes('not allowed').

src/components/Payment/Views/MantecaFulfillment.view.tsx (2)

35-43: Avoid hardcoding Argentina; import canonical CountryData.

Define and reuse AR data from the central country list to prevent drift (title/currency/path/ISO). If a shared constant already exists, import that instead of duplicating here.


64-69: Title nit: “Send” may confuse on a deposit/fulfillment screen.

Consider “Fulfill request” or “Deposit instructions” to better match the view’s purpose.

src/app/(mobile-ui)/withdraw/page.tsx (5)

34-36: Update comment to reflect behavior.

Code shows any selected method moves to 'inputAmount' (not crypto-only). Adjust the comment to avoid confusion.


69-84: Simplify step effect and avoid dependency on step to prevent redundant runs.

Set step solely from selectedMethod and reset amount when transitioning back. Example:

-useEffect(() => {
-    if (selectedMethod) {
-        setStep('inputAmount')
-        if (amountFromContext && !rawTokenAmount) {
-            setRawTokenAmount(amountFromContext)
-        }
-    } else {
-        setStep('selectMethod')
-        // clear the raw token amount when switching back to method selection
-        if (step !== 'selectMethod') {
-            setRawTokenAmount('')
-            setTokenInputKey((k) => k + 1)
-        }
-    }
-}, [selectedMethod, amountFromContext, step, rawTokenAmount])
+useEffect(() => {
+    if (selectedMethod) {
+        setStep('inputAmount')
+        if (amountFromContext && !rawTokenAmount) {
+            setRawTokenAmount(amountFromContext)
+        }
+    } else {
+        setStep('selectMethod')
+        setRawTokenAmount('')
+        setTokenInputKey((k) => k + 1)
+    }
+    // eslint-disable-next-line react-hooks/exhaustive-deps
+}, [selectedMethod, amountFromContext])

117-119: Clarify error copy.

Use “Minimum withdrawal is $1.” (or “1 USD”) for clarity.

- message = 'Minimum withdrawal is 1.'
+ message = 'Minimum withdrawal is $1.'

212-214: Remove unused methodTitle.

This constant isn’t used. Delete to satisfy linters and reduce noise.

-        const methodTitle = selectedMethod?.title || selectedMethod?.countryPath || 'Selected method'

92-129: Consider reusing shared amount validation.

There’s a common validator at src/lib/validation/amount.ts. Reuse or align behavior to avoid divergent rules between flows.

src/components/Payment/PaymentForm/index.tsx (1)

482-490: Stabilize URL-driven gating; don’t depend on user.

Depending on user can briefly unset the flag before auth hydrates. Rely on the URL step only and include the setter in deps.

-useEffect(() => {
-    const stepFromURL = searchParams.get('step')
-    if (user && stepFromURL === 'regional-req-fulfill') {
-        setFulfillUsingManteca(true)
-    } else {
-        setFulfillUsingManteca(false)
-    }
-}, [user, searchParams])
+useEffect(() => {
+    const stepFromURL = searchParams.get('step')
+    setFulfillUsingManteca(stepFromURL === 'regional-req-fulfill')
+}, [searchParams, setFulfillUsingManteca])
src/components/TransactionDetails/TransactionDetailsReceipt.tsx (1)

208-222: Guard isPublic in the dependencies as well.

shouldShowShareReceipt short-circuits on isPublic, yet the memo ignores that prop. If the component toggles between private/public modes, the share button will keep the old state. Add isPublic to the dependency array to avoid stale UI.

@jjramirezn jjramirezn merged commit 676d5c8 into peanut-wallet-dev Oct 1, 2025
5 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Oct 6, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants