Skip to content

feat: limits cards in payment flows#1621

Merged
kushagrasarathe merged 9 commits intofeat/limitsfrom
feat/limits-integration
Jan 20, 2026
Merged

feat: limits cards in payment flows#1621
kushagrasarathe merged 9 commits intofeat/limitsfrom
feat/limits-integration

Conversation

@kushagrasarathe
Copy link
Contributor

  • contributes to TASK-17858
  • also contributes to TASK-17860
  • also contributes to TASK-17859
image Screenshot 2026-01-15 at 4 31 28 PM Screenshot 2026-01-15 at 4 34 41 PM

@vercel
Copy link

vercel bot commented Jan 15, 2026

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

Project Deployment Review Updated (UTC)
peanut-wallet Ready Ready Preview, Comment Jan 20, 2026 8:57am

Request Review

@notion-workspace
Copy link

@notion-workspace
Copy link

@notion-workspace
Copy link

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 15, 2026

Walkthrough

Adds a centralized limits system: new useLimitsValidation hook, consolidated limits utilities and QR-country derivation, LimitsWarningCard UI, flag/currency helpers, integration of limits checks into add-money, qr-pay, and withdraw flows, removal of legacy limits.utils, and a small icon addition.

Changes

Cohort / File(s) Summary
Limits core & utils
src/features/limits/hooks/useLimitsValidation.ts, src/features/limits/utils.ts, src/features/limits/consts.ts
New hook useLimitsValidation, consolidated utilities (currency/flag/format helpers, LIMITS_COPY, getLimitsWarningCardProps, provider routing), and QR-country derivation. Replaces removed utils/limits.utils.ts. Review validation logic, exports, and currency/QR derivation.
Limits UI components
src/features/limits/components/LimitsWarningCard.tsx, src/features/limits/components/LimitsProgressBar.tsx
New LimitsWarningCard component and import-path update in LimitsProgressBar. Check props, support modal integration, rendering of list/link items, and updated imports.
Add-money / Input UI
src/components/AddMoney/components/InputAmountStep.tsx, src/components/AddMoney/components/MantecaAddMoney.tsx, src/app/(mobile-ui)/add-money/[country]/bank/page.tsx
Threaded limitsValidation and limitsCurrency into input flows; render LimitsWarningCard conditionally; Continue button gating on limitsValidation.isBlocking; removed inline max deposit checks in favor of limits logic. Verify new props and callers.
QR-pay page
src/app/(mobile-ui)/qr-pay/page.tsx
Added limits check for qr-payment flow, KYC status integration, conditional LimitsWarningCard rendering, and pay-button disabling when limits block. Validate currency mapping and KYC interplay.
Withdraw flows
src/app/(mobile-ui)/withdraw/page.tsx, src/app/(mobile-ui)/withdraw/manteca/page.tsx
Integrates limitsValidation for offramp (non-crypto and manteca), shows limits card in amount input, disables Continue when blocking, keeps min checks (uses MIN_MANTECA_WITHDRAW_AMOUNT), and suppresses balance errors when blocking. Confirm gating and min-amount logic.
Limits pages & views
src/features/limits/views/*, src/app/(mobile-ui)/limits/[provider]/page.tsx
Views updated to use new utils; replaced hardcoded QR lists/formatters with centralized utilities and QR-country derivation; minor import reorders. Validate data sources and formatting.
Country / currency helpers
src/constants/countryCurrencyMapping.ts
Added getFlagUrl, getCurrencyFlagCode, getCurrencyFlagUrl, getCountryByCurrency. Ensure mappings and case-insensitive lookups align with limits utils.
Constants
src/constants/payment.consts.ts
Added MAX_MANTECA_WITHDRAW_AMOUNT = 2000 and MIN_MANTECA_WITHDRAW_AMOUNT = 1. Note: MAX may be superseded by limits validation.
Icon update
src/components/Global/Icons/Icon.tsx
Added new info-filled icon variant and mapping. Check consumers of IconName.
Removed legacy utils
src/features/limits/utils/limits.utils.ts
Deleted legacy helpers and constants (routing, color, thresholds). Ensure replacements in src/features/limits/utils.ts cover prior usages.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 76.19% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: limits cards in payment flows' accurately describes the main change: adding limit warning/error cards to multiple payment flow pages to validate and display transaction limits.
Description check ✅ Passed The description links to three related tasks and includes screenshots demonstrating the UI changes across payment flows, providing context for the limits card integration work.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/limits-integration

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

@kushagrasarathe
Copy link
Contributor Author

@coderabbitai review in depth.

Evaluate:

  • risk of introducing new bugs and breaking existing functionality
  • performance impact and latency
  • code quality and DRY

Also, write a very concise testing guide for QA

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 15, 2026

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash

# Get PR details and list of changed files
gh pr view 1621 --json files,additions,deletions,changedFiles --jq '.files[] | .path'

echo "---SEPARATOR---"

# Get the diff for the PR
gh pr diff 1621

Length of output: 43838


@kushagrasarathe I'll provide a comprehensive in-depth review of this PR.

🔴 Risk Assessment: MEDIUM-HIGH

Critical Risks

1. Loss of Max Amount Validation (HIGH)
In withdraw/manteca/page.tsx, the local max amount validation was removed:

- } else if (paymentAmount > parseUnits(MAX_MANTECA_WITHDRAW_AMOUNT.toString()...)) {
-     setError(`Deposit amount exceeds maximum limit of $${MAX_MANTECA_WITHDRAW_AMOUNT}`)

Now relies entirely on limitsValidation. If the limits hook fails to load or has bugs, users could bypass max limits.

2. Complex Conditional Logic (HIGH)
The useLimitsValidation hook has 3 different validation paths (manteca/bridge/foreign) with overlapping logic. Edge cases to watch:

  • Users with both manteca AND bridge KYC
  • Currency not in (ARS, BRL, USD) defaults to USD without validation
  • QR payments for local users with non-manteca processor

3. Error Message Hiding (MEDIUM)
Pattern used everywhere:

{error && !showLimitsCard && <ErrorAlert description={error} />}

If limitsValidation triggers but has incorrect data, it hides legitimate errors from other validations.

4. Tight Coupling in QR Pay (MEDIUM)

const isLocalUser = isUserMantecaKycApproved && paymentProcessor === 'MANTECA'

This couples user location to payment processor selection. Changes to processor logic could break limit validation.

5. Race Conditions (LOW-MEDIUM)
The hook calls useLimits() and useKycStatus() which may load asynchronously. Components use validation immediately but don't always check isLoading state before blocking users.

Breaking Scenarios to Test

  • User with pending KYC approval
  • User switches payment processor mid-flow
  • Limits API returns null/undefined
  • User in unsupported currency country
  • Amount exactly at limit boundaries (2000, 0.1, etc.)

⚡ Performance Analysis: LOW IMPACT

Positive

✅ Proper use of useMemo throughout (11 instances)
✅ No additional API calls (client-side validation)
✅ Validation hook result properly cached
✅ Conditional rendering prevents unnecessary component mounts

Concerns

⚠️ Recalculation frequency: Hook recalculates on every keystroke as amount changes. For fast typing, this triggers multiple useMemo recalculations including date math.

⚠️ Date calculation overhead: daysUntilMonthlyReset calculates on every render:

const daysUntilMonthlyReset = useMemo(() => {
    const now = new Date()
    const nextMonth = new Date(now.getFullYear(), now.getMonth() + 1, 1)
    const diffTime = nextMonth.getTime() - now.getTime()
    return Math.ceil(diffTime / (1000 * 60 * 60 * 24))
}, [])

Empty dependency array is good, but this could be computed once globally.

Verdict: Performance impact is negligible. The calculations are lightweight.


📊 Code Quality & DRY: NEEDS IMPROVEMENT

✅ Excellent Patterns

  1. Great Abstraction: useLimitsValidation hook centralizes complex logic
  2. Type Safety: Proper TypeScript with LimitFlowType, LimitCurrency types
  3. Reusable Component: LimitsWarningCard is well-parameterized
  4. Constants Extraction: Magic numbers moved to payment.consts.ts

❌ DRY Violations

1. Currency Mapping Duplicated (3 locations)

Repeated in withdraw/manteca/page.tsx, MantecaAddMoney.tsx, and qr-pay/page.tsx:

const limitsCurrency = useMemo<LimitCurrency>(() => {
    const currency = selectedCountry?.currency?.toUpperCase()
    if (currency === 'ARS' || currency === 'BRL') return currency as LimitCurrency
    return 'USD'
}, [selectedCountry?.currency])

Fix: Create utility utils/limits.utils.ts:

export const mapToLimitCurrency = (currency?: string): LimitCurrency => {
    const upper = currency?.toUpperCase()
    if (upper === 'ARS' || upper === 'BRL') return upper as LimitCurrency
    return 'USD'
}

2. Card Integration Code Duplicated (6 locations)

Every flow has nearly identical code:

{showLimitsCard && (
    <LimitsWarningCard
        type={limitsValidation.isBlocking ? 'error' : 'warning'}
        title={
            limitsValidation.isBlocking
                ? 'Amount too high, try a smaller amount.'
                : "You're close to your limit."
        }
        items={[...]} // Similar but slightly different
    />
)}

Fix: Create wrapper hook or component:

export function useLimitsCardProps(validation: LimitValidationResult, currency: string) {
    return useMemo(() => ({
        type: validation.isBlocking ? 'error' : 'warning',
        title: validation.isBlocking ? 'Amount too high...' : "You're close...",
        items: [/* construct items */]
    }), [validation, currency])
}

3. Magic Strings (not constants)

  • "Amount too high, try a smaller amount."
  • "You're close to your limit."
  • "Check my limits."

Should be in constants file.

4. Button Disable Logic Pattern
Repeated across files:

disabled={
    !parseFloat(rawTokenAmount) ||
    parseFloat(rawTokenAmount) < minimumAmount ||
    error.showError ||
    isCreatingOnramp ||
    limitsValidation.isBlocking
}

5. Error Conditional Pattern

{error.showError && !!error.errorMessage && !showLimitsCard && (
    <ErrorAlert description={error.errorMessage} />
)}

Minor Issues

  • WARNING_THRESHOLD_PERCENT = 30 should be exported constant
  • Some flows check daysUntilReset, others don't (inconsistent UX)
  • Mix of $${formatExtendedNumber(...)} and ${formatExtendedNumber(...)} ${currency} patterns

🧪 QA Testing Guide

Test Matrix

Setup Required

  • User accounts for each KYC state: None, Manteca-only, Bridge-only, Both
  • Test in countries: Argentina (ARS), Brazil (BRL), USA (USD), Mexico (MXN)

1. Onramp (Add Money) - Bridge Flow

Path: /add-money/[country]/bank

Test Case Amount Expected Behavior
Below min $0.50 Error: minimum amount
Within limits $100 No limit card shown
Near limit 80% of remaining ⚠️ Yellow warning card
Exceeds limit Above per-tx max 🔴 Red error card, button disabled
At exact limit Exactly at limit Warning or blocking (verify boundary)

Verify:

  • Card shows correct remaining limit
  • "Check my limits" link navigates to /limits
  • Continue button disabled when blocking
  • Error alert hidden when limit card shown

2. Onramp - Manteca Flow (ARS/BRL)

Path: /add-money (Argentina/Brazil)

Test Case Amount (ARS/BRL) Expected
Below $1 USD equivalent < min Error: min $1
$1500 USD equivalent Valid No card (under $2000 max)
Near monthly limit Within 30% of remaining ⚠️ Warning with days until reset
Exceeds per-tx max > $2000 USD 🔴 "Maximum deposit is $2000 per transaction"
Exceeds monthly remaining > available monthly 🔴 "Amount exceeds remaining limit" + reset days

Verify:

  • Currency shown in local currency (ARS/BRL)
  • Days until reset displayed (1st of next month)
  • Support link shown for Manteca users

3. QR Payments

Path: /qr-pay

Foreign User (No Manteca KYC)

Amount Expected
$100 No card
$1900 No card/warning
$2001 🔴 "QR payment limit is $2,000 per transaction"

Local User (Manteca KYC + PIX/MercadoPago)

Amount Expected
Within monthly limit No card
Near limit (70%+ used) ⚠️ Warning with remaining in BRL/ARS
Exceeds monthly 🔴 Blocked with days until reset

Verify:

  • Correct currency displayed (USD for foreign, BRL/ARS for local)
  • Balance error hidden when limit card shown
  • Payment button disabled when blocking
  • Points card still shows (not affected by limits)

4. Offramp (Withdraw) - Manteca

Path: /withdraw/manteca

Amount Expected
$0.50 Error: min $1
$500 No limit card
Near monthly limit ⚠️ Warning
$2001 🔴 "Maximum withdrawal is $2000 per transaction"
Exceeds balance Balance error (not limit card)
Exceeds monthly limit 🔴 Blocked with reset days

Critical: Verify max limit still enforced (was removed from local validation)

5. Offramp (Withdraw) - Generic Flow

Path: /withdraw

For Bank Withdrawal:

Amount Expected
Below min Min error
Valid amount No card for crypto, limit card for bank
Exceeds limit 🔴 Blocked only for bank withdrawals

For Crypto Withdrawal:

Amount Expected
Any amount No limit card (crypto has no fiat limits)

Verify:

  • Limits only apply when selectedMethod?.type !== 'crypto'
  • Crypto withdrawals unaffected

6. Edge Cases (ALL FLOWS)

  • Loading state: Test with slow network - verify button not blocked incorrectly
  • Zero amount: Should not show warning card
  • Rapid typing: Type quickly, verify no UI flicker or race conditions
  • Decimal precision: Test 0.10000 vs 0.1 formatting
  • Currency switch: Change country mid-flow (if applicable)
  • KYC state change: Simulate KYC approval via WebSocket
  • Limits API failure: Mock API returning null/error
  • Multiple errors: Verify precedence (limit card > balance error > other errors)
  • Browser back: Navigate back, verify state persists correctly
  • Deep linking: Direct URL with amount in params

7. Visual/UX Testing

  • Warning card is yellow with info-filled icon
  • Error card is red with same icon
  • "Check my limits" link has external-link icon
  • Support link only shown for Manteca users
  • Proper spacing between card and other elements
  • Mobile responsive (cards fit screen)
  • Text readable at all sizes

8. Accessibility

  • Card content announced by screen readers
  • Links are keyboard navigable
  • Error states have proper ARIA labels
  • Color contrast meets WCAG (red error text)

📋 Recommendations

Before Merge

  1. Add fallback validation: Keep max amount checks in local validation as backup
  2. Handle loading state: Disable buttons while limitsValidation.isLoading === true
  3. Test edge cases: Especially currency mapping for unsupported currencies

Follow-up Refactoring (Optional)

  1. Extract currency mapping utility
  2. Create useLimitsCard hook to reduce duplication
  3. Move magic strings to constants
  4. Consider making warning threshold configurable per flow

Would you like me to elaborate on any specific risk area or create additional test scenarios?


🧠 Learnings used
Learnt from: Hugo0
Repo: peanutprotocol/peanut-ui PR: 852
File: src/hooks/useTransactionHistory.ts:177-239
Timestamp: 2025-05-14T21:16:06.369Z
Learning: When reviewing code, pay attention to and resolve CodeRabbit comments as they can contain useful refactoring suggestions and insights.

Learnt from: Hugo0
Repo: peanutprotocol/peanut-ui PR: 594
File: src/components/Request/Pay/Views/Initial.view.tsx:188-213
Timestamp: 2024-12-31T15:08:15.640Z
Learning: Always resolve coderabbit comments when reviewing code, since they can contain useful suggestions.
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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

🤖 Fix all issues with AI agents
In `@src/app/`(mobile-ui)/withdraw/manteca/page.tsx:
- Line 483: The button's disabled condition must also prevent clicks while
limits are still loading; update the expression that sets disabled (currently
referencing usdAmount, balanceErrorMessage, and limitsValidation.isBlocking) to
include limitsValidation.isLoading so the button is disabled when
limitsValidation.isLoading is true (e.g., add a clause combining
limitsValidation.isLoading with the existing conditions).

In `@src/app/`(mobile-ui)/withdraw/page.tsx:
- Around line 327-329: The continue button's disabled prop only checks
limitsValidation.isBlocking for non-crypto methods and can be clicked while
limits validation is still loading; update the disabled expression used where
disabled={isContinueDisabled || (selectedMethod?.type !== 'crypto' &&
limitsValidation.isBlocking)} to also include limitsValidation.isLoading for
non-crypto methods (e.g. disabled={isContinueDisabled || (selectedMethod?.type
!== 'crypto' && (limitsValidation.isBlocking || limitsValidation.isLoading))})
so the button is disabled while validation is loading or blocking.

In `@src/features/limits/components/LimitsWarningCard.tsx`:
- Around line 40-47: LimitsWarningCard renders InfoCard with a redundant ternary
for the variant prop (type === 'error' ? 'warning' : 'warning'), so update the
InfoCard call in the LimitsWarningCard component to either use a fixed variant
(variant="warning") if errors should look the same, or change the ternary to
return an appropriate error variant (e.g., variant={type === 'error' ? 'error' :
'warning'}) so the 'type' prop actually affects the InfoCard styling; adjust
only the variant prop on the InfoCard invocation.
🧹 Nitpick comments (4)
src/features/limits/components/LimitsWarningCard.tsx (1)

51-64: Consider: Link items use hardcoded yellow color even for error type.

The link text and icon use text-yellow-11 regardless of whether type is 'error' or 'warning'. If blocking errors should have distinct styling (e.g., red tones), consider conditionally applying color classes based on type.

If the unified yellow styling is intentional for consistency, this is fine as-is.

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

393-411: Consider using mapToLimitCurrency utility for consistency.

The limitsCurrency logic duplicates the mapping that exists in mapToLimitCurrency from limits.utils.ts. While the QR-specific mapping is slightly different (it maps by QR type rather than currency string), consider whether this could be unified to reduce duplication.

Current approach works correctly, but note the mapToLimitCurrency utility is available if the mapping logic needs to be shared.

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

17-22: Consider extracting LimitsValidationProps to a shared location.

This interface duplicates fields from the useLimitsValidation hook's return type. While prop isolation is reasonable, consider exporting a subset type from the hook file to ensure consistency.

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

334-337: Minor: Consider using showLimitsCard for consistency.

The error suppression uses !limitsValidation.isBlocking directly, while the card display uses the showLimitsCard variable which already accounts for the crypto exclusion. Using showLimitsCard would make the logic more self-documenting.

Optional refactor for consistency
-                    {error.showError && !!error.errorMessage && !limitsValidation.isBlocking && (
+                    {error.showError && !!error.errorMessage && !(showLimitsCard && limitsValidation.isBlocking) && (
                         <ErrorAlert description={error.errorMessage} />
                     )}

Or more simply, since showLimitsCard already implies blocking/warning for non-crypto:

-                    {error.showError && !!error.errorMessage && !limitsValidation.isBlocking && (
+                    {error.showError && !!error.errorMessage && !(showLimitsCard && limitsValidation.isBlocking) && (
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3bf6517 and 2170000.

📒 Files selected for processing (8)
  • src/app/(mobile-ui)/add-money/[country]/bank/page.tsx
  • src/app/(mobile-ui)/qr-pay/page.tsx
  • src/app/(mobile-ui)/withdraw/manteca/page.tsx
  • src/app/(mobile-ui)/withdraw/page.tsx
  • src/components/AddMoney/components/InputAmountStep.tsx
  • src/components/AddMoney/components/MantecaAddMoney.tsx
  • src/features/limits/components/LimitsWarningCard.tsx
  • src/features/limits/utils/limits.utils.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/AddMoney/components/MantecaAddMoney.tsx
🧰 Additional context used
🧠 Learnings (31)
📓 Common learnings
Learnt from: Hugo0
Repo: peanutprotocol/peanut-ui PR: 420
File: src/components/Offramp/Offramp.consts.ts:27-28
Timestamp: 2024-10-08T20:13:42.967Z
Learning: In `src/components/Offramp/Offramp.consts.ts`, the `MIN_CASHOUT_LIMIT` is set to $10 because smaller amounts are impractical due to approximately $1 fee per cashout.
📚 Learning: 2025-08-14T14:42:54.411Z
Learnt from: Zishan-7
Repo: peanutprotocol/peanut-ui PR: 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/features/limits/utils/limits.utils.ts
  • src/app/(mobile-ui)/add-money/[country]/bank/page.tsx
📚 Learning: 2024-10-08T20:13:42.967Z
Learnt from: Hugo0
Repo: peanutprotocol/peanut-ui PR: 420
File: src/components/Offramp/Offramp.consts.ts:27-28
Timestamp: 2024-10-08T20:13:42.967Z
Learning: In `src/components/Offramp/Offramp.consts.ts`, the `MIN_CASHOUT_LIMIT` is set to $10 because smaller amounts are impractical due to approximately $1 fee per cashout.

Applied to files:

  • src/features/limits/utils/limits.utils.ts
  • src/app/(mobile-ui)/withdraw/page.tsx
  • src/app/(mobile-ui)/withdraw/manteca/page.tsx
  • src/app/(mobile-ui)/add-money/[country]/bank/page.tsx
📚 Learning: 2024-10-29T12:19:41.968Z
Learnt from: jjramirezn
Repo: peanutprotocol/peanut-ui PR: 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
  • src/app/(mobile-ui)/withdraw/page.tsx
  • src/app/(mobile-ui)/withdraw/manteca/page.tsx
  • src/app/(mobile-ui)/add-money/[country]/bank/page.tsx
📚 Learning: 2025-10-24T13:44:39.473Z
Learnt from: Zishan-7
Repo: peanutprotocol/peanut-ui PR: 1332
File: src/components/Global/TokenAmountInput/index.tsx:141-150
Timestamp: 2025-10-24T13:44:39.473Z
Learning: In the `TokenAmountInput` component (`src/components/Global/TokenAmountInput/index.tsx`), the slider feature (controlled by `showSlider` prop) is only shown for USD input mode. When the slider is used with `maxAmount`, the `selectedAmount` is computed in USD and `isInputUsd` is always `true`, so the conversion in `onChange` handles it correctly.

Applied to files:

  • src/components/AddMoney/components/InputAmountStep.tsx
  • src/app/(mobile-ui)/withdraw/page.tsx
  • src/app/(mobile-ui)/add-money/[country]/bank/page.tsx
📚 Learning: 2024-10-29T12:20:47.207Z
Learnt from: jjramirezn
Repo: peanutprotocol/peanut-ui PR: 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
  • src/app/(mobile-ui)/withdraw/page.tsx
📚 Learning: 2024-10-25T11:33:46.776Z
Learnt from: jjramirezn
Repo: peanutprotocol/peanut-ui PR: 484
File: src/components/Cashout/Components/Initial.view.tsx:273-274
Timestamp: 2024-10-25T11:33:46.776Z
Learning: In the `InitialCashoutView` component (`src/components/Cashout/Components/Initial.view.tsx`), linked bank accounts should not generate error states, and the `ValidatedInput` component will clear any error messages if needed. Therefore, it's unnecessary to manually clear the error state when selecting or clearing linked bank accounts.

Applied to files:

  • src/components/AddMoney/components/InputAmountStep.tsx
  • src/app/(mobile-ui)/withdraw/page.tsx
  • src/app/(mobile-ui)/withdraw/manteca/page.tsx
  • src/app/(mobile-ui)/qr-pay/page.tsx
  • src/app/(mobile-ui)/add-money/[country]/bank/page.tsx
📚 Learning: 2025-11-06T08:19:05.201Z
Learnt from: Zishan-7
Repo: peanutprotocol/peanut-ui PR: 1407
File: src/components/AddWithdraw/DynamicBankAccountForm.tsx:459-475
Timestamp: 2025-11-06T08:19:05.201Z
Learning: In src/components/AddWithdraw/DynamicBankAccountForm.tsx, the street address field has a 35-character maximum length limit because the backend provider enforces this constraint, applicable to both US and Mexican addresses.

Applied to files:

  • src/components/AddMoney/components/InputAmountStep.tsx
  • src/app/(mobile-ui)/withdraw/page.tsx
  • src/app/(mobile-ui)/withdraw/manteca/page.tsx
  • src/app/(mobile-ui)/add-money/[country]/bank/page.tsx
📚 Learning: 2024-12-11T10:13:22.806Z
Learnt from: jjramirezn
Repo: peanutprotocol/peanut-ui PR: 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
📚 Learning: 2025-08-22T07:25:59.304Z
Learnt from: Zishan-7
Repo: peanutprotocol/peanut-ui PR: 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
  • src/app/(mobile-ui)/withdraw/page.tsx
📚 Learning: 2025-10-29T11:27:59.248Z
Learnt from: Zishan-7
Repo: peanutprotocol/peanut-ui PR: 1368
File: src/components/Common/ActionList.tsx:109-111
Timestamp: 2025-10-29T11:27:59.248Z
Learning: In `src/components/Common/ActionList.tsx`, the `balance` from `useWallet()` hook is always in USDC (as a formatted string), making it directly comparable to USD amounts without conversion. The comparison `Number(balance) >= amountInUsd` is intentional and correct.

Applied to files:

  • src/components/AddMoney/components/InputAmountStep.tsx
  • src/app/(mobile-ui)/withdraw/page.tsx
  • src/app/(mobile-ui)/withdraw/manteca/page.tsx
  • src/app/(mobile-ui)/qr-pay/page.tsx
  • src/app/(mobile-ui)/add-money/[country]/bank/page.tsx
📚 Learning: 2025-11-07T11:51:58.900Z
Learnt from: Zishan-7
Repo: peanutprotocol/peanut-ui PR: 1419
File: src/components/Global/BalanceWarningModal/index.tsx:94-94
Timestamp: 2025-11-07T11:51:58.900Z
Learning: In `src/components/Global/BalanceWarningModal/index.tsx`, the casual and congratulatory phrase "You're rich! Congrats on having a high balance." is intentional and by design.

Applied to files:

  • src/components/AddMoney/components/InputAmountStep.tsx
  • src/app/(mobile-ui)/withdraw/page.tsx
  • src/app/(mobile-ui)/withdraw/manteca/page.tsx
  • src/features/limits/components/LimitsWarningCard.tsx
  • src/app/(mobile-ui)/qr-pay/page.tsx
  • src/app/(mobile-ui)/add-money/[country]/bank/page.tsx
📚 Learning: 2025-08-26T15:25:53.328Z
Learnt from: Zishan-7
Repo: peanutprotocol/peanut-ui PR: 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/AddMoney/components/InputAmountStep.tsx
  • src/app/(mobile-ui)/withdraw/page.tsx
  • src/app/(mobile-ui)/withdraw/manteca/page.tsx
  • src/app/(mobile-ui)/qr-pay/page.tsx
  • src/app/(mobile-ui)/add-money/[country]/bank/page.tsx
📚 Learning: 2024-10-23T09:38:27.670Z
Learnt from: jjramirezn
Repo: peanutprotocol/peanut-ui PR: 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/components/AddMoney/components/InputAmountStep.tsx
  • src/app/(mobile-ui)/withdraw/page.tsx
  • src/app/(mobile-ui)/add-money/[country]/bank/page.tsx
📚 Learning: 2025-09-18T09:30:42.901Z
Learnt from: Zishan-7
Repo: peanutprotocol/peanut-ui PR: 1230
File: src/app/(mobile-ui)/withdraw/page.tsx:92-97
Timestamp: 2025-09-18T09:30:42.901Z
Learning: In src/app/(mobile-ui)/withdraw/page.tsx, the useEffect that calls setShowAllWithdrawMethods(true) when amountFromContext exists is intentionally designed to run only on component mount (empty dependency array), not when amountFromContext changes. This is the correct behavior for the withdraw flow where showing all methods should only happen on initial load when an amount is already present.

Applied to files:

  • src/app/(mobile-ui)/withdraw/page.tsx
  • src/app/(mobile-ui)/withdraw/manteca/page.tsx
  • src/app/(mobile-ui)/qr-pay/page.tsx
  • src/app/(mobile-ui)/add-money/[country]/bank/page.tsx
📚 Learning: 2025-05-22T15:38:48.586Z
Learnt from: kushagrasarathe
Repo: peanutprotocol/peanut-ui PR: 869
File: src/app/(mobile-ui)/withdraw/page.tsx:82-88
Timestamp: 2025-05-22T15:38:48.586Z
Learning: The country-specific withdrawal route exists at src/app/(mobile-ui)/withdraw/[...country]/page.tsx and renders the AddWithdrawCountriesList component with flow="withdraw".

Applied to files:

  • src/app/(mobile-ui)/withdraw/page.tsx
  • src/app/(mobile-ui)/withdraw/manteca/page.tsx
  • src/app/(mobile-ui)/qr-pay/page.tsx
  • src/app/(mobile-ui)/add-money/[country]/bank/page.tsx
📚 Learning: 2025-10-02T15:23:01.513Z
Learnt from: Zishan-7
Repo: peanutprotocol/peanut-ui PR: 1266
File: src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx:46-57
Timestamp: 2025-10-02T15:23:01.513Z
Learning: In the withdraw flow at src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx, the points calculation query intentionally uses crypto.randomUUID() in the queryKey dependency array to bypass React Query caching, ensuring fresh points estimates on every render. This is the intended behavior.

Applied to files:

  • src/app/(mobile-ui)/withdraw/page.tsx
  • src/app/(mobile-ui)/withdraw/manteca/page.tsx
  • src/app/(mobile-ui)/qr-pay/page.tsx
  • src/app/(mobile-ui)/add-money/[country]/bank/page.tsx
📚 Learning: 2024-10-07T15:25:45.170Z
Learnt from: jjramirezn
Repo: peanutprotocol/peanut-ui PR: 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/app/(mobile-ui)/withdraw/page.tsx
  • src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2026-01-02T13:14:32.635Z
Learnt from: Hugo0
Repo: peanutprotocol/peanut-ui PR: 0
File: :0-0
Timestamp: 2026-01-02T13:14:32.635Z
Learning: In the peanut-ui codebase, the add-money flows (bank and Manteca) use nuqs library for URL-based state management (step, amount, currency via useQueryStates), while RequestFulfillmentFlowContext still uses enum-based context state. This creates intentional partial migration where URL state is used for user-facing flows that benefit from deep linking and bookmarking.

Applied to files:

  • src/app/(mobile-ui)/withdraw/page.tsx
  • src/app/(mobile-ui)/withdraw/manteca/page.tsx
  • src/app/(mobile-ui)/qr-pay/page.tsx
  • src/app/(mobile-ui)/add-money/[country]/bank/page.tsx
📚 Learning: 2024-10-08T20:13:42.967Z
Learnt from: jjramirezn
Repo: peanutprotocol/peanut-ui PR: 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)/withdraw/page.tsx
📚 Learning: 2025-06-18T19:56:55.443Z
Learnt from: jjramirezn
Repo: peanutprotocol/peanut-ui PR: 919
File: src/components/Withdraw/views/Initial.withdraw.view.tsx:87-87
Timestamp: 2025-06-18T19:56:55.443Z
Learning: In withdraw flows for Peanut Wallet, the PeanutActionDetailsCard should always display "USDC" as the token symbol because it shows the amount being withdrawn from the Peanut Wallet (which holds USDC), regardless of the destination token/chain selected by the user. The TokenSelector is used for choosing the withdrawal destination, not the source display.

Applied to files:

  • src/app/(mobile-ui)/withdraw/page.tsx
  • src/app/(mobile-ui)/withdraw/manteca/page.tsx
📚 Learning: 2025-05-19T19:40:43.138Z
Learnt from: jjramirezn
Repo: peanutprotocol/peanut-ui PR: 868
File: src/components/Payment/PaymentForm/index.tsx:284-293
Timestamp: 2025-05-19T19:40:43.138Z
Learning: When converting between USD and token amounts, always check if the token price (divisor) is valid and non-zero before performing the division to prevent Infinity, NaN, or errors. Implementing validation like `if (!tokenPrice || isNaN(tokenPrice) || tokenPrice === 0)` before division operations is crucial for handling cases where price data might be unavailable.

Applied to files:

  • src/app/(mobile-ui)/withdraw/page.tsx
📚 Learning: 2024-12-02T17:19:18.532Z
Learnt from: jjramirezn
Repo: peanutprotocol/peanut-ui PR: 551
File: src/components/Request/Create/Views/Initial.view.tsx:151-156
Timestamp: 2024-12-02T17:19:18.532Z
Learning: In the `InitialView` component at `src/components/Request/Create/Views/Initial.view.tsx`, when setting the default chain and token in the `useEffect` triggered by `isPeanutWallet`, it's acceptable to omit the setters from the dependency array and not include additional error handling for invalid defaults.

Applied to files:

  • src/app/(mobile-ui)/withdraw/page.tsx
📚 Learning: 2024-10-04T13:10:49.199Z
Learnt from: Hugo0
Repo: peanutprotocol/peanut-ui PR: 413
File: src/components/Request/Pay/Views/Initial.view.tsx:71-72
Timestamp: 2024-10-04T13:10:49.199Z
Learning: In `src/components/Request/Pay/Views/Initial.view.tsx`, it's acceptable to use the `!` operator in TypeScript to assert that `selectedTokenData` is not `null` or `undefined`, and potential runtime errors from accessing its properties without checks can be disregarded.

Applied to files:

  • src/app/(mobile-ui)/withdraw/page.tsx
  • src/app/(mobile-ui)/add-money/[country]/bank/page.tsx
📚 Learning: 2024-10-08T20:13:42.967Z
Learnt from: Hugo0
Repo: peanutprotocol/peanut-ui PR: 422
File: src/components/Request/Pay/Pay.tsx:103-111
Timestamp: 2024-10-08T20:13:42.967Z
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)/withdraw/page.tsx
📚 Learning: 2025-08-20T09:08:19.266Z
Learnt from: kushagrasarathe
Repo: peanutprotocol/peanut-ui PR: 1112
File: src/components/Claim/Link/views/BankFlowManager.view.tsx:336-343
Timestamp: 2025-08-20T09:08:19.266Z
Learning: In the KYC flow implementation, `setJustCompletedKyc` must be called after `await fetchUser()` in the `handleKycSuccess` callback. Setting `justCompletedKyc` before fetching the user would cause a re-fetching loop because `handleKycSuccess` is set in a useEffect inside the KYC hook, which would cause the UI flow to get stuck in one view.

Applied to files:

  • src/app/(mobile-ui)/withdraw/manteca/page.tsx
  • src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2025-08-11T10:35:02.715Z
Learnt from: kushagrasarathe
Repo: peanutprotocol/peanut-ui PR: 1078
File: src/hooks/useKycFlow.ts:129-141
Timestamp: 2025-08-11T10:35:02.715Z
Learning: In the KYC flow implementation in `src/hooks/useKycFlow.ts`, when Terms of Service (ToS) is accepted, there will always be a KYC link available in the `apiResponse`. The system ensures this invariant, so defensive checks for missing KYC links after ToS acceptance are unnecessary.

Applied to files:

  • src/app/(mobile-ui)/withdraw/manteca/page.tsx
📚 Learning: 2024-10-07T15:28:25.280Z
Learnt from: jjramirezn
Repo: peanutprotocol/peanut-ui PR: 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/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2025-09-08T03:13:09.111Z
Learnt from: jjramirezn
Repo: peanutprotocol/peanut-ui PR: 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:13:09.111Z
Learnt from: jjramirezn
Repo: peanutprotocol/peanut-ui PR: 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: 2024-10-08T20:13:42.967Z
Learnt from: Hugo0
Repo: peanutprotocol/peanut-ui PR: 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
🧬 Code graph analysis (5)
src/features/limits/utils/limits.utils.ts (1)
src/features/limits/hooks/useLimitsValidation.ts (1)
  • LimitCurrency (27-27)
src/app/(mobile-ui)/withdraw/page.tsx (4)
src/features/limits/hooks/useLimitsValidation.ts (1)
  • useLimitsValidation (41-279)
src/features/limits/components/LimitsWarningCard.tsx (1)
  • LimitsWarningCard (31-84)
src/features/limits/utils/limits.utils.ts (1)
  • LIMITS_COPY (6-11)
src/utils/general.utils.ts (1)
  • formatExtendedNumber (708-756)
src/app/(mobile-ui)/withdraw/manteca/page.tsx (7)
src/features/limits/utils/limits.utils.ts (2)
  • mapToLimitCurrency (17-21)
  • LIMITS_COPY (6-11)
src/features/limits/hooks/useLimitsValidation.ts (1)
  • useLimitsValidation (41-279)
src/constants/payment.consts.ts (1)
  • MIN_MANTECA_WITHDRAW_AMOUNT (12-12)
src/constants/zerodev.consts.ts (1)
  • PEANUT_WALLET_TOKEN_DECIMALS (16-16)
src/features/limits/components/LimitsWarningCard.tsx (1)
  • LimitsWarningCard (31-84)
src/utils/general.utils.ts (1)
  • formatExtendedNumber (708-756)
src/components/0_Bruddle/Button.tsx (1)
  • Button (80-283)
src/features/limits/components/LimitsWarningCard.tsx (3)
src/components/Global/Icons/Icon.tsx (2)
  • IconProps (151-154)
  • Icon (292-301)
src/context/ModalsContext.tsx (1)
  • useModalsContext (81-87)
src/features/limits/utils/limits.utils.ts (1)
  • LIMITS_COPY (6-11)
src/app/(mobile-ui)/qr-pay/page.tsx (6)
src/hooks/useKycStatus.tsx (1)
  • useKycStatus (12-36)
src/features/limits/hooks/useLimitsValidation.ts (1)
  • useLimitsValidation (41-279)
src/features/limits/components/LimitsWarningCard.tsx (1)
  • LimitsWarningCard (31-84)
src/features/limits/utils/limits.utils.ts (1)
  • LIMITS_COPY (6-11)
src/utils/general.utils.ts (1)
  • formatExtendedNumber (708-756)
src/constants/payment.consts.ts (1)
  • MAX_QR_PAYMENT_AMOUNT_FOREIGN (16-16)
🔇 Additional comments (21)
src/features/limits/utils/limits.utils.ts (2)

5-11: LGTM! Centralized copy for consistent user messaging.

Good practice extracting UI strings into a centralized constant for consistency and maintainability across flows.


13-21: LGTM! Clean currency mapping implementation.

The function correctly normalizes to uppercase and safely defaults unsupported currencies to USD. The type assertion is valid since the conditional guards ensure only valid values are returned.

src/features/limits/components/LimitsWarningCard.tsx (1)

66-79: LGTM! Support link integration.

The support link is well implemented with proper modal integration and clear call-to-action text.

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

69-74: LGTM! Clean imports for limits integration.

All necessary imports are properly added for the limits validation feature.


1565-1593: LGTM! Well-structured limits warning card integration.

The implementation correctly:

  • Hides balance errors when limits are blocking (avoiding duplicate error messages)
  • Shows appropriate messaging for local vs foreign users
  • Includes optional daysUntilReset messaging
  • Provides navigation to limits page
  • Controls support link visibility based on user type

1618-1619: LGTM! Button correctly disabled when limits are blocking.

The pay button is now properly disabled when limitsValidation.isBlocking is true, preventing users from attempting transactions that exceed their limits.

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

11-13: LGTM! Appropriate imports for limits feature.

Clean import additions for the limits warning card functionality.


112-119: LGTM! Correct disabled and error display logic.

The button correctly disables when limits are blocking, and errors are appropriately hidden when the limits card is displayed to avoid redundant messaging.

src/app/(mobile-ui)/add-money/[country]/bank/page.tsx (4)

29-32: LGTM! Clean imports for limits integration.

Appropriate imports added for the limits validation feature in the bank onramp flow.


104-110: LGTM! Correct limits validation setup for bridge flow.

The hook is correctly configured:

  • flowType: 'onramp' matches this add-money flow
  • currency: 'USD' is appropriate for bridge transactions
  • isLocalUser: false correctly identifies this as a non-Manteca (bridge) flow

414-425: LGTM! Correct button disable and error display logic.

The implementation properly:

  • Disables the Continue button when limits are blocking
  • Suppresses the ErrorAlert when the limits blocking card is shown to avoid duplicate error messaging

381-399: Bridge limits are per-transaction and intentionally exclude daysUntilReset.

The omission is correct by design. The useLimitsValidation hook explicitly documents this on line 101 with the comment // per-tx limit doesn't reset, returning daysUntilReset: null for all bridge flow scenarios. Unlike Manteca's monthly limits (which include daysUntilMonthlyReset), bridge limits have no reset period, so the field is appropriately absent from the card.

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

48-52: LGTM!

Imports are correctly organized for the limits validation feature integration.


107-118: LGTM!

The limits currency mapping and validation initialization correctly handles the manteca-specific flow with isLocalUser: true and the appropriate currency based on the selected country.


308-310: Good use of constants for minimum amount validation.

Using MIN_MANTECA_WITHDRAW_AMOUNT constant improves maintainability. The comment clarifies the responsibility split between local validation (min + balance) and limits validation (max).

Note: If the limits hook fails to load or returns no data, there's no fallback maximum check. The balance check (line 311) provides some protection, but consider whether a defensive local max fallback is warranted for resilience.


446-468: LGTM!

The LimitsWarningCard integration is well-structured with:

  • Appropriate type mapping (blocking → error, warning → warning)
  • Dynamic content including remaining limit, reset days, and limits page link
  • Support link visibility tied to manteca user status

488-491: LGTM!

The conditional error rendering prevents duplicate error states while allowing warnings to coexist with balance errors. This is a reasonable UX decision.

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

17-19: LGTM!

Imports are correctly added for the limits validation feature.


85-92: LGTM!

The limits validation is correctly initialized with:

  • flowType: 'offramp' for withdrawal flows
  • Dynamic isLocalUser determination based on selected method type
  • USD currency appropriate for this general withdraw page

262-264: LGTM!

The showLimitsCard computation correctly excludes crypto withdrawals which don't have fiat limits.


299-321: LGTM!

The LimitsWarningCard implementation is well-suited for this flow:

  • USD formatting with $ prefix is appropriate since currency is hardcoded to USD
  • Conditional "per transaction" text helps clarify bridge limits vs. monthly limits
  • showSupportLink={false} is reasonable for a page serving multiple withdrawal methods

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

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.

@kushagrasarathe i'm blocking this; i think this and the limit settings page both PRs are not up to par in quality. They have a lot of repetition, and imo are contributing inelegant bugprone code

initialDenomination,
setDisplayedAmount,
limitsValidation,
limitsCurrency = 'USD',
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this one special? where does this default come from?? this will be a nightmare to debug!

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

Caution

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

⚠️ Outside diff range comments (1)
src/app/(mobile-ui)/qr-pay/page.tsx (1)

1582-1597: Block payments while limits are still loading.
If limits data hasn’t loaded yet, isBlocking stays false and users can pay before validation completes.

🐛 Suggested fix
-                            limitsValidation.isBlocking
+                            limitsValidation.isBlocking || limitsValidation.isLoading
🤖 Fix all issues with AI agents
In `@src/features/limits/hooks/useLimitsValidation.ts`:
- Around line 65-68: The current logic treats hasMantecaLimits as true whenever
mantecaLimits exists, causing code paths to use Manteca validation even when
relevantMantecaLimit is null for the current currency; update the
hasMantecaLimits/usage in useLimitsValidation so it only returns true (or takes
the Manteca path) when a matching relevantMantecaLimit exists (i.e., derive
hasMantecaLimits from relevantMantecaLimit !== null or update downstream
conditionals to check relevantMantecaLimit directly); ensure you adjust both the
initial computation around relevantMantecaLimit and the similar checks in the
later block referenced (around the 249-275 logic) to avoid skipping bridge
validation when no currency-specific Manteca limit is found.
- Around line 58-62: The numeric parsing in useLimitsValidation's numericAmount
useMemo incorrectly parses comma-formatted strings (e.g., "1,200") because
parseFloat stops at the comma; update the normalization to strip commas from the
string before parsing: in the numericAmount useMemo, when amount is a string
remove all commas (e.g., replace(/,/g, '')) then call parseFloat on the
sanitized string, keep existing branch for numeric input, and continue to return
0 for NaN; this change touches the numericAmount useMemo and the amount handling
logic to ensure limit checks are accurate.
♻️ Duplicate comments (2)
src/app/(mobile-ui)/withdraw/manteca/page.tsx (1)

468-468: Consider adding limitsValidation.isLoading to the disabled gate (already noted).

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

314-316: Consider disabling while limits are loading (already noted).

🧹 Nitpick comments (1)
src/features/limits/consts.ts (1)

11-20: Normalize iso2 casing when filtering countryData.
MANTECA_COUNTRY_ISO2 is uppercase, so a lowercase countryData.iso2 would result in an empty QR list. Normalizing avoids silent mismatches.

♻️ Suggested change
-const derivedQrCountries = countryData
-    .filter((c) => c.type === 'country' && c.iso2 && MANTECA_COUNTRY_ISO2.includes(c.iso2))
+const derivedQrCountries = countryData
+    .filter(
+        (c) =>
+            c.type === 'country' &&
+            c.iso2 &&
+            MANTECA_COUNTRY_ISO2.includes(c.iso2.toUpperCase())
+    )

Based on learnings, existing country code maps rely on uppercase ISO codes, so normalizing here is safer.

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 (2)
src/app/(mobile-ui)/qr-pay/page.tsx (1)

1580-1602: Pay button doesn't respect limits loading state — potential bypass risk.

The button's disabled condition includes limitsValidation.isBlocking but not limitsValidation.isLoading. If the limits API is slow or the hook hasn't finished fetching, users can click "Pay" before validation completes, potentially bypassing limits.

The PR objectives explicitly recommend: "disable actions while limitsValidation.isLoading is true."

🔒 Suggested fix
                         disabled={
                             !!errorInitiatingPayment ||
                             isBlockingError ||
                             !amount ||
                             isLoading ||
                             !!balanceErrorMessage ||
                             shouldBlockPay ||
                             !usdAmount ||
                             usdAmount === '0.00' ||
                             isWaitingForWebSocket ||
-                            limitsValidation.isBlocking
+                            limitsValidation.isBlocking ||
+                            limitsValidation.isLoading
                         }
src/components/AddMoney/components/InputAmountStep.tsx (1)

1-110: Prettier check failed for this file.
Run pnpm prettier --write src/components/AddMoney/components/InputAmountStep.tsx to satisfy the pipeline warning.

🤖 Fix all issues with AI agents
In `@src/app/`(mobile-ui)/qr-pay/page.tsx:
- Around line 1555-1568: Currently balanceErrorMessage is hidden when
limitsValidation.isBlocking is true and LimitsWarningCard is rendered via an
inline IIFE; change this to surface both issues and make limits props computed
with useMemo: always render ErrorAlert when balanceErrorMessage exists (or
combine balanceErrorMessage into the limits card message) instead of skipping it
when limitsValidation.isBlocking is true, and replace the IIFE that calls
getLimitsWarningCardProps with a useMemo hook (e.g., compute limitsCardProps =
useMemo(() => getLimitsWarningCardProps({...}), [limitsValidation,
limitsCurrency]) ) and then render {limitsCardProps ? <LimitsWarningCard
{...limitsCardProps} /> : null}; use the existing symbols balanceErrorMessage,
limitsValidation.isBlocking, ErrorAlert, getLimitsWarningCardProps,
LimitsWarningCard, and useMemo.

In `@src/components/AddMoney/components/InputAmountStep.tsx`:
- Around line 106-107: The current conditional in InputAmountStep hides
ErrorAlert whenever limitsValidation?.isBlocking is true, which can mask
non-limit errors; change the rendering logic so ErrorAlert is shown for
non-limit errors even when a limits blocking card is displayed—for example,
render ErrorAlert unconditionally when error is truthy or split error sources by
showing ErrorAlert when error exists and the limits-related message only when
limitsValidation?.isBlocking is true; locate the ErrorAlert usage and the error
and limitsValidation?.isBlocking checks in InputAmountStep to implement this
separation.
- Around line 79-82: The code sets primaryDenomination using
currencyData.symbol! and currencyData.price!.buy which can throw if useCurrency
returns null/undefined; update the InputAmountStep logic to only assign
primaryDenomination when currencyData and both currencyData.symbol and
currencyData.price?.buy are present (guard against null), e.g., check
currencyData !== null and typeof currencyData.symbol === 'string' and
currencyData.price?.buy != null before creating the object, otherwise skip
setting primaryDenomination or provide a safe fallback; update any dependent
code in the component (e.g., wherever primaryDenomination is used) to handle the
absent case safely.
♻️ Duplicate comments (1)
src/app/(mobile-ui)/qr-pay/page.tsx (1)

391-401: Currency mapping concern already flagged; loading state not respected.

The currency-to-limit mapping duplication was already noted in previous reviews. However, there's an additional concern: limitsValidation.isLoading is not checked before allowing payment actions. If the limits API is slow or fails, users could potentially bypass limits validation.

Per the PR objectives, actions should be disabled while limitsValidation.isLoading is true to prevent race conditions.

Verify the loading state handling and consider adding limitsValidation.isLoading to the button's disabled condition at line 1594.

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

125-125: Unused destructured variable.

isUserMantecaKycApproved is extracted from useKycStatus() but never used in this file. The useLimitsValidation hook already internally calls useKycStatus() to determine user type. This redundant call can be removed.

♻️ Suggested fix
-    const { isUserMantecaKycApproved } = useKycStatus()

If this was intentionally added for future use, consider adding a comment or removing until needed.

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

260-308: Keep limits card currency in sync with validation.

Use the normalized limitsValidation.currency to ensure the warning text matches the currency used by the validation logic.

♻️ Proposed tweak
-                                currency: 'USD',
+                                currency: limitsValidation.currency,

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

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

⚠️ Outside diff range comments (1)
src/app/(mobile-ui)/qr-pay/page.tsx (1)

1583-1595: Disable Pay while limits validation is loading.

Right now Pay is only blocked on limitsValidation.isBlocking. If limits are still loading, users can submit before validation is ready. Line 1583-1595.

🐛 Proposed fix
-                            isWaitingForWebSocket ||
-                            limitsValidation.isBlocking
+                            isWaitingForWebSocket ||
+                            limitsValidation.isLoading ||
+                            limitsValidation.isBlocking
♻️ Duplicate comments (2)
src/features/limits/hooks/useLimitsValidation.ts (2)

58-61: Fix numeric parsing for comma-formatted amounts (limits can be bypassed).

parseFloat("1,200") becomes 1, which weakens limit enforcement. Normalize commas before parsing. Line 58-61.

🐛 Proposed fix
-    const numericAmount = useMemo(() => {
-        if (!amount) return 0
-        const parsed = typeof amount === 'string' ? parseFloat(amount) : amount
-        return isNaN(parsed) ? 0 : parsed
-    }, [amount])
+    const numericAmount = useMemo(() => {
+        if (amount === null || amount === undefined || amount === '') return 0
+        const normalized = typeof amount === 'string' ? amount.replace(/,/g, '') : amount
+        const parsed = typeof normalized === 'string' ? parseFloat(normalized) : normalized
+        return isNaN(parsed) ? 0 : parsed
+    }, [amount])

Based on learnings, TokenAmountInput returns decimal strings with commas, so normalization is required before parsing.


240-246: Avoid skipping QR limits when Manteca KYC exists but no matching limit.

In the QR path, local users always take mantecaValidation, which returns neutral when relevantMantecaLimit is null—so foreign QR limits never apply. This can allow bypassing limits for unsupported currencies. Line 240-246.

🐛 Proposed fix
-        if (flowType === 'qr-payment') {
-            // local users (manteca kyc) use manteca limits
-            if (isLocalUser && isUserMantecaKycApproved) {
-                return mantecaValidation
-            }
-            // foreign users have fixed per-tx limit
-            return foreignQrValidation
-        }
+        if (flowType === 'qr-payment') {
+            // local users (manteca kyc) use manteca limits only when a matching limit exists
+            if (isLocalUser && isUserMantecaKycApproved && relevantMantecaLimit) {
+                return mantecaValidation
+            }
+            // fallback to foreign per-tx limit when no relevant manteca limit exists
+            return foreignQrValidation
+        }

@kushagrasarathe
Copy link
Contributor Author

@coderabbitai review in depth

@kushagrasarathe kushagrasarathe requested a review from Hugo0 January 20, 2026 06:37
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 20, 2026

@kushagrasarathe I provided a comprehensive in-depth review in my previous comment covering:

  1. 🔴 Risk Assessment (MEDIUM-HIGH) - 5 critical risk areas identified
  2. ⚡ Performance Analysis (LOW IMPACT) - detailed performance evaluation
  3. 📊 Code Quality & DRY - specific DRY violations with code examples and fixes
  4. 🧪 QA Testing Guide - complete test matrix with 8 sections

Was there something specific from that review you'd like me to:

  • Elaborate on further?
  • Analyze from a different angle?
  • Provide code examples for the suggested fixes?
  • Focus on a particular file or flow?

Or would you like me to trigger a fresh automated review of all the changes? If so, I can do that with the `` action tag.

Please let me know what additional depth or focus area would be most helpful! 🔍


🧠 Learnings used
Learnt from: Hugo0
Repo: peanutprotocol/peanut-ui PR: 852
File: src/hooks/useTransactionHistory.ts:177-239
Timestamp: 2025-05-14T21:16:06.369Z
Learning: When reviewing code, pay attention to and resolve CodeRabbit comments as they can contain useful refactoring suggestions and insights.

Learnt from: Hugo0
Repo: peanutprotocol/peanut-ui PR: 594
File: src/components/Request/Pay/Views/Initial.view.tsx:188-213
Timestamp: 2024-12-31T15:08:15.640Z
Learning: Always resolve coderabbit comments when reviewing code, since they can contain useful suggestions.

Learnt from: jjramirezn
Repo: peanutprotocol/peanut-ui PR: 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.
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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

Caution

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

⚠️ Outside diff range comments (1)
src/app/(mobile-ui)/withdraw/manteca/page.tsx (1)

301-308: Removal of local max-amount check creates single point of failure.

The AI-generated summary and PR comments note this as a risk. If useLimitsValidation fails to load, returns an error, or is slow, there's no fallback max-amount validation. The previous explicit max check provided defense-in-depth.

Consider keeping a fallback local max check alongside the limits hook, as recommended in the PR objectives.

🛡️ Suggested fallback check
+const MAX_MANTECA_WITHDRAW_AMOUNT = 5000 // Fallback max if limits fail to load
+
 // only check min amount and balance here - max amount is handled by limits validation
 if (paymentAmount < parseUnits(MIN_MANTECA_WITHDRAW_AMOUNT.toString(), PEANUT_WALLET_TOKEN_DECIMALS)) {
     setBalanceErrorMessage(`Withdraw amount must be at least $${MIN_MANTECA_WITHDRAW_AMOUNT}`)
+} else if (!limitsValidation.remainingLimit && paymentAmount > parseUnits(MAX_MANTECA_WITHDRAW_AMOUNT.toString(), PEANUT_WALLET_TOKEN_DECIMALS)) {
+    // Fallback max check when limits validation hasn't loaded
+    setBalanceErrorMessage(`Withdraw amount cannot exceed $${MAX_MANTECA_WITHDRAW_AMOUNT}`)
 } else if (paymentAmount > balance) {
     setBalanceErrorMessage('Not enough balance to complete withdrawal.')
 } else {
     setBalanceErrorMessage(null)
 }
🤖 Fix all issues with AI agents
In `@src/app/`(mobile-ui)/qr-pay/page.tsx:
- Around line 1589-1590: The button's disabled logic currently uses
isWaitingForWebSocket || limitsValidation.isBlocking but misses the loading
state, so add limitsValidation.isLoading to the condition (i.e., disable when
limitsValidation.isLoading is true) so users can't proceed while limits are
still loading; update the component that renders the button (referencing
isWaitingForWebSocket, limitsValidation.isBlocking, limitsValidation.isLoading)
to include the loading flag in the disabled expression and any related aria-busy
or spinner logic if present.

In `@src/components/AddMoney/components/MantecaAddMoney.tsx`:
- Around line 71-77: The submission flow doesn't wait for limits data and
doesn't block when limits indicate restriction; update handleAmountSubmit to
first check limitsValidation.isLoading and limitsValidation.isBlocking and
prevent proceeding (show appropriate user feedback) while isLoading is true or
isBlocking is true, and ensure you re-run validation by including
limitsValidation (or its isLoading/isBlocking/validation result) in the
handleAmountSubmit dependency array so the handler uses the latest limits state;
reference the useLimitsValidation hook, the limitsValidation object, and the
handleAmountSubmit function when making these changes.
♻️ Duplicate comments (4)
src/features/limits/utils.ts (2)

77-79: Duplicate currency symbol mapping logic.

This function duplicates logic already available in SYMBOLS_BY_CURRENCY_CODE. While the fallback to uppercase currency code is reasonable, the existing useCurrency.ts already has this exact pattern. Based on learnings, there's an existing comment about code smell regarding repeated currency maps.


179-186: UX copy concern: message doesn't explain why the limit exists.

Based on past review comments, the copy was flagged as not clearly communicating to the user that they are exceeding their limit. The current messages say "You can add/withdraw/pay up to X" but don't explain why they're seeing this message or that they've exceeded their limit.

Consider adding context like "You've reached your monthly limit" or "This amount exceeds your per-transaction limit" for better user understanding.

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

1551-1564: Error masking and IIFE pattern concerns persist.

Two issues from past review that should be addressed:

  1. Error masking: When limitsValidation.isBlocking is true, balance errors are hidden. Users with both issues will only see limits errors first.

  2. IIFE pattern: The inline IIFE for rendering LimitsWarningCard is non-idiomatic. A useMemo would be cleaner.

♻️ Suggested refactor using useMemo
+    // Compute limits card props once
+    const limitsCardProps = useMemo(() => {
+        return getLimitsWarningCardProps({
+            validation: limitsValidation,
+            flowType: 'qr-payment',
+            currency: limitsValidation.currency,
+        })
+    }, [limitsValidation])

     {/* only show balance error if limits blocking card is not displayed (warnings can coexist) */}
     {balanceErrorMessage && !limitsValidation.isBlocking && (
         <ErrorAlert description={balanceErrorMessage} />
     )}

     {/* Limits Warning/Error Card */}
-    {(() => {
-        const limitsCardProps = getLimitsWarningCardProps({
-            validation: limitsValidation,
-            flowType: 'qr-payment',
-            currency: limitsValidation.currency,
-        })
-        return limitsCardProps ? <LimitsWarningCard {...limitsCardProps} /> : null
-    })()}
+    {limitsCardProps && <LimitsWarningCard {...limitsCardProps} />}
src/app/(mobile-ui)/withdraw/manteca/page.tsx (1)

463-463: Button doesn't disable during limits loading - addressed in past review.

This was flagged in a past review comment. The fix should include limitsValidation.isLoading in the disabled condition.

🔧 Suggested fix
-disabled={!Number(usdAmount) || !!balanceErrorMessage || limitsValidation.isBlocking}
+disabled={!Number(usdAmount) || !!balanceErrorMessage || limitsValidation.isBlocking || limitsValidation.isLoading}
🧹 Nitpick comments (4)
src/components/AddMoney/components/MantecaAddMoney.tsx (1)

206-219: Limits integration looks correct; minor redundancy noted.

The limitsValidation object is properly passed to InputAmountStep. The limitsCurrency prop is derived from limitsValidation.currency, which works but is slightly redundant since InputAmountStep could access it directly from limitsValidation.currency.

If InputAmountStep only needs the currency for display purposes independent of the full validation object, consider whether both props are necessary or if one could be derived internally.

src/features/limits/utils.ts (2)

67-71: Currency mapping may have unintended fallback behavior.

The function defaults any unrecognized currency to 'USD'. This is noted in the AI-generated summary as a potential edge case concern. Consider whether this silent fallback could hide bugs when an unexpected currency is passed (e.g., 'MXN', 'EUR'). Adding a console warning in development mode could help surface such cases.


179-186: Complex ternary expressions reduce readability.

The currency symbol formatting logic is repeated three times with identical complex ternary expressions. This is hard to read and maintain.

♻️ Suggested refactor to extract helper
+    // Helper to format currency amount consistently
+    const formatCurrencyDisplay = (symbol: string, amount: string) => {
+        if (symbol === '$') return `$${amount}`
+        return `${symbol} ${amount}`
+    }
+
     // build the limit message based on flow type
     let limitMessage = ''
     if (flowType === 'onramp') {
-        limitMessage = `You can add up to ${currencySymbol === '$' ? '' : currencySymbol + ' '}${currencySymbol === '$' ? '$' : ''}${formattedLimit}${validation.daysUntilReset ? '' : ' per transaction'}`
+        limitMessage = `You can add up to ${formatCurrencyDisplay(currencySymbol, formattedLimit)}${validation.daysUntilReset ? '' : ' per transaction'}`
     } else if (flowType === 'offramp') {
-        limitMessage = `You can withdraw up to ${currencySymbol === '$' ? '' : currencySymbol + ' '}${currencySymbol === '$' ? '$' : ''}${formattedLimit}${validation.daysUntilReset ? '' : ' per transaction'}`
+        limitMessage = `You can withdraw up to ${formatCurrencyDisplay(currencySymbol, formattedLimit)}${validation.daysUntilReset ? '' : ' per transaction'}`
     } else {
         // qr-payment
-        limitMessage = `You can pay up to ${currencySymbol === '$' ? '' : currencySymbol + ' '}${currencySymbol === '$' ? '$' : ''}${formattedLimit} per transaction`
+        limitMessage = `You can pay up to ${formatCurrencyDisplay(currencySymbol, formattedLimit)} per transaction`
     }
src/app/(mobile-ui)/withdraw/manteca/page.tsx (1)

439-448: IIFE pattern for limits card - same issue as QR pay flow.

This duplicates the non-idiomatic IIFE pattern. Consider using useMemo for consistency.

♻️ Suggested refactor using useMemo
+    const limitsCardProps = useMemo(() => {
+        return getLimitsWarningCardProps({
+            validation: limitsValidation,
+            flowType: 'offramp',
+            currency: limitsValidation.currency,
+        })
+    }, [limitsValidation])

-    {/* limits warning/error card - uses centralized helper for props */}
-    {(() => {
-        const limitsCardProps = getLimitsWarningCardProps({
-            validation: limitsValidation,
-            flowType: 'offramp',
-            currency: limitsValidation.currency,
-        })
-        return limitsCardProps ? <LimitsWarningCard {...limitsCardProps} /> : null
-    })()}
+    {/* limits warning/error card - uses centralized helper for props */}
+    {limitsCardProps && <LimitsWarningCard {...limitsCardProps} />}

Comment on lines +1589 to +1590
isWaitingForWebSocket ||
limitsValidation.isBlocking
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 | 🟡 Minor

Consider disabling button while limits are loading.

Similar to the concern raised in the withdraw flow, the disabled condition doesn't account for limitsValidation.isLoading. If limits data hasn't loaded yet, isBlocking will be false, potentially allowing the user to proceed before validation completes.

🔧 Suggested fix
                         isWaitingForWebSocket ||
-                        limitsValidation.isBlocking
+                        limitsValidation.isBlocking ||
+                        limitsValidation.isLoading
🤖 Prompt for AI Agents
In `@src/app/`(mobile-ui)/qr-pay/page.tsx around lines 1589 - 1590, The button's
disabled logic currently uses isWaitingForWebSocket ||
limitsValidation.isBlocking but misses the loading state, so add
limitsValidation.isLoading to the condition (i.e., disable when
limitsValidation.isLoading is true) so users can't proceed while limits are
still loading; update the component that renders the button (referencing
isWaitingForWebSocket, limitsValidation.isBlocking, limitsValidation.isLoading)
to include the loading flag in the disabled expression and any related aria-busy
or spinner logic if present.

Comment on lines +71 to +77
// validates deposit amount against user's limits
// currency comes from country config - hook normalizes it internally
const limitsValidation = useLimitsValidation({
flowType: 'onramp',
amount: usdAmount,
currency: selectedCountry?.currency,
})
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 | 🟠 Major

Consider guarding submission on limits validation loading and blocking state.

The useLimitsValidation hook provides isLoading and isBlocking properties, but handleAmountSubmit (lines 146-185) doesn't check these before proceeding with the deposit. This creates a potential race condition where a user could submit while limits data is still loading, or submit an amount that exceeds their limits.

Suggested fix in handleAmountSubmit
 const handleAmountSubmit = useCallback(async () => {
     if (!selectedCountry?.currency) return
     if (isCreatingDeposit) return
+    if (limitsValidation.isLoading) return
+    if (limitsValidation.isBlocking) return

     // check if we still need to determine KYC status

Also update the dependency array:

-}, [currentDenomination, selectedCountry, displayedAmount, isMantecaKycRequired, isCreatingDeposit, setUrlState])
+}, [currentDenomination, selectedCountry, displayedAmount, isMantecaKycRequired, isCreatingDeposit, setUrlState, limitsValidation.isLoading, limitsValidation.isBlocking])
🤖 Prompt for AI Agents
In `@src/components/AddMoney/components/MantecaAddMoney.tsx` around lines 71 - 77,
The submission flow doesn't wait for limits data and doesn't block when limits
indicate restriction; update handleAmountSubmit to first check
limitsValidation.isLoading and limitsValidation.isBlocking and prevent
proceeding (show appropriate user feedback) while isLoading is true or
isBlocking is true, and ensure you re-run validation by including
limitsValidation (or its isLoading/isBlocking/validation result) in the
handleAmountSubmit dependency array so the handler uses the latest limits state;
reference the useLimitsValidation hook, the limitsValidation object, and the
handleAmountSubmit function when making these changes.

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

🤖 Fix all issues with AI agents
In `@src/app/`(mobile-ui)/add-money/[country]/bank/page.tsx:
- Around line 132-138: The Continue button can be clicked before limits
validation finishes; update the button's disabled condition to also check
limitsValidation.isLoading so actions are disabled while validation is pending.
Locate the Continue button render (the element that currently checks things like
canContinue or any existing disabled flag around the Continue/submit action) and
add limitsValidation.isLoading to the boolean expression (e.g., disabled={... ||
limitsValidation.isLoading}), ensuring you reference the useLimitsValidation
result (limitsValidation) used earlier in this file; no other behavior changes
needed besides preventing clicks while loading.
- Around line 118-130: The usdEquivalent calculation and the payment button need
to account for exchange-rate fetch failures: destructure isError from
useExchangeRate where exchangeRate is obtained, update the useMemo for
usdEquivalent (function named usdEquivalent that reads rawTokenAmount,
localCurrency, exchangeRate) to treat a failed fetch (isError true) as an error
case (e.g., return NaN or throw/flag) instead of silently returning 0, and
update the button disabled logic to include isError alongside isRateLoading so
the button is disabled (or an error UI is shown) when the rate fetch failed;
reference the useExchangeRate hook, the usdEquivalent useMemo, rawTokenAmount,
localCurrency, exchangeRate, and the button's disabled prop to find all places
to change.
🧹 Nitpick comments (1)
src/app/(mobile-ui)/add-money/[country]/bank/page.tsx (1)

409-418: Consider memoizing limits card props.

The IIFE pattern works but recalculates getLimitsWarningCardProps on every render when the card is shown. For slightly cleaner code and consistency with other memoized values, consider extracting this to a useMemo.

Suggested refactor
+    const limitsCardProps = useMemo(() => {
+        if (!limitsValidation.isBlocking && !limitsValidation.isWarning) return null
+        return getLimitsWarningCardProps({
+            validation: limitsValidation,
+            flowType: 'onramp',
+            currency: 'USD',
+        })
+    }, [limitsValidation])
+
     if (urlState.step === 'inputAmount') {
-        const showLimitsCard = limitsValidation.isBlocking || limitsValidation.isWarning
-
         return (
             // ...
-                    {showLimitsCard &&
-                        (() => {
-                            const limitsCardProps = getLimitsWarningCardProps({
-                                validation: limitsValidation,
-                                flowType: 'onramp',
-                                currency: 'USD',
-                            })
-                            return limitsCardProps ? <LimitsWarningCard {...limitsCardProps} /> : null
-                        })()}
+                    {limitsCardProps && <LimitsWarningCard {...limitsCardProps} />}

Comment on lines +118 to +130
// convert input amount to USD for limits validation
// bridge limits are always in USD, but user inputs in local currency
const usdEquivalent = useMemo(() => {
if (!rawTokenAmount) return 0
const numericAmount = parseFloat(rawTokenAmount.replace(/,/g, ''))
if (isNaN(numericAmount)) return 0

// for USD, no conversion needed
if (localCurrency === 'USD') return numericAmount

// convert local currency to USD
return exchangeRate > 0 ? numericAmount * exchangeRate : 0
}, [rawTokenAmount, localCurrency, exchangeRate])
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 | 🟡 Minor

Handle exchange rate fetch errors.

When exchangeRate is 0 due to a fetch error (not just loading), usdEquivalent will be 0, potentially bypassing limits validation for non-USD currencies. The button's disabled state only guards against isRateLoading, not isError.

Consider also destructuring isError from useExchangeRate and either disabling the button or showing an error when the rate fetch fails.

Suggested approach
     // get exchange rate: local currency → USD (for limits validation)
     // skip for USD since it's 1:1
-    const { exchangeRate, isLoading: isRateLoading } = useExchangeRate({
+    const { exchangeRate, isLoading: isRateLoading, isError: isRateError } = useExchangeRate({
         sourceCurrency: localCurrency,
         destinationCurrency: 'USD',
         enabled: localCurrency !== 'USD',
     })

Then in the button disabled state (line 437):

-                            (localCurrency !== 'USD' && isRateLoading)
+                            (localCurrency !== 'USD' && (isRateLoading || isRateError))
🤖 Prompt for AI Agents
In `@src/app/`(mobile-ui)/add-money/[country]/bank/page.tsx around lines 118 -
130, The usdEquivalent calculation and the payment button need to account for
exchange-rate fetch failures: destructure isError from useExchangeRate where
exchangeRate is obtained, update the useMemo for usdEquivalent (function named
usdEquivalent that reads rawTokenAmount, localCurrency, exchangeRate) to treat a
failed fetch (isError true) as an error case (e.g., return NaN or throw/flag)
instead of silently returning 0, and update the button disabled logic to include
isError alongside isRateLoading so the button is disabled (or an error UI is
shown) when the rate fetch failed; reference the useExchangeRate hook, the
usdEquivalent useMemo, rawTokenAmount, localCurrency, exchangeRate, and the
button's disabled prop to find all places to change.

Comment on lines +132 to +138
// validate against user's bridge limits
// uses USD equivalent to correctly compare against USD-denominated limits
const limitsValidation = useLimitsValidation({
flowType: 'onramp',
amount: usdEquivalent,
currency: 'USD',
})
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 | 🟠 Major

Guard against limits loading state.

The limitsValidation.isLoading state is not used to disable the Continue button. This creates a race condition where users could proceed before limits validation completes, potentially bypassing blocking limits.

Per the PR review comments recommendation: "Disable actions while limitsValidation.isLoading is true."

Suggested fix in button disabled state (around line 431)
                         disabled={
                             !parseFloat(rawTokenAmount) ||
                             parseFloat(rawTokenAmount) < minimumAmount ||
                             error.showError ||
                             isCreatingOnramp ||
                             limitsValidation.isBlocking ||
-                            (localCurrency !== 'USD' && isRateLoading)
+                            (localCurrency !== 'USD' && isRateLoading) ||
+                            limitsValidation.isLoading
                         }
🤖 Prompt for AI Agents
In `@src/app/`(mobile-ui)/add-money/[country]/bank/page.tsx around lines 132 -
138, The Continue button can be clicked before limits validation finishes;
update the button's disabled condition to also check limitsValidation.isLoading
so actions are disabled while validation is pending. Locate the Continue button
render (the element that currently checks things like canContinue or any
existing disabled flag around the Continue/submit action) and add
limitsValidation.isLoading to the boolean expression (e.g., disabled={... ||
limitsValidation.isLoading}), ensuring you reference the useLimitsValidation
result (limitsValidation) used earlier in this file; no other behavior changes
needed besides preventing clicks while loading.

@Hugo0
Copy link
Contributor

Hugo0 commented Jan 20, 2026

@coderabbitai review latest changes, especially in regard to hugos commetns

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 20, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash

# Get all comments from Hugo0 on this PR
echo "=== Hugo0's comments on PR `#1621` ==="
gh pr view 1621 --json comments --jq '.comments[] | select(.author.login == "Hugo0") | {created: .createdAt, body: .body}'

echo ""
echo "=== Hugo0's review comments ==="
gh api repos/peanutprotocol/peanut-ui/pulls/1621/comments --jq '.[] | select(.user.login == "Hugo0") | {path: .path, line: .line, body: .body, created_at: .created_at}'

echo ""
echo "=== Recent commits (last 5) ==="
gh pr view 1621 --json commits --jq '.commits[-5:] | .[] | {sha: .oid[0:7], message: .messageHeadline, author: .authors[0].login, date: .committedDate}'

echo ""
echo "=== PR status ==="
gh pr view 1621 --json headRefName,baseRefName,updatedAt,commits --jq '{source: .headRefName, target: .baseRefName, lastUpdated: .updatedAt, totalCommits: (.commits | length)}'

Length of output: 3142


🏁 Script executed:

#!/bin/bash

# Get the diff for the most recent 5 commits to see what changed
echo "=== Latest changes (last 5 commits diff) ==="
gh api repos/peanutprotocol/peanut-ui/pulls/1621 --jq '.head.sha' | xargs -I {} git log --oneline {}~5..{} 2>/dev/null || echo "Cannot access git log"

echo ""
echo "=== Current state of files Hugo commented on ==="

# Check current state of key files Hugo flagged
echo "--- src/app/(mobile-ui)/qr-pay/page.tsx (lines with limitsValidation and isLocalUser) ---"
gh pr view 1621 --json files --jq '.files[] | select(.path == "src/app/(mobile-ui)/qr-pay/page.tsx")' | head -5

echo ""
echo "--- src/components/AddMoney/components/InputAmountStep.tsx (limitsCurrency default) ---"
gh pr view 1621 --json files --jq '.files[] | select(.path == "src/components/AddMoney/components/InputAmountStep.tsx")' | head -5

echo ""
echo "=== Getting current file contents for reviewed files ==="

# Get the current state of the main files Hugo commented on
echo "--- Checking if DRY violations were addressed ---"
gh pr diff 1621 -- src/app/(mobile-ui)/qr-pay/page.tsx | grep -A5 -B5 "limitsValidation\|isLocalUser" | head -40

echo ""
echo "--- Checking currency handling ---"
gh pr diff 1621 -- src/components/AddMoney/components/InputAmountStep.tsx | grep -A3 -B3 "limitsCurrency\|currency" | head -40

echo ""
echo "--- Checking for abstraction/DRY improvements ---"
gh pr diff 1621 -- src/features/limits/utils.ts | head -80

Length of output: 1072


🏁 Script executed:

#!/bin/bash

# Get diff for specific files - escape parentheses properly
echo "=== Diff for qr-pay/page.tsx ==="
gh pr diff 1621 | grep -A200 "diff --git a/src/app/(mobile-ui)/qr-pay/page.tsx" | head -100

echo ""
echo "=== Diff for InputAmountStep.tsx ==="
gh pr diff 1621 | grep -A150 "diff --git a/src/components/AddMoney/components/InputAmountStep.tsx" | head -80

echo ""
echo "=== Check if getLimitsWarningCardProps was added (DRY fix) ==="
gh pr diff 1621 | grep -A30 "getLimitsWarningCardProps"

echo ""
echo "=== Check utils.ts for new abstractions ==="
gh pr diff 1621 | grep -A50 "diff --git a/src/features/limits/utils.ts" | head -80

echo ""
echo "=== Check if currency mapping was centralized ==="
gh pr diff 1621 | grep -B5 -A10 "mapToLimitCurrency"

Length of output: 28748


@Hugo0, I've reviewed the latest changes against your comments. Here's the status:

✅ Issues Addressed

1. DRY Violations Fixed (Comments #6, #8, #9)

The repetitive limits card code has been centralized into getLimitsWarningCardProps() utility:

// Now used consistently across all flows:
const limitsCardProps = getLimitsWarningCardProps({
    validation: limitsValidation,
    flowType: 'qr-payment',
    currency: limitsValidation.currency,
})
return limitsCardProps ? <LimitsWarningCard {...limitsCardProps} /> : null

2. Currency Mapping Centralized (Comment #2)

Added mapToLimitCurrency() utility in utils.ts:

export function mapToLimitCurrency(currency?: string): LimitCurrency {
    const upper = currency?.toUpperCase()
    if (upper === 'ARS' || upper === 'BRL') return upper as LimitCurrency
    return 'USD'
}

3. isLocalUser Logic Fixed (Comment #3 - Code Smell)

isLocalUser is now computed inside useLimitsValidation hook:

// Inside hook:
const isLocalUser = isUserMantecaKycApproved

No longer passed externally. Validation logic is self-contained.

4. Currency Display Fixed (Comment #1 & #5)

  • Added exchange rate conversion for onramp flows (bridge)
  • Uses limitsCurrency from validation result for correct currency display
  • Limits now show in the correct currency (USD for bridge, ARS/BRL for manteca)

⚠️ Issues Partially Addressed

5. Copy Quality (Comment #4)

The copy in getLimitsWarningCardProps() still says:

limitMessage = `You can pay up to ${symbol}${formattedLimit} per transaction`

This still doesn't explain WHY the user hit their limit or what happens next. Suggestions:

  • Blocking: "This amount exceeds your monthly limit of $X. Limit resets in Y days."
  • Warning: "This would use 70% of your remaining monthly limit ($X remaining)."

6. limitsCurrency Prop Requirement (Comment #10)

InputAmountStep now requires limitsCurrency prop with a comment:

// required - must be provided by caller based on the payment flow's currency (ARS, BRL, USD)
limitsCurrency: LimitCurrency

Better, but still a code smell. The currency should ideally come from the validation itself. Consider making it optional and deriving it from limitsValidation.currency when available:

limitsCurrency?: LimitCurrency
// Inside component:
const effectiveCurrency = limitsCurrency ?? limitsValidation?.currency ?? 'USD'

📋 Summary

Issue Status Notes
DRY violations ✅ Fixed getLimitsWarningCardProps centralized
Currency mapping ✅ Fixed mapToLimitCurrency utility
isLocalUser code smell ✅ Fixed Now internal to hook
Currency display (ARS/USD) ✅ Fixed Exchange rate conversion added
Copy quality ⚠️ Partial Still doesn't explain limit context well
limitsCurrency default ⚠️ Partial Required prop with comment, but could be optional

🔧 Remaining Recommendations

  1. Improve copy messaging - explain the "why" to users
  2. Make limitsCurrency optional - derive from validation when available
  3. Add loading state checks - ensure buttons don't enable prematurely while limits load

The major architectural concerns (DRY, currency handling, validation logic) have been addressed well. The remaining issues are UX polish and minor API design improvements.


🧠 Learnings used
Learnt from: Hugo0
Repo: peanutprotocol/peanut-ui PR: 852
File: src/hooks/useTransactionHistory.ts:177-239
Timestamp: 2025-05-14T21:16:06.369Z
Learning: When reviewing code, pay attention to and resolve CodeRabbit comments as they can contain useful refactoring suggestions and insights.

Learnt from: Hugo0
Repo: peanutprotocol/peanut-ui PR: 594
File: src/components/Request/Pay/Views/Initial.view.tsx:188-213
Timestamp: 2024-12-31T15:08:15.640Z
Learning: Always resolve coderabbit comments when reviewing code, since they can contain useful suggestions.

Learnt from: Hugo0
Repo: peanutprotocol/peanut-ui PR: 823
File: src/context/kernelClient.context.tsx:79-86
Timestamp: 2025-04-29T19:36:38.121Z
Learning: When Hugo0 asks to "resolve coderabbit comments", they want to acknowledge the comment without necessarily implementing the suggested changes, as the current implementation might be intentional for their specific use case.

Learnt from: Hugo0
Repo: peanutprotocol/peanut-ui PR: 942
File: src/components/AddMoney/consts/index.ts:2151-2162
Timestamp: 2025-06-30T10:44:08.048Z
Learning: Hugo0 often agrees with refactoring suggestions but defers implementation due to time constraints, preferring to track improvements as follow-up issues when they're part of larger architectural changes.

@kushagrasarathe kushagrasarathe merged commit 7388ce2 into feat/limits Jan 20, 2026
4 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Jan 20, 2026
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