Skip to content

Conversation

jjramirezn
Copy link
Contributor

No description provided.

Copy link

vercel bot commented Sep 8, 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 Sep 15, 2025 6:19pm

Copy link

Copy link
Contributor

coderabbitai bot commented Sep 8, 2025

Walkthrough

Adds a mobile QR-pay layout and page, a Manteca API client and QR payment types, routes PIX/MERCADO_PAGO to the qr-pay flow, centralizes avatar URL resolution and threads avatarUrl through transaction drawer/receipt, changes currency price shape to {buy,sell} and updates callers, restructures TokenAmountInput UI, and exports a PIX asset.

Changes

Cohort / File(s) Summary
QR-pay pages & Manteca service
src/app/(mobile-ui)/qr-pay/layout.tsx, src/app/(mobile-ui)/qr-pay/page.tsx, src/services/manteca.ts, src/services/services.types.ts
Adds QR-pay layout and client page; implements manteca API client (initiate/complete/getPrices) and QR payment types/interfaces; integrates QR pay flows and types used by the page.
DirectSendQR routing & detection
src/components/Global/DirectSendQR/index.tsx, src/components/Global/DirectSendQR/utils.ts
Broadens PIX regex, adds PAYMENT_PROCESSOR_REGEXES and isPaymentProcessorQR, and moves PIX into the redirect flow alongside MERCADO_PAGO while preserving original QR casing and adding timestamp.
Transaction details & avatar plumbing
src/components/TransactionDetails/TransactionCard.tsx, src/components/TransactionDetails/TransactionDetailsDrawer.tsx, src/components/TransactionDetails/TransactionDetailsHeaderCard.tsx, src/components/TransactionDetails/TransactionDetailsReceipt.tsx, src/components/TransactionDetails/transactionTransformer.ts, src/utils/history.utils.ts, src/utils/index.ts
Adds getAvatarUrl util and re-export; threads optional avatarUrl through transactionTransformer → Drawer → Receipt; adds 'qr_payment' direction and 'pay' transaction type; updates avatar rendering and receipt/exchange-rate behavior.
Currency symbol & price shape changes
src/hooks/useCurrency.ts, src/app/actions/currency.ts, src/app/actions/onramp.ts, src/hooks/useCreateOnramp.ts, src/app/[...recipient]/client.tsx, src/components/AddMoney/components/InputAmountStep.tsx, src/components/Common/ActionListDaimoPayButton.tsx, src/components/Request/views/ReqFulfillBankFlowManager.tsx, src/components/Claim/Link/views/Confirm.bank-claim.view.tsx
Renames/exports SYMBOLS_BY_CURRENCY_CODE and extends it; changes price shape to {buy,sell}; updates getCurrencyPrice to return buy/sell (adds manteca price path) and updates call sites to use price.buy/price.sell.
TokenAmountInput & Card UI
src/components/Global/TokenAmountInput/index.tsx, src/components/0_Bruddle/Card.tsx
Restructures TokenAmountInput layout, conversion display logic, and fiat-symbol handling; adds rounded-sm to Card root classes.
Assets export
src/assets/payment-apps/index.ts
Adds PIX SVG export.
History enum, consts & loading states
src/hooks/useTransactionHistory.ts, src/services/services.types.ts, src/constants/general.consts.ts, src/constants/loadingStates.consts.ts
Adds EHistoryEntryType.MANTECA_QR_PAYMENT, appends QR-related service types, exposes PEANUT_API_KEY, and adds LoadingStates literals ('Fetching details', 'Paying').
Transaction drawer props & transformer (plumbing)
src/components/TransactionDetails/TransactionDetailsDrawer.tsx, src/components/TransactionDetails/TransactionDetailsReceipt.tsx, src/components/TransactionDetails/transactionTransformer.ts
Adds optional avatarUrl prop to Drawer and Receipt, forwards avatarUrl to Receipt, stores avatarUrl in transaction extraDataForDrawer, and maps MANTECA_QR_PAYMENT into qr_payment direction.
Tests & utils cleanup
src/utils/__tests__/bridge.utils.test.ts, src/utils/currency.ts, src/utils/index.ts
Removes getCurrencySymbol test and re-export; currency utils now use SYMBOLS_BY_CURRENCY_CODE; re-exports history.utils from utils index.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • Zishan-7
  • kushagrasarathe

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The pull request description is empty and provides no context or summary of the changes, so it fails the lenient description check because it contains no information related to the changeset. Please add a PR description that summarizes the purpose and scope, highlights key files and public API/type changes (for example the new manteca service types, PEANUT_API_KEY env var, TransactionDetails/Drawer prop updates), lists any migration or compatibility notes, and includes testing or QA steps so reviewers can evaluate impact quickly.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The PR title "[TASK-12883] Add manteca QR payments" is concise and accurately reflects the primary change set—adding Manteca QR payment support across services, UI, assets, and history integration—so it clearly summarizes the main intent for reviewers.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ 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-qr

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8009ee5 and 4f53108.

📒 Files selected for processing (1)
  • src/app/actions/currency.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/app/actions/currency.ts

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.


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

❤️ Share

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

Caution

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

⚠️ Outside diff range comments (3)
src/hooks/useCurrency.ts (2)

4-9: Add BRL symbol to support PIX.

PIX flows will need BRL -> R$.

Apply:

 const SYMBOLS_BY_CURRENCY_CODE: Record<string, string> = {
   ARS: 'AR$',
   USD: '$',
   EUR: '€',
   MXN: 'MX$',
+  BRL: 'R$',
 }

11-16: Prop/state sync: update when currencyCode changes.

Without syncing, changing currencyCode after mount won’t refetch or update the symbol.

Apply:

 export const useCurrency = (currencyCode: string | null) => {
   const [code, setCode] = useState<string | null>(currencyCode?.toUpperCase() ?? null)
@@
   const [isLoading, setIsLoading] = useState<boolean>(false)
 
+  useEffect(() => {
+    setCode(currencyCode?.toUpperCase() ?? null)
+  }, [currencyCode])
src/components/TransactionDetails/TransactionCard.tsx (1)

136-141: Remove duplicate currency symbol for token display

finalDisplayAmount already includes displaySymbol. Appending it again yields outputs like $$123.00 ETH.

-            finalDisplayAmount = `${displaySymbol}${finalDisplayAmount} ${transaction.tokenSymbol}`
+            finalDisplayAmount = `${finalDisplayAmount} ${transaction.tokenSymbol}`
🧹 Nitpick comments (15)
src/hooks/useTransactionHistory.ts (1)

156-160: DTO typing mismatch in map().

data.entries.map((entry: HistoryEntry) => ...) treats raw JSON as already-normalized HistoryEntry. Define a DTO (ISO strings) and map to the strongly-typed model to avoid silent type drift.

Apply this minimal refactor:

- entries: data.entries.map((entry: HistoryEntry) => {
+ type HistoryEntryDTO = Omit<HistoryEntry, 'timestamp'|'cancelledAt'|'createdAt'|'completedAt'> & {
+   timestamp: string
+   cancelledAt?: string
+   createdAt?: string
+   completedAt?: string
+ }
+ entries: (data.entries as HistoryEntryDTO[]).map((entry) => {
src/hooks/useCurrency.ts (2)

30-35: Use key-in check instead of Object.keys().includes().

Cleaner and avoids an array allocation.

Apply:

-        if (!Object.keys(SYMBOLS_BY_CURRENCY_CODE).includes(code)) {
+        if (!(code in SYMBOLS_BY_CURRENCY_CODE)) {

36-42: Set symbol immediately to reduce UI flicker.

Pre-set symbol before awaiting price.

Apply:

         setIsLoading(true)
+        setSymbol(SYMBOLS_BY_CURRENCY_CODE[code])
         getCurrencyPrice(code)
             .then((price) => {
-                setSymbol(SYMBOLS_BY_CURRENCY_CODE[code])
                 setPrice(price)
                 setIsLoading(false)
             })
src/app/(mobile-ui)/qr-pay/layout.tsx (1)

5-9: Metadata: OK.

Static metadata via helper keeps layout simple. If you plan localized titles/descriptions later, consider feeding i18n here.

src/services/manteca.ts (1)

70-88: Minor: guard missing auth and prefer explicit Accept header.

If Cookies.get('jwt-token') is undefined, consider throwing a 401-friendly error before the network call. Add Accept: application/json for clarity.

Apply:

         const response = await fetchWithSentry(`${PEANUT_API_URL}/manteca/qr-payment`, {
             method: 'POST',
             headers: {
-                'Content-Type': 'application/json',
+                'Content-Type': 'application/json',
+                Accept: 'application/json',
                 Authorization: `Bearer ${Cookies.get('jwt-token')}`,
             },
             body: JSON.stringify(data),
         })
src/components/TransactionDetails/TransactionDetailsHeaderCard.tsx (1)

168-170: Fix Image alt and intrinsic size to avoid layout shift

  • alt="Icon" is non-informative; if decorative, prefer empty alt and aria-hidden.
  • width/height (35) don’t match CSS size-16 (64px), which can cause CLS.

Apply:

-                    <div className="flex h-16 w-16 items-center justify-center rounded-full">
-                        <Image src={avatarUrl} alt="Icon" className="size-16 object-contain" width={35} height={35} />
+                    <div className="flex h-16 w-16 items-center justify-center rounded-full">
+                        <Image
+                            src={avatarUrl}
+                            alt=""
+                            aria-hidden
+                            className="size-16 object-contain"
+                            width={64}
+                            height={64}
+                        />
                     </div>

Note: Leveraging past learning on decorative images using empty alt to prevent layout issues.

src/utils/history.utils.ts (1)

5-19: Make currency code mapping case-insensitive

Some backends send lowercase (e.g., 'brl'). Normalize before switch.

-    if (transaction.extraDataForDrawer?.originalType === EHistoryEntryType.MANTECA_QR_PAYMENT) {
-        switch (transaction.currency?.code) {
+    if (transaction.extraDataForDrawer?.originalType === EHistoryEntryType.MANTECA_QR_PAYMENT) {
+        const code = transaction.currency?.code?.toUpperCase()
+        switch (code) {
             case 'ARS':
                 return MERCADO_PAGO
             case 'BRL':
                 return PIX
             default:
                 return undefined
         }
     }
src/components/TransactionDetails/TransactionCard.tsx (1)

152-157: Fix Image alt and intrinsic size to avoid layout shift

Mirror the Header fix for consistency.

-                            <div className={'relative flex h-12 w-12 items-center justify-center rounded-full'}>
+                            <div className="relative flex h-12 w-12 items-center justify-center rounded-full">
                                 <Image
                                     src={avatarUrl}
-                                    alt="Icon"
-                                    className="size-12 object-contain"
-                                    width={30}
-                                    height={30}
+                                    alt=""
+                                    aria-hidden
+                                    className="size-12 object-contain"
+                                    width={48}
+                                    height={48}
                                 />
src/components/Global/TokenAmountInput/index.tsx (2)

210-235: Add accessible name to the input

Screen readers lack context for the numeric field.

-                    <input
+                    <input
+                        aria-label="Amount"

249-260: Use a button for the conversion toggle (keyboard + a11y)

Clickable divs are not accessible. Use a button with proper semantics.

-            {showConversion && (
-                <div
-                    className="absolute right-0 top-1/2 -translate-x-1/2 -translate-y-1/2 transform cursor-pointer"
-                    onClick={(e) => {
+            {showConversion && (
+                <button
+                    type="button"
+                    aria-label="Switch input currency"
+                    aria-pressed={!isInputUsd}
+                    className="absolute right-0 top-1/2 -translate-x-1/2 -translate-y-1/2 transform cursor-pointer"
+                    onClick={(e) => {
                         e.preventDefault()
                         const currentValue = displayValue
                         setDisplayValue(alternativeDisplayValue)
                         setAlternativeDisplayValue(currentValue)
                         setIsInputUsd(!isInputUsd)
                     }}
-                >
-                    <Icon name={'switch'} className="ml-5 rotate-90 cursor-pointer" width={32} height={32} />
-                </div>
+                >
+                    <Icon name="switch" className="ml-5 rotate-90" width={32} height={32} />
+                </button>
             )}
src/app/(mobile-ui)/qr-pay/page.tsx (5)

148-154: Clear “Insufficient funds” when balance/amount becomes valid.

Right now the error latches. Add an else branch to reset it.

     useEffect(() => {
         if (!usdAmount || balance === undefined) return
         if (parseUnits(usdAmount, PEANUT_WALLET_TOKEN_DECIMALS) > balance) {
             setErrorMessage('Insufficient funds')
+        } else if (errorMessage === 'Insufficient funds') {
+            setErrorMessage(null)
         }
-    }, [usdAmount, balance, setErrorMessage])
+    }, [usdAmount, balance, errorMessage])

181-184: Avoid throwing in render; show a safe fallback instead.

Throwing here can crash the page in production. Render a friendly error state.

-    //Success
-    if (isSuccess && !qrPayment) {
-        // This should never happen, if this happens there is dev error
-        throw new Error('Invalid state, successful payment but no QR payment data')
+    // Success
+    if (isSuccess && !qrPayment) {
+        return (
+            <div className="my-auto flex h-full flex-col justify-center space-y-4">
+                <Card className="shadow-4">
+                    <Card.Header>
+                        <Card.Title>Payment completed</Card.Title>
+                        <Card.Description>We couldn’t load the receipt details. Please check your history.</Card.Description>
+                    </Card.Header>
+                    <Card.Content>
+                        <Button onClick={() => router.push('/home')} variant="purple">
+                            Back to home
+                        </Button>
+                    </Card.Content>
+                </Card>
+            </div>
+        )

258-263: Null-guard the icon and fix alt text.

methodIcon can be null for unknown types; also the alt is always “Mercado Pago”.

-                        <div className="flex items-center justify-center rounded-full bg-white">
-                            <Image src={methodIcon} alt="Mercado Pago" width={50} height={50} />
-                        </div>
+                        <div className="flex items-center justify-center rounded-full bg-white">
+                            {methodIcon && (
+                                <Image
+                                    src={methodIcon}
+                                    alt={(qrPayment?.type ?? paymentLock?.type) === 'PIX' ? 'PIX' : 'Mercado Pago'}
+                                    width={50}
+                                    height={50}
+                                />
+                            )}
+                        </div>

78-93: Harden currency resolution: catch price errors; consider proper symbols.

  • getCurrencyPrice can throw; surface a user-friendly error.
  • Optional: map code→symbol instead of symbol: code for better UX.
@@
             return {
                 code: currencyCode,
-                symbol: currencyCode,
+                symbol: currencyCode, // TODO: replace with symbol mapping if available
                 price,
             }
         }
@@
-        getCurrencyObject().then(setCurrency)
+        getCurrencyObject()
+            .then(setCurrency)
+            .catch((e: any) => {
+                setErrorMessage(e?.message ?? 'Failed to resolve currency.')
+            })

If you have a symbol map (e.g., SYMBOLS_BY_CURRENCY_CODE), switch to:

- symbol: currencyCode,
+ symbol: SYMBOLS_BY_CURRENCY_CODE[currencyCode] ?? currencyCode,

and import the map from your currency utils.

Also applies to: 101-104


58-62: Query config nit: staleTime=1ms is unusual.

Either rely on refetchOnMount: 'always' with staleTime: 0, or set a sensible window (e.g., 30_000). Current setting adds noise without benefit.

-        staleTime: 1,
+        staleTime: 0,
         refetchOnMount: 'always',
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a4d10d8 and 1b7aad5.

⛔ Files ignored due to path filters (2)
  • src/assets/payment-apps/mercado-pago.svg is excluded by !**/*.svg
  • src/assets/payment-apps/pix.svg is excluded by !**/*.svg
📒 Files selected for processing (16)
  • src/app/(mobile-ui)/qr-pay/layout.tsx (1 hunks)
  • src/app/(mobile-ui)/qr-pay/page.tsx (1 hunks)
  • src/assets/payment-apps/index.ts (1 hunks)
  • src/components/0_Bruddle/Card.tsx (1 hunks)
  • src/components/Global/DirectSendQR/index.tsx (1 hunks)
  • src/components/Global/TokenAmountInput/index.tsx (4 hunks)
  • src/components/TransactionDetails/TransactionCard.tsx (4 hunks)
  • src/components/TransactionDetails/TransactionDetailsDrawer.tsx (3 hunks)
  • src/components/TransactionDetails/TransactionDetailsHeaderCard.tsx (1 hunks)
  • src/components/TransactionDetails/TransactionDetailsReceipt.tsx (3 hunks)
  • src/hooks/useCurrency.ts (3 hunks)
  • src/hooks/useTransactionHistory.ts (1 hunks)
  • src/services/manteca.ts (1 hunks)
  • src/services/services.types.ts (1 hunks)
  • src/utils/history.utils.ts (1 hunks)
  • src/utils/index.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (12)
📚 Learning: 2024-10-22T18:11:36.864Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#469
File: src/app/request/pay/page.tsx:32-49
Timestamp: 2024-10-22T18:11:36.864Z
Learning: In `src/app/request/pay/page.tsx`, the `id` parameter is accessed via `searchParams.id` in the `generateMetadata` function.

Applied to files:

  • src/app/(mobile-ui)/qr-pay/layout.tsx
📚 Learning: 2024-10-23T09:38:27.670Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#469
File: src/app/request/pay/page.tsx:32-64
Timestamp: 2024-10-23T09:38:27.670Z
Learning: In `src/app/request/pay/page.tsx`, if `linkRes` is not OK in the `generateMetadata` function, the desired behavior is to use the standard title and preview image without throwing an error.

Applied to files:

  • src/app/(mobile-ui)/qr-pay/layout.tsx
  • src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2024-10-22T18:10:56.955Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#469
File: src/app/request/pay/page.tsx:25-25
Timestamp: 2024-10-22T18:10:56.955Z
Learning: In the `src/app/request/pay/page.tsx` file, the `PreviewType` enum values are strings, so when adding `previewType` to `URLSearchParams`, there's no need to convert them to strings.

Applied to files:

  • src/components/Global/DirectSendQR/index.tsx
📚 Learning: 2025-07-24T10:57:15.315Z
Learnt from: Zishan-7
PR: peanutprotocol/peanut-ui#1000
File: src/components/og/ProfileCardOG.tsx:0-0
Timestamp: 2025-07-24T10:57:15.315Z
Learning: In `src/components/og/ProfileCardOG.tsx`, the scribble image should have an empty alt attribute (alt="") to prevent layout issues if the image fails to load. Since it's a decorative element positioned absolutely over the username text, showing alt text would interfere with the layout and username display.

Applied to files:

  • src/components/TransactionDetails/TransactionDetailsHeaderCard.tsx
📚 Learning: 2025-08-14T14:42:54.411Z
Learnt from: Zishan-7
PR: peanutprotocol/peanut-ui#1094
File: src/utils/withdraw.utils.ts:181-191
Timestamp: 2025-08-14T14:42:54.411Z
Learning: The countryCodeMap in src/components/AddMoney/consts/index.ts uses uppercase 3-letter country codes as keys (like 'AUT', 'BEL', 'CZE') that map to 2-letter country codes, requiring input normalization to uppercase for proper lookups.

Applied to files:

  • src/hooks/useCurrency.ts
📚 Learning: 2024-10-29T12:19:41.968Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#495
File: src/components/Global/TokenAmountInput/index.tsx:23-30
Timestamp: 2024-10-29T12:19:41.968Z
Learning: In the `TokenAmountInput` component (`src/components/Global/TokenAmountInput/index.tsx`), when the 'Max' button is clicked, we intentionally set the input denomination to 'TOKEN' because we are setting the value as token.

Applied to files:

  • src/components/Global/TokenAmountInput/index.tsx
📚 Learning: 2025-08-22T07:25:59.304Z
Learnt from: Zishan-7
PR: peanutprotocol/peanut-ui#1104
File: src/components/Payment/PaymentForm/index.tsx:596-600
Timestamp: 2025-08-22T07:25:59.304Z
Learning: The `TokenAmountInput` component in `src/components/Global/TokenAmountInput/` always returns decimal strings (e.g., "1,234.56"), not base units. When passing these values to external APIs like Daimo's `toUnits` prop, simply stripping commas with `.replace(/,/g, '')` is sufficient.

Applied to files:

  • src/components/Global/TokenAmountInput/index.tsx
📚 Learning: 2024-10-29T12:20:47.207Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#495
File: src/components/Create/Link/Input.view.tsx:244-248
Timestamp: 2024-10-29T12:20:47.207Z
Learning: In the `TokenAmountInput` component within `src/components/Global/TokenAmountInput/index.tsx`, when `balance` is undefined, the `maxValue` prop should be set to an empty string `''`.

Applied to files:

  • src/components/Global/TokenAmountInput/index.tsx
📚 Learning: 2024-10-08T20:13:42.967Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#413
File: src/context/tokenSelector.context.tsx:118-123
Timestamp: 2024-10-08T20:13:42.967Z
Learning: In the `TokenContextProvider` component within `src/context/tokenSelector.context.tsx`, in the TypeScript React application, when data changes and before calling `fetchAndSetTokenPrice`, it is necessary to reset `selectedTokenData`, `selectedTokenPrice`, `selectedTokenDecimals`, and `inputDenomination` to discard stale data.

Applied to files:

  • src/components/Global/TokenAmountInput/index.tsx
📚 Learning: 2024-10-07T15:25:45.170Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#422
File: src/components/Request/Pay/Views/Initial.view.tsx:76-78
Timestamp: 2024-10-07T15:25:45.170Z
Learning: In `src/components/Request/Pay/Views/Initial.view.tsx`, both `txFee` and `utils.formatTokenAmount(...)` return strings, ensuring that `calculatedFee` consistently returns a string without the need for additional type conversion.

Applied to files:

  • src/components/Global/TokenAmountInput/index.tsx
📚 Learning: 2024-10-07T15:28:25.280Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#422
File: src/components/Request/Pay/Views/Initial.view.tsx:76-78
Timestamp: 2024-10-07T15:28:25.280Z
Learning: In `src/components/Request/Pay/Views/Initial.view.tsx`, both `txFee` and `utils.formatTokenAmount(estimatedGasCost, 3)` return strings, ensuring consistent return types for `calculatedFee`.

Applied to files:

  • src/components/Global/TokenAmountInput/index.tsx
📚 Learning: 2024-12-02T17:21:45.515Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#551
File: src/context/walletContext/walletContext.tsx:87-88
Timestamp: 2024-12-02T17:21:45.515Z
Learning: When converting `totalBalance` (in USD) to a `BigInt` balance in `src/context/walletContext/walletContext.tsx`, multiplying by `1e6` is intentional to maintain compatibility with USDC's 6 decimal places. The application displays only 2 decimal places, so this level of precision is sufficient.

Applied to files:

  • src/components/Global/TokenAmountInput/index.tsx
🧬 Code graph analysis (6)
src/services/services.types.ts (1)
src/services/manteca.ts (1)
  • QrPaymentResponse (63-68)
src/utils/history.utils.ts (1)
src/components/TransactionDetails/transactionTransformer.ts (1)
  • TransactionDetails (41-122)
src/app/(mobile-ui)/qr-pay/page.tsx (7)
src/hooks/wallet/useWallet.ts (1)
  • useWallet (21-123)
src/services/manteca.ts (4)
  • QrPayment (11-31)
  • QrPaymentCharge (33-43)
  • QrPaymentLock (45-61)
  • mantecaApi (70-88)
src/hooks/useTransactionDetailsDrawer.ts (1)
  • useTransactionDetailsDrawer (3-22)
src/app/actions/currency.ts (1)
  • getCurrencyPrice (7-58)
src/utils/general.utils.ts (1)
  • isTxReverted (1288-1291)
src/services/charges.ts (1)
  • chargesApi (6-103)
src/components/TransactionDetails/TransactionDetailsReceipt.tsx (1)
  • TransactionDetailsReceipt (31-1045)
src/services/manteca.ts (3)
src/services/services.types.ts (1)
  • QrPaymentResponse (323-331)
src/utils/sentry.utils.ts (1)
  • fetchWithSentry (26-104)
src/constants/general.consts.ts (1)
  • PEANUT_API_URL (43-47)
src/components/TransactionDetails/TransactionCard.tsx (1)
src/utils/history.utils.ts (1)
  • getAvatarUrl (5-19)
src/components/Global/TokenAmountInput/index.tsx (2)
src/utils/general.utils.ts (1)
  • formatAmountWithoutComma (476-482)
src/components/Global/Icons/Icon.tsx (1)
  • Icon (195-204)
🪛 GitHub Actions: Tests
src/assets/payment-apps/index.ts

[error] 1-1: Jest test run failed due to an SVG asset import. Unexpected token '<' found when importing apple-pay.svg in this file. This indicates the test environment does not transform or mock SVG assets. Add a Jest transformer or moduleNameMapper entry for '*.svg' (e.g., mock SVGs or use SVGR transformer).

🔇 Additional comments (8)
src/hooks/useTransactionHistory.ts (2)

22-23: Enum addition looks good.

New type MANTECA_QR_PAYMENT added to EHistoryEntryType is clear and scoped.


153-233: EHistoryEntryType missing MANTECA_QR_PAYMENT
The enum EHistoryEntryType has no MANTECA_QR_PAYMENT value, so the proposed case will never execute – confirm the correct entry type or add MANTECA_QR_PAYMENT to the enum before handling its currency symbol.

Likely an incorrect or invalid review comment.

src/components/0_Bruddle/Card.tsx (1)

32-33: Rounded corners: OK.

rounded-sm as a default is reasonable and can be overridden via className thanks to twMerge.

src/utils/index.ts (1)

8-8: Barrel export safe—no import cycle detected.

history.utils.ts contains no imports from @/utils, so this re-export does not introduce a circular dependency.

src/components/TransactionDetails/TransactionDetailsReceipt.tsx (1)

41-53: LGTM: avatar plumbed cleanly through Drawer → Receipt → Header

Prop is optional and safely forwarded. No behavior regressions spotted.

Also applies to: 298-300

src/components/TransactionDetails/TransactionDetailsDrawer.tsx (1)

12-13: LGTM: Drawer now accepts and forwards avatarUrl

Straightforward prop plumbing; no side-effects.

Also applies to: 24-25, 58-59

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

158-165: Confirm “U$D” labeling is intentional across locales

If this is AR-specific, consider gating by locale or deriving from a currency map to avoid surprising users expecting “USD”/“$”.
Would you like a quick helper to centralize currency display symbols per locale?

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

100-104: Nice use of dataUpdatedAt to drive effect updates.

This pattern avoids stale refs when TanStack reuses result objects. Good call.

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: 1

♻️ Duplicate comments (1)
src/app/(mobile-ui)/qr-pay/page.tsx (1)

33-35: Prevent double-send and surface failures: add an isPaying guard and try/catch.

Users can tap Send twice before global isLoading flips; errors from sendMoney also go unhandled.

Apply:

@@
-    const [isSuccess, setIsSuccess] = useState(false)
+    const [isSuccess, setIsSuccess] = useState(false)
+    const [isPaying, setIsPaying] = useState(false)
@@
-    const payQR = useCallback(async () => {
-        if (!qrPayment || !charge || !usdAmount) return
-        const { userOpHash, receipt } = await sendMoney(qrPayment.details.depositAddress, usdAmount)
-        if (receipt !== null && isTxReverted(receipt)) {
-            setErrorMessage('Transaction reverted by the network.')
-            setIsSuccess(false)
-            return
-        }
-        const txHash = receipt?.transactionHash ?? userOpHash
-        chargesApi.createPayment({
-            chargeId: charge.uuid,
-            chainId: charge.chainId,
-            hash: txHash,
-            tokenAddress: charge.tokenAddress,
-            payerAddress: address,
-        })
-        setIsSuccess(true)
-    }, [qrPayment, charge, sendMoney, usdAmount])
+    const payQR = useCallback(async () => {
+        if (!qrPayment || !charge || !usdAmount) return
+        if (isPaying) return
+        setIsPaying(true)
+        try {
+            const { userOpHash, receipt } = await sendMoney(qrPayment.details.depositAddress, usdAmount)
+            if (receipt !== null && isTxReverted(receipt)) {
+                setErrorMessage('Transaction reverted by the network.')
+                setIsSuccess(false)
+                return
+            }
+            const txHash = receipt?.transactionHash ?? userOpHash
+            // Non-blocking by design per previous discussion
+            chargesApi.createPayment({
+                chargeId: charge.uuid,
+                chainId: charge.chainId,
+                hash: txHash,
+                tokenAddress: charge.tokenAddress,
+                payerAddress: address,
+            })
+            setIsSuccess(true)
+        } catch (e: any) {
+            setErrorMessage(e?.message ?? 'Failed to complete payment.')
+            setIsSuccess(false)
+        } finally {
+            setIsPaying(false)
+        }
+    }, [qrPayment, charge, sendMoney, usdAmount, address, isPaying])
@@
-                    loading={isFetching || isLoading}
-                    disabled={isErrorInitiatingPayment || !!errorMessage || isFetching || !amount || isLoading}
+                    loading={isFetching || isLoading || isPaying}
+                    disabled={isErrorInitiatingPayment || !!errorMessage || isFetching || !amount || isLoading || isPaying}

Also applies to: 131-148, 288-303

🧹 Nitpick comments (3)
src/app/(mobile-ui)/qr-pay/page.tsx (3)

151-156: Auto-clear “Insufficient funds” once balance is adequate.

Avoids a sticky error that keeps the button disabled after funds become sufficient.

 useEffect(() => {
     if (!usdAmount || balance === undefined) return
-    if (parseUnits(usdAmount, PEANUT_WALLET_TOKEN_DECIMALS) > balance) {
-        setErrorMessage('Insufficient funds')
-    }
-}, [usdAmount, balance, setErrorMessage])
+    const needed = parseUnits(usdAmount, PEANUT_WALLET_TOKEN_DECIMALS)
+    if (needed > balance) {
+        setErrorMessage('Insufficient funds')
+    } else {
+        setErrorMessage((prev) => (prev === 'Insufficient funds' ? null : prev))
+    }
+}, [usdAmount, balance])

262-264: Guard Image when icon is null and fix alt text.

Next/Image will error on null src; also the alt is hardcoded to “Mercado Pago.”

-                        <div className="flex items-center justify-center rounded-full bg-white">
-                            <Image src={methodIcon} alt="Mercado Pago" width={50} height={50} />
-                        </div>
+                        <div className="flex items-center justify-center rounded-full bg-white">
+                            {methodIcon ? (
+                                <Image
+                                    src={methodIcon}
+                                    alt={(qrPayment?.type ?? paymentLock?.type) === 'PIX' ? 'PIX' : 'Mercado Pago'}
+                                    width={50}
+                                    height={50}
+                                />
+                            ) : (
+                                <Icon name="qr-code" size={32} />
+                            )}
+                        </div>

Also applies to: 114-124


52-64: Minor: re-evaluate query staleness strategy.

staleTime: 1 with refetchOnMount: 'always' forces extra network churn. If you rely on manual refetch, consider staleTime: 0 or remove refetchOnMount. Not blocking.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1b7aad5 and a3e15c0.

📒 Files selected for processing (1)
  • src/app/(mobile-ui)/qr-pay/page.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (12)
📓 Common learnings
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#1190
File: src/app/(mobile-ui)/qr-pay/page.tsx:156-176
Timestamp: 2025-09-08T03:13:09.093Z
Learning: In the peanut-ui mobile app, the `/qr-pay` route is only accessed through the DirectSendQR component which always includes the qrCode parameter in the URL when redirecting users to the QR pay page after scanning MERCADO_PAGO or PIX QR codes.
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#1190
File: src/app/(mobile-ui)/qr-pay/page.tsx:156-176
Timestamp: 2025-09-08T03:13:09.093Z
Learning: In the peanut-ui mobile app, the `/qr-pay` route is only accessed through the DirectSendQR component which always includes the qrCode parameter in the URL when redirecting users to the QR pay page.
📚 Learning: 2025-09-08T03:13:09.093Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#1190
File: src/app/(mobile-ui)/qr-pay/page.tsx:156-176
Timestamp: 2025-09-08T03:13:09.093Z
Learning: In the peanut-ui mobile app, the `/qr-pay` route is only accessed through the DirectSendQR component which always includes the qrCode parameter in the URL when redirecting users to the QR pay page after scanning MERCADO_PAGO or PIX QR codes.

Applied to files:

  • src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2025-09-08T03:13:09.093Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#1190
File: src/app/(mobile-ui)/qr-pay/page.tsx:156-176
Timestamp: 2025-09-08T03:13:09.093Z
Learning: In the peanut-ui mobile app, the `/qr-pay` route is only accessed through the DirectSendQR component which always includes the qrCode parameter in the URL when redirecting users to the QR pay page.

Applied to files:

  • src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2025-08-26T15:25:53.328Z
Learnt from: Zishan-7
PR: peanutprotocol/peanut-ui#1132
File: src/app/[...recipient]/client.tsx:394-397
Timestamp: 2025-08-26T15:25:53.328Z
Learning: In `src/components/Common/ActionListDaimoPayButton.tsx`, the `handleCompleteDaimoPayment` function should not display error messages to users when DB update fails because the Daimo payment itself has succeeded - showing errors would be confusing since the payment was successful.

Applied to files:

  • src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2025-09-08T03:11:57.201Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#1190
File: src/app/(mobile-ui)/qr-pay/page.tsx:64-73
Timestamp: 2025-09-08T03:11:57.201Z
Learning: In QR payment flows, user-entered amounts should be cleared and overridden by API response amounts to ensure the authoritative payment amount from the API takes precedence over user input.

Applied to files:

  • src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2024-10-08T20:13:42.967Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#413
File: src/context/tokenSelector.context.tsx:118-123
Timestamp: 2024-10-08T20:13:42.967Z
Learning: In the `TokenContextProvider` component within `src/context/tokenSelector.context.tsx`, in the TypeScript React application, when data changes and before calling `fetchAndSetTokenPrice`, it is necessary to reset `selectedTokenData`, `selectedTokenPrice`, `selectedTokenDecimals`, and `inputDenomination` to discard stale data.

Applied to files:

  • src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2024-10-23T09:38:27.670Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#469
File: src/app/request/pay/page.tsx:32-64
Timestamp: 2024-10-23T09:38:27.670Z
Learning: In `src/app/request/pay/page.tsx`, if `linkRes` is not OK in the `generateMetadata` function, the desired behavior is to use the standard title and preview image without throwing an error.

Applied to files:

  • src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2024-10-08T20:13:42.967Z
Learnt from: Hugo0
PR: peanutprotocol/peanut-ui#422
File: src/components/Request/Pay/Pay.tsx:113-123
Timestamp: 2024-10-08T20:13:42.967Z
Learning: In the `PayRequestLink` component (`src/components/Request/Pay/Pay.tsx`), when resolving ENS names, handle errors by displaying an appropriate error message to the user if the ENS cannot be resolved.

Applied to files:

  • src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2025-09-08T03:11:00.076Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#1190
File: src/app/(mobile-ui)/qr-pay/page.tsx:31-41
Timestamp: 2025-09-08T03:11:00.076Z
Learning: In QR payment flows, the `createPayment` API call for payment tracking can be non-awaited when called after successful transaction execution, as it's a non-blocking logging operation and doesn't affect the user's success state.

Applied to files:

  • src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2024-10-22T18:11:36.864Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#469
File: src/app/request/pay/page.tsx:32-49
Timestamp: 2024-10-22T18:11:36.864Z
Learning: In `src/app/request/pay/page.tsx`, the `id` parameter is accessed via `searchParams.id` in the `generateMetadata` function.

Applied to files:

  • src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2025-09-05T07:31:11.396Z
Learnt from: Zishan-7
PR: peanutprotocol/peanut-ui#1185
File: src/components/Claim/useClaimLink.tsx:14-0
Timestamp: 2025-09-05T07:31:11.396Z
Learning: In the peanut-ui codebase, `window.history.replaceState` is preferred over `router.replace` when immediate/synchronous URL parameter updates are required, as `router.replace` is asynchronous and doesn't guarantee instant URL changes that subsequent code can rely on. This pattern is used consistently across usePaymentInitiator.ts, Confirm.payment.view.tsx, and useClaimLink.tsx.

Applied to files:

  • src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2024-12-11T10:13:22.806Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#564
File: src/components/Request/Pay/Views/Initial.view.tsx:430-430
Timestamp: 2024-12-11T10:13:22.806Z
Learning: In the React TypeScript file `src/components/Request/Pay/Views/Initial.view.tsx`, when reviewing the `InitialView` component, do not flag potential issues with using non-null assertion `!` on the `slippagePercentage` variable, as handling undefined values in this context is considered out of scope.

Applied to files:

  • src/app/(mobile-ui)/qr-pay/page.tsx
🧬 Code graph analysis (1)
src/app/(mobile-ui)/qr-pay/page.tsx (8)
src/hooks/wallet/useWallet.ts (1)
  • useWallet (21-123)
src/services/manteca.ts (4)
  • QrPayment (11-31)
  • QrPaymentCharge (33-43)
  • QrPaymentLock (45-61)
  • mantecaApi (70-88)
src/hooks/useTransactionDetailsDrawer.ts (1)
  • useTransactionDetailsDrawer (3-22)
src/app/actions/currency.ts (1)
  • getCurrencyPrice (7-58)
src/utils/general.utils.ts (1)
  • isTxReverted (1288-1291)
src/services/charges.ts (1)
  • chargesApi (6-103)
src/constants/zerodev.consts.ts (1)
  • PEANUT_WALLET_TOKEN_DECIMALS (19-19)
src/components/TransactionDetails/TransactionDetailsReceipt.tsx (1)
  • TransactionDetailsReceipt (31-1045)
🔇 Additional comments (1)
src/app/(mobile-ui)/qr-pay/page.tsx (1)

139-147: Non-blocking tracking call looks good.

Not awaiting chargesApi.createPayment matches the intended post-success logging behavior noted earlier.

@jjramirezn jjramirezn requested a review from Hugo0 September 8, 2025 20:44
Copy link
Contributor

@Hugo0 Hugo0 left a comment

Choose a reason for hiding this comment

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

temp

<div className="flex items-center gap-1 font-bold">
<label className={`text-2xl ${displayValue ? 'text-black' : 'text-gray-2'}`}>{displaySymbol}</label>
<input
className={`h-12 w-[4ch] max-w-80 bg-transparent text-6xl font-black outline-none transition-colors placeholder:text-h1 focus:border-primary-1 dark:border-white dark:bg-n-1 dark:text-white dark:placeholder:text-white/75 dark:focus:border-primary-1`}
Copy link
Contributor

Choose a reason for hiding this comment

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

I really think, in general, we have wayyy too little reuse and too much component specific code. This applies to both styling (as you can see with 100000 million tailwind classses in FE code) and with logic

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is literally the TokenAmountInput which is reused in a lot of place and is a building block of the application. But in general, I agree

}
| { paymentLock: QrPaymentLock }

export const mantecaApi = {
Copy link
Contributor

Choose a reason for hiding this comment

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

jsdoc would be good here

Copy link
Contributor

Choose a reason for hiding this comment

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

:(

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I dont see what JSDoc would add here. We have this pattern all over the app and is pretty self explanatory

Copy link
Contributor

@Hugo0 Hugo0 left a comment

Choose a reason for hiding this comment

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

Good PR overall! I have various blockers

  • currency rate
  • doubts on prefunded $

New payment flow where we have to init the lock and then complete it
after the user sends the money to the manteca address. Also history
items
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: 3

Caution

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

⚠️ Outside diff range comments (4)
src/components/AddMoney/components/InputAmountStep.tsx (1)

52-58: Possible crash: non‑null assertion on price when fetch fails.

currencyData.price!.buy will throw when price fetch fails (hook sets price to null on catch). Use a safe fallback per prior patterns.

- price: currencyData.price!.buy,
+ price: currencyData.price?.buy ?? 0,
src/app/[...recipient]/client.tsx (1)

472-480: Possible crash: non‑null assertion on currencyPrice.

If price fetch fails, currencyPrice may be null; this non‑null assertion will throw in client. Use safe fallback.

- price: currencyPrice!.buy,
+ price: currencyPrice?.buy ?? 0,
src/components/Request/views/ReqFulfillBankFlowManager.tsx (1)

22-23: getCurrencyPrice is a Server Action — do NOT import it into this client component.

src/app/actions/currency.ts starts with 'use server' (line 1), so getCurrencyPrice is a server action. Importing it into src/components/Request/views/ReqFulfillBankFlowManager.tsx (lines 22–23), which is a client component, will break the build or expose server internals.

Fix options:

  • Move the call into a Server Component (or server-side parent).
  • Expose the logic via a client-safe API route/wrapper and fetch from the client.
  • Remove 'use client' from this component if it can be server-only.
src/services/manteca.ts (1)

1-146: Do not expose PEANUT_API_KEY to client bundles (critical)

mantecaApi is imported by a client component (src/app/(mobile-ui)/qr-pay/page.tsx — "use client"), while src/services/manteca.ts imports PEANUT_API_KEY from src/constants/general.consts.ts and uses it in getPrices. That top-level import will cause the API key to be present in the client bundle.

Actions to fix:

  • Move server-only logic that needs the key (getPrices / any 'api-key' usage) into a server-only module (e.g., src/services/manteca.server.ts) that imports PEANUT_API_KEY. Update server code (src/app/actions/currency.ts) to call the server module.
  • Keep client-facing code (initiateQrPayment/completeQrPayment) in a client-safe module that does NOT import PEANUT_API_KEY, or have the client call a server action/route instead.
  • Quick mitigation: remove PEANUT_API_KEY import from src/services/manteca.ts and ensure no client files import modules that reference the key.

Locations to change: src/services/manteca.ts, src/app/(mobile-ui)/qr-pay/page.tsx, src/constants/general.consts.ts, src/app/actions/currency.ts.

♻️ Duplicate comments (3)
src/hooks/useCurrency.ts (1)

4-19: Good: centralized, exported symbol map replaces ad‑hoc defaults.

Moving to SYMBOLS_BY_CURRENCY_CODE addresses prior feedback about hardcoding $. Nice.

src/app/(mobile-ui)/qr-pay/page.tsx (2)

24-24: Don’t import server-only action into a client component; use a client-safe wrapper/API.

getCurrencyPrice is a server action (uses unstable_cache). Route via an API or a client wrapper instead.

-import { getCurrencyPrice } from '@/app/actions/currency'
+import { getCurrencyPriceClient } from '@/lib/client/currency'
@@
-    if (paymentLock.code === '') {
-        price = (await getCurrencyPrice(currencyCode)).sell
+    if (paymentLock.code === '') {
+        price = (await getCurrencyPriceClient(currencyCode)).sell

Client wrapper:

// src/lib/client/currency.ts
export async function getCurrencyPriceClient(code: string) {
  const res = await fetch(`/api/currency/price?code=${encodeURIComponent(code)}`, { cache: 'no-store' })
  if (!res.ok) throw new Error('Failed to fetch currency price')
  return res.json() as Promise<{ buy: number; sell: number }>
}

API route (server):

// src/app/api/currency/price/route.ts
import { NextResponse } from 'next/server'
import { getCurrencyPrice } from '@/app/actions/currency'
export async function GET(req: Request) {
  const code = new URL(req.url).searchParams.get('code')
  if (!code) return NextResponse.json({ error: 'Missing code' }, { status: 400 })
  try { return NextResponse.json(await getCurrencyPrice(code)) }
  catch (e: any) { return NextResponse.json({ error: e?.message ?? 'Failed' }, { status: 500 }) }
}

Also applies to: 96-115


144-187: Add a submit guard to prevent double-pay taps.

Use an isPaying flag around payQR and wire it into the button’s loading/disabled state.

+ const [isPaying, setIsPaying] = useState(false)
@@
- const payQR = useCallback(async () => {
+ const payQR = useCallback(async () => {
+   if (isPaying) return
+   setIsPaying(true)
    try {
      // ...existing logic...
-     setIsSuccess(true)
+     setIsSuccess(true)
    } catch (error) {
      // ...existing error handling...
    } finally {
-     setLoadingState('Idle')
+     setLoadingState('Idle')
+     setIsPaying(false)
    }
- }, [paymentLock?.code, sendMoney, usdAmount, qrCode, currencyAmount])
+ }, [paymentLock?.code, sendMoney, usdAmount, qrCode, currencyAmount, isPaying])
@@
- loading={isLoading}
- disabled={!!errorInitiatingPayment || !!errorMessage || !amount || isLoading || isBalanceError}
+ loading={isLoading || isPaying}
+ disabled={!!errorInitiatingPayment || !!errorMessage || !amount || isLoading || isPaying || isBalanceError}

Also applies to: 350-358

🧹 Nitpick comments (23)
src/components/Global/DirectSendQR/utils.ts (3)

53-54: Tighten PIX regex (anchor + CRC) to reduce false positives

Broader .* matches can misclassify non-EMV strings. Anchoring to start and requiring CRC aligns with EMVCo payloads and mirrors the stricter MP pattern.

Apply this diff:

-const PIX_REGEX = /^.*000201.*0014br\.gov\.bcb\.pix.*5303986.*5802BR.*$/i
+const PIX_REGEX = /^000201((?!6304).)*(?:26|27)\d{2}0014br\.gov\.bcb\.pix((?!6304).)*5303986((?!6304).)*5802BR((?!6304).)*6304[0-9A-F]{4}$/i

55-58: Prefer Partial<Record<...>> for readability

Same type, clearer intent for a sparse mapping.

-export const QR_PAY_REGEXES: { [key in QrType]?: RegExp } = {
+export const QR_PAY_REGEXES: Partial<Record<QrType, RegExp>> = {
     [EQrType.MERCADO_PAGO]: MP_AR_REGEX,
     [EQrType.PIX]: PIX_REGEX,
 }

96-104: Simplify isQRPay by delegating to recognizeQr (avoid drift/dup regex runs)

Leverage the existing detection and just gate on the two QR‑pay types.

-export const isQRPay = (data: string): boolean => {
-    for (const [_type, regex] of Object.entries(QR_PAY_REGEXES)) {
-        if (regex.test(data)) {
-            return true
-        }
-    }
-    return false
-}
+export const isQRPay = (data: string): boolean => {
+    const type = recognizeQr(data)
+    return type === EQrType.MERCADO_PAGO || type === EQrType.PIX
+}
src/components/Common/ActionListDaimoPayButton.tsx (2)

55-56: Correct buy/sell handling with safe fallback.

Using currencyPrice?.buy || 0 is fine; consider the nullish coalescing equivalent to avoid treating 0 as falsy.

- price: currencyPrice?.buy || 0,
+ price: currencyPrice?.buy ?? 0,

28-33: Sanitize formatted numbers before parsing/using.

usdAmount may include commas; parseFloat('1,234.56') yields 1. Sanitize before validation and before sending.

- if (!usdAmount || parseFloat(usdAmount) <= 0) {
+ const normalizedUsd = (usdAmount ?? '').replace(/,/g, '')
+ if (!normalizedUsd || parseFloat(normalizedUsd) <= 0) {
     console.error('Invalid amount entered')
     dispatch(paymentActions.setError('Invalid amount'))
     return false
   }
src/hooks/useCurrency.ts (3)

24-26: isLoading default false can cause a brief stale render.

Initialize isLoading to true when code is present to avoid a flash of “not loading” before the effect flips it.

- const [isLoading, setIsLoading] = useState<boolean>(false)
+ const [isLoading, setIsLoading] = useState<boolean>(!!(currencyCode?.toUpperCase()))

40-45: Avoid stale state when rejecting unsupported codes.

When invalid, also clear symbol and price to prevent consumers from reading previous values.

- if (!Object.keys(SYMBOLS_BY_CURRENCY_CODE).includes(code)) {
-   setCode(null)
+ if (!(code in SYMBOLS_BY_CURRENCY_CODE)) {
+   setCode(null)
+   setSymbol(null)
+   setPrice(null)
    setIsLoading(false)
    return
 }

46-56: Add a safe fallback on fetch failure to match consumers expecting numbers.

Some callers non‑null assert price (e.g., pages/components). Set a safe fallback object to avoid crashes when the fetch fails.

 .catch((err) => {
   console.error(err)
-  setIsLoading(false)
+  // Keep symbol (if set) but ensure price shape exists to avoid null-deref downstream.
+  setPrice((prev) => prev ?? { buy: 0, sell: 0 })
+  setIsLoading(false)
 })
src/app/actions/onramp.ts (1)

126-128: Minor: remove redundant await on cookies().

cookies() is synchronous in Next.js App Router. The await is unnecessary.

- const cookieStore = cookies()
- const jwtToken = (await cookieStore).get('jwt-token')?.value
+ const jwtToken = cookies().get('jwt-token')?.value
src/components/Request/views/ReqFulfillBankFlowManager.tsx (1)

65-66: Buy leg usage looks right; add sanitize and failure fallback.

If getCurrencyPrice fails or usdAmount has commas, this can miscompute. Normalize input and handle { buy: 0 } fallback.

- const currencyAmount = Number(usdAmount) * price.buy
+ const currencyAmount = Number(String(usdAmount).replace(/,/g, '')) * (price?.buy ?? 0)
src/components/Claim/Link/views/Confirm.bank-claim.view.tsx (5)

74-75: Make the conversion-failure check numeric-safe.

Use a finite-number guard to avoid edge cases (undefined, null, NaN, non-number).

Apply:

-        return currencyCode !== 'USD' && !isLoadingCurrency && (!price?.sell || isNaN(price.sell))
+        return (
+            currencyCode !== 'USD' &&
+            !isLoadingCurrency &&
+            !(typeof price?.sell === 'number' && Number.isFinite(price.sell))
+        )

81-83: Stabilize conversion + formatting.

Guard with Number.isFinite and use locale formatting for consistency with other displays.

-        if (!price?.sell || isNaN(price.sell)) return usdAmount
-        const converted = (Number(usdAmount) * price.sell).toFixed(2)
-        return converted
+        const rate = price?.sell
+        if (!(typeof rate === 'number' && Number.isFinite(rate))) return usdAmount
+        const converted = Number(usdAmount) * rate
+        return converted.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })

91-91: Unify symbol fallback with utils.

Prefer the centralized resolver so unknown codes don’t render raw currency codes as symbols.

-        return resolvedSymbol ?? currencyCode
+        return resolvedSymbol ?? getDisplayCurrencySymbol(currencyCode)

Add import at top (outside this hunk):

import { getDisplayCurrencySymbol } from '@/utils/currency'

56-60: formatUnits fallback should use bigint.

formatUnits(value, decimals) expects a bigint value. Passing 0 (number) can be brittle in TS.

-    const usdAmount = useMemo(
-        () => formatUnits(claimLinkData?.amount ?? 0, claimLinkData?.tokenDecimals) || '0.00',
+    const usdAmount = useMemo(
+        () => formatUnits((claimLinkData?.amount ?? 0n) as bigint, claimLinkData?.tokenDecimals) || '0.00',
         [claimLinkData]
     )

124-124: Fee currency should match display currency.

Currently hard-coded as USD. Use displaySymbol for consistency.

-                    <PaymentInfoRow hideBottomBorder label="Fee" value={`$ 0.00`} />
+                    <PaymentInfoRow hideBottomBorder label="Fee" value={`${displaySymbol} 0.00`} />
src/components/TransactionDetails/transactionTransformer.ts (2)

74-75: Propagate avatarUrl through to the drawer.

You added the type but don’t set the value. Pass through any provided avatar from history.extraData.

 export interface TransactionDetails {
   ...
   extraDataForDrawer?: {
     ...
-    avatarUrl?: string
+    avatarUrl?: string
     ...
   }
 }
@@
         extraDataForDrawer: {
           addressExplorerUrl,
           originalType: entry.type as EHistoryEntryType,
           originalUserRole: entry.userRole as EHistoryUserRole,
           link: entry.extraData?.link,
           isLinkTransaction: isLinkTx,
           transactionCardType,
           rewardData,
           fulfillmentType: entry.extraData?.fulfillmentType,
           bridgeTransferId: entry.extraData?.bridgeTransferId,
+          avatarUrl: entry.extraData?.avatarUrl,
           depositInstructions:

Also applies to: 456-470


439-442: Derive currency symbol from currency code, not hard-coded '$'.

If entry.currency is non-USD, the UI still shows '$'. Use the resolver for correctness.

-        currencySymbol: `${entry.userRole === EHistoryUserRole.SENDER ? '-' : '+'}$`,
+        currencySymbol: `${entry.userRole === EHistoryUserRole.SENDER ? '-' : '+'}${getDisplayCurrencySymbol(entry.currency?.code ?? 'USD')}`,

Add import (top of file, outside hunk):

import { getDisplayCurrencySymbol } from '@/utils/currency'
src/utils/currency.ts (1)

7-7: Prefer fallbackSymbol over echoing unknown codes.

Echoing the code (e.g., 'XYZ') as a symbol can look broken. Favor the caller-provided fallback.

-    return SYMBOLS_BY_CURRENCY_CODE[upperCode] ?? upperCode
+    return SYMBOLS_BY_CURRENCY_CODE[upperCode] ?? fallbackSymbol
src/components/TransactionDetails/TransactionDetailsHeaderCard.tsx (2)

115-122: Copy for qr_payment titles reads well.

Optional: For failed state, “Payment to X failed” may be clearer than “Payment to X”.

-                    titleText = `Payment to ${displayName}`
+                    titleText = `Payment to ${displayName} failed`

179-181: Improve avatar image accessibility and sizing consistency.

Use a meaningful alt (or empty if decorative) and match width/height to CSS to prevent layout shifts on Next/Image.

-                    <div className="flex h-16 w-16 items-center justify-center rounded-full">
-                        <Image src={avatarUrl} alt="Icon" className="size-16 object-contain" width={35} height={35} />
+                    <div className="flex h-16 w-16 items-center justify-center rounded-full">
+                        <Image
+                            src={avatarUrl}
+                            alt={userName ? `${userName} logo` : ''}
+                            className="size-16 object-contain"
+                            width={64}
+                            height={64}
+                        />
                     </div>
src/components/TransactionDetails/TransactionDetailsReceipt.tsx (1)

443-461: Unify currency display for USD line.

Use the same symbolization logic used elsewhere to avoid “U$D” inconsistency.

- value={`${formatAmount(transaction.amount.toString())} U$D`}
+ value={`$ ${formatAmount(Number(transaction.amount))}`}
src/app/(mobile-ui)/qr-pay/page.tsx (1)

160-163: Remove stray no-op expression.

finalPaymentLock on its own line does nothing; likely a leftover.

-        if (finalPaymentLock.code === '') {
-            finalPaymentLock
+        if (finalPaymentLock.code === '') {
src/services/manteca.ts (1)

33-44: Remove unused types to reduce surface area.

QrPaymentCharge and QrPaymentResponse aren’t used with the current API. Delete to avoid confusion.

- export type QrPaymentCharge = {
-   uuid: string
-   createdAt: string
-   link: string
-   chainId: string
-   tokenAmount: string
-   tokenAddress: string
-   tokenDecimals: number
-   tokenType: string
-   tokenSymbol: string
- }
@@
- export type QrPaymentResponse =
-   | { qrPayment: QrPayment; charge: QrPaymentCharge }
-   | { paymentLock: QrPaymentLock }

Also applies to: 63-69

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1e6f3dd and 5642079.

📒 Files selected for processing (24)
  • src/app/(mobile-ui)/qr-pay/layout.tsx (1 hunks)
  • src/app/(mobile-ui)/qr-pay/page.tsx (1 hunks)
  • src/app/[...recipient]/client.tsx (1 hunks)
  • src/app/actions/currency.ts (2 hunks)
  • src/app/actions/onramp.ts (1 hunks)
  • src/components/AddMoney/components/InputAmountStep.tsx (1 hunks)
  • src/components/Claim/Link/views/Confirm.bank-claim.view.tsx (1 hunks)
  • src/components/Common/ActionListDaimoPayButton.tsx (1 hunks)
  • src/components/Global/DirectSendQR/index.tsx (1 hunks)
  • src/components/Global/DirectSendQR/utils.ts (2 hunks)
  • src/components/Global/TokenAmountInput/index.tsx (4 hunks)
  • src/components/Request/views/ReqFulfillBankFlowManager.tsx (1 hunks)
  • src/components/TransactionDetails/TransactionCard.tsx (7 hunks)
  • src/components/TransactionDetails/TransactionDetailsHeaderCard.tsx (4 hunks)
  • src/components/TransactionDetails/TransactionDetailsReceipt.tsx (7 hunks)
  • src/components/TransactionDetails/transactionTransformer.ts (3 hunks)
  • src/constants/general.consts.ts (1 hunks)
  • src/constants/loadingStates.consts.ts (2 hunks)
  • src/hooks/useCreateOnramp.ts (1 hunks)
  • src/hooks/useCurrency.ts (3 hunks)
  • src/services/manteca.ts (1 hunks)
  • src/utils/__tests__/bridge.utils.test.ts (0 hunks)
  • src/utils/currency.ts (1 hunks)
  • src/utils/index.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • src/utils/tests/bridge.utils.test.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/utils/index.ts
  • src/components/Global/DirectSendQR/index.tsx
  • src/app/(mobile-ui)/qr-pay/layout.tsx
  • src/components/Global/TokenAmountInput/index.tsx
🧰 Additional context used
🧠 Learnings (25)
📓 Common learnings
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#1190
File: src/app/(mobile-ui)/qr-pay/page.tsx:156-176
Timestamp: 2025-09-08T03:13:09.111Z
Learning: In the peanut-ui mobile app, the `/qr-pay` route is only accessed through the DirectSendQR component which always includes the qrCode parameter in the URL when redirecting users to the QR pay page after scanning MERCADO_PAGO or PIX QR codes.
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#1190
File: src/app/(mobile-ui)/qr-pay/page.tsx:156-176
Timestamp: 2025-09-08T03:13:09.111Z
Learning: In the peanut-ui mobile app, the `/qr-pay` route is only accessed through the DirectSendQR component which always includes the qrCode parameter in the URL when redirecting users to the QR pay page.
📚 Learning: 2024-10-07T15:25:45.170Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#422
File: src/components/Request/Pay/Views/Initial.view.tsx:76-78
Timestamp: 2024-10-07T15:25:45.170Z
Learning: In `src/components/Request/Pay/Views/Initial.view.tsx`, both `txFee` and `utils.formatTokenAmount(...)` return strings, ensuring that `calculatedFee` consistently returns a string without the need for additional type conversion.

Applied to files:

  • src/components/Request/views/ReqFulfillBankFlowManager.tsx
  • src/components/Claim/Link/views/Confirm.bank-claim.view.tsx
  • src/components/AddMoney/components/InputAmountStep.tsx
  • src/app/[...recipient]/client.tsx
📚 Learning: 2024-10-07T15:28:25.280Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#422
File: src/components/Request/Pay/Views/Initial.view.tsx:76-78
Timestamp: 2024-10-07T15:28:25.280Z
Learning: In `src/components/Request/Pay/Views/Initial.view.tsx`, both `txFee` and `utils.formatTokenAmount(estimatedGasCost, 3)` return strings, ensuring consistent return types for `calculatedFee`.

Applied to files:

  • src/components/Request/views/ReqFulfillBankFlowManager.tsx
  • src/components/Claim/Link/views/Confirm.bank-claim.view.tsx
  • src/app/[...recipient]/client.tsx
📚 Learning: 2024-10-07T13:42:00.443Z
Learnt from: Hugo0
PR: peanutprotocol/peanut-ui#422
File: src/components/Request/Pay/Pay.tsx:103-111
Timestamp: 2024-10-07T13:42:00.443Z
Learning: When the token price cannot be fetched in `src/components/Request/Pay/Pay.tsx` within the `PayRequestLink` component, set `tokenPriceData.price` to 0 to ensure the UI remains functional. Since Squid uses their own price engine for x-chain fulfillment transactions, this approach will not affect the transaction computation.

Applied to files:

  • src/components/Request/views/ReqFulfillBankFlowManager.tsx
  • src/components/Common/ActionListDaimoPayButton.tsx
  • src/components/AddMoney/components/InputAmountStep.tsx
  • src/app/[...recipient]/client.tsx
  • src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2024-10-08T20:13:42.967Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#422
File: src/components/Request/Pay/Pay.consts.ts:34-34
Timestamp: 2024-10-08T20:13:42.967Z
Learning: In `src/components/Request/Pay` components, the `tokenPrice` property in the `IPayScreenProps` interface is only relevant to these views. Other components using `IPayScreenProps` do not need to handle `tokenPriceData` when it's updated in these components.

Applied to files:

  • src/components/Request/views/ReqFulfillBankFlowManager.tsx
  • src/components/Common/ActionListDaimoPayButton.tsx
  • src/components/AddMoney/components/InputAmountStep.tsx
  • src/app/[...recipient]/client.tsx
📚 Learning: 2025-08-26T15:25:53.328Z
Learnt from: Zishan-7
PR: peanutprotocol/peanut-ui#1132
File: src/app/[...recipient]/client.tsx:394-397
Timestamp: 2025-08-26T15:25:53.328Z
Learning: In `src/components/Common/ActionListDaimoPayButton.tsx`, the `handleCompleteDaimoPayment` function should not display error messages to users when DB update fails because the Daimo payment itself has succeeded - showing errors would be confusing since the payment was successful.

Applied to files:

  • src/components/Common/ActionListDaimoPayButton.tsx
  • src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2024-11-18T21:36:11.486Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#535
File: src/components/Claim/Claim.tsx:142-146
Timestamp: 2024-11-18T21:36:11.486Z
Learning: In `src/components/Claim/Claim.tsx`, external calls like token price fetching and cross-chain details retrieval are already encapsulated within existing `try...catch` blocks, so additional error handling may be unnecessary.

Applied to files:

  • src/components/Claim/Link/views/Confirm.bank-claim.view.tsx
📚 Learning: 2024-10-18T01:51:35.247Z
Learnt from: Hugo0
PR: peanutprotocol/peanut-ui#458
File: src/components/Offramp/Confirm.view.tsx:141-141
Timestamp: 2024-10-18T01:51:35.247Z
Learning: The `handleConfirm` function in `src/components/Create/Link/Confirm.view.tsx` is separate from the one in `src/components/Offramp/Confirm.view.tsx` and does not need to be renamed when refactoring `handleConfirm` in `src/components/Offramp/Confirm.view.tsx`.

Applied to files:

  • src/components/Claim/Link/views/Confirm.bank-claim.view.tsx
📚 Learning: 2025-08-12T17:47:28.362Z
Learnt from: Zishan-7
PR: peanutprotocol/peanut-ui#1089
File: src/app/api/bridge/exchange-rate/route.ts:4-19
Timestamp: 2025-08-12T17:47:28.362Z
Learning: In the Bridge exchange rate API route (src/app/api/bridge/exchange-rate/route.ts), the ExchangeRateResponse interface uses numeric types for rates because the route converts string values from the Bridge API to floats using parseFloat() before returning the response.

Applied to files:

  • src/components/Claim/Link/views/Confirm.bank-claim.view.tsx
  • src/app/actions/currency.ts
  • src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2024-10-29T12:19:41.968Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#495
File: src/components/Global/TokenAmountInput/index.tsx:23-30
Timestamp: 2024-10-29T12:19:41.968Z
Learning: In the `TokenAmountInput` component (`src/components/Global/TokenAmountInput/index.tsx`), when the 'Max' button is clicked, we intentionally set the input denomination to 'TOKEN' because we are setting the value as token.

Applied to files:

  • src/components/AddMoney/components/InputAmountStep.tsx
📚 Learning: 2024-10-08T20:13:42.967Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#413
File: src/context/tokenSelector.context.tsx:118-123
Timestamp: 2024-10-08T20:13:42.967Z
Learning: In the `TokenContextProvider` component within `src/context/tokenSelector.context.tsx`, in the TypeScript React application, when data changes and before calling `fetchAndSetTokenPrice`, it is necessary to reset `selectedTokenData`, `selectedTokenPrice`, `selectedTokenDecimals`, and `inputDenomination` to discard stale data.

Applied to files:

  • src/components/AddMoney/components/InputAmountStep.tsx
  • src/app/[...recipient]/client.tsx
  • src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2025-08-22T07:25:59.304Z
Learnt from: Zishan-7
PR: peanutprotocol/peanut-ui#1104
File: src/components/Payment/PaymentForm/index.tsx:596-600
Timestamp: 2025-08-22T07:25:59.304Z
Learning: The `TokenAmountInput` component in `src/components/Global/TokenAmountInput/` always returns decimal strings (e.g., "1,234.56"), not base units. When passing these values to external APIs like Daimo's `toUnits` prop, simply stripping commas with `.replace(/,/g, '')` is sufficient.

Applied to files:

  • src/components/AddMoney/components/InputAmountStep.tsx
📚 Learning: 2024-10-29T12:20:47.207Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#495
File: src/components/Create/Link/Input.view.tsx:244-248
Timestamp: 2024-10-29T12:20:47.207Z
Learning: In the `TokenAmountInput` component within `src/components/Global/TokenAmountInput/index.tsx`, when `balance` is undefined, the `maxValue` prop should be set to an empty string `''`.

Applied to files:

  • src/components/AddMoney/components/InputAmountStep.tsx
📚 Learning: 2024-12-11T10:13:22.806Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#564
File: src/components/Request/Pay/Views/Initial.view.tsx:430-430
Timestamp: 2024-12-11T10:13:22.806Z
Learning: In the React TypeScript file `src/components/Request/Pay/Views/Initial.view.tsx`, when reviewing the `InitialView` component, do not flag potential issues with using non-null assertion `!` on the `slippagePercentage` variable, as handling undefined values in this context is considered out of scope.

Applied to files:

  • src/components/AddMoney/components/InputAmountStep.tsx
  • src/app/[...recipient]/client.tsx
  • src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2025-09-08T03:13:09.111Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#1190
File: src/app/(mobile-ui)/qr-pay/page.tsx:156-176
Timestamp: 2025-09-08T03:13:09.111Z
Learning: In the peanut-ui mobile app, the `/qr-pay` route is only accessed through the DirectSendQR component which always includes the qrCode parameter in the URL when redirecting users to the QR pay page after scanning MERCADO_PAGO or PIX QR codes.

Applied to files:

  • src/components/Global/DirectSendQR/utils.ts
  • src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2025-08-14T14:42:54.411Z
Learnt from: Zishan-7
PR: peanutprotocol/peanut-ui#1094
File: src/utils/withdraw.utils.ts:181-191
Timestamp: 2025-08-14T14:42:54.411Z
Learning: The countryCodeMap in src/components/AddMoney/consts/index.ts uses uppercase 3-letter country codes as keys (like 'AUT', 'BEL', 'CZE') that map to 2-letter country codes, requiring input normalization to uppercase for proper lookups.

Applied to files:

  • src/utils/currency.ts
  • src/hooks/useCurrency.ts
📚 Learning: 2025-07-24T10:57:15.315Z
Learnt from: Zishan-7
PR: peanutprotocol/peanut-ui#1000
File: src/components/og/ProfileCardOG.tsx:0-0
Timestamp: 2025-07-24T10:57:15.315Z
Learning: In `src/components/og/ProfileCardOG.tsx`, the scribble image should have an empty alt attribute (alt="") to prevent layout issues if the image fails to load. Since it's a decorative element positioned absolutely over the username text, showing alt text would interfere with the layout and username display.

Applied to files:

  • src/components/TransactionDetails/TransactionDetailsHeaderCard.tsx
📚 Learning: 2025-09-08T03:13:09.111Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#1190
File: src/app/(mobile-ui)/qr-pay/page.tsx:156-176
Timestamp: 2025-09-08T03:13:09.111Z
Learning: In the peanut-ui mobile app, the `/qr-pay` route is only accessed through the DirectSendQR component which always includes the qrCode parameter in the URL when redirecting users to the QR pay page.

Applied to files:

  • src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2025-09-08T03:11:57.230Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#1190
File: src/app/(mobile-ui)/qr-pay/page.tsx:64-73
Timestamp: 2025-09-08T03:11:57.230Z
Learning: In QR payment flows, user-entered amounts should be cleared and overridden by API response amounts to ensure the authoritative payment amount from the API takes precedence over user input.

Applied to files:

  • src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2024-10-23T09:38:27.670Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#469
File: src/app/request/pay/page.tsx:32-64
Timestamp: 2024-10-23T09:38:27.670Z
Learning: In `src/app/request/pay/page.tsx`, if `linkRes` is not OK in the `generateMetadata` function, the desired behavior is to use the standard title and preview image without throwing an error.

Applied to files:

  • src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2024-10-08T20:13:42.967Z
Learnt from: Hugo0
PR: peanutprotocol/peanut-ui#422
File: src/components/Request/Pay/Pay.tsx:113-123
Timestamp: 2024-10-08T20:13:42.967Z
Learning: In the `PayRequestLink` component (`src/components/Request/Pay/Pay.tsx`), when resolving ENS names, handle errors by displaying an appropriate error message to the user if the ENS cannot be resolved.

Applied to files:

  • src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2025-09-08T03:11:00.114Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#1190
File: src/app/(mobile-ui)/qr-pay/page.tsx:31-41
Timestamp: 2025-09-08T03:11:00.114Z
Learning: In QR payment flows, the `createPayment` API call for payment tracking can be non-awaited when called after successful transaction execution, as it's a non-blocking logging operation and doesn't affect the user's success state.

Applied to files:

  • src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2024-10-22T18:11:36.864Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#469
File: src/app/request/pay/page.tsx:32-49
Timestamp: 2024-10-22T18:11:36.864Z
Learning: In `src/app/request/pay/page.tsx`, the `id` parameter is accessed via `searchParams.id` in the `generateMetadata` function.

Applied to files:

  • src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2025-09-05T07:31:11.396Z
Learnt from: Zishan-7
PR: peanutprotocol/peanut-ui#1185
File: src/components/Claim/useClaimLink.tsx:14-0
Timestamp: 2025-09-05T07:31:11.396Z
Learning: In the peanut-ui codebase, `window.history.replaceState` is preferred over `router.replace` when immediate/synchronous URL parameter updates are required, as `router.replace` is asynchronous and doesn't guarantee instant URL changes that subsequent code can rely on. This pattern is used consistently across usePaymentInitiator.ts, Confirm.payment.view.tsx, and useClaimLink.tsx.

Applied to files:

  • src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2025-09-11T17:46:12.507Z
Learnt from: Hugo0
PR: peanutprotocol/peanut-ui#1200
File: src/app/(mobile-ui)/recover-funds/page.tsx:9-9
Timestamp: 2025-09-11T17:46:12.507Z
Learning: Functions in Next.js that are not marked with "use server" and contain secrets are unsafe to import in client components, as they get bundled into the client JavaScript and can leak environment variables to the browser.

Applied to files:

  • src/app/(mobile-ui)/qr-pay/page.tsx
🧬 Code graph analysis (6)
src/app/actions/currency.ts (1)
src/services/manteca.ts (1)
  • mantecaApi (89-145)
src/utils/currency.ts (1)
src/hooks/useCurrency.ts (1)
  • SYMBOLS_BY_CURRENCY_CODE (4-19)
src/app/(mobile-ui)/qr-pay/page.tsx (9)
src/hooks/wallet/useWallet.ts (1)
  • useWallet (21-123)
src/services/manteca.ts (3)
  • QrPaymentLock (45-61)
  • QrPayment (11-31)
  • mantecaApi (89-145)
src/hooks/useTransactionDetailsDrawer.ts (1)
  • useTransactionDetailsDrawer (3-22)
src/components/Global/DirectSendQR/utils.ts (1)
  • isQRPay (96-103)
src/app/actions/currency.ts (1)
  • getCurrencyPrice (7-62)
src/utils/general.utils.ts (1)
  • isTxReverted (1288-1291)
src/constants/zerodev.consts.ts (1)
  • PEANUT_WALLET_TOKEN_DECIMALS (19-19)
src/components/TransactionDetails/TransactionDetailsDrawer.tsx (1)
  • TransactionDetailsDrawer (19-63)
src/components/Payment/PaymentInfoRow.tsx (1)
  • PaymentInfoRow (7-81)
src/services/manteca.ts (3)
src/services/services.types.ts (1)
  • QrPaymentResponse (323-331)
src/utils/sentry.utils.ts (1)
  • fetchWithSentry (26-104)
src/constants/general.consts.ts (2)
  • PEANUT_API_URL (43-47)
  • PEANUT_API_KEY (49-49)
src/components/TransactionDetails/TransactionCard.tsx (3)
src/utils/history.utils.ts (1)
  • getAvatarUrl (5-19)
src/utils/currency.ts (1)
  • getDisplayCurrencySymbol (4-8)
src/utils/general.utils.ts (1)
  • formatNumberForDisplay (348-390)
src/components/TransactionDetails/TransactionDetailsReceipt.tsx (3)
src/utils/general.utils.ts (1)
  • formatAmount (400-440)
src/components/Payment/PaymentInfoRow.tsx (1)
  • PaymentInfoRow (7-81)
src/utils/currency.ts (1)
  • getDisplayCurrencySymbol (4-8)
🔇 Additional comments (12)
src/app/actions/onramp.ts (1)

78-83: Use buy leg correctly; ensure consistent rounding.

(Number(params.amount) * price.buy).toFixed(2) is correct. Consider using Math.max(0, ...) guard if negative inputs are possible upstream.

Do we ever pass formatted strings (with commas) into params.amount? If yes, sanitize before Number(...).

src/constants/general.consts.ts (1)

49-50: Do not export PEANUT_API_KEY from shared modules — move it to a server-only module.

Exporting the secret from src/constants/general.consts.ts risks bundling it into client code; move the key to a server-only file and consume it only in server actions / API routes.

Apply this diff:

- export const PEANUT_API_KEY = process.env.PEANUT_API_KEY!

Server-only example:

// src/server/config.ts
import 'server-only'
export const PEANUT_API_KEY = process.env.PEANUT_API_KEY!

Update server callers to import from src/server/config.ts and confirm no client components import '@/constants/general.consts'.

src/utils/currency.ts (2)

12-12: LGTM—centralized symbol resolver used in formatter.


1-1: Move SYMBOLS_BY_CURRENCY_CODE out of hooks into a shared constants module

utils importing from hooks is a layering smell and can pull client-only code into server bundles. Extract SYMBOLS_BY_CURRENCY_CODE into src/constants/currencySymbols.consts.ts (export the constant) and update imports in src/utils/currency.ts and src/hooks/useCurrency.ts to import from '@/constants/currencySymbols.consts'.

src/components/TransactionDetails/TransactionDetailsHeaderCard.tsx (2)

26-26: Direction union updated with 'qr_payment'—good.


140-141: Icon mapping aligns with outflow—good.

src/constants/loadingStates.consts.ts (1)

12-12: Add exhaustive UI mappings for new loading states

  • Ensure state→copy/icon/spinner mappings include 'Fetching details' and 'Paying' so users don't see generic loaders.
  • Gate analytics emitted per loading phase to avoid noisy/duplicate events.
  • Consider centralizing UI metadata for LoadingStates (label, icon, aria-label) to keep consumers in sync.

Location: src/constants/loadingStates.consts.ts:12. Automated search returned no files — verification inconclusive; manually verify mappings and analytics across consumers.

src/components/TransactionDetails/transactionTransformer.ts (1)

296-301: QR payment mapping verified — no changes required.

Header (src/components/TransactionDetails/TransactionDetailsHeaderCard.tsx) and Receipt (src/components/TransactionDetails/TransactionDetailsReceipt.tsx) explicitly handle 'qr_payment'; transactionTransformer maps MANTECA_QR_PAYMENT → direction 'qr_payment' and transactionCardType 'pay' (src/components/TransactionDetails/transactionTransformer.ts); app/(mobile-ui)/qr-pay/page.tsx opens details with direction 'qr_payment'. No generic fallback observed.

src/app/actions/currency.ts (1)

35-36: Confirm types from getExchangeRate; drop parseFloat if already numeric.

Bridge route returns numeric rates per prior work. If buy_rate/sell_rate are numbers, use them directly to avoid TS errors.

Would you like me to scan the repo and surface the ExchangeRateResponse type?

src/components/TransactionDetails/TransactionDetailsReceipt.tsx (1)

41-53: Avatar prop + fallback looks good.

Forwarded avatarUrl with fallback to drawer data is consistent with header usage.

Also applies to: 298-300

src/components/TransactionDetails/TransactionCard.tsx (2)

30-31: Additions for 'pay' type/icon look correct.

Type union and icon mapping are coherent with existing patterns.

Also applies to: 240-243


68-71: Centralized avatar URL resolution is a good improvement.

getAvatarUrl(transaction) simplifies card and drawer cohesion.

Comment on lines 39 to 46
case 'ARS':
case 'BRL':
case 'COP':
case 'CRC':
case 'PUSD':
case 'GTQ':
case 'PHP':
case 'BOB':
Copy link
Contributor

Choose a reason for hiding this comment

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

issue: not blocking but this doesn't seem very DRY. Don't we have a MANTECA_CURRENCIES or smth somewhere?

sell = parseFloat(data.sell_rate)
}
break
case 'ARS':
Copy link
Contributor

Choose a reason for hiding this comment

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

note: this switch case notation is a bit confusing to read. Maybe comment sep would be better

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What's confusing about it?

}
| { paymentLock: QrPaymentLock }

export const mantecaApi = {
Copy link
Contributor

Choose a reason for hiding this comment

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

:(

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: 3

♻️ Duplicate comments (3)
src/components/TransactionDetails/TransactionDetailsReceipt.tsx (1)

445-463: Inconsistent currency display format: U$D vs USD.

The code displays "Value in USD" but uses the format {amount} USD in the value. For consistency with other parts of the codebase, consider using either $ {amount} or {amount} USD throughout.

Apply this diff for consistency:

-                                        value={`${formatAmount(transaction.amount.toString())} USD`}
+                                        value={`$ ${formatAmount(transaction.amount.toString())}`}
src/components/Global/TokenAmountInput/index.tsx (1)

159-163: Currency symbol handling needs consistency.

When isInputUsd is true in FIAT mode, you display 'USD' as the symbol, but the alternativeDisplaySymbol shows the currency symbol. Consider whether 'USD' or '$' should be used consistently across the application.

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

1-24: Server action import in client component will break bundling.

The import of getCurrencyPrice from a server action file will fail at runtime since server actions use Next.js server-only APIs like unstable_cache.

#!/bin/bash
# Verify that getCurrencyPrice uses server-only APIs
cat src/app/actions/currency.ts | head -20
🧹 Nitpick comments (3)
src/components/TransactionDetails/TransactionDetailsReceipt.tsx (1)

464-464: Address the TODO about snake_case usage.

The TODO comment correctly identifies that exchange_rate uses snake_case, which is inconsistent with the TypeScript/JavaScript camelCase convention used elsewhere in the codebase.

Would you like me to help refactor the codebase to use camelCase consistently for the exchange_rate field and update all related references?

src/app/(mobile-ui)/qr-pay/page.tsx (2)

162-167: Unnecessary variable reference after check.

Line 162 references finalPaymentLock alone without any operation, which appears to be leftover debug code.

Apply this diff to remove the unnecessary line:

         if (finalPaymentLock.code === '') {
-            finalPaymentLock
             setErrorMessage('Could not fetch qr payment details')

231-234: Dev error case needs proper handling.

Returning null silently when isSuccess && !qrPayment could leave users with a blank screen. Add error tracking.

Apply this diff to handle the edge case:

 if (isSuccess && !qrPayment) {
-    // This should never happen, if this happens there is dev error
-    return null
+    captureException(new Error('Success state without qrPayment data'))
+    return <ErrorAlert description="An unexpected error occurred. Please contact support." />
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5642079 and 8009ee5.

📒 Files selected for processing (6)
  • src/app/(mobile-ui)/qr-pay/page.tsx (1 hunks)
  • src/app/[...recipient]/client.tsx (1 hunks)
  • src/components/Global/DirectSendQR/utils.ts (2 hunks)
  • src/components/Global/TokenAmountInput/index.tsx (4 hunks)
  • src/components/Request/views/ReqFulfillBankFlowManager.tsx (1 hunks)
  • src/components/TransactionDetails/TransactionDetailsReceipt.tsx (7 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/components/Request/views/ReqFulfillBankFlowManager.tsx
  • src/app/[...recipient]/client.tsx
🧰 Additional context used
🧠 Learnings (21)
📓 Common learnings
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#1190
File: src/app/(mobile-ui)/qr-pay/page.tsx:156-176
Timestamp: 2025-09-08T03:13:09.111Z
Learning: In the peanut-ui mobile app, the `/qr-pay` route is only accessed through the DirectSendQR component which always includes the qrCode parameter in the URL when redirecting users to the QR pay page after scanning MERCADO_PAGO or PIX QR codes.
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#1190
File: src/app/(mobile-ui)/qr-pay/page.tsx:156-176
Timestamp: 2025-09-08T03:13:09.111Z
Learning: In the peanut-ui mobile app, the `/qr-pay` route is only accessed through the DirectSendQR component which always includes the qrCode parameter in the URL when redirecting users to the QR pay page.
📚 Learning: 2025-09-08T03:13:09.111Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#1190
File: src/app/(mobile-ui)/qr-pay/page.tsx:156-176
Timestamp: 2025-09-08T03:13:09.111Z
Learning: In the peanut-ui mobile app, the `/qr-pay` route is only accessed through the DirectSendQR component which always includes the qrCode parameter in the URL when redirecting users to the QR pay page after scanning MERCADO_PAGO or PIX QR codes.

Applied to files:

  • src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2025-09-08T03:13:09.111Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#1190
File: src/app/(mobile-ui)/qr-pay/page.tsx:156-176
Timestamp: 2025-09-08T03:13:09.111Z
Learning: In the peanut-ui mobile app, the `/qr-pay` route is only accessed through the DirectSendQR component which always includes the qrCode parameter in the URL when redirecting users to the QR pay page.

Applied to files:

  • src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2025-08-26T15:25:53.328Z
Learnt from: Zishan-7
PR: peanutprotocol/peanut-ui#1132
File: src/app/[...recipient]/client.tsx:394-397
Timestamp: 2025-08-26T15:25:53.328Z
Learning: In `src/components/Common/ActionListDaimoPayButton.tsx`, the `handleCompleteDaimoPayment` function should not display error messages to users when DB update fails because the Daimo payment itself has succeeded - showing errors would be confusing since the payment was successful.

Applied to files:

  • src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2025-09-08T03:11:57.230Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#1190
File: src/app/(mobile-ui)/qr-pay/page.tsx:64-73
Timestamp: 2025-09-08T03:11:57.230Z
Learning: In QR payment flows, user-entered amounts should be cleared and overridden by API response amounts to ensure the authoritative payment amount from the API takes precedence over user input.

Applied to files:

  • src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2024-10-08T20:13:42.967Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#413
File: src/context/tokenSelector.context.tsx:118-123
Timestamp: 2024-10-08T20:13:42.967Z
Learning: In the `TokenContextProvider` component within `src/context/tokenSelector.context.tsx`, in the TypeScript React application, when data changes and before calling `fetchAndSetTokenPrice`, it is necessary to reset `selectedTokenData`, `selectedTokenPrice`, `selectedTokenDecimals`, and `inputDenomination` to discard stale data.

Applied to files:

  • src/app/(mobile-ui)/qr-pay/page.tsx
  • src/components/Global/TokenAmountInput/index.tsx
📚 Learning: 2024-10-23T09:38:27.670Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#469
File: src/app/request/pay/page.tsx:32-64
Timestamp: 2024-10-23T09:38:27.670Z
Learning: In `src/app/request/pay/page.tsx`, if `linkRes` is not OK in the `generateMetadata` function, the desired behavior is to use the standard title and preview image without throwing an error.

Applied to files:

  • src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2024-10-08T20:13:42.967Z
Learnt from: Hugo0
PR: peanutprotocol/peanut-ui#422
File: src/components/Request/Pay/Pay.tsx:113-123
Timestamp: 2024-10-08T20:13:42.967Z
Learning: In the `PayRequestLink` component (`src/components/Request/Pay/Pay.tsx`), when resolving ENS names, handle errors by displaying an appropriate error message to the user if the ENS cannot be resolved.

Applied to files:

  • src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2025-09-08T03:11:00.114Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#1190
File: src/app/(mobile-ui)/qr-pay/page.tsx:31-41
Timestamp: 2025-09-08T03:11:00.114Z
Learning: In QR payment flows, the `createPayment` API call for payment tracking can be non-awaited when called after successful transaction execution, as it's a non-blocking logging operation and doesn't affect the user's success state.

Applied to files:

  • src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2024-10-22T18:11:36.864Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#469
File: src/app/request/pay/page.tsx:32-49
Timestamp: 2024-10-22T18:11:36.864Z
Learning: In `src/app/request/pay/page.tsx`, the `id` parameter is accessed via `searchParams.id` in the `generateMetadata` function.

Applied to files:

  • src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2025-09-05T07:31:11.396Z
Learnt from: Zishan-7
PR: peanutprotocol/peanut-ui#1185
File: src/components/Claim/useClaimLink.tsx:14-0
Timestamp: 2025-09-05T07:31:11.396Z
Learning: In the peanut-ui codebase, `window.history.replaceState` is preferred over `router.replace` when immediate/synchronous URL parameter updates are required, as `router.replace` is asynchronous and doesn't guarantee instant URL changes that subsequent code can rely on. This pattern is used consistently across usePaymentInitiator.ts, Confirm.payment.view.tsx, and useClaimLink.tsx.

Applied to files:

  • src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2024-12-11T10:13:22.806Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#564
File: src/components/Request/Pay/Views/Initial.view.tsx:430-430
Timestamp: 2024-12-11T10:13:22.806Z
Learning: In the React TypeScript file `src/components/Request/Pay/Views/Initial.view.tsx`, when reviewing the `InitialView` component, do not flag potential issues with using non-null assertion `!` on the `slippagePercentage` variable, as handling undefined values in this context is considered out of scope.

Applied to files:

  • src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2024-10-07T13:42:00.443Z
Learnt from: Hugo0
PR: peanutprotocol/peanut-ui#422
File: src/components/Request/Pay/Pay.tsx:103-111
Timestamp: 2024-10-07T13:42:00.443Z
Learning: When the token price cannot be fetched in `src/components/Request/Pay/Pay.tsx` within the `PayRequestLink` component, set `tokenPriceData.price` to 0 to ensure the UI remains functional. Since Squid uses their own price engine for x-chain fulfillment transactions, this approach will not affect the transaction computation.

Applied to files:

  • src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2025-09-11T17:46:12.507Z
Learnt from: Hugo0
PR: peanutprotocol/peanut-ui#1200
File: src/app/(mobile-ui)/recover-funds/page.tsx:9-9
Timestamp: 2025-09-11T17:46:12.507Z
Learning: Functions in Next.js that are not marked with "use server" and contain secrets are unsafe to import in client components, as they get bundled into the client JavaScript and can leak environment variables to the browser.

Applied to files:

  • src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2025-08-12T17:47:28.362Z
Learnt from: Zishan-7
PR: peanutprotocol/peanut-ui#1089
File: src/app/api/bridge/exchange-rate/route.ts:4-19
Timestamp: 2025-08-12T17:47:28.362Z
Learning: In the Bridge exchange rate API route (src/app/api/bridge/exchange-rate/route.ts), the ExchangeRateResponse interface uses numeric types for rates because the route converts string values from the Bridge API to floats using parseFloat() before returning the response.

Applied to files:

  • src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2024-10-29T12:19:41.968Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#495
File: src/components/Global/TokenAmountInput/index.tsx:23-30
Timestamp: 2024-10-29T12:19:41.968Z
Learning: In the `TokenAmountInput` component (`src/components/Global/TokenAmountInput/index.tsx`), when the 'Max' button is clicked, we intentionally set the input denomination to 'TOKEN' because we are setting the value as token.

Applied to files:

  • src/components/Global/TokenAmountInput/index.tsx
📚 Learning: 2025-08-22T07:25:59.304Z
Learnt from: Zishan-7
PR: peanutprotocol/peanut-ui#1104
File: src/components/Payment/PaymentForm/index.tsx:596-600
Timestamp: 2025-08-22T07:25:59.304Z
Learning: The `TokenAmountInput` component in `src/components/Global/TokenAmountInput/` always returns decimal strings (e.g., "1,234.56"), not base units. When passing these values to external APIs like Daimo's `toUnits` prop, simply stripping commas with `.replace(/,/g, '')` is sufficient.

Applied to files:

  • src/components/Global/TokenAmountInput/index.tsx
📚 Learning: 2024-10-29T12:20:47.207Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#495
File: src/components/Create/Link/Input.view.tsx:244-248
Timestamp: 2024-10-29T12:20:47.207Z
Learning: In the `TokenAmountInput` component within `src/components/Global/TokenAmountInput/index.tsx`, when `balance` is undefined, the `maxValue` prop should be set to an empty string `''`.

Applied to files:

  • src/components/Global/TokenAmountInput/index.tsx
📚 Learning: 2024-10-07T15:25:45.170Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#422
File: src/components/Request/Pay/Views/Initial.view.tsx:76-78
Timestamp: 2024-10-07T15:25:45.170Z
Learning: In `src/components/Request/Pay/Views/Initial.view.tsx`, both `txFee` and `utils.formatTokenAmount(...)` return strings, ensuring that `calculatedFee` consistently returns a string without the need for additional type conversion.

Applied to files:

  • src/components/Global/TokenAmountInput/index.tsx
📚 Learning: 2024-10-07T15:28:25.280Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#422
File: src/components/Request/Pay/Views/Initial.view.tsx:76-78
Timestamp: 2024-10-07T15:28:25.280Z
Learning: In `src/components/Request/Pay/Views/Initial.view.tsx`, both `txFee` and `utils.formatTokenAmount(estimatedGasCost, 3)` return strings, ensuring consistent return types for `calculatedFee`.

Applied to files:

  • src/components/Global/TokenAmountInput/index.tsx
📚 Learning: 2024-12-02T17:21:45.515Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#551
File: src/context/walletContext/walletContext.tsx:87-88
Timestamp: 2024-12-02T17:21:45.515Z
Learning: When converting `totalBalance` (in USD) to a `BigInt` balance in `src/context/walletContext/walletContext.tsx`, multiplying by `1e6` is intentional to maintain compatibility with USDC's 6 decimal places. The application displays only 2 decimal places, so this level of precision is sufficient.

Applied to files:

  • src/components/Global/TokenAmountInput/index.tsx
🧬 Code graph analysis (3)
src/app/(mobile-ui)/qr-pay/page.tsx (10)
src/hooks/wallet/useWallet.ts (1)
  • useWallet (21-123)
src/services/manteca.ts (3)
  • QrPaymentLock (45-61)
  • QrPayment (11-31)
  • mantecaApi (89-145)
src/hooks/useTransactionDetailsDrawer.ts (1)
  • useTransactionDetailsDrawer (3-22)
src/components/Global/DirectSendQR/utils.ts (1)
  • isPaymentProcessorQR (100-107)
src/app/actions/currency.ts (1)
  • getCurrencyPrice (7-62)
src/utils/general.utils.ts (1)
  • isTxReverted (1303-1306)
src/constants/zerodev.consts.ts (1)
  • PEANUT_WALLET_TOKEN_DECIMALS (19-19)
src/components/Global/PeanutLoading/index.tsx (1)
  • PeanutLoading (4-19)
src/components/TransactionDetails/TransactionDetailsDrawer.tsx (1)
  • TransactionDetailsDrawer (19-63)
src/components/Payment/PaymentInfoRow.tsx (1)
  • PaymentInfoRow (7-81)
src/components/Global/TokenAmountInput/index.tsx (2)
src/utils/general.utils.ts (1)
  • formatAmountWithoutComma (476-482)
src/components/Global/Icons/Icon.tsx (1)
  • Icon (196-205)
src/components/TransactionDetails/TransactionDetailsReceipt.tsx (3)
src/utils/general.utils.ts (1)
  • formatAmount (400-440)
src/components/Payment/PaymentInfoRow.tsx (1)
  • PaymentInfoRow (7-81)
src/utils/currency.ts (1)
  • getDisplayCurrencySymbol (4-8)
🔇 Additional comments (9)
src/components/Global/DirectSendQR/utils.ts (2)

53-53: Pattern broadening looks good for PIX QR detection.

The regex pattern update from "00020126" to "000201" provides broader compatibility with PIX QR code variations while maintaining the required structural validation.


55-107: Clean abstraction for payment processor QR codes.

The addition of PAYMENT_PROCESSOR_REGEXES mapping and isPaymentProcessorQR helper function provides a clean separation of concerns, making it easy to identify payment processor QRs independently from the general recognition logic.

src/components/TransactionDetails/TransactionDetailsReceipt.tsx (2)

120-124: Good extension for QR payment exchange rate visibility.

Adding qr_payment to the exchange rate visibility condition is consistent with the new payment flow requirements.


754-754: Nice touch with the sponsored fee message!

The change from "$ 0" to "Sponsored by Peanut!" provides better user communication about fee sponsorship.

src/components/Global/TokenAmountInput/index.tsx (2)

128-130: Conversion UI logic is properly gated.

The showConversion memoization correctly handles the visibility logic for the conversion UI elements.


203-269: UI restructuring improves mobile UX.

The vertical layout with larger text (text-6xl) and centered alignment provides better touch targets and readability on mobile devices. The conversion toggle positioning is well-placed.

src/app/(mobile-ui)/qr-pay/page.tsx (3)

70-73: Good defensive check for QR code validity.

The validation using isPaymentProcessorQR provides proper error handling for invalid QR codes.


196-204: Payment amount validation implemented correctly.

Good implementation of both maximum payment limit ($200) and balance checks with clear error messages.


104-108: Extract the sell price correctly from currency price object.

The getCurrencyPrice returns { buy: number; sell: number }, so accessing .sell is needed.

The code should properly extract the sell price:

             if (paymentLock.code === '') {
-                price = (await getCurrencyPrice(currencyCode)).sell
+                const priceData = await getCurrencyPrice(currencyCode)
+                price = priceData.sell
             } else {

Likely an incorrect or invalid review comment.

Comment on lines +29 to +30
const MANTECA_DEPOSIT_ADDRESS = '0x959e088a09f61aB01cb83b0eBCc74b2CF6d62053'
const MAX_QR_PAYMENT_AMOUNT = '200'
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Hardcoded deposit address needs configuration management.

The MANTECA_DEPOSIT_ADDRESS is hardcoded. This should be managed through environment variables for different environments (dev/staging/prod).

Consider moving to environment configuration:

-const MANTECA_DEPOSIT_ADDRESS = '0x959e088a09f61aB01cb83b0eBCc74b2CF6d62053'
+const MANTECA_DEPOSIT_ADDRESS = process.env.NEXT_PUBLIC_MANTECA_DEPOSIT_ADDRESS || '0x959e088a09f61aB01cb83b0eBCc74b2CF6d62053'
📝 Committable suggestion

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

Suggested change
const MANTECA_DEPOSIT_ADDRESS = '0x959e088a09f61aB01cb83b0eBCc74b2CF6d62053'
const MAX_QR_PAYMENT_AMOUNT = '200'
const MANTECA_DEPOSIT_ADDRESS = process.env.NEXT_PUBLIC_MANTECA_DEPOSIT_ADDRESS || '0x959e088a09f61aB01cb83b0eBCc74b2CF6d62053'
const MAX_QR_PAYMENT_AMOUNT = '200'
🤖 Prompt for AI Agents
In src/app/(mobile-ui)/qr-pay/page.tsx around lines 29-30, the
MANTECA_DEPOSIT_ADDRESS is hardcoded; replace it with a configurable environment
variable (e.g. use process.env.NEXT_PUBLIC_MANTECA_DEPOSIT_ADDRESS for
client-side code or process.env.MANTECA_DEPOSIT_ADDRESS for server-only), add a
runtime check that validates the env value (log or throw a clear error if
missing/invalid), add the new variable to your .env/.env.local and to
environment configuration for staging/production, and update any
README/deployment docs to instruct setting this env var.

originalUserRole: EHistoryUserRole.SENDER,
avatarUrl: methodIcon,
receipt: {
exchange_rate: currency.price.toString(),
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Type mismatch: price is an object, not a number.

Based on the getCurrencyPrice return type { buy: number; sell: number }, currency.price is an object, but you're converting it to string. This should use currency.price.sell or store the sell price separately.

Apply this diff to fix the type issue:

-                                        exchange_rate: currency.price.toString(),
+                                        exchange_rate: currency.price.sell.toString(),
📝 Committable suggestion

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

Suggested change
exchange_rate: currency.price.toString(),
exchange_rate: currency.price.sell.toString(),
🤖 Prompt for AI Agents
In src/app/(mobile-ui)/qr-pay/page.tsx around line 295, the code sets
exchange_rate: currency.price.toString() but currency.price is an object ({ buy:
number; sell: number }), causing a type mismatch; replace usage with the numeric
sell price (e.g., exchange_rate: currency.price.sell.toString() or better:
exchange_rate: String(currency.price.sell)) or alternatively store
currency.price.sell in a separate variable and use that where a string is
required, ensuring the value is the sell number converted to string.

<Card className="space-y-0 px-4">
<PaymentInfoRow
label="Exchange Rate"
value={`1 USD = ${currency.price} ${currency.code.toUpperCase()}`}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Exchange rate display uses price incorrectly.

The currency.price is used directly but should access the specific buy/sell value based on the context.

Apply this diff:

-                        value={`1 USD = ${currency.price} ${currency.code.toUpperCase()}`}
+                        value={`1 USD = ${currency.price.sell || currency.price} ${currency.code.toUpperCase()}`}
📝 Committable suggestion

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

Suggested change
value={`1 USD = ${currency.price} ${currency.code.toUpperCase()}`}
value={`1 USD = ${currency.price.sell || currency.price} ${currency.code.toUpperCase()}`}
🤖 Prompt for AI Agents
In src/app/(mobile-ui)/qr-pay/page.tsx around line 353, the code uses
currency.price directly for the exchange-rate display; update it to reference
the specific buy/sell rate instead (e.g., currency.buyPrice or
currency.sellPrice) based on the current transaction context (use the existing
flag/prop like isBuying/isSelling or transactionType to pick the correct field),
and format the number for display (e.g., toFixed(2)) before interpolating into
the string so it reads "1 USD = <buy|sell> <CURRENCY_CODE>".

/**
* Returns true if the given string is a payment processor QR code.
* For example, Mercado Pago, Pix, etc.
*/
Copy link
Contributor

Choose a reason for hiding this comment

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

thx

@jjramirezn jjramirezn merged commit 8b00772 into feat/manteca-integration Sep 15, 2025
4 checks passed
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.

2 participants