Skip to content

Conversation

Hugo0
Copy link
Contributor

@Hugo0 Hugo0 commented May 15, 2025

Issues this PR tackles

Currency Input Issues

  1. Missing Thousands Separators in ARS Input

    • Currently displays "2000" instead of "20,000"
    • Affects readability of large amounts
    • Needs consistent formatting with locale standards
  2. Currency Conversion Precision Loss

    • When switching between ARS and USD, amounts lose precision
    • Example: 10 ARS -> USD -> ARS becomes 9.9999999
    • Likely due to floating point math issues
  3. History View Sign Issues

    • Argentinian currency transactions show incorrect +/- signs in history
  4. Automatic Redirect After Payment

    • Payment completion auto-redirects to home screen
    • Creates poor UX for external wallet users
  5. Home Screen Transaction Labels

    • Transaction descriptions in home feed lack clarity
    • Need more descriptive and user-friendly labels
    • Improve readability of transaction history

Summary of Changes

This PR significantly polishes currency handling, focusing on robust input behavior, correct decimal precision for both fiat (ARS, USD) and cryptocurrencies, and accurate display formatting in inputs and transaction history.

Key Enhancements & Fixes:

  1. TokenAmountInput.tsx (Currency Input Component):

    • Dynamic Decimal Precision: Fiat inputs (ARS, USD via currency prop) correctly display with 2 decimal places. Direct token inputs (e.g., ETH, BTC) now respect their native selectedTokenData.decimals for display in the input field (defaulting to 6), allowing for necessary high precision.
    • Improved Input UX & Stability:
      • Fixed input field resets/emptying when typing numbers that trigger thousands separators (e.g., ARS > 999) or when switching from large USD to ARS.
    • Standardized Input Method: Utilizes type="text" with inputMode="decimal" for all currency inputs. This is a common fintech practice to enable custom formatting (like thousands separators) while suggesting a numeric keyboard on mobile devices.
    • Consistent Formatting & Precision: Balance display and secondary (converted) amount displays are consistently formatted. Underlying tokenValue maintains appropriate precision (e.g., 6 decimals for USD equivalents from ARS, full precision for direct token inputs).
    • Float Rounding: Addressed minor floating-point inaccuracies when toggling between ARS and USD display modes.
  2. TransactionCard.tsx (Transaction History List Item):

    • Accurate Financial Signs: Correctly displays + or - signs for both ARS and USD amounts based on transaction type and user role.
    • Consistent Number Display: Both ARS and USD amounts use a unified formatting utility (formatNumberForDisplay) for thousands separators and appropriate decimal places (2 for fiat in this context).
  3. general.utils.ts (Utility Functions):

    • formatNumberForDisplay enhanced to accept a maxDecimals option, providing flexibility for fiat (default 2) vs. crypto display needs. It standardizes on en-US locale for consistent, parsable output (comma-thousands, period-decimal) suitable for input field display and simple list views.
    • Removed redundant formatting utilities.
  4. Status.payment.view.tsx (Payment Flow):

    • Removed the automatic redirect to home after payment completion.

existing bugs or things we didnt think about:

  • /pay/hugotest124/10?currency=ARS
    leads to AR$ 11,660 payment (base is 10usd). But it should be 10 ars and 0.000xx usd. Else merchants cant make a specific ARS payment
  • the transaction drawer LOSES the pesos amount. We have to tackle this, it should keep both the info
  • we'll also need to tackle the multi currency issue more holistically and enable currency setting in profile.

BEFORE:
Screencast from 2025-05-14 23-46-51.webm

AFTER:
Screencast from 2025-05-15 00-26-43.webm

Copy link
Contributor

coderabbitai bot commented May 15, 2025

"""

Walkthrough

This update refactors the TokenAmountInput component to use a text input with a decoupled visible input state, improving formatting, validation, and unified currency mode toggling. It also enhances transaction amount display logic, updates fallback text in transaction transformation, adds new locale-aware number and currency formatting utilities, removes automatic redirect in a payment status view, and simplifies state synchronization in the payment form.

Changes

File(s) Change Summary
src/components/Global/TokenAmountInput/index.tsx Refactored to replace displayValue with inputValue state, improved input sanitization and validation, unified currency mode toggling, enhanced formatting and dynamic input width, and improved wallet balance display formatting.
src/components/Payment/Views/Status.payment.view.tsx Removed automatic redirect to home after payment success; retained only showing check mark with a single timeout.
src/components/TransactionDetails/TransactionCard.tsx Replaced formatAmount with formatNumberForDisplay, added enums for transaction type and role, and refined transaction amount display logic with explicit currency symbol and sign handling, especially for ARS currency.
src/components/TransactionDetails/transactionTransformer.ts Changed fallback sender name text for DIRECT_SEND transactions when user is not sender from "Payment via public link." to "Requested via Link."
src/utils/general.utils.ts Added formatNumberForDisplay function to format numeric strings with locale-specific thousands separators and configurable decimal places, preserving intermediate input states.
src/utils/format.utils.ts Added formatCurrencyWithIntl function to format numbers as localized currency or decimal strings using Intl.NumberFormat with fallback formatting and error handling.
src/components/Payment/PaymentForm/index.tsx Removed multiple redundant useEffect hooks for state synchronization, improved clearing behavior of token and currency amounts, and adjusted callbacks passed to TokenAmountInput for consistent state management.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant TokenAmountInput
    participant FormattingUtils

    User->>TokenAmountInput: Types or edits input
    TokenAmountInput->>TokenAmountInput: Updates inputValue state
    TokenAmountInput->>FormattingUtils: Format input for display
    FormattingUtils-->>TokenAmountInput: Returns formatted string
    TokenAmountInput-->>User: Displays formatted value
    User->>TokenAmountInput: Clicks currency toggle or max button
    TokenAmountInput->>TokenAmountInput: Toggles mode or sets max, updates inputValue and tokenValue
    TokenAmountInput->>FormattingUtils: Format new value
    FormattingUtils-->>TokenAmountInput: Returns formatted string
    TokenAmountInput-->>User: Updates input display
Loading

Possibly related PRs

Suggested reviewers

  • Hugo0
  • panosfilianos
    """

Note

⚡️ AI Code Reviews for VS Code, Cursor, Windsurf

CodeRabbit now has a plugin for VS Code, Cursor and Windsurf. This brings AI code reviews directly in the code editor. Each commit is reviewed immediately, finding bugs before the PR is raised. Seamless context handoff to your AI code agent ensures that you can easily incorporate review feedback.
Learn more here.


Note

⚡️ Faster reviews with caching

CodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 16th. To opt out, configure Review - Disable Cache at either the organization or repository level. If you prefer to disable all data retention across your organization, simply turn off the Data Retention setting under your Organization Settings.
Enjoy the performance boost—your workflow just got faster.


📜 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 0207b9e and ebe82fb.

📒 Files selected for processing (1)
  • src/components/Global/TokenAmountInput/index.tsx (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/Global/TokenAmountInput/index.tsx
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Deploy-Preview
✨ Finishing Touches
  • 📝 Generate Docstrings

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

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

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

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

vercel bot commented May 15, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
peanut-ui ✅ Ready (Inspect) Visit Preview 💬 Add feedback May 15, 2025 5:55pm

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

🧹 Nitpick comments (3)
src/components/Global/TokenAmountInput/index.tsx (3)

46-68: Avoid extra renders by memoising formatted value

formatNumberForDisplay is executed on every render even if tokenValue, currencyMode, etc. did not actually change the formatted result. Since the result is deterministic for the given dependency tuple, consider wrapping the formatting block in useMemo and only updating displayValue when the memoised value changes.
This reduces re-renders/DOM writes when the parent of TokenAmountInput itself re-renders frequently.


208-216: Key filtering blocks common editing/navigation keys

The current filter prevents Enter, Escape, Home, End, and comma – the latter makes pasting formatted numbers impossible. Users on macOS will also expect Cmd+A / Cmd+V to work in all cases (already mostly covered, but still brittle).

Consider switching to inputMode="decimal" + pattern validation and drop manual onKeyDown filtering, or at least whitelist the additional keys:

-!['Backspace','Delete','ArrowLeft','ArrowRight','ArrowUp','ArrowDown','Tab'].includes(key)
+!['Backspace','Delete','ArrowLeft','ArrowRight','ArrowUp','ArrowDown','Tab','Enter','Escape','Home','End',','].includes(key)

233-238: Potential rounding issues when showing wallet balance

walletBalance is cast to Number, which loses precision for large token balances (common with 18-dec tokens). Displayed balances may be off by fractions of a cent.

Prefer the same arbitrary-precision utility suggested above or, at minimum, BigInt/BigNumber for the intermediate multiplication:

-Number(walletBalance) * currency.price
+new BigNumber(walletBalance).multipliedBy(currency.price).toString()
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d59e939 and b1d2a2a.

📒 Files selected for processing (5)
  • src/components/Global/TokenAmountInput/index.tsx (5 hunks)
  • src/components/Payment/Views/Status.payment.view.tsx (1 hunks)
  • src/components/TransactionDetails/TransactionCard.tsx (3 hunks)
  • src/components/TransactionDetails/transactionTransformer.ts (1 hunks)
  • src/utils/general.utils.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
src/components/Global/TokenAmountInput/index.tsx (3)
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.
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 `''`.
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.
🧬 Code Graph Analysis (1)
src/components/TransactionDetails/TransactionCard.tsx (4)
src/components/Global/Badges/StatusBadge.tsx (1)
  • StatusType (4-4)
src/components/Global/Card/index.tsx (1)
  • CardPosition (4-4)
src/components/TransactionDetails/transactionTransformer.ts (1)
  • TransactionDetails (15-41)
src/utils/general.utils.ts (1)
  • formatNumberForDisplay (248-259)
🔇 Additional comments (8)
src/utils/general.utils.ts (1)

244-259: Well-designed utility function for consistent number formatting

The new formatNumberForDisplay function provides a clean approach to standardize number formatting across the application. I appreciate the handling of edge cases (undefined, empty, NaN), the configurable decimal places, and the forward-looking comment about internationalization.

This function will help address the missing thousands separators in ARS input fields mentioned in the PR objectives.

src/components/Payment/Views/Status.payment.view.tsx (1)

57-66: Good UX improvement by removing automatic redirect

The removal of automatic redirect after payment completion is a positive change. It allows users (especially those with external wallets) to properly view their payment confirmation before manually proceeding to the home screen.

This addresses one of the key objectives in the PR regarding disrupted user experience after payment completion.

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

81-81: Improved clarity in transaction labeling

Changing the fallback text from "Payment via public link" to "Requested via Link" provides a more accurate description of the transaction type, aligning with the PR objective to improve transaction labels.

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

9-12: Appropriate imports for the new implementation

Good choice importing formatNumberForDisplay and the necessary enums to support the refined transaction amount display logic.


27-31: Useful helper function for currency symbol standardization

The new getDisplayCurrencySymbol helper function centralizes the logic for currency symbol display, making it easier to maintain and extend for additional currencies in the future.


58-87: Comprehensive fix for currency display issues

This overhaul of the currency display logic properly addresses the issue with incorrect plus/minus signs for ARS transactions mentioned in the PR objectives. The approach is thorough and considers:

  1. Different currencies (especially ARS vs USD)
  2. Transaction direction based on type and user role
  3. Proper formatting with thousands separators

The conditional logic is clear and well-structured, making the code maintainable.


119-119: Simplified rendering logic

Using the precomputed finalDisplayAmount variable simplifies the JSX rendering and makes the code more readable.

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

100-108: Hard-coding 6 decimals can under-represent many ERC-20 tokens

parseFloat(usdEquivalent.toFixed(6)) limits the on-chain value to 6 d.p. Tokens such as USDC (6) are fine, but 18-dec tokens (ETH, WBTC, etc.) lose precision and may round to zero for small transfers.

Prefer using the token’s native decimals:

-const usdEquivalent = customCurrencyNumber / currency.price
-setTokenValue(parseFloat(usdEquivalent.toFixed(6)).toString())
+const usdEquivalent = customCurrencyNumber.div(currency.price)
+setTokenValue(usdEquivalent.toFixed(selectedTokenData?.decimals ?? 18))

Comment on lines 70 to 122
const onChange = (rawInputValue: string) => {
const numberStrWithoutCommas = rawInputValue.replace(/,/g, '')

if (numberStrWithoutCommas === '') {
setDisplayValue('')
setTokenValue('')
if (setCurrencyAmount) setCurrencyAmount('')
return
}

if (/^\d*\.?\d*$/.test(numberStrWithoutCommas)) {
let formattedForDisplay
const currentMaxDecimals = currency ? 2 : (selectedTokenData?.decimals ?? 6)

if (numberStrWithoutCommas.endsWith('.') && (numberStrWithoutCommas.match(/\./g) || []).length === 1) {
const partBeforeDecimal = numberStrWithoutCommas.slice(0, -1)
formattedForDisplay =
formatNumberForDisplay(partBeforeDecimal, { maxDecimals: currentMaxDecimals }) + '.'
} else {
formattedForDisplay = formatNumberForDisplay(numberStrWithoutCommas, {
maxDecimals: currentMaxDecimals,
})
}

if (formattedForDisplay === '' && numberStrWithoutCommas === '.') {
setDisplayValue('.')
} else {
setDisplayValue(formattedForDisplay)
}

if (currency && currencyMode === 'CUSTOM') {
if (setCurrencyAmount) setCurrencyAmount(formattedForDisplay)
const customCurrencyNumber = Number(numberStrWithoutCommas)
if (!isNaN(customCurrencyNumber) && currency.price > 0) {
const usdEquivalent = customCurrencyNumber / currency.price
setTokenValue(parseFloat(usdEquivalent.toFixed(6)).toString())
} else {
setTokenValue('')
}
} else {
setTokenValue(numberStrWithoutCommas)
if (currency && currency.price > 0) {
const customEquivalent = Number(numberStrWithoutCommas) * currency.price
if (setCurrencyAmount)
setCurrencyAmount(formatNumberForDisplay(customEquivalent.toString(), { maxDecimals: 2 }))
} else if (!currency && setCurrencyAmount && selectedTokenData?.price && selectedTokenData.price > 0) {
const usdEquivalent = Number(numberStrWithoutCommas) * selectedTokenData.price
setCurrencyAmount(formatNumberForDisplay(usdEquivalent.toString(), { maxDecimals: 2 }))
} else if (setCurrencyAmount) {
setCurrencyAmount('')
}
}
}
Copy link
Contributor

@coderabbitai coderabbitai bot May 15, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

Number + floating-point math introduces precision loss for currency values

All conversions here (Number(…), / currency.price, parseFloat(...).toFixed(6), * currency.price, etc.) rely on IEEE-754 number, which will truncate/round amounts above 2¹⁵³ or with >15 significant digits – typical with on-chain 18-dec tokens.

Consider switching to an arbitrary-precision library (e.g. bignumber.js or ethers.BigNumber) for every monetary path:

-import { ... } from '@/utils'
+import { ... } from '@/utils'
+import BigNumber from 'bignumber.js'
-const customCurrencyNumber = Number(numberStrWithoutCommas)
-...
-const usdEquivalent = customCurrencyNumber / currency.price
-setTokenValue(parseFloat(usdEquivalent.toFixed(6)).toString())
+const customCurrencyNumber = new BigNumber(numberStrWithoutCommas)
+...
+const usdEquivalent = customCurrencyNumber.div(currency.price)
+setTokenValue(usdEquivalent.toFixed(selectedTokenData?.decimals ?? 18))

This ensures ARS ↔ USD and token conversions do not silently lose sub-cent amounts.

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

🤖 Prompt for AI Agents
In src/components/Global/TokenAmountInput/index.tsx between lines 70 and 122,
the code uses JavaScript's Number type and floating-point arithmetic for
currency conversions, which causes precision loss for large or highly precise
token amounts. Replace all Number conversions and arithmetic operations
involving currency and token values with an arbitrary-precision library like
bignumber.js or ethers.BigNumber. Update calculations such as division,
multiplication, and toFixed formatting to use the library's methods to maintain
precision in all monetary conversions.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

can we use viem bignum or js native bigint

Copy link
Contributor

Choose a reason for hiding this comment

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

🧩 Analysis chain

@Hugo0 Yes, you can use viem's bigint utilities for this case since they're designed specifically for handling crypto token amounts with proper decimal precision. Viem provides formatters like parseUnits and formatUnits that work well with decimal values.

Native JavaScript BigInt would be problematic here because it can only represent integers (no decimal points), which doesn't work for currency values with cents or token amounts with decimals.

I recommend using viem if it's already in your project dependencies, as it will properly handle the conversions while maintaining precision for both fiat currencies (like ARS) and tokens.


🏁 Script executed:

#!/bin/bash
# Check if viem is a dependency in package.json
grep -E '"viem":' package.json || echo "viem not found in package.json"

# Check if viem is imported anywhere in the codebase
rg "import .* from ['\"]viem['\"]" --type ts --type tsx

# Look for any bigint usage in the codebase
rg "BigInt" --type ts --type tsx

Length of output: 239


@Hugo0 Yes, you can use viem's bigint utilities since viem is already in your project dependencies ("viem": "^2.21.48"). This would be my recommended approach because:

  1. Viem provides parseUnits and formatUnits functions specifically designed for handling token decimal precision
  2. These functions properly handle the conversion between human-readable decimals and on-chain integer representations

Native JavaScript BigInt would be problematic for your currency conversions because it can only represent integers (no decimal points), which doesn't work well for amounts with fractional components.

For implementation, you could replace the current Number-based conversions with something like:

// Instead of:
const customCurrencyNumber = Number(numberStrWithoutCommas)
const usdEquivalent = customCurrencyNumber / currency.price
setTokenValue(parseFloat(usdEquivalent.toFixed(6)).toString())

// Using viem:
import { parseUnits, formatUnits } from 'viem'
const decimals = selectedTokenData?.decimals ?? 6
const customCurrencyBigInt = parseUnits(numberStrWithoutCommas, 2) // ARS uses 2 decimals
const priceBigInt = parseUnits(currency.price.toString(), decimals)
const usdEquivalent = formatUnits((customCurrencyBigInt * BigInt(10**decimals)) / priceBigInt, decimals)
setTokenValue(usdEquivalent)

This approach will maintain precision throughout all your currency conversions.


🧬 Code Graph Analysis Results

Relevant snippets for understanding src/components/Global/TokenAmountInput/index.tsx and the user comment about using viem bignum or JS native bigint:


File: src/constants/zerodev.consts.ts (line 20)

export const PEANUT_WALLET_TOKEN = '0xaf88d065e77c8cc2239327c5edb3a432268e5831' // USDC Arbitrum address
  • Defines the constant address for the Peanut Wallet token (USDC on Arbitrum).
  • Used in TokenAmountInput to check if the selected token address matches this token.

File: src/context/tokenSelector.context.tsx (lines 25-45)

export const tokenSelectorContext = createContext({
    selectedTokenAddress: '',
    selectedChainID: '',
    selectedTokenDecimals: 0 as number | undefined,
    setSelectedTokenDecimals: (decimals: number | undefined) => {},
    setSelectedTokenAddress: (address: string) => {},
    setSelectedChainID: (chainID: string) => {},
    updateSelectedChainID: (chainID: string) => {},
    selectedTokenPrice: 0 as number | undefined,
    setSelectedTokenPrice: (price: number | undefined) => {},
    inputDenomination: 'TOKEN' as inputDenominationType,
    setInputDenomination: (denomination: inputDenominationType) => {},
    refetchXchainRoute: false as boolean,
    setRefetchXchainRoute: (value: boolean) => {},
    resetTokenContextProvider: () => {},
    isXChain: false as boolean,
    setIsXChain: (value: boolean) => {},
    selectedTokenData: undefined as ITokenPriceData | undefined,
    isFetchingTokenData: false as boolean,
    supportedSquidChainsAndTokens: {} as Record<string, interfaces.ISquidChain & { tokens: interfaces.ISquidToken[] }>,
})
  • Provides context for token selection state, including selected token address, decimals, price, and input denomination.
  • selectedTokenData contains token metadata such as decimals and price, used in TokenAmountInput for formatting and conversions.

File: src/utils/general.utils.ts (lines 248-259)

export const formatNumberForDisplay = (valueStr: string | undefined, options?: { maxDecimals?: number }): string => {
    if (valueStr === undefined || valueStr === null || valueStr.trim() === '') return ''
    const num = Number(valueStr)
    if (isNaN(num)) return ''

    const maxDecimals = options?.maxDecimals ?? 2 // Default to 2 if not specified

    return num.toLocaleString('en-US', {
        minimumFractionDigits: 0,
        maximumFractionDigits: maxDecimals,
    })
}
  • Formats a numeric string for display with a configurable maximum number of decimals.
  • Used extensively in TokenAmountInput to format token and currency amounts for user display.

File: src/utils/general.utils.ts (lines 322-343)

export function formatTokenAmount(amount?: number, maxFractionDigits?: number) {
    if (amount === undefined) return undefined
    maxFractionDigits = maxFractionDigits ?? 6

    // floor the amount
    const flooredAmount = Math.floor(amount * Math.pow(10, maxFractionDigits)) / Math.pow(10, maxFractionDigits)

    // Convert number to string to count significant digits

    const amountString = flooredAmount.toFixed(maxFractionDigits)
    const significantDigits = amountString.replace(/^0+\./, '').replace(/\.$/, '').replace(/0+$/, '').length

    // Calculate the number of fraction digits needed to have at least two significant digits
    const fractionDigits = Math.max(2 - significantDigits, 0)

    // Format the number with the calculated fraction digits
    const formattedAmount = flooredAmount.toLocaleString('en-US', {
        minimumFractionDigits: fractionDigits,
        maximumFractionDigits: maxFractionDigits,
    })
    return formattedAmount
}
  • Formats a token amount number with a floor operation and dynamic fraction digits to ensure at least two significant digits.
  • Used in TokenAmountInput to display token amounts with appropriate precision.

File: src/utils/general.utils.ts (lines 704-711)

export const estimateIfIsStableCoinFromPrice = (tokenPrice: number) => {
    // if the tokenprice is between .995 and 1.005, return 1
    if (tokenPrice >= 0.995 && tokenPrice <= 1.005) {
        return true
    } else {
        return false
    }
}
  • Utility to estimate if a token is a stablecoin based on its price being close to 1.
  • Used in TokenAmountInput to adjust display logic for stablecoins.

File: src/hooks/wallet/useWallet.ts (lines 20-82)

export const useWallet = () => {
    const dispatch = useAppDispatch()
    const { address, isKernelClientReady, handleSendUserOpEncoded } = useZeroDev()
    const { balance } = useWalletStore()

    const sendTransactions = useCallback(
        async (unsignedTxs: peanutInterfaces.IPeanutUnsignedTransaction[], chainId?: string) => {
            const params = unsignedTxs.map((tx: peanutInterfaces.IPeanutUnsignedTransaction) => ({
                to: tx.to! as Hex,
                value: tx.value?.valueOf() ?? 0n,
                data: (tx.data as Hex | undefined) ?? '0x',
            }))
            chainId = chainId ?? PEANUT_WALLET_CHAIN.id.toString()
            let receipt = await handleSendUserOpEncoded(params, chainId)
            return receipt
        },
        [handleSendUserOpEncoded]
    )

    const fetchBalance = useCallback(async () => {
        if (!address) {
            console.warn('Cannot fetch balance, address is undefined.')
            return
        }
        const balance = await peanutPublicClient.readContract({
            address: PEANUT_WALLET_TOKEN,
            abi: erc20Abi,
            functionName: 'balanceOf',
            args: [address as Hex],
        })
        dispatch(walletActions.setBalance(balance))
    }, [address, dispatch])

    const getRewardWalletBalance = useCallback(async () => {
        if (!address) {
            console.warn('Cannot fetch reward balance, address is undefined.')
            return ''
        }
        const balance = await pintaPublicClient.readContract({
            address: PINTA_WALLET_TOKEN,
            abi: erc20Abi,
            functionName: 'balanceOf',
            args: [getAddress(address)],
        })
        const formatedBalance = formatAmount(formatUnits(balance, PINTA_WALLET_TOKEN_DECIMALS))
        dispatch(walletActions.setRewardWalletBalance(formatedBalance))
    }, [address, dispatch])

    useEffect(() => {
        if (!address) return
        fetchBalance()
        getRewardWalletBalance()
    }, [address, fetchBalance, getRewardWalletBalance])

    return {
        address: address!,
        balance: BigInt(balance),
        isConnected: isKernelClientReady,
        sendTransactions,
        getRewardWalletBalance,
        fetchBalance,
    }
}
  • Custom React hook managing wallet state and interactions.
  • Uses BigInt for balance values (balance: BigInt(balance)).
  • Shows usage of native JS BigInt for balances.
  • Relevant to the user comment about using viem bignum or JS native bigint.

Summary of relevance:

  • The TokenAmountInput component currently uses string and number types for token amounts and conversions, with formatting utilities that rely on JS Number and string formatting.
  • The wallet hook useWallet uses native JS BigInt for balance values.
  • No direct usage of viem bignum or other big number libraries is visible in TokenAmountInput.
  • Utilities for formatting numbers (formatNumberForDisplay, formatTokenAmount) use JS Number and toLocaleString.
  • The user comment about using viem bignum or JS native bigint relates to how token amounts and balances are handled and formatted in this component and possibly the wallet hook.

If further investigation or code changes are needed regarding big number handling, these snippets provide the context of current usage of number types and formatting in the relevant files.


🧠 Learnings used
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.

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

♻️ Duplicate comments (1)
src/components/Global/TokenAmountInput/index.tsx (1)

70-122: ⚠️ Potential issue

Number + floating-point math introduces precision loss for currency values

All conversions here (Number(…), / currency.price, parseFloat(...).toFixed(6), * currency.price, etc.) rely on IEEE-754 number, which can lead to precision issues with currency values.

Consider using a decimal math library like bignumber.js for all currency conversions:

import { estimateIfIsStableCoinFromPrice, formatTokenAmount, formatNumberForDisplay, formatAmount } from '@/utils'
+import BigNumber from 'bignumber.js'
-                const customCurrencyNumber = Number(numberStrWithoutCommas)
-                if (!isNaN(customCurrencyNumber) && currency.price > 0) {
-                    const usdEquivalent = customCurrencyNumber / currency.price
-                    setTokenValue(parseFloat(usdEquivalent.toFixed(6)).toString())
+                const customCurrencyNumber = new BigNumber(numberStrWithoutCommas)
+                if (!customCurrencyNumber.isNaN() && currency.price > 0) {
+                    const usdEquivalent = customCurrencyNumber.dividedBy(currency.price)
+                    setTokenValue(usdEquivalent.toFixed(6))
🧹 Nitpick comments (2)
src/components/Global/TokenAmountInput/index.tsx (2)

46-68: Use useMemo instead of recalculating in useEffect

The display value calculation logic would benefit from being moved to a useMemo hook instead of recalculating in useEffect, which would better follow React best practices and potentially reduce rerenders.

-useEffect(() => {
+const newDisplayValueBasedOnTokenValue = useMemo(() => {
     let newDisplayValueBasedOnTokenValue = ''
     if (currency && currencyMode === 'CUSTOM') {
         if (tokenValue && currency.price > 0) {
             const customCurrencyValue = Number(tokenValue) * currency.price
             newDisplayValueBasedOnTokenValue = formatNumberForDisplay(customCurrencyValue.toString(), {
                 maxDecimals: 2,
             })
         } else {
             newDisplayValueBasedOnTokenValue = ''
         }
     } else {
         newDisplayValueBasedOnTokenValue = tokenValue
             ? formatNumberForDisplay(tokenValue, { maxDecimals: primaryDisplayMaxDecimals })
             : ''
     }
+    return newDisplayValueBasedOnTokenValue
+}, [tokenValue, currencyMode, currency, primaryDisplayMaxDecimals])

+useEffect(() => {
     if (displayValue.endsWith('.') && newDisplayValueBasedOnTokenValue === displayValue.slice(0, -1)) {
         // Do nothing; preserve user's partial decimal input
     } else if (newDisplayValueBasedOnTokenValue !== displayValue) {
         setDisplayValue(newDisplayValueBasedOnTokenValue)
     }
-}, [tokenValue, currencyMode, currency, displayValue, primaryDisplayMaxDecimals])
+}, [newDisplayValueBasedOnTokenValue, displayValue])

125-139: Add error handling for invalid max value

The max click handler should protect against NaN or invalid inputs that could result from an invalid maxValue.

const handleMaxClick = (e: React.MouseEvent) => {
    e.preventDefault()
    e.stopPropagation()
    if (maxValue) {
+       if (isNaN(Number(maxValue))) {
+           console.warn('Invalid maxValue provided to TokenAmountInput')
+           return
+       }
        setTokenValue(maxValue)
        if (currency && currency.price > 0) {
            const customEquivalent = Number(maxValue) * currency.price
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b1d2a2a and f9f24ce.

📒 Files selected for processing (2)
  • src/components/Global/TokenAmountInput/index.tsx (5 hunks)
  • src/components/TransactionDetails/TransactionCard.tsx (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/TransactionDetails/TransactionCard.tsx
🧰 Additional context used
🧠 Learnings (1)
src/components/Global/TokenAmountInput/index.tsx (3)
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.
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 `''`.
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.
🔇 Additional comments (5)
src/components/Global/TokenAmountInput/index.tsx (5)

38-42: Input handling improvements are excellent!

The switch to always using type="text" with inputMode="decimal" combined with a separate displayValue state provides better control over formatting while maintaining a good mobile experience. This decoupling allows for proper thousands separators without disrupting the editing experience.

Also applies to: 70-122


83-92: Good handling of decimal point during input

The special case handling for decimal points during input preserves the user's input state when typing, which greatly improves the user experience. This is a thoughtful addition.


167-174: Clean currency mode toggle refactoring

Extracting the toggle logic into a separate function eliminates duplication and centralizes the mode switching logic. This is a good refactoring that improves maintainability.


208-228: Excellent input filtering with detailed comments

The key filtering logic is thorough and well-documented. It properly handles numeric input, single decimal points, and essential control keys while preventing invalid characters.


258-310: Comprehensive converted amount display with clear documentation

The secondary display logic handles all conversion scenarios clearly with detailed comments. This makes the code much more maintainable and easier to understand.

Copy link
Contributor

@kushagrasarathe kushagrasarathe left a comment

Choose a reason for hiding this comment

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

@Hugo0 see blocker comment

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

♻️ Duplicate comments (1)
src/components/Global/TokenAmountInput/index.tsx (1)

127-150: Floating-point conversions still introduce precision loss

All arithmetic here (parseFloat, * currency.price, / currency.price, toFixed(...)) uses IEEE-754 number.
Values such as 0.1 * 1e18 or 18-dec token amounts lose sub-cent precision, the exact bug reported in past reviews (see previous comment thread).

Please switch to an arbitrary-precision approach (e.g. viem’s parseUnits / formatUnits or bignumber.js) for every monetary calculation.

🧹 Nitpick comments (4)
src/utils/format.utils.ts (1)

80-85: Fallback formatting should respect maximumFractionDigits

When Intl.NumberFormat throws, the fallback always uses minDigits.
Using toFixed(minDigits) can show too many decimals if the caller deliberately set a lower maxDigits.

-return `${currencyCode} ${numericValue.toFixed(minDigits)}`
+return `${currencyCode} ${numericValue.toFixed(maxDigits)}`
src/components/Global/TokenAmountInput/index.tsx (3)

217-224: onBlur mutates state twice – potential race with async React updates

Calling setInputValue and immediately onChange (which itself calls setInputValue & setTokenValue) may cause stale closures or double renders.

Instead, compute cleaned once and call only onChange:

-if (inputValue.endsWith('.')) {
-    const cleaned = inputValue.slice(0, -1) || ''
-    setInputValue(cleaned)
-    onChange(cleaned)
-}
+if (inputValue.endsWith('.')) {
+    onChange(inputValue.slice(0, -1))
+}

327-340: Key-filter logic blocks ‘E’/‘+’/‘-’ but allows Ctrl/Meta variations

The intent is to prevent scientific notation. However, users can still paste "1e6" or "1e+6".
Consider sanitising pasted content in onPaste instead of relying solely on onKeyDown.


355-375: Wallet balance formatting duplicates complex branching

This 40-line ternary forest is hard to read and error-prone. Extract a helper:

const formatBalance = () => {
  if (isNaN(numericWalletBalance)) return walletBalance
  ...
}

Then call {formatBalance()} in JSX.
Improves readability and unit-testability.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f9f24ce and 67bcfc5.

📒 Files selected for processing (4)
  • src/components/Global/TokenAmountInput/index.tsx (3 hunks)
  • src/components/Payment/PaymentForm/index.tsx (2 hunks)
  • src/utils/format.utils.ts (1 hunks)
  • src/utils/general.utils.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/utils/general.utils.ts
🧰 Additional context used
🧠 Learnings (2)
src/components/Payment/PaymentForm/index.tsx (3)
Learnt from: Hugo0
PR: peanutprotocol/peanut-ui#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.
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.
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.
src/components/Global/TokenAmountInput/index.tsx (2)
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.
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 `''`.
🧬 Code Graph Analysis (1)
src/components/Global/TokenAmountInput/index.tsx (3)
src/redux/slices/send-flow-slice.ts (1)
  • setTokenValue (48-50)
src/utils/format.utils.ts (1)
  • formatCurrencyWithIntl (39-87)
src/utils/general.utils.ts (1)
  • estimateIfIsStableCoinFromPrice (735-742)

Comment on lines +48 to +50
const numericValue = typeof value === 'string' ? parseFloat(value) : value
if (isNaN(numericValue)) return '' // Return empty if not a valid number

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Strip thousands separators before parseFloat to avoid silent mis-parsing

parseFloat('1,234.56') stops at the comma and returns 1, which will silently corrupt values entered with thousands separators – exactly the problem this PR tries to fix elsewhere.
Sanitise the string first:

-const numericValue = typeof value === 'string' ? parseFloat(value) : value
+const numericValue = typeof value === 'string'
+  ? parseFloat(value.replace(/,/g, ''))
+  : value
📝 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 numericValue = typeof value === 'string' ? parseFloat(value) : value
if (isNaN(numericValue)) return '' // Return empty if not a valid number
const numericValue = typeof value === 'string'
? parseFloat(value.replace(/,/g, ''))
: value
if (isNaN(numericValue)) return '' // Return empty if not a valid number
🤖 Prompt for AI Agents
In src/utils/format.utils.ts around lines 48 to 50, the code uses parseFloat
directly on strings that may contain thousands separators like commas, causing
incorrect parsing. To fix this, sanitize the input string by removing all
thousands separators (e.g., commas) before calling parseFloat, ensuring the
numeric value is correctly parsed without silent corruption.

Comment on lines +69 to +71
// Ensure minDigits is not greater than maxDigits
minDigits = Math.min(minDigits, maxDigits)

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

minDigits = Math.min(minDigits, maxDigits) risks NaN when both are undefined

If the caller passes both minimumFractionDigits and maximumFractionDigits as undefined and style !== 'currency', the earlier branch leaves them undefined; Math.min(undefined, undefined) returns NaN, later triggering RangeError in Intl.NumberFormat.

Guard explicitly:

-if (minDigits === undefined) minDigits = 0
-if (maxDigits === undefined) maxDigits = 20
+minDigits = minDigits ?? 0
+maxDigits = maxDigits ?? 20

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

🤖 Prompt for AI Agents
In src/utils/format.utils.ts around lines 69 to 71, the assignment minDigits =
Math.min(minDigits, maxDigits) can result in NaN if both minDigits and maxDigits
are undefined, causing a RangeError later. To fix this, add explicit checks to
ensure minDigits and maxDigits are defined before calling Math.min, and provide
default values or handle the undefined case to prevent NaN from being assigned.

Comment on lines 137 to 144
const maxAllowedDecimals = currency || inputDenomination === 'USD' ? 2 : (selectedTokenData?.decimals ?? 6)

// Convert to actual token value for payment processing
if (currency && currencyMode === 'CUSTOM') {
// Convert from custom currency to token value
const actualTokenValue = formatTokenAmount(Number(inputValue) / currency.price)!
setCurrencyAmount?.(inputValue)
setTokenValue(actualTokenValue.toString())
if (maxAllowedDecimals === 0 && sanitizedValue.includes('.')) {
sanitizedValue = sanitizedValue.split('.')[0]
} else if (parts.length === 2 && parts[1].length > maxAllowedDecimals) {
sanitizedValue = `${parts[0]}.${parts[1].substring(0, maxAllowedDecimals)}`
}

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

maxAllowedDecimals can exceed the 20-digit limit of Intl.NumberFormat

When a token has decimals > 20 (some have 24+), the later call to formatCurrencyWithIntl will throw a RangeError.
Clamp the value:

-const maxAllowedDecimals = currency || inputDenomination === 'USD'
-  ? 2
-  : (selectedTokenData?.decimals ?? 6)
+const DECIMAL_LIMIT = 20
+const maxAllowedDecimals = currency || inputDenomination === 'USD'
+  ? 2
+  : Math.min(selectedTokenData?.decimals ?? 6, DECIMAL_LIMIT)
📝 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 maxAllowedDecimals = currency || inputDenomination === 'USD' ? 2 : (selectedTokenData?.decimals ?? 6)
// Convert to actual token value for payment processing
if (currency && currencyMode === 'CUSTOM') {
// Convert from custom currency to token value
const actualTokenValue = formatTokenAmount(Number(inputValue) / currency.price)!
setCurrencyAmount?.(inputValue)
setTokenValue(actualTokenValue.toString())
if (maxAllowedDecimals === 0 && sanitizedValue.includes('.')) {
sanitizedValue = sanitizedValue.split('.')[0]
} else if (parts.length === 2 && parts[1].length > maxAllowedDecimals) {
sanitizedValue = `${parts[0]}.${parts[1].substring(0, maxAllowedDecimals)}`
}
const DECIMAL_LIMIT = 20
const maxAllowedDecimals = currency || inputDenomination === 'USD'
? 2
: Math.min(selectedTokenData?.decimals ?? 6, DECIMAL_LIMIT)
if (maxAllowedDecimals === 0 && sanitizedValue.includes('.')) {
sanitizedValue = sanitizedValue.split('.')[0]
} else if (parts.length === 2 && parts[1].length > maxAllowedDecimals) {
sanitizedValue = `${parts[0]}.${parts[1].substring(0, maxAllowedDecimals)}`
}
🤖 Prompt for AI Agents
In src/components/Global/TokenAmountInput/index.tsx around lines 137 to 144, the
maxAllowedDecimals value can exceed 20, which causes a RangeError in
Intl.NumberFormat. Fix this by clamping maxAllowedDecimals to a maximum of 20
before using it in formatting functions. This ensures the decimal precision does
not exceed Intl.NumberFormat's supported limit.

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

🧹 Nitpick comments (3)
src/components/TransactionDetails/TransactionCard.tsx (3)

27-32: Consider moving the currency symbol helper to utils

The helper function getDisplayCurrencySymbol is well-implemented but as noted in the comment, it would be better placed in a utils file for reuse across the application. This would promote code reuse and maintainability.

- // Helper function to get currency symbol based on code - can be moved to utils if used elsewhere
- const getDisplayCurrencySymbol = (code?: string, fallbackSymbol: string = '$'): string => {
-     if (code === 'ARS') return 'AR$'
-     if (code === 'USD') return '$'
-     return fallbackSymbol
- }

And add to an appropriate utils file:

// In src/utils/currency.utils.ts or similar
export const getDisplayCurrencySymbol = (code?: string, fallbackSymbol: string = '$'): string => {
    if (code === 'ARS') return 'AR$'
    if (code === 'USD') return '$'
    return fallbackSymbol
}

94-99: Consider extracting token decimal logic to a helper function

The decimal precision determination is a bit complex and could be extracted to a helper function for better readability and maintainability.

- const decimalsForDisplay = actualCurrencyCode // If it's a known currency (USD, ARS)
-     ? defaultDisplayDecimals
-     : transaction.extraDataForDrawer?.originalType === EHistoryEntryType.SEND_LINK // Example: check token specific decimals if available
-       ? ((transaction.extraDataForDrawer as any)?.tokenDecimalsForDisplay ?? 6) // Fallback to 6 for tokens
-       : 6 // General fallback for other tokens
+ const decimalsForDisplay = getDisplayDecimals(transaction, actualCurrencyCode, defaultDisplayDecimals)

And add a helper function:

const getDisplayDecimals = (
  transaction: TransactionDetails, 
  currencyCode?: string, 
  defaultCurrencyDecimals: number = 2
): number => {
  if (currencyCode) {
    return defaultCurrencyDecimals;
  }
  
  // For tokens, check if we have specific decimals
  if (transaction.extraDataForDrawer?.originalType === EHistoryEntryType.SEND_LINK) {
    return (transaction.extraDataForDrawer as any)?.tokenDecimalsForDisplay ?? 6;
  }
  
  // General fallback for other tokens
  return 6;
}

97-97: Avoid type casting with as any when possible

Using as any bypasses TypeScript's type checking, which can lead to runtime errors. Consider adding proper type definitions for the extraDataForDrawer object to include tokenDecimalsForDisplay.

- ? ((transaction.extraDataForDrawer as any)?.tokenDecimalsForDisplay ?? 6) // Fallback to 6 for tokens
+ ? (transaction.extraDataForDrawer?.tokenDecimalsForDisplay ?? 6) // Fallback to 6 for tokens

You would need to update the TransactionDetails interface to include this property:

// In the TransactionDetails interface
extraDataForDrawer?: {
    originalType: EHistoryEntryType
    originalUserRole: EHistoryUserRole
    link?: string
    isLinkTransaction?: boolean
    transactionCardType?: TransactionCardType
    tokenDecimalsForDisplay?: number // Add this property
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cddd46d and 0207b9e.

📒 Files selected for processing (1)
  • src/components/TransactionDetails/TransactionCard.tsx (3 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/components/TransactionDetails/TransactionCard.tsx (4)
src/components/Global/Badges/StatusBadge.tsx (1)
  • StatusType (4-4)
src/components/Global/Card/index.tsx (1)
  • CardPosition (4-4)
src/components/TransactionDetails/transactionTransformer.ts (1)
  • TransactionDetails (15-41)
src/utils/general.utils.ts (1)
  • formatNumberForDisplay (248-290)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Deploy-Preview
🔇 Additional comments (5)
src/components/TransactionDetails/TransactionCard.tsx (5)

9-9: Updated imports to support improved currency formatting

The imports now include formatNumberForDisplay and transaction history enums which are essential for the new currency formatting logic. This is a good change that aligns with the PR's objective to standardize currency formatting.

Also applies to: 12-12


60-63: Good initialization for currency display logic

Setting up the display variables and handling special cases like JPY's zero decimal places is a good practice. The code now correctly prepares for the different currency formatting requirements.


64-85: Improved sign handling for ARS transactions

This change properly addresses the incorrect plus/minus signs issue mentioned in the PR objectives. The sign is now determined based on the transaction type and user role, ensuring consistent financial notation (debits as negative, credits as positive).


86-105: Enhanced currency and token amount formatting

The refactored code appropriately handles different scenarios:

  1. Token transactions without fiat values
  2. Currency transactions with appropriate symbols
  3. Different decimal precision for different currency types

This implementation addresses the precision and formatting issues mentioned in the PR objectives.


140-140: Simplified amount display in JSX

Using the pre-computed finalDisplayAmount variable in the JSX makes the template cleaner and more maintainable compared to having inline formatting logic.

@kushagrasarathe kushagrasarathe merged commit 8649339 into peanut-wallet-dev May 15, 2025
5 checks passed
@Hugo0 Hugo0 mentioned this pull request May 15, 2025
@coderabbitai coderabbitai bot mentioned this pull request Jun 28, 2025
@Hugo0 Hugo0 deleted the fix/ars-fixes1 branch July 3, 2025 18:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants