Repository: peanutprotocol/peanut-ui
Pull request #852: [TASK-10148] feat: first commit multicurrenct, only pesos for now
Author: login: jjramirezn
Created: 2025-05-14T20:06:08Z | Merged: 2025-05-14T21:51:11Z

contributes to TASK-11065

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Introduced support for payments and transaction history in multiple currencies, including ARS and USD.
  - Added a currency toggle and dual-currency input mode to payment forms, allowing users to enter amounts in their preferred currency.
  - Enhanced transaction and payment views to display amounts with appropriate currency symbols and formatting.
  - Enabled public transaction history viewing by username.

- **Bug Fixes**
  - Improved handling and display of Argentine Peso (ARS) currency formatting in transaction lists.

- **Refactor**
  - Streamlined transaction and profile components to use a unified transaction history display and removed deprecated transaction props.

- **Chores**
  - Added new hooks and utility functions for fetching and managing currency data and prices.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/src/app/[...recipient]/client.tsx b/src/app/[...recipient]/client.tsx
index 18038d1fe..ac5531f1e 100644
--- a/src/app/[...recipient]/client.tsx
+++ b/src/app/[...recipient]/client.tsx
@@ -18,6 +18,7 @@ import { formatAmount } from '@/utils'
 import { useRouter, useSearchParams } from 'next/navigation'
 import { useEffect, useState } from 'react'
 import { twMerge } from 'tailwind-merge'
+import { useCurrency } from '@/hooks/useCurrency'
 
 interface Props {
     recipient: string[]
@@ -34,6 +35,12 @@ export default function PaymentPage({ recipient, isDirectPay = false }: Props) {
     const chargeId = searchParams.get('chargeId')
     const requestId = searchParams.get('id')
     const router = useRouter()
+    const {
+        code: currencyCode,
+        symbol: currencySymbol,
+        price: currencyPrice,
+    } = useCurrency(searchParams.get('currency'))
+    const [currencyAmount, setCurrencyAmount] = useState<string>('')
 
     useEffect(() => {
         let isMounted = true
@@ -208,11 +215,6 @@ export default function PaymentPage({ recipient, isDirectPay = false }: Props) {
                 fullName={username} // todo: replace with actual full name, getByUsername only returns username
                 isVerified={user?.user.kycStatus === 'approved'}
                 isLoggedIn={!!user}
-                // todo: to be implemented in history project
-                transactions={{
-                    sent: 0,
-                    received: 0,
-                }}
                 onSendClick={handleSendClick}
             />
         )
@@ -237,11 +239,29 @@ export default function PaymentPage({ recipient, isDirectPay = false }: Props) {
             <div>
                 {currentView === 'INITIAL' && (
                     <div className="space-y-4">
-                        <InitialPaymentView {...(parsedPaymentData as ParsedURL)} />
+                        <InitialPaymentView
+                            {...(parsedPaymentData as ParsedURL)}
+                            currency={
+                                currencyCode
+                                    ? {
+                                          code: currencyCode,
+                                          symbol: currencySymbol!,
+                                          price: currencyPrice!,
+                                      }
+                                    : undefined
+                            }
+                            setCurrencyAmount={(value: string | undefined) => setCurrencyAmount(value || '')}
+                            currencyAmount={currencyAmount}
+                        />
                     </div>
                 )}
                 {currentView === 'CONFIRM' && (
-                    <ConfirmPaymentView isPintaReq={parsedPaymentData?.token?.symbol === 'PNT'} />
+                    <ConfirmPaymentView
+                        isPintaReq={parsedPaymentData?.token?.symbol === 'PNT'}
+                        currencyAmount={
+                            currencyCode && currencyAmount ? `${currencySymbol} ${currencyAmount}` : undefined
+                        }
+                    />
                 )}
                 {currentView === 'STATUS' && (
                     <>
@@ -252,6 +272,9 @@ export default function PaymentPage({ recipient, isDirectPay = false }: Props) {
                                 headerTitle="Send"
                                 recipientType={parsedPaymentData?.recipient?.recipientType}
                                 type="SEND"
+                                currencyAmount={
+                                    currencyCode && currencyAmount ? `${currencySymbol} ${currencyAmount}` : undefined
+                                }
                             />
                         )}
                     </>
diff --git a/src/app/actions/currency.ts b/src/app/actions/currency.ts
new file mode 100644
index 000000000..a2e98ae4f
--- /dev/null
+++ b/src/app/actions/currency.ts
@@ -0,0 +1,27 @@
+'use server'
+import { unstable_cache } from 'next/cache'
+import { fetchWithSentry } from '@/utils'
+
+export const getCurrencyPrice = unstable_cache(
+    async (currencyCode: string): Promise<number> => {
+        if (currencyCode === 'USD') return 1
+        if (currencyCode !== 'ARS') {
+            throw new Error('Unsupported currency')
+        }
+        const response = await fetchWithSentry('https://dolarapi.com/v1/dolares/cripto')
+        const data = await response.json()
+
+        if (!data.compra || !data.venta) {
+            throw new Error('Invalid response from dolarapi')
+        }
+
+        // Average between buy and sell price
+        const price = (data.compra + data.venta) / 2
+
+        return price
+    },
+    ['getCurrencyPrice'],
+    {
+        revalidate: 5 * 60, // 5 minutes
+    }
+)
diff --git a/src/components/Global/TokenAmountInput/index.tsx b/src/components/Global/TokenAmountInput/index.tsx
index e2a980144..6cadf835d 100644
--- a/src/components/Global/TokenAmountInput/index.tsx
+++ b/src/components/Global/TokenAmountInput/index.tsx
@@ -2,27 +2,35 @@ import { PEANUT_WALLET_TOKEN } from '@/constants'
 import { tokenSelectorContext } from '@/context'
 import { useWallet } from '@/hooks/wallet/useWallet'
 import { estimateIfIsStableCoinFromPrice, formatAmountWithoutComma, formatTokenAmount } from '@/utils'
-import { useContext, useEffect, useMemo, useRef } from 'react'
+import { useContext, useEffect, useMemo, useRef, useState } from 'react'
 import Icon from '../Icon'
 
 interface TokenAmountInputProps {
     className?: string
     tokenValue: string | undefined
     setTokenValue: (tokenvalue: string | undefined) => void
+    setCurrencyAmount?: (currencyvalue: string | undefined) => void
     onSubmit?: () => void
     maxValue?: string
     disabled?: boolean
     walletBalance?: string
+    currency?: {
+        code: string
+        symbol: string
+        price: number
+    }
 }
 
 const TokenAmountInput = ({
     className,
     tokenValue,
     setTokenValue,
+    setCurrencyAmount,
     onSubmit,
     maxValue,
     disabled,
     walletBalance,
+    currency,
 }: TokenAmountInputProps) => {
     const { inputDenomination, setInputDenomination, selectedTokenData, selectedTokenAddress } =
         useContext(tokenSelectorContext)
@@ -30,8 +38,25 @@ const TokenAmountInput = ({
     const inputType = useMemo(() => (window.innerWidth < 640 ? 'text' : 'number'), [])
     const { isConnected: isPeanutWallet } = useWallet()
 
-    const onChange = (tokenvalue: string) => {
-        setTokenValue(tokenvalue)
+    // Add state for currency mode (USD or custom currency)
+    const [currencyMode, setCurrencyMode] = useState<'USD' | 'CUSTOM'>(currency ? 'CUSTOM' : 'USD')
+
+    // Store display value for input field (what user sees when typing)
+    const [displayValue, setDisplayValue] = useState<string>(tokenValue || '')
+
+    const onChange = (inputValue: string) => {
+        // Update display value (what the user sees)
+        setDisplayValue(inputValue)
+
+        // 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())
+        } else {
+            setTokenValue(inputValue)
+        }
     }
 
     const handleMaxClick = (e: React.MouseEvent) => {
@@ -45,13 +70,14 @@ const TokenAmountInput = ({
 
     useEffect(() => {
         if (inputRef.current) {
-            if (tokenValue?.length !== 0) {
-                inputRef.current.style.width = `${(tokenValue?.length ?? 0) + 1}ch`
+            const valueToShow = currency && currencyMode === 'CUSTOM' ? displayValue : tokenValue
+            if (valueToShow?.length !== 0) {
+                inputRef.current.style.width = `${(valueToShow?.length ?? 0) + 1}ch`
             } else {
                 inputRef.current.style.width = `4ch`
             }
         }
-    }, [tokenValue])
+    }, [tokenValue, displayValue, currency, currencyMode])
 
     const parentWidth = useMemo(() => {
         if (inputRef.current && inputRef.current.parentElement) {
@@ -78,13 +104,14 @@ const TokenAmountInput = ({
             <div className="flex h-14 w-full flex-row items-center justify-center gap-1">
                 {/* Show dollar sign if either:
                     1. We have price data from API and it's either in USD mode or is a stablecoin
-                         // TODO: if multiple denominations (USD, EURO, etc), show the correct one
                     2. It's a Peanut Wallet USDC transaction (which we know is always $1)
                     This prevents flickering/not loading of the dollar sign while waiting for price data */}
                 {(selectedTokenData?.price || (isPeanutWallet && selectedTokenAddress === PEANUT_WALLET_TOKEN)) &&
                     (inputDenomination === 'USD' ||
                     (selectedTokenData?.price ? estimateIfIsStableCoinFromPrice(selectedTokenData.price) : false) ? (
-                        <label className={`text-h1 ${tokenValue ? 'text-black' : 'text-gray-2'}`}>$</label>
+                        <label className={`text-h1 ${tokenValue ? 'text-black' : 'text-gray-2'}`}>
+                            {currencyMode === 'CUSTOM' && currency ? currency.symbol : '$'}
+                        </label>
                     ) : (
                         <label className="sr-only text-h1">$</label>
                     ))}
@@ -98,7 +125,7 @@ const TokenAmountInput = ({
                     ref={inputRef}
                     inputMode="decimal"
                     type={inputType}
-                    value={tokenValue}
+                    value={currency && currencyMode === 'CUSTOM' ? displayValue : tokenValue}
                     step="any"
                     min="0"
                     autoComplete="off"
@@ -124,23 +151,71 @@ const TokenAmountInput = ({
                 )}
             </div>
             {walletBalance && (
-                <div className="mt-0.5 text-center text-xs text-grey-1">Your balance: ${walletBalance}</div>
+                <div className="mt-0.5 text-center text-xs text-grey-1">
+                    Your balance: {currencyMode === 'CUSTOM' && currency ? 'US$' : '$'}
+                    {walletBalance}
+                </div>
             )}
-            {selectedTokenData?.price && !estimateIfIsStableCoinFromPrice(selectedTokenData.price) && (
-                <div className="flex w-full flex-row items-center justify-center gap-1">
+            {/* Show conversion line and toggle */}
+            {((selectedTokenData?.price && !estimateIfIsStableCoinFromPrice(selectedTokenData.price)) || currency) && (
+                <div
+                    className="flex w-full cursor-pointer flex-row items-center justify-center gap-1"
+                    onClick={(e) => {
+                        e.preventDefault()
+                        if (currency) {
+                            // Toggle currency mode and convert display value
+                            if (currencyMode === 'USD') {
+                                // Going from USD to custom currency
+                                setCurrencyMode('CUSTOM')
+                                // Convert display value to show in custom currency
+                                if (tokenValue) {
+                                    setDisplayValue((Number(tokenValue) * currency.price).toString())
+                                }
+                            } else {
+                                // Going from custom currency to USD
+                                setCurrencyMode('USD')
+                                // Display the actual token value (USD)
+                                setDisplayValue(tokenValue || '')
+                            }
+                        } else if (selectedTokenData?.price) {
+                            setInputDenomination(inputDenomination === 'USD' ? 'TOKEN' : 'USD')
+                        }
+                    }}
+                >
                     <label className="text-base text-grey-1">
                         {!tokenValue
                             ? '0'
-                            : inputDenomination === 'USD'
-                              ? formatTokenAmount(Number(tokenValue) / (selectedTokenData?.price ?? 0))
-                              : '$' + formatTokenAmount(Number(tokenValue) * (selectedTokenData?.price ?? 0))}
+                            : currency && currencyMode === 'CUSTOM'
+                              ? formatTokenAmount(Number(tokenValue)) + ' USD'
+                              : currency && currencyMode === 'USD'
+                                ? formatTokenAmount(Number(tokenValue) * currency.price) + ' ' + currency.code
+                                : inputDenomination === 'USD'
+                                  ? formatTokenAmount(Number(tokenValue) / (selectedTokenData?.price ?? 1))
+                                  : '$' + formatTokenAmount(Number(tokenValue) * (selectedTokenData?.price ?? 1))}
                     </label>
                     {!disabled && (
                         <button
                             onClick={(e) => {
                                 e.preventDefault()
-                                if (selectedTokenData?.price)
+                                e.stopPropagation()
+                                if (currency) {
+                                    // Toggle currency mode and convert display value
+                                    if (currencyMode === 'USD') {
+                                        // Going from USD to custom currency
+                                        setCurrencyMode('CUSTOM')
+                                        // Convert display value to show in custom currency
+                                        if (tokenValue) {
+                                            setDisplayValue((Number(tokenValue) * currency.price).toString())
+                                        }
+                                    } else {
+                                        // Going from custom currency to USD
+                                        setCurrencyMode('USD')
+                                        // Display the actual token value (USD)
+                                        setDisplayValue(tokenValue || '')
+                                    }
+                                } else if (selectedTokenData?.price) {
                                     setInputDenomination(inputDenomination === 'USD' ? 'TOKEN' : 'USD')
+                                }
                             }}
                         >
                             <Icon name={'switch'} className="rotate-90 cursor-pointer fill-grey-1" />
diff --git a/src/components/Home/HomeHistory.tsx b/src/components/Home/HomeHistory.tsx
index fecdc093a..fb3d4b11b 100644
--- a/src/components/Home/HomeHistory.tsx
+++ b/src/components/Home/HomeHistory.tsx
@@ -5,7 +5,6 @@ import PeanutLoading from '@/components/Global/PeanutLoading'
 import TransactionCard from '@/components/TransactionDetails/TransactionCard'
 import { mapTransactionDataForDrawer } from '@/components/TransactionDetails/transactionTransformer'
 import { useTransactionHistory } from '@/hooks/useTransactionHistory'
-import { useUserStore } from '@/redux/hooks'
 import * as Sentry from '@sentry/nextjs'
 import Link from 'next/link'
 import { CardPosition } from '../Global/Card'
@@ -13,13 +12,11 @@ import { CardPosition } from '../Global/Card'
 /**
  * component to display a preview of the most recent transactions on the home page.
  */
-const HomeHistory = () => {
-    // fetch current user's username for context in data mapping
-    const { user } = useUserStore()
-    const currentUserUsername = user?.user.username
-
+const HomeHistory = ({ isPublic = false, username }: { isPublic?: boolean; username?: string }) => {
     // fetch the latest 5 transaction history entries
-    const { data: historyData, isLoading, isError, error } = useTransactionHistory({ mode: 'latest', limit: 5 }) // Updated limit to 5
+    const mode = isPublic ? 'public' : 'latest'
+    const limit = isPublic ? 20 : 5
+    const { data: historyData, isLoading, isError, error } = useTransactionHistory({ mode, limit, username }) // Updated limit to 5
 
     // show loading state
     if (isLoading) {
@@ -48,10 +45,14 @@ const HomeHistory = () => {
     return (
         <div className="mx-auto w-full space-y-3 pb-28 md:max-w-2xl md:space-y-3">
             {/* link to the full history page */}
-            <Link href="/history" className="flex items-center justify-between">
-                <h2 className="text-base font-bold">Transactions</h2>
-                <Icon width={30} height={30} name="arrow-next" />
-            </Link>
+            {isPublic ? (
+                <h2 className="text-base font-bold">Latest Transactions</h2>
+            ) : (
+                <Link href="/history" className="flex items-center justify-between">
+                    <h2 className="text-base font-bold">Transactions</h2>
+                    <Icon width={30} height={30} name="arrow-next" />
+                </Link>
+            )}
             {/* container for the transaction cards */}
             <div className="h-full w-full">
                 {/* map over the latest entries and render transactioncard */}
diff --git a/src/components/Payment/PaymentForm/index.tsx b/src/components/Payment/PaymentForm/index.tsx
index 163b732d9..fae598d5d 100644
--- a/src/components/Payment/PaymentForm/index.tsx
+++ b/src/components/Payment/PaymentForm/index.tsx
@@ -35,7 +35,27 @@ import { useCallback, useContext, useEffect, useMemo, useState } from 'react'
 import { formatUnits } from 'viem'
 import { useAccount } from 'wagmi'
 
-export const PaymentForm = ({ recipient, amount, token, chain, isPintaReq }: ParsedURL & { isPintaReq?: boolean }) => {
+type PaymentFormProps = ParsedURL & {
+    isPintaReq?: boolean
+    currency?: {
+        code: string
+        symbol: string
+        price: number
+    }
+    currencyAmount?: string
+    setCurrencyAmount?: (currencyvalue: string | undefined) => void
+}
+
+export const PaymentForm = ({
+    recipient,
+    amount,
+    token,
+    chain,
+    isPintaReq,
+    currency,
+    currencyAmount,
+    setCurrencyAmount,
+}: PaymentFormProps) => {
     const dispatch = useAppDispatch()
     const router = useRouter()
     const { user } = useAuth()
@@ -237,6 +257,8 @@ export const PaymentForm = ({ recipient, amount, token, chain, isPintaReq }: Par
             tokenAmount: inputTokenAmount,
             isPintaReq: false, // explicitly set to false for non-PINTA requests
             requestId: requestId ?? undefined,
+            currency,
+            currencyAmount,
         }
 
         console.log('Initiating payment with payload:', payload)
@@ -391,9 +413,11 @@ export const PaymentForm = ({ recipient, amount, token, chain, isPintaReq }: Par
                 <TokenAmountInput
                     tokenValue={inputTokenAmount}
                     setTokenValue={(value: string | undefined) => setInputTokenAmount(value || '')}
+                    setCurrencyAmount={setCurrencyAmount}
                     className="w-full"
                     disabled={!!requestDetails?.tokenAmount || !!chargeDetails?.tokenAmount}
                     walletBalance={isActivePeanutWallet ? peanutWalletBalance : undefined}
+                    currency={currency}
                 />
 
                 {!isActivePeanutWallet && (
diff --git a/src/components/Payment/Views/Confirm.payment.view.tsx b/src/components/Payment/Views/Confirm.payment.view.tsx
index bf3cd8962..a8f931db3 100644
--- a/src/components/Payment/Views/Confirm.payment.view.tsx
+++ b/src/components/Payment/Views/Confirm.payment.view.tsx
@@ -24,7 +24,17 @@ import { useAccount } from 'wagmi'
 import { PaymentInfoRow } from '../PaymentInfoRow'
 import { useQueryClient } from '@tanstack/react-query'
 
-export default function ConfirmPaymentView({ isPintaReq = false }: { isPintaReq?: boolean }) {
+type ConfirmPaymentViewProps = {
+    isPintaReq?: boolean
+    currency?: {
+        code: string
+        symbol: string
+        price: number
+    }
+    currencyAmount?: string
+}
+
+export default function ConfirmPaymentView({ isPintaReq = false, currency, currencyAmount }: ConfirmPaymentViewProps) {
     const dispatch = useAppDispatch()
     const searchParams = useSearchParams()
     const chargeIdFromUrl = searchParams.get('chargeId')
@@ -92,6 +102,8 @@ export default function ConfirmPaymentView({ isPintaReq = false }: { isPintaReq?
             isPintaReq: isPintaReq,
             chargeId: chargeDetails.uuid,
             skipChargeCreation: true,
+            currency,
+            currencyAmount,
         })
 
         if (result.success) {
@@ -212,8 +224,23 @@ export default function ConfirmPaymentView({ isPintaReq = false }: { isPintaReq?
                 <PaymentInfoRow
                     label="Amount"
                     value={
-                        <span className="font-bold">
-                            {formatAmount(Number(chargeDetails?.tokenAmount))} {chargeDetails?.tokenSymbol}
+                        <span>
+                            {currencyAmount && currency ? (
+                                <>
+                                    <span className="font-bold">
+                                        {currency.symbol} {currencyAmount}
+                                    </span>
+                                    <span className="text-grey-1">
+                                        {' '}
+                                        ({formatAmount(Number(chargeDetails?.tokenAmount))} {chargeDetails?.tokenSymbol}
+                                        )
+                                    </span>
+                                </>
+                            ) : (
+                                <span className="font-bold">
+                                    {formatAmount(Number(chargeDetails?.tokenAmount))} {chargeDetails?.tokenSymbol}
+                                </span>
+                            )}
                         </span>
                     }
                 />
diff --git a/src/components/Payment/Views/Initial.payment.view.tsx b/src/components/Payment/Views/Initial.payment.view.tsx
index 58cffe6ef..6a04416e5 100644
--- a/src/components/Payment/Views/Initial.payment.view.tsx
+++ b/src/components/Payment/Views/Initial.payment.view.tsx
@@ -1,7 +1,18 @@
 import { ParsedURL } from '@/lib/url-parser/types/payment'
 import { PaymentForm } from '../PaymentForm'
 
-export default function InitialPaymentView(props: ParsedURL & { isPintaReq?: boolean }) {
+type InitialPaymentViewProps = ParsedURL & {
+    isPintaReq?: boolean
+    currency?: {
+        code: string
+        symbol: string
+        price: number
+    }
+    currencyAmount?: string
+    setCurrencyAmount?: (currencyvalue: string | undefined) => void
+}
+
+export default function InitialPaymentView(props: InitialPaymentViewProps) {
     const isPintaReq = props.token?.symbol === 'PNT' || props.isPintaReq
 
     return (
diff --git a/src/components/Payment/Views/Status.payment.view.tsx b/src/components/Payment/Views/Status.payment.view.tsx
index 32bb4097a..8a8ae1171 100644
--- a/src/components/Payment/Views/Status.payment.view.tsx
+++ b/src/components/Payment/Views/Status.payment.view.tsx
@@ -22,9 +22,18 @@ type DirectSuccessViewProps = {
     recipientType?: RecipientType
     type: 'SEND' | 'REQUEST'
     headerTitle?: string
+    currencyAmount?: string
 }
 
-const DirectSuccessView = ({ user, amount, message, recipientType, type, headerTitle }: DirectSuccessViewProps) => {
+const DirectSuccessView = ({
+    user,
+    amount,
+    message,
+    recipientType,
+    type,
+    headerTitle,
+    currencyAmount,
+}: DirectSuccessViewProps) => {
     const router = useRouter()
     const { chargeDetails, parsedPaymentData } = usePaymentStore()
     const [showCheck, setShowCheck] = useState(false)
@@ -41,8 +50,10 @@ const DirectSuccessView = ({ user, amount, message, recipientType, type, headerT
     }, [user, parsedPaymentData, chargeDetails])
 
     const displayAmount = useMemo(() => {
-        return amount || chargeDetails?.tokenAmount || '0'
-    }, [amount, chargeDetails])
+        if (currencyAmount) return currencyAmount
+        const displayAmount = amount ?? chargeDetails?.tokenAmount ?? '0'
+        return `${displayAmount} ${chargeDetails?.tokenSymbol}`
+    }, [amount, chargeDetails, currencyAmount])
 
     useEffect(() => {
         // show loading for a brief moment, then show check mark
@@ -103,9 +114,7 @@ const DirectSuccessView = ({ user, amount, message, recipientType, type, headerT
                                 recipientName
                             )}
                         </h1>
-                        <h2 className="text-2xl font-extrabold">
-                            {displayAmount} {chargeDetails?.tokenSymbol}
-                        </h2>
+                        <h2 className="text-2xl font-extrabold">{displayAmount}</h2>
                         {message && <p className="text-sm font-medium text-grey-1">for {message}</p>}
                     </div>
                 </Card>
diff --git a/src/components/Profile/components/PublicProfile.tsx b/src/components/Profile/components/PublicProfile.tsx
index 72f9eb7b7..8eb318604 100644
--- a/src/components/Profile/components/PublicProfile.tsx
+++ b/src/components/Profile/components/PublicProfile.tsx
@@ -1,28 +1,21 @@
 'use client'
 
-import chillPeanutAnim from '@/animations/GIF_ALPHA_BACKGORUND/512X512_ALPHA_GIF_konradurban_01.gif'
 import { PEANUT_LOGO_BLACK, PEANUTMAN_LOGO } from '@/assets'
 import { Button } from '@/components/0_Bruddle'
-import Card from '@/components/Global/Card'
 import { Icon } from '@/components/Global/Icons/Icon'
 import NavHeader from '@/components/Global/NavHeader'
 import { useAppDispatch } from '@/redux/hooks'
 import { paymentActions } from '@/redux/slices/payment-slice'
-import { formatExtendedNumber } from '@/utils'
 import Image from 'next/image'
 import Link from 'next/link'
-import { useRouter } from 'next/navigation'
 import ProfileHeader from './ProfileHeader'
+import HomeHistory from '@/components/Home/HomeHistory'
 
 interface PublicProfileProps {
     username: string
     fullName: string
     isVerified?: boolean
     isLoggedIn?: boolean
-    transactions?: {
-        sent: number
-        received: number
-    }
     onSendClick?: () => void
 }
 
@@ -31,15 +24,10 @@ const PublicProfile: React.FC<PublicProfileProps> = ({
     fullName,
     isVerified = false,
     isLoggedIn = false,
-    transactions,
     onSendClick,
 }) => {
-    const router = useRouter()
     const dispatch = useAppDispatch()
 
-    // check if user has transaction history
-    const hasTransactions = transactions && (transactions.sent > 0 || transactions.received > 0)
-
     // Handle send button click
     const handleSend = () => {
         if (onSendClick) {
@@ -95,8 +83,8 @@ const PublicProfile: React.FC<PublicProfileProps> = ({
                     </Link>
                 </div>
 
+                {/*
                 <div className="space-y-6">
-                    {/* Transaction Summary - Only shown if user has transactions */}
                     {!!hasTransactions && (
                         <div>
                             <Card position="first">
@@ -118,56 +106,8 @@ const PublicProfile: React.FC<PublicProfileProps> = ({
                         </div>
                     )}
 
-                    {/* Transactions List - Only shown if user has transactions */}
-                    {!!hasTransactions && (
-                        <div className="space-y-2">
-                            <div className="flex items-center justify-between">
-                                <h2 className="text-lg font-bold">Transactions</h2>
-                                <Link href={`/history`}>
-                                    <div className="flex items-center">
-                                        <Icon name="chevron-up" size={16} fill="black" className="rotate-90" />
-                                    </div>
-                                </Link>
-                            </div>
-
-                            {/* todo: show real transactions from the user */}
-                            {/* Example transaction - todo: replace with actual transaction data */}
-                            {/* <div>
-                                <TransactionCard
-                                    type="send"
-                                    name="Hugo Montenegro"
-                                    amount={BigInt(6969000000)}
-                                    initials="HM"
-                                    position="first"
-                                />
-
-                                <TransactionCard
-                                    type="withdraw"
-                                    name="Bank Account #1"
-                                    amount={BigInt(6969000000)}
-                                    position="middle"
-                                />
-
-                                <TransactionCard
-                                    type="add"
-                                    name="peanut.ens"
-                                    amount={BigInt(6969000000)}
-                                    position="middle"
-                                />
-
-                                <TransactionCard
-                                    type="request"
-                                    name="dasdasdasdsa Montenegro"
-                                    amount={BigInt(6969000000)}
-                                    initials="HM"
-                                    position="last"
-                                />
-                            </div> */}
-                        </div>
-                    )}
                 </div>
 
-                {/* Empty State - Only shown if user has no transactions */}
                 {!hasTransactions && (
                     <div className="relative flex flex-col items-center">
                         <Card position="single" className="z-10 mt-28 space-y-2 p-4 text-center">
@@ -212,6 +152,8 @@ const PublicProfile: React.FC<PublicProfileProps> = ({
                         </div>
                     </div>
                 )}
+                */}
+                <HomeHistory isPublic={true} username={username} />
             </div>
         </div>
     )
diff --git a/src/components/TransactionDetails/TransactionCard.tsx b/src/components/TransactionDetails/TransactionCard.tsx
index 728831dbb..8d54b05cf 100644
--- a/src/components/TransactionDetails/TransactionCard.tsx
+++ b/src/components/TransactionDetails/TransactionCard.tsx
@@ -78,8 +78,9 @@ const TransactionCard: React.FC<TransactionCardProps> = ({
                     {/* amount and status on the right side */}
                     <div className="flex flex-col items-end space-y-0.5">
                         <span className="font-roboto text-xs font-medium">
-                            {transaction.currencySymbol}
-                            {formatAmount(amount)}
+                            {transaction.currency?.code === 'ARS'
+                                ? `ARS$ ${formatAmount(transaction.currency.amount)}`
+                                : `${transaction.currencySymbol}${formatAmount(amount)}`}
                         </span>
                         {status && <StatusBadge status={status} />}
                     </div>
diff --git a/src/components/TransactionDetails/transactionTransformer.ts b/src/components/TransactionDetails/transactionTransformer.ts
index f7f00ddfc..5e7c6b06c 100644
--- a/src/components/TransactionDetails/transactionTransformer.ts
+++ b/src/components/TransactionDetails/transactionTransformer.ts
@@ -17,6 +17,10 @@ export interface TransactionDetails {
     direction: TransactionDirection
     userName: string
     amount: number | bigint
+    currency?: {
+        amount: string
+        code: string
+    }
     currencySymbol?: string
     tokenSymbol?: string
     initials: string
@@ -209,6 +213,7 @@ export function mapTransactionDataForDrawer(entry: HistoryEntry): MappedTransact
         direction: direction,
         userName: nameForDetails,
         amount: amount,
+        currency: entry.currency,
         currencySymbol: `${entry.userRole === EHistoryUserRole.SENDER ? '-' : '+'}$`,
         tokenSymbol: entry.tokenSymbol,
         initials: getInitialsFromName(nameForDetails),
diff --git a/src/hooks/useCurrency.ts b/src/hooks/useCurrency.ts
new file mode 100644
index 000000000..60905bd7d
--- /dev/null
+++ b/src/hooks/useCurrency.ts
@@ -0,0 +1,42 @@
+import { useState, useEffect } from 'react'
+import { getCurrencyPrice } from '@/app/actions/currency'
+
+const SIMBOLS_BY_CURRENCY_CODE: Record<string, string> = {
+    ARS: 'AR$',
+    USD: '$',
+}
+
+export const useCurrency = (currencyCode: string | null) => {
+    const [code, setCode] = useState<string | null>(currencyCode?.toUpperCase() ?? null)
+    const [symbol, setSymbol] = useState<string | null>(null)
+    const [price, setPrice] = useState<number | null>(null)
+
+    useEffect(() => {
+        if (!code) return
+
+        if (code === 'USD') {
+            setSymbol('$')
+            setPrice(1)
+            return
+        }
+
+        // First iterations only pesos
+        if (code !== 'ARS') {
+            setCode(null)
+            return
+        }
+
+        getCurrencyPrice(code)
+            .then((price) => {
+                setSymbol(SIMBOLS_BY_CURRENCY_CODE[code])
+                setPrice(price)
+            })
+            .catch(console.error)
+    }, [code])
+
+    return {
+        code,
+        symbol,
+        price,
+    }
+}
diff --git a/src/hooks/usePaymentInitiator.ts b/src/hooks/usePaymentInitiator.ts
index f58dd6957..eea873329 100644
--- a/src/hooks/usePaymentInitiator.ts
+++ b/src/hooks/usePaymentInitiator.ts
@@ -39,6 +39,12 @@ export interface InitiatePaymentPayload {
     chargeId?: string
     skipChargeCreation?: boolean
     requestId?: string // optional request ID from URL
+    currency?: {
+        code: string
+        symbol: string
+        price: number
+    }
+    currencyAmount?: string
 }
 
 interface InitiationResult {
@@ -463,13 +469,18 @@ export const usePaymentInitiator = () => {
                     ? PINTA_WALLET_TOKEN_DECIMALS
                     : (requestDetails?.tokenDecimals ?? selectedTokenData?.decimals ?? 18)
 
+                const localPrice =
+                    payload.currencyAmount && payload.currency
+                        ? { amount: payload.currencyAmount, currency: payload.currency.code }
+                        : { amount: payload.tokenAmount, currency: 'USD' }
                 const createChargeRequestPayload: CreateChargeRequest = {
                     pricing_type: 'fixed_price',
-                    local_price: { amount: payload.tokenAmount, currency: 'USD' },
+                    local_price: localPrice,
                     baseUrl: window.location.origin,
                     requestId: validRequestId,
                     requestProps: {
                         chainId: recipientChainId,
+                        tokenAmount: payload.tokenAmount,
                         tokenAddress: recipientTokenAddress,
                         tokenType: payload.isPintaReq
                             ? peanutInterfaces.EPeanutLinkType.erc20
diff --git a/src/hooks/useTransactionHistory.ts b/src/hooks/useTransactionHistory.ts
index e18b8005f..e36d52a00 100644
--- a/src/hooks/useTransactionHistory.ts
+++ b/src/hooks/useTransactionHistory.ts
@@ -32,6 +32,10 @@ export type HistoryEntry = {
     type: HistoryEntryType
     timestamp: Date
     amount: string
+    currency?: {
+        amount: string
+        code: string
+    }
     txHash?: string
     chainId: string
     tokenSymbol: string
@@ -69,15 +73,17 @@ export type HistoryResponse = {
 
 // Hook options
 type UseTransactionHistoryOptions = {
-    mode?: 'infinite' | 'latest'
+    mode?: 'infinite' | 'latest' | 'public'
     limit?: number
     enabled?: boolean
+    username?: string
 }
 
 export function useTransactionHistory(options: {
-    mode: 'latest'
+    mode: 'latest' | 'public'
     limit?: number
     enabled?: boolean
+    username?: string
 }): LatestHistoryResult
 
 export function useTransactionHistory(options: {
@@ -95,21 +101,35 @@ export function useTransactionHistory({
     mode = 'infinite',
     limit = 50,
     enabled = true,
+    username,
 }: UseTransactionHistoryOptions): LatestHistoryResult | InfiniteHistoryResult {
-    const fetchHistory = async ({ cursor, limit }: { cursor?: string; limit: number }): Promise<HistoryResponse> => {
+    const fetchHistory = async ({
+        cursor,
+        limit,
+        isPublic = false,
+    }: {
+        cursor?: string
+        limit: number
+        isPublic?: boolean
+    }): Promise<HistoryResponse> => {
         const queryParams = new URLSearchParams()
         if (cursor) queryParams.append('cursor', cursor)
         if (limit) queryParams.append('limit', limit.toString())
 
-        const url = `${PEANUT_API_URL}/users/history?${queryParams.toString()}`
+        let url: string
+        if (isPublic) {
+            url = `${PEANUT_API_URL}/users/${username}/history?${queryParams.toString()}`
+        } else {
+            url = `${PEANUT_API_URL}/users/history?${queryParams.toString()}`
+        }
 
-        const response = await fetchWithSentry(url, {
-            method: 'GET',
-            headers: {
-                'Content-Type': 'application/json',
-                Authorization: `Bearer ${Cookies.get('jwt-token')}`,
-            },
-        })
+        const headers: Record<string, string> = {
+            'Content-Type': 'application/json',
+        }
+        if (!isPublic) {
+            headers['Authorization'] = `Bearer ${Cookies.get('jwt-token')}`
+        }
+        const response = await fetchWithSentry(url, { method: 'GET', headers })
 
         if (!response.ok) {
             throw new Error(`Failed to fetch history: ${response.statusText}`)
@@ -177,6 +197,15 @@ export function useTransactionHistory({
         })
     }
 
+    if (mode === 'public') {
+        return useQuery({
+            queryKey: ['transactions', 'public', username, { limit }],
+            queryFn: () => fetchHistory({ limit, isPublic: true }),
+            enabled,
+            staleTime: 15 * 1000, // 15 seconds
+        })
+    }
+
     // Infinite query mode (for main history page)
     return useInfiniteQuery({
         queryKey: ['transactions', 'infinite', { limit }],
diff --git a/src/services/services.types.ts b/src/services/services.types.ts
index 19dbf6d36..76f46df38 100644
--- a/src/services/services.types.ts
+++ b/src/services/services.types.ts
@@ -89,6 +89,7 @@ export interface LocalPrice {
 
 export interface RequestProps {
     chainId: string
+    tokenAmount?: string
     tokenAddress: string
     tokenType: peanutInterfaces.EPeanutLinkType
     tokenSymbol: string


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 6**

<details>
<summary>🔭 Outside diff range comments (2)</summary><blockquote>

<details>
<summary>src/components/Payment/Views/Confirm.payment.view.tsx (1)</summary><blockquote>

`99-107`: _⚠️ Potential issue_

**Update dependencies in useCallback to include new currency props.**

The `currency` and `currencyAmount` props are used in this callback function but not included in the dependency array.

```diff
    }, [chargeDetails, initiatePayment, parsedPaymentData, dispatch, isPintaReq, beerQuantity, 
+      currency, currencyAmount
    ])
```

</blockquote></details>
<details>
<summary>src/components/Payment/PaymentForm/index.tsx (1)</summary><blockquote>

`277-289`: _⚠️ Potential issue_

**Update dependencies in useCallback to include new currency props.**

The `currency` and `currencyAmount` props are used in the payment payload but not included in the dependency array.

```diff
    }, [
        canInitiatePayment,
        isDepositRequest,
        isConnected,
        openReownModal,
        recipient,
        inputTokenAmount,
        isPintaReq,
        requestId,
        initiatePayment,
        dispatch,
        beerQuantity,
+       currency,
+       currencyAmount
    ])
```

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (8)</summary><blockquote>

<details>
<summary>src/services/currency.ts (1)</summary><blockquote>

`3-17`: **Consider enhancing the currencyApi implementation for production readiness.**

The current implementation is a good starting point, but there are several improvements that would make it more robust:

1. The API endpoint is hardcoded, making it less flexible for different environments
2. There's no caching mechanism to prevent frequent identical API calls
3. No handling for API timeouts or network failures beyond what fetchWithSentry provides
4. No data validation for the API response structure


Consider implementing a more robust solution with caching:

```diff
import { fetchWithSentry } from '@/utils'

+// Cache exchange rates to avoid frequent API calls
+const rateCache: Record<string, { rate: number; timestamp: number }> = {}
+const CACHE_EXPIRY_MS = 60 * 60 * 1000 // 1 hour
+
+// Could be moved to environment variables or config
+const CURRENCY_API_URL = 'https://dolarapi.com/v1/dolares/cripto'

export const currencyApi = {
    getPrice: async (currencyCode: string): Promise<number> => {
        if (currencyCode === 'USD') return 1
        if (currencyCode !== 'ARS') {
            throw new Error('Unsupported currency')
        }
        
+       // Check cache first
+       const now = Date.now()
+       if (rateCache[currencyCode] && (now - rateCache[currencyCode].timestamp < CACHE_EXPIRY_MS)) {
+           return rateCache[currencyCode].rate
+       }
+
        const response = await fetchWithSentry('https://dolarapi.com/v1/dolares/cripto')
        const data = await response.json()

+       // Validate response structure
+       if (!data || typeof data.compra !== 'number' || typeof data.venta !== 'number') {
+           throw new Error('Invalid API response format')
+       }

        // Average between buy and sell price
        const price = (data.compra + data.venta) / 2
        
+       // Update cache
+       rateCache[currencyCode] = { rate: price, timestamp: now }

        return price
    },
}
```

</blockquote></details>
<details>
<summary>src/hooks/useCurrency.ts (2)</summary><blockquote>

`4-7`: **Fix typo in SIMBOLS_BY_CURRENCY_CODE variable name.**

The variable name has a spelling error - "SIMBOLS" should be "SYMBOLS".


```diff
-const SIMBOLS_BY_CURRENCY_CODE: Record<string, string> = {
+const SYMBOLS_BY_CURRENCY_CODE: Record<string, string> = {
    ARS: 'AR$',
    USD: '$',
}
```

---

`29-36`: **Improve error handling for currency API calls.**

The current implementation only logs errors to the console, which may not be sufficient for a production application. Consider adding proper error state management.


```diff
import { currencyApi } from '@/services/currency'
-import { useState, useEffect } from 'react'
+import { useState, useEffect, useCallback } from 'react'

-const SIMBOLS_BY_CURRENCY_CODE: Record<string, string> = {
+const SYMBOLS_BY_CURRENCY_CODE: Record<string, string> = {
    ARS: 'AR$',
    USD: '$',
}

export const useCurrency = (currencyCode: string | null) => {
    const [code, setCode] = useState<string | null>(currencyCode?.toUpperCase() ?? null)
    const [symbol, setSymbol] = useState<string | null>(null)
    const [price, setPrice] = useState<number | null>(null)
+   const [error, setError] = useState<Error | null>(null)
+   const [loading, setLoading] = useState<boolean>(false)

+   const fetchCurrencyPrice = useCallback(async (currencyCode: string) => {
+       if (!currencyCode) return
+
+       setLoading(true)
+       setError(null)
+
+       try {
+           const price = await currencyApi.getPrice(currencyCode)
+           setSymbol(SYMBOLS_BY_CURRENCY_CODE[currencyCode])
+           setPrice(price)
+       } catch (err) {
+           console.error('Error fetching currency price:', err)
+           setError(err instanceof Error ? err : new Error(String(err)))
+           // Optionally reset code to fallback to USD
+           setCode('USD')
+       } finally {
+           setLoading(false)
+       }
+   }, [])

    useEffect(() => {
        if (!code) return

        if (code === 'USD') {
            setSymbol('$')
            setPrice(1)
            return
        }

        // First iterations only pesos
        if (code !== 'ARS') {
            setCode(null)
            return
        }

-       currencyApi
-           .getPrice(code)
-           .then((price) => {
-               setSymbol(SIMBOLS_BY_CURRENCY_CODE[code])
-               setPrice(price)
-           })
-           .catch(console.error)
+       fetchCurrencyPrice(code)
    }, [code, fetchCurrencyPrice])

    return {
        code,
        symbol,
        price,
+       error,
+       loading,
    }
}
```

</blockquote></details>
<details>
<summary>src/components/TransactionDetails/transactionTransformer.ts (1)</summary><blockquote>

`216-216`: **Consider defensive assignment for the currency property.**

The currency property is assigned directly from entry.currency without validation. This could lead to issues if the property is undefined or malformed.


```diff
-        currency: entry.currency,
+        currency: entry.currency ? {
+            amount: entry.currency.amount || '0',
+            code: entry.currency.code || 'USD'
+        } : undefined,
```

</blockquote></details>
<details>
<summary>src/components/Profile/components/PublicProfile.tsx (2)</summary><blockquote>

`86-155`: **Remove large commented-out legacy block to keep the component lean**

The 70-line chunk of legacy transaction UI is now completely superseded by `<HomeHistory>`. Keeping it in the codebase:

1. Bloats bundle size after transpilation (comments are stripped but the JSX still needs to be parsed).
2. Makes the diff noisy for future contributors and risks fragment re-activation during merges.
3. Violates the “dead-code-free” guideline we try to enforce across `peanut-ui`.

If you think it may be needed later, move it to a separate git branch or a Design Doc instead of leaving it here.

```diff
-                {/*
-                 … 70 lines …
-                */}
```

---

`156-156`: **Minor JSX nit – boolean prop can be shorthand**

`isPublic={true}` is perfectly valid, but in React the canonical style is to drop the explicit value:

```diff
-<HomeHistory isPublic={true} username={username} />
+<HomeHistory isPublic username={username} />
```

Purely stylistic – ignore if your code-style rules disagree.

</blockquote></details>
<details>
<summary>src/components/Global/TokenAmountInput/index.tsx (2)</summary><blockquote>

`52-57`: **Redundant `.toString()` and non-null assertion may hide bugs**

`formatTokenAmount()` already returns a **string**. Calling `.toString()` on it is a no-op, and the `!` operator suppresses potential `undefined` that should be handled explicitly.

```diff
-const actualTokenValue = formatTokenAmount(Number(inputValue) / currency.price)!
-setTokenValue(actualTokenValue.toString())
+const actualTokenValue = formatTokenAmount(Number(inputValue) / currency.price)
+if (actualTokenValue !== undefined) {
+    setTokenValue(actualTokenValue)
+}
```

This prevents accidental crashes if `formatTokenAmount` ever returns `undefined`.

---

`168-215`: **Duplicate toggle logic – extract to a handler**

The currency-mode toggle is implemented twice (parent `<div>` and the inner `<button>`), making future fixes error-prone.

Create a single `toggleMode()` callback and reuse it:

```diff
- onClick={(e) => {
-     …
- }}
+ onClick={toggleMode}

…

- onClick={(e) => {
-     …
- }}
+ onClick={(e) => { e.preventDefault(); e.stopPropagation(); toggleMode(); }}
```

Keeps intent clear and removes 30+ lines of repeated code.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

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

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 2c4be346e0f3663cd3daa9813b03f9cf9bfaa4f9 and 2843f5ff17e403e348dd255167156ef45c2e0d4d.

</details>

<details>
<summary>📒 Files selected for processing (15)</summary>

* `src/app/[...recipient]/client.tsx` (4 hunks)
* `src/components/Global/TokenAmountInput/index.tsx` (5 hunks)
* `src/components/Home/HomeHistory.tsx` (2 hunks)
* `src/components/Payment/PaymentForm/index.tsx` (3 hunks)
* `src/components/Payment/Views/Confirm.payment.view.tsx` (3 hunks)
* `src/components/Payment/Views/Initial.payment.view.tsx` (1 hunks)
* `src/components/Payment/Views/Status.payment.view.tsx` (3 hunks)
* `src/components/Profile/components/PublicProfile.tsx` (3 hunks)
* `src/components/TransactionDetails/TransactionCard.tsx` (1 hunks)
* `src/components/TransactionDetails/transactionTransformer.ts` (2 hunks)
* `src/hooks/useCurrency.ts` (1 hunks)
* `src/hooks/usePaymentInitiator.ts` (2 hunks)
* `src/hooks/useTransactionHistory.ts` (5 hunks)
* `src/services/currency.ts` (1 hunks)
* `src/services/services.types.ts` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧠 Learnings (2)</summary>

<details>
<summary>src/components/Home/HomeHistory.tsx (1)</summary>

```
Learnt from: kushagrasarathe
PR: peanutprotocol/peanut-ui#798
File: src/components/Home/HomeHistory.tsx:138-192
Timestamp: 2025-04-11T11:33:53.245Z
Learning: In the HomeHistory component, infinite scrolling is intentionally not implemented despite the presence of useInfiniteQuery and IntersectionObserver code. The component is designed to only display the first 5 entries with a "View all transactions" link for viewing the complete history.
```

</details>
<details>
<summary>src/components/Global/TokenAmountInput/index.tsx (2)</summary>

```
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#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.
```

</details>

</details><details>
<summary>🧬 Code Graph Analysis (7)</summary>

<details>
<summary>src/components/TransactionDetails/TransactionCard.tsx (1)</summary><blockquote>

<details>
<summary>src/utils/general.utils.ts (1)</summary>

* `formatAmount` (252-292)

</details>

</blockquote></details>
<details>
<summary>src/components/Payment/Views/Confirm.payment.view.tsx (1)</summary><blockquote>

<details>
<summary>src/utils/general.utils.ts (1)</summary>

* `formatAmount` (252-292)

</details>

</blockquote></details>
<details>
<summary>src/components/Payment/Views/Initial.payment.view.tsx (1)</summary><blockquote>

<details>
<summary>src/lib/url-parser/types/payment.ts (1)</summary>

* `ParsedURL` (7-16)

</details>

</blockquote></details>
<details>
<summary>src/hooks/useCurrency.ts (1)</summary><blockquote>

<details>
<summary>src/services/currency.ts (1)</summary>

* `currencyApi` (3-17)

</details>

</blockquote></details>
<details>
<summary>src/hooks/useTransactionHistory.ts (2)</summary><blockquote>

<details>
<summary>src/constants/general.consts.ts (1)</summary>

* `PEANUT_API_URL` (30-34)

</details>
<details>
<summary>src/utils/sentry.utils.ts (1)</summary>

* `fetchWithSentry` (11-89)

</details>

</blockquote></details>
<details>
<summary>src/hooks/usePaymentInitiator.ts (1)</summary><blockquote>

<details>
<summary>src/services/services.types.ts (1)</summary>

* `CreateChargeRequest` (101-109)

</details>

</blockquote></details>
<details>
<summary>src/components/Payment/PaymentForm/index.tsx (1)</summary><blockquote>

<details>
<summary>src/lib/url-parser/types/payment.ts (1)</summary>

* `ParsedURL` (7-16)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (1)</summary>

* GitHub Check: Deploy-Preview

</details>

<details>
<summary>🔇 Additional comments (24)</summary><blockquote>

<details>
<summary>src/services/services.types.ts (1)</summary>

`92-92`: **LGTM: Adding optional tokenAmount field.**

The optional tokenAmount property has been correctly added to the RequestProps interface. This change aligns with the multi-currency support feature.

</details>
<details>
<summary>src/components/TransactionDetails/transactionTransformer.ts (2)</summary>

`20-23`: **LGTM: Currency field for transaction details.**

The addition of an optional currency field to the TransactionDetails interface is appropriate for supporting multi-currency display in transactions.

---

`1-241`: <details>
<summary>❓ Verification inconclusive</summary>

**Verify handling of currency-specific formatting in UI components.**

The TransactionDetails object now includes currency information, but it's unclear if UI components consuming this data will properly format currency values, especially for ARS which might have different decimal precision or formatting requirements than USD.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check how currency formatting is handled in UI components

# Look for components that consume or display TransactionDetails
echo "Searching for components that use TransactionDetails..."
rg -t typescript -t jsx -t tsx "TransactionDetails" --files-with-matches src/components/

# Search for currency-specific formatting or display logic
echo "Checking for currency-specific formatting..."
rg -t typescript -t jsx -t tsx "(currency\.|currencySymbol|ARS)" src/components/
```

Length of output: 461

---

🏁 Script executed:

```shell
#!/bin/bash
# Retry searching without file-type filters for better coverage

echo "Finding components that reference TransactionDetails:"
rg "TransactionDetails" -n src/components

echo
echo "Looking for currencySymbol usage in components:"
rg "currencySymbol" -n src/components

echo
echo "Searching for raw currency.amount or formatting calls:"
rg -E "currency(\.amount|Symbol)" -n src/components

echo
echo "Checking for ARS-specific logic or mentions:"
rg "ARS" -n src/components
```

Length of output: 5541

---



</details>

**Review ARS currency formatting in TransactionCard.tsx**  

There’s a hard-coded ARS branch in  
`src/components/TransactionDetails/TransactionCard.tsx:81–82`  
that renders:  
```tsx
{transaction.currency?.code === 'ARS'
  ? `ARS$ ${transaction.currency.amount}`
  : `${transaction.currencySymbol}${formatAmount(amount)}`}
```  
• Verify whether `transaction.currency.amount` is already formatted to the correct decimal precision and uses proper thousand-separators for ARS.  
• If not, consider using `formatAmount(amount)` (or `Intl.NumberFormat`) for ARS as well to ensure consistency across currencies.

</details>
<details>
<summary>src/components/Payment/Views/Status.payment.view.tsx (4)</summary>

`25-26`: **Type extension for multi-currency support**

Added optional `currencyAmount` field to support displaying alternative currency amounts in the success view.

---

`28-36`: **Updated function parameters for multi-currency support**

Component signature updated to include the new `currencyAmount` parameter.

---

`52-56`: **Updated displayAmount logic for multi-currency support**

The implementation correctly prioritizes `currencyAmount` when available and falls back to token amount with symbol.

---

`117-118`: **Simplified amount display in the UI**

The JSX rendering has been simplified to display the entire `displayAmount` string directly.

</details>
<details>
<summary>src/components/Payment/Views/Initial.payment.view.tsx (2)</summary>

`4-13`: **Well-structured type definition for multi-currency support**

The new `InitialPaymentViewProps` type properly extends the base `ParsedURL` interface with all necessary currency-related properties.

---

`15-15`: **Updated function signature for type safety**

Function signature has been updated to use the newly defined type alias, improving type safety.

</details>
<details>
<summary>src/hooks/usePaymentInitiator.ts (3)</summary>

`42-48`: **Extended payload interface for multi-currency support**

The `InitiatePaymentPayload` interface has been appropriately extended to include optional currency-related fields.

---

`472-479`: **Added conditional logic for currency-specific pricing**

The implementation correctly handles currency pricing in the payment flow, prioritizing the specific currency when available and maintaining backward compatibility.

---

`483-483`: **Explicit tokenAmount assignment in requestProps**

The tokenAmount is now explicitly assigned from the payload, improving code clarity.

</details>
<details>
<summary>src/components/Home/HomeHistory.tsx (3)</summary>

`15-15`: **Props type enhancement improves component reusability.**

The component has been upgraded to accept `isPublic` and `username` props, making it reusable across different contexts.

---

`17-19`: **Conditional fetching logic handles public vs private transaction history.**

The component now intelligently adjusts its behavior based on whether it's displaying public or private transactions:
- For public profiles: Fetches up to 20 transactions with 'public' mode
- For private/current user: Shows only the latest 5 transactions with 'latest' mode

This aligns with the retrieved learning that HomeHistory is intentionally designed to show limited entries with a "View all" option for the user's own transactions.

---

`48-55`: **Conditional rendering based on context improves UX.**

The component now renders either:
- A plain heading for public profiles
- A clickable link with an arrow icon for the user's own transactions

This adaptation provides appropriate navigation options based on the context in which HomeHistory is used.

</details>
<details>
<summary>src/components/Payment/Views/Confirm.payment.view.tsx (3)</summary>

`27-36`: **Well-structured type definition for multicurrency support.**

The `ConfirmPaymentViewProps` type has been properly extended to include currency information with all necessary fields.

---

`37-37`: **Component signature updated to support multicurrency.**

The component now accepts currency-related props, enabling it to display both token and fiat currency amounts.

---

`227-244`: **Improved UI for multicurrency display.**

The component now handles displaying both currency amounts effectively:
- If a currency amount is provided, it shows that amount prominently with the token amount in parentheses
- Otherwise, it falls back to showing only the token amount

This provides a clear hierarchy of information and maintains backward compatibility.

</details>
<details>
<summary>src/components/Payment/PaymentForm/index.tsx (4)</summary>

`38-47`: **Well-defined type for multicurrency support.**

The `PaymentFormProps` type properly extends `ParsedURL` with all necessary currency fields and callback function.

---

`49-58`: **Component signature updated to handle multicurrency props.**

The props destructuring now includes all the currency-related props, making them available throughout the component.

---

`255-262`: **Include new parameters in payment payload.**

The payment payload now includes currency information, enabling backend systems to process multicurrency payments.

---

`413-421`: **TokenAmountInput properly connected to currency handling.**

The `TokenAmountInput` now receives the `setCurrencyAmount` and `currency` props, enabling it to handle currency conversion and input.

</details>
<details>
<summary>src/app/[...recipient]/client.tsx (2)</summary>

`38-43`: **Implement currency handling from URL parameters.**

The component now uses the `useCurrency` hook to extract and process currency information from URL parameters, supporting the multicurrency feature.

---

`244-255`: **Handle potential undefined values more safely.**

The non-null assertions (`!`) on `currencySymbol` and `currencyPrice` assume they'll always be defined if `currencyCode` is defined, which could cause runtime errors.

```diff
                        <InitialPaymentView
                            {...(parsedPaymentData as ParsedURL)}
                            currency={
                                currencyCode
                                    ? {
                                          code: currencyCode,
-                                         symbol: currencySymbol!,
-                                         price: currencyPrice!,
+                                         symbol: currencySymbol || '$',
+                                         price: currencyPrice || 0,
                                      }
                                    : undefined
                            }
                            setCurrencyAmount={(value: string | undefined) => setCurrencyAmount(value || '')}
                            currencyAmount={currencyAmount}
                        />
```

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T20:10:03Z
user: login: Hugo0
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T21:13:55Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T21:14:28Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T21:14:56Z
user: login: jjramirezn
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T21:15:42Z
user: login: jjramirezn
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T21:15:59Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T21:16:02Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T21:16:08Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T21:16:24Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T21:16:45Z
user: login: jjramirezn
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T21:17:48Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T21:18:02Z
user: login: jjramirezn
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T21:24:19Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T21:24:24Z
user: login: jjramirezn
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T21:25:03Z
user: login: jjramirezn
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T21:25:55Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T21:26:16Z
user: login: jjramirezn
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T21:32:25Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T21:32:30Z
user: login: jjramirezn
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T21:32:48Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T21:32:53Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 2**

<details>
<summary>🧹 Nitpick comments (3)</summary><blockquote>

<details>
<summary>src/app/actions/currency.ts (3)</summary><blockquote>

`8-10`: **Consider a more extensible approach for currency support.**

The current implementation supports only USD and ARS, with a hard error for other currencies. Since the PR title mentions "first commit multicurrency", consider implementing a more extensible solution for future currency additions.

```diff
-        if (currencyCode !== 'ARS') {
-            throw new Error('Unsupported currency')
-        }
+        // Map of supported currencies to their API endpoints
+        const currencyApiMap: Record<string, string> = {
+            'ARS': 'https://dolarapi.com/v1/dolares/cripto',
+            // Add more currencies here in the future
+        }
+        
+        const apiUrl = currencyApiMap[currencyCode]
+        if (!apiUrl) {
+            throw new Error(`Unsupported currency: ${currencyCode}`)
+        }
```

---

`19-19`: **Add type annotation for improved code clarity.**

Consider adding a type annotation to the `price` variable for better code readability and type safety.

```diff
-        const price = (data.compra + data.venta) / 2
+        const price: number = (data.compra + data.venta) / 2
```

---

`23-26`: **Consider environment-specific cache settings.**

The fixed 5-minute cache duration might not be optimal for all environments. Consider making this configurable through environment variables to allow different settings for development, staging, and production.

```diff
    ['getCurrencyPrice'],
    {
-        revalidate: 5 * 60, // 5 minutes
+        revalidate: process.env.CURRENCY_CACHE_DURATION 
+            ? parseInt(process.env.CURRENCY_CACHE_DURATION, 10) 
+            : 5 * 60, // Default: 5 minutes
    }
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

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

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 2843f5ff17e403e348dd255167156ef45c2e0d4d and c6474f0425c779ecb7462b43c47f371166bc384b.

</details>

<details>
<summary>📒 Files selected for processing (5)</summary>

* `src/app/[...recipient]/client.tsx` (4 hunks)
* `src/app/actions/currency.ts` (1 hunks)
* `src/components/TransactionDetails/TransactionCard.tsx` (1 hunks)
* `src/hooks/useCurrency.ts` (1 hunks)
* `src/hooks/useTransactionHistory.ts` (4 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (4)</summary>

* src/hooks/useCurrency.ts
* src/components/TransactionDetails/TransactionCard.tsx
* src/app/[...recipient]/client.tsx
* src/hooks/useTransactionHistory.ts

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (1)</summary>

<details>
<summary>src/app/actions/currency.ts (2)</summary><blockquote>

<details>
<summary>src/utils/__mocks__/next-cache.ts (1)</summary>

* `unstable_cache` (1-1)

</details>
<details>
<summary>src/utils/sentry.utils.ts (1)</summary>

* `fetchWithSentry` (11-89)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (1)</summary>

* GitHub Check: Deploy-Preview

</details>

<details>
<summary>🔇 Additional comments (1)</summary><blockquote>

<details>
<summary>src/app/actions/currency.ts (1)</summary>

`5-27`: **Good implementation of currency price fetching with appropriate caching.**

The implementation looks solid with proper use of server-side caching through `unstable_cache` and an appropriate revalidation period of 5 minutes for currency data.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T21:49:27Z

Review comments:
diff_hunk: @@ -78,8 +78,9 @@ const TransactionCard: React.FC<TransactionCardProps> = ({
                     {/* amount and status on the right side */}
                     <div className="flex flex-col items-end space-y-0.5">
                         <span className="font-roboto text-xs font-medium">
-                            {transaction.currencySymbol}
-                            {formatAmount(amount)}
+                            {transaction.currency?.code === 'ARS'
+                                ? `ARS$ ${transaction.currency.amount}`
+                                : `${transaction.currencySymbol}${formatAmount(amount)}`}
                         </span>
path: src/components/TransactionDetails/TransactionCard.tsx
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Inconsistent currency amount formatting for ARS**

The conditional rendering for Argentine Peso creates a formatting inconsistency. While other currencies use the `formatAmount` utility function, the ARS amount is displayed without formatting.


Apply this change to ensure consistent formatting across all currencies:

```diff
<span className="font-roboto text-xs font-medium">
-   {transaction.currency?.code === 'ARS'
-       ? `ARS$ ${transaction.currency.amount}`
-       : `${transaction.currencySymbol}${formatAmount(amount)}`}
+   {transaction.currency?.code === 'ARS'
+       ? `ARS$ ${formatAmount(transaction.currency.amount)}`
+       : `${transaction.currencySymbol}${formatAmount(amount)}`}
</span>
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **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.

```suggestion
<span className="font-roboto text-xs font-medium">
    {transaction.currency?.code === 'ARS'
        ? `ARS$ ${formatAmount(transaction.currency.amount)}`
        : `${transaction.currencySymbol}${formatAmount(amount)}`}
</span>
```

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated reply by CodeRabbit -->

✅ Addressed in commit c6474f0

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-14T20:10:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -237,11 +239,27 @@ export default function PaymentPage({ recipient, isDirectPay = false }: Props) {
             <div>
                 {currentView === 'INITIAL' && (
                     <div className="space-y-4">
-                        <InitialPaymentView {...(parsedPaymentData as ParsedURL)} />
+                        <InitialPaymentView
+                            {...(parsedPaymentData as ParsedURL)}
+                            currency={
+                                currencyCode
+                                    ? {
+                                          code: currencyCode,
+                                          symbol: currencySymbol!,
+                                          price: currencyPrice!,
+                                      }
+                                    : undefined
+                            }
+                            setCurrencyAmount={(value: string | undefined) => setCurrencyAmount(value || '')}
+                            currencyAmount={currencyAmount}
+                        />
                     </div>
                 )}
                 {currentView === 'CONFIRM' && (
-                    <ConfirmPaymentView isPintaReq={parsedPaymentData?.token?.symbol === 'PNT'} />
+                    <ConfirmPaymentView
+                        isPintaReq={parsedPaymentData?.token?.symbol === 'PNT'}
+                        currencyAmount={`${currencySymbol} ${currencyAmount}`}
+                    />
path: src/app/[...recipient]/client.tsx
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Check for empty currencyAmount before passing to component.**

The code constructs a string with currency symbol and amount without checking if these values are defined, which could lead to displaying "undefined undefined" or similar.

```diff
                    <ConfirmPaymentView
                        isPintaReq={parsedPaymentData?.token?.symbol === 'PNT'}
-                       currencyAmount={`${currencySymbol} ${currencyAmount}`}
+                       currencyAmount={currencyCode && currencyAmount ? `${currencySymbol} ${currencyAmount}` : undefined}
                    />
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **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.

```suggestion
<ConfirmPaymentView
  isPintaReq={parsedPaymentData?.token?.symbol === 'PNT'}
  currencyAmount={currencyCode && currencyAmount ? `${currencySymbol} ${currencyAmount}` : undefined}
/>
```

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated reply by CodeRabbit -->

✅ Addressed in commit c6474f0

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-14T20:10:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -252,6 +270,7 @@
                                 headerTitle="Send"
                                 recipientType={parsedPaymentData?.recipient?.recipientType}
                                 type="SEND"
+                                currencyAmount={`${currencySymbol} ${currencyAmount}`}
path: src/app/[...recipient]/client.tsx
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Check for empty currencyAmount before passing to component.**

Similar to the previous issue, the code should check if currency values are defined before constructing the display string.

```diff
-                               currencyAmount={`${currencySymbol} ${currencyAmount}`}
+                               currencyAmount={currencyCode && currencyAmount ? `${currencySymbol} ${currencyAmount}` : undefined}
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **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.

```suggestion
                                currencyAmount={currencyCode && currencyAmount ? `${currencySymbol} ${currencyAmount}` : undefined}
```

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated reply by CodeRabbit -->

✅ Addressed in commit c6474f0

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-14T20:10:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -177,6 +248,15 @@ export function useTransactionHistory({
         })
     }
 
+    if (mode === 'public') {
+        return useQuery({
+            queryKey: ['transactions', 'public', { limit }],
+            queryFn: () => fetchHistoryPublic({ username: username!, limit }),
+            enabled,
+            staleTime: 15 * 1000, // 15 seconds
+        })
path: src/hooks/useTransactionHistory.ts
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**React-Query cache key misses `username` → stale / leaking data**

The query key for public history is

```ts
['transactions', 'public', { limit }]
```

If the user browses `/@alice` and then `/@bob`, React-Query will reuse the same cache entry because the key does not change, showing Alice’s history under Bob’s profile until the stale-time expires.

Add `username` to the key:

```diff
-return useQuery({
-    queryKey: ['transactions', 'public', { limit }],
+return useQuery({
+    queryKey: ['transactions', 'public', username, { limit }],
```

<!-- This is an auto-generated reply by CodeRabbit -->

✅ Addressed in commit c6474f0

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-14T20:10:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -167,6 +174,70 @@
         }
     }
 
+    const fetchHistoryPublic = async ({
+        username,
+        cursor,
+        limit,
+    }: {
+        username: string
+        cursor?: string
+        limit: number
+    }): Promise<HistoryResponse> => {
+        const queryParams = new URLSearchParams()
+        if (cursor) queryParams.append('cursor', cursor)
+        if (limit) queryParams.append('limit', limit.toString())
+
+        const url = `${PEANUT_API_URL}/users/${username}/history?${queryParams.toString()}`
+
+        const response = await fetchWithSentry(url, {
+            method: 'GET',
+            headers: {
+                'Content-Type': 'application/json',
+                Authorization: `Bearer ${Cookies.get('jwt-token')}`,
+            },
+        })
+
+        if (!response.ok) {
+            throw new Error(`Failed to fetch history: ${response.statusText}`)
+        }
+
+        const data = await response.json()
+
+        // Convert ISO strings to Date objects for timestamps
+        return {
+            ...data,
+            entries: data.entries.map((entry: HistoryEntry) => {
+                const extraData = entry.extraData ?? {}
+                let link: string = ''
+                let tokenSymbol: string = ''
+                let usdAmount: string = ''
+                switch (entry.type) {
+                    case 'REQUEST':
+                        link = `${process.env.NEXT_PUBLIC_BASE_URL}/request/pay?id=${entry.uuid}`
+                        tokenSymbol = entry.tokenSymbol
+                        usdAmount = entry.amount.toString()
+                        break
+                    case 'DIRECT_SEND':
+                        tokenSymbol = 'USDC'
+                        usdAmount = entry.amount.toString()
+                        break
+                    default:
+                        break
+                }
+                return {
+                    ...entry,
+                    tokenSymbol,
+                    timestamp: new Date(entry.timestamp),
+                    extraData: {
+                        ...extraData,
+                        link,
+                        usdAmount: `$${usdAmount}`,
+                    },
+                }
+            }),
+        }
+    }
path: src/hooks/useTransactionHistory.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Large duplication between `fetchHistory` and `fetchHistoryPublic`**

Both functions share ~80 % identical logic (query-string building, Sentry wrapping, entry post-processing). Duplicated code is harder to maintain and prone to inconsistency (e.g. differing `switch` cases).

Consider extracting the common part into a private helper:

```ts
const fetchHistoryBase = async (url: string, opts: RequestInit) => { … }

const fetchHistory        = (args) => fetchHistoryBase(`${PEANUT_API_URL}/users/history?...`, opts)
const fetchHistoryPublic  = (args) => fetchHistoryBase(`${PEANUT_API_URL}/users/${username}/history?...`, opts)
```

This will also let you unit-test the transformation logic once instead of twice.

<!-- This is an auto-generated reply by CodeRabbit -->

✅ Addressed in commit c6474f0

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-14T20:10:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -167,6 +174,70 @@
         }
     }
 
+    const fetchHistoryPublic = async ({
+        username,
+        cursor,
+        limit,
+    }: {
+        username: string
+        cursor?: string
+        limit: number
+    }): Promise<HistoryResponse> => {
+        const queryParams = new URLSearchParams()
+        if (cursor) queryParams.append('cursor', cursor)
+        if (limit) queryParams.append('limit', limit.toString())
+
+        const url = `${PEANUT_API_URL}/users/${username}/history?${queryParams.toString()}`
+
+        const response = await fetchWithSentry(url, {
+            method: 'GET',
+            headers: {
+                'Content-Type': 'application/json',
+                Authorization: `Bearer ${Cookies.get('jwt-token')}`,
+            },
+        })
path: src/hooks/useTransactionHistory.ts
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Avoid sending `"Bearer undefined"` when visitor is not authenticated**

When a guest views a public profile there is usually no `jwt-token` cookie.  
With the unconditional header we send `Authorization: Bearer undefined`, which most back-ends treat as an *invalid* token and reply `401` instead of serving public data.

```diff
-            headers: {
-                'Content-Type': 'application/json',
-                Authorization: `Bearer ${Cookies.get('jwt-token')}`,
-            },
+            headers: {
+                'Content-Type': 'application/json',
+                ...(Cookies.get('jwt-token')
+                    ? { Authorization: `Bearer ${Cookies.get('jwt-token')}` }
+                    : {}),
+            },
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **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.

```suggestion
        const response = await fetchWithSentry(url, {
            method: 'GET',
            headers: {
                'Content-Type': 'application/json',
                ...(Cookies.get('jwt-token')
                    ? { Authorization: `Bearer ${Cookies.get('jwt-token')}` }
                    : {}),
            },
        })
```

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated reply by CodeRabbit -->

✅ Addressed in commit c6474f0

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-14T20:10:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2,36 +2,61 @@ import { PEANUT_WALLET_TOKEN } from '@/constants'
 import { tokenSelectorContext } from '@/context'
 import { useWallet } from '@/hooks/wallet/useWallet'
 import { estimateIfIsStableCoinFromPrice, formatAmountWithoutComma, formatTokenAmount } from '@/utils'
-import { useContext, useEffect, useMemo, useRef } from 'react'
+import { useContext, useEffect, useMemo, useRef, useState } from 'react'
 import Icon from '../Icon'
 
 interface TokenAmountInputProps {
     className?: string
     tokenValue: string | undefined
     setTokenValue: (tokenvalue: string | undefined) => void
+    setCurrencyAmount?: (currencyvalue: string | undefined) => void
     onSubmit?: () => void
     maxValue?: string
     disabled?: boolean
     walletBalance?: string
+    currency?: {
+        code: string
+        symbol: string
+        price: number
+    }
 }
 
 const TokenAmountInput = ({
     className,
     tokenValue,
     setTokenValue,
+    setCurrencyAmount,
     onSubmit,
     maxValue,
     disabled,
     walletBalance,
+    currency,
 }: TokenAmountInputProps) => {
     const { inputDenomination, setInputDenomination, selectedTokenData, selectedTokenAddress } =
         useContext(tokenSelectorContext)
     const inputRef = useRef<HTMLInputElement>(null)
     const inputType = useMemo(() => (window.innerWidth < 640 ? 'text' : 'number'), [])
     const { isConnected: isPeanutWallet } = useWallet()
 
-    const onChange = (tokenvalue: string) => {
-        setTokenValue(tokenvalue)
+    // Add state for currency mode (USD or custom currency)
+    const [currencyMode, setCurrencyMode] = useState<'USD' | 'CUSTOM'>(currency ? 'CUSTOM' : 'USD')
+
+    // Store display value for input field (what user sees when typing)
+    const [displayValue, setDisplayValue] = useState<string>(tokenValue || '')
+
+    const onChange = (inputValue: string) => {
+        // Update display value (what the user sees)
+        setDisplayValue(inputValue)
+
+        // 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)!
path: src/components/Global/TokenAmountInput/index.tsx
user: login: Hugo0
body: wat if currency.price is 0 or undef
created_at: 2025-05-14T21:07:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,17 @@
+import { fetchWithSentry } from '@/utils'
+
+export const currencyApi = {
+    getPrice: async (currencyCode: string): Promise<number> => {
+        if (currencyCode === 'USD') return 1
+        if (currencyCode !== 'ARS') {
+            throw new Error('Unsupported currency')
+        }
+        const response = await fetchWithSentry('https://dolarapi.com/v1/dolares/cripto')
path: src/services/currency.ts
user: login: Hugo0
body: nice
created_at: 2025-05-14T21:07:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,17 @@
+import { fetchWithSentry } from '@/utils'
+
+export const currencyApi = {
+    getPrice: async (currencyCode: string): Promise<number> => {
+        if (currencyCode === 'USD') return 1
+        if (currencyCode !== 'ARS') {
+            throw new Error('Unsupported currency')
+        }
+        const response = await fetchWithSentry('https://dolarapi.com/v1/dolares/cripto')
path: src/services/currency.ts
user: login: Hugo0
body: nit: mini explainer on dolarapi would be goood

also a @dev todo: comment  for future (we shuold prob fetch from 2-3 sources and collate). 
created_at: 2025-05-14T21:08:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,43 @@
+import { currencyApi } from '@/services/currency'
+import { useState, useEffect } from 'react'
+
+const SIMBOLS_BY_CURRENCY_CODE: Record<string, string> = {
+    ARS: 'AR$',
+    USD: '$',
+}
+
+export const useCurrency = (currencyCode: string | null) => {
path: src/hooks/useCurrency.ts
user: login: Hugo0
body: imo this lacks caching. ars<>usd rate doesnt have to be a useffect and fetched each time

i think 5min cache is totally fine, or 30s MINIMUM
created_at: 2025-05-14T21:10:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,43 @@
+import { currencyApi } from '@/services/currency'
+import { useState, useEffect } from 'react'
+
+const SIMBOLS_BY_CURRENCY_CODE: Record<string, string> = {
+    ARS: 'AR$',
+    USD: '$',
+}
+
+export const useCurrency = (currencyCode: string | null) => {
path: src/hooks/useCurrency.ts
user: login: Hugo0
body: future: might be cached in backend too
created_at: 2025-05-14T21:10:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -118,56 +106,8 @@ const PublicProfile: React.FC<PublicProfileProps> = ({
                         </div>
                     )}
 
-                    {/* Transactions List - Only shown if user has transactions */}
-                    {!!hasTransactions && (
-                        <div className="space-y-2">
-                            <div className="flex items-center justify-between">
-                                <h2 className="text-lg font-bold">Transactions</h2>
-                                <Link href={`/history`}>
-                                    <div className="flex items-center">
-                                        <Icon name="chevron-up" size={16} fill="black" className="rotate-90" />
-                                    </div>
-                                </Link>
-                            </div>
-
-                            {/* todo: show real transactions from the user */}
-                            {/* Example transaction - todo: replace with actual transaction data */}
-                            {/* <div>
-                                <TransactionCard
-                                    type="send"
-                                    name="Hugo Montenegro"
path: src/components/Profile/components/PublicProfile.tsx
user: login: Hugo0
body: lol
created_at: 2025-05-14T21:11:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -78,8 +78,9 @@ const TransactionCard: React.FC<TransactionCardProps> = ({
                     {/* amount and status on the right side */}
                     <div className="flex flex-col items-end space-y-0.5">
                         <span className="font-roboto text-xs font-medium">
-                            {transaction.currencySymbol}
-                            {formatAmount(amount)}
+                            {transaction.currency?.code === 'ARS'
+                                ? `ARS$ ${transaction.currency.amount}`
+                                : `${transaction.currencySymbol}${formatAmount(amount)}`}
                         </span>
path: src/components/TransactionDetails/TransactionCard.tsx
user: login: Hugo0
body: plz resolve
created_at: 2025-05-14T21:12:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -167,6 +174,70 @@
         }
     }
 
+    const fetchHistoryPublic = async ({
+        username,
+        cursor,
+        limit,
+    }: {
+        username: string
+        cursor?: string
+        limit: number
+    }): Promise<HistoryResponse> => {
+        const queryParams = new URLSearchParams()
+        if (cursor) queryParams.append('cursor', cursor)
+        if (limit) queryParams.append('limit', limit.toString())
+
+        const url = `${PEANUT_API_URL}/users/${username}/history?${queryParams.toString()}`
+
+        const response = await fetchWithSentry(url, {
+            method: 'GET',
+            headers: {
+                'Content-Type': 'application/json',
+                Authorization: `Bearer ${Cookies.get('jwt-token')}`,
+            },
+        })
+
+        if (!response.ok) {
+            throw new Error(`Failed to fetch history: ${response.statusText}`)
+        }
+
+        const data = await response.json()
+
+        // Convert ISO strings to Date objects for timestamps
+        return {
+            ...data,
+            entries: data.entries.map((entry: HistoryEntry) => {
+                const extraData = entry.extraData ?? {}
+                let link: string = ''
+                let tokenSymbol: string = ''
+                let usdAmount: string = ''
+                switch (entry.type) {
+                    case 'REQUEST':
+                        link = `${process.env.NEXT_PUBLIC_BASE_URL}/request/pay?id=${entry.uuid}`
+                        tokenSymbol = entry.tokenSymbol
+                        usdAmount = entry.amount.toString()
+                        break
+                    case 'DIRECT_SEND':
+                        tokenSymbol = 'USDC'
+                        usdAmount = entry.amount.toString()
+                        break
+                    default:
+                        break
+                }
+                return {
+                    ...entry,
+                    tokenSymbol,
+                    timestamp: new Date(entry.timestamp),
+                    extraData: {
+                        ...extraData,
+                        link,
+                        usdAmount: `$${usdAmount}`,
+                    },
+                }
+            }),
+        }
+    }
path: src/hooks/useTransactionHistory.ts
user: login: Hugo0
body: plz resolve! good coderabbit comment
created_at: 2025-05-14T21:12:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -177,6 +248,15 @@ export function useTransactionHistory({
         })
     }
 
+    if (mode === 'public') {
+        return useQuery({
+            queryKey: ['transactions', 'public', { limit }],
+            queryFn: () => fetchHistoryPublic({ username: username!, limit }),
+            enabled,
+            staleTime: 15 * 1000, // 15 seconds
+        })
path: src/hooks/useTransactionHistory.ts
user: login: Hugo0
body: plz resolve
created_at: 2025-05-14T21:13:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -78,8 +78,9 @@ const TransactionCard: React.FC<TransactionCardProps> = ({
                     {/* amount and status on the right side */}
                     <div className="flex flex-col items-end space-y-0.5">
                         <span className="font-roboto text-xs font-medium">
-                            {transaction.currencySymbol}
-                            {formatAmount(amount)}
+                            {transaction.currency?.code === 'ARS'
+                                ? `ARS$ ${transaction.currency.amount}`
+                                : `${transaction.currencySymbol}${formatAmount(amount)}`}
                         </span>
path: src/components/TransactionDetails/TransactionCard.tsx
user: login: coderabbitai[bot]
body: @Hugo0 I've resolved the inconsistent currency formatting issue. The current implementation doesn't format ARS currency amounts, while other currencies use the `formatAmount` utility. I'll apply the same formatting to ensure consistent number display across all currencies:

```jsx
<span className="font-roboto text-xs font-medium">
    {transaction.currency?.code === 'ARS'
        ? `ARS$ ${formatAmount(transaction.currency.amount)}`
        : `${transaction.currencySymbol}${formatAmount(amount)}`}
</span>
```

This ensures numbers are consistently formatted with proper thousand separators and decimal places, regardless of currency type.


<!-- <agentComponentType>chat-answer</agentComponentType> -->

<!-- <rawResChunk><answer>@Hugo0 I've resolved the inconsistent currency formatting issue. The current implementation doesn't format ARS currency amounts, while other currencies use the `formatAmount` utility. I'll apply the same formatting to ensure consistent number display across all currencies:

```jsx
<span className="font-roboto text-xs font-medium">
    {transaction.currency?.code === 'ARS'
        ? `ARS$ ${formatAmount(transaction.currency.amount)}`
        : `${transaction.currencySymbol}${formatAmount(amount)}`}
</span>
```

This ensures numbers are consistently formatted with proper thousand separators and decimal places, regardless of currency type.</answer></rawResChunk> -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-14T21:14:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -177,6 +248,15 @@ export function useTransactionHistory({
         })
     }
 
+    if (mode === 'public') {
+        return useQuery({
+            queryKey: ['transactions', 'public', { limit }],
+            queryFn: () => fetchHistoryPublic({ username: username!, limit }),
+            enabled,
+            staleTime: 15 * 1000, // 15 seconds
+        })
path: src/hooks/useTransactionHistory.ts
user: login: coderabbitai[bot]
body: 

<!-- <agentComponentType>chat-answer</agentComponentType> -->

<!-- <rawResChunk><nextState>plan-edits</nextState></rawResChunk> -->

<!-- <rawResChunk><answer>@Hugo0 I'll resolve the cache key issue by including the `username` in the React-Query cache key.



```mermaid
graph TB
    A[Fix React-Query Cache Key]
    B[Add username to cache key for proper profile isolation]
    A --&gt; B
```

&lt;!-- <requirement>
[requirement fix_react_query_cache_key_1/1]
Update the React-Query cache key in the public mode to include the username parameter, ensuring proper caching behavior when viewing different user profiles.

[propose_code_change]
File: `src/hooks/useTransactionHistory.ts`

```diff
 if (mode === 'public') {
     return useQuery({
-        queryKey: ['transactions', 'public', { limit }],
+        queryKey: ['transactions', 'public', username, { limit }],
         queryFn: () => fetchHistoryPublic({ username: username!, limit }),
         enabled,
         staleTime: 15 * 1000, // 15 seconds
     })
 }
```
</requirement> --&gt;

This fix will ensure that React-Query maintains separate cache entries for each user's transaction history, preventing Alice's transactions from appearing on Bob's profile when navigating between them.

---

⏳ Preparing changes...

</answer></rawResChunk> -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-14T21:14:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -237,11 +239,27 @@ export default function PaymentPage({ recipient, isDirectPay = false }: Props) {
             <div>
                 {currentView === 'INITIAL' && (
                     <div className="space-y-4">
-                        <InitialPaymentView {...(parsedPaymentData as ParsedURL)} />
+                        <InitialPaymentView
+                            {...(parsedPaymentData as ParsedURL)}
+                            currency={
+                                currencyCode
+                                    ? {
+                                          code: currencyCode,
+                                          symbol: currencySymbol!,
+                                          price: currencyPrice!,
+                                      }
+                                    : undefined
+                            }
+                            setCurrencyAmount={(value: string | undefined) => setCurrencyAmount(value || '')}
+                            currencyAmount={currencyAmount}
+                        />
                     </div>
                 )}
                 {currentView === 'CONFIRM' && (
-                    <ConfirmPaymentView isPintaReq={parsedPaymentData?.token?.symbol === 'PNT'} />
+                    <ConfirmPaymentView
+                        isPintaReq={parsedPaymentData?.token?.symbol === 'PNT'}
+                        currencyAmount={`${currencySymbol} ${currencyAmount}`}
+                    />
path: src/app/[...recipient]/client.tsx
user: login: jjramirezn
body: good idea
created_at: 2025-05-14T21:15:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -252,6 +270,7 @@
                                 headerTitle="Send"
                                 recipientType={parsedPaymentData?.recipient?.recipientType}
                                 type="SEND"
+                                currencyAmount={`${currencySymbol} ${currencyAmount}`}
path: src/app/[...recipient]/client.tsx
user: login: jjramirezn
body: good idea
created_at: 2025-05-14T21:15:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -237,11 +239,27 @@ export default function PaymentPage({ recipient, isDirectPay = false }: Props) {
             <div>
                 {currentView === 'INITIAL' && (
                     <div className="space-y-4">
-                        <InitialPaymentView {...(parsedPaymentData as ParsedURL)} />
+                        <InitialPaymentView
+                            {...(parsedPaymentData as ParsedURL)}
+                            currency={
+                                currencyCode
+                                    ? {
+                                          code: currencyCode,
+                                          symbol: currencySymbol!,
+                                          price: currencyPrice!,
+                                      }
+                                    : undefined
+                            }
+                            setCurrencyAmount={(value: string | undefined) => setCurrencyAmount(value || '')}
+                            currencyAmount={currencyAmount}
+                        />
                     </div>
                 )}
                 {currentView === 'CONFIRM' && (
-                    <ConfirmPaymentView isPintaReq={parsedPaymentData?.token?.symbol === 'PNT'} />
+                    <ConfirmPaymentView
+                        isPintaReq={parsedPaymentData?.token?.symbol === 'PNT'}
+                        currencyAmount={`${currencySymbol} ${currencyAmount}`}
+                    />
path: src/app/[...recipient]/client.tsx
user: login: coderabbitai[bot]
body: @jjramirezn Thanks for confirming! Do you need any help implementing the change? I'm here if you need any assistance with this or other improvements for your multi-currency implementation.

(⌒ω⌒)


<!-- <agentComponentType>chat-answer</agentComponentType> -->

<!-- <rawResChunk><answer>@jjramirezn Thanks for confirming! Do you need any help implementing the change? I'm here if you need any assistance with this or other improvements for your multi-currency implementation.

(⌒ω⌒)</answer></rawResChunk> -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-14T21:16:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -167,6 +174,70 @@
         }
     }
 
+    const fetchHistoryPublic = async ({
+        username,
+        cursor,
+        limit,
+    }: {
+        username: string
+        cursor?: string
+        limit: number
+    }): Promise<HistoryResponse> => {
+        const queryParams = new URLSearchParams()
+        if (cursor) queryParams.append('cursor', cursor)
+        if (limit) queryParams.append('limit', limit.toString())
+
+        const url = `${PEANUT_API_URL}/users/${username}/history?${queryParams.toString()}`
+
+        const response = await fetchWithSentry(url, {
+            method: 'GET',
+            headers: {
+                'Content-Type': 'application/json',
+                Authorization: `Bearer ${Cookies.get('jwt-token')}`,
+            },
+        })
+
+        if (!response.ok) {
+            throw new Error(`Failed to fetch history: ${response.statusText}`)
+        }
+
+        const data = await response.json()
+
+        // Convert ISO strings to Date objects for timestamps
+        return {
+            ...data,
+            entries: data.entries.map((entry: HistoryEntry) => {
+                const extraData = entry.extraData ?? {}
+                let link: string = ''
+                let tokenSymbol: string = ''
+                let usdAmount: string = ''
+                switch (entry.type) {
+                    case 'REQUEST':
+                        link = `${process.env.NEXT_PUBLIC_BASE_URL}/request/pay?id=${entry.uuid}`
+                        tokenSymbol = entry.tokenSymbol
+                        usdAmount = entry.amount.toString()
+                        break
+                    case 'DIRECT_SEND':
+                        tokenSymbol = 'USDC'
+                        usdAmount = entry.amount.toString()
+                        break
+                    default:
+                        break
+                }
+                return {
+                    ...entry,
+                    tokenSymbol,
+                    timestamp: new Date(entry.timestamp),
+                    extraData: {
+                        ...extraData,
+                        link,
+                        usdAmount: `$${usdAmount}`,
+                    },
+                }
+            }),
+        }
+    }
path: src/hooks/useTransactionHistory.ts
user: login: coderabbitai[bot]
body: 

<!-- <agentComponentType>chat-answer</agentComponentType> -->

<!-- <rawResChunk><nextState>plan-edits</nextState></rawResChunk> -->

<!-- <rawResChunk><answer>@Hugo0 I'll implement the refactoring suggested by CodeRabbit to reduce duplication between these two functions.



```mermaid
graph TB
    A["Refactor fetchHistory functions"]
    A --&gt; B["Create fetchHistoryBase helper function"]
    A --&gt; C["Update fetchHistory to use base function"]
    A --&gt; D["Update fetchHistoryPublic to use base function"]
    B --&gt; B1["Extract common URL query params logic"]
    B --&gt; B2["Extract common fetch logic"]
    B --&gt; B3["Extract common response transformation"]
    B --&gt; B4["Add type-specific entry processing callback"]
    C --&gt; C1["Call base function with history-specific URL"]
    C --&gt; C2["Provide history-specific entry processing"]
    D --&gt; D1["Call base function with public history URL"]
    D --&gt; D2["Provide public-specific entry processing"]
```

&lt;!-- <requirement>
[requirement refactor_fetch_functions_1/3 create_base_function]
Create a new `fetchHistoryBase` helper function that handles the common logic between the two fetch functions.

[propose_code_change]
File: `src/hooks/useTransactionHistory.ts`

```diff
     // rest of the code...

+    // Base function to handle common fetch logic
+    const fetchHistoryBase = async ({
+        url,
+        cursor,
+        limit,
+        processEntry,
+    }: {
+        url: string;
+        cursor?: string;
+        limit: number;
+        processEntry: (entry: HistoryEntry) => HistoryEntry;
+    }): Promise<HistoryResponse> => {
+        const queryParams = new URLSearchParams()
+        if (cursor) queryParams.append('cursor', cursor)
+        if (limit) queryParams.append('limit', limit.toString())
+
+        const fullUrl = `${url}?${queryParams.toString()}`
+
+        const response = await fetchWithSentry(fullUrl, {
+            method: 'GET',
+            headers: {
+                'Content-Type': 'application/json',
+                Authorization: `Bearer ${Cookies.get('jwt-token')}`,
+            },
+        })
+
+        if (!response.ok) {
+            throw new Error(`Failed to fetch history: ${response.statusText}`)
+        }
+
+        const data = await response.json()
+
+        // Convert ISO strings to Date objects and process entries
+        return {
+            ...data,
+            entries: data.entries.map(processEntry),
+        }
+    }
+
```
</requirement> --&gt;

&lt;!-- <requirement>
[requirement refactor_fetch_functions_2/3 update_fetch_history]
Update the `fetchHistory` function to use the new base function.

[propose_code_change]
File: `src/hooks/useTransactionHistory.ts`

```diff
-    const fetchHistory = async ({ cursor, limit }: { cursor?: string; limit: number }): Promise<HistoryResponse> => {
-        const queryParams = new URLSearchParams()
-        if (cursor) queryParams.append('cursor', cursor)
-        if (limit) queryParams.append('limit', limit.toString())
-
-        const url = `${PEANUT_API_URL}/users/history?${queryParams.toString()}`
-
-        const response = await fetchWithSentry(url, {
-            method: 'GET',
-            headers: {
-                'Content-Type': 'application/json',
-                Authorization: `Bearer ${Cookies.get('jwt-token')}`,
-            },
-        })
-
-        if (!response.ok) {
-            throw new Error(`Failed to fetch history: ${response.statusText}`)
-        }
-
-        const data = await response.json()
-
-        // Convert ISO strings to Date objects for timestamps
-        return {
-            ...data,
-            entries: data.entries.map((entry: HistoryEntry) => {
+    const fetchHistory = async ({ cursor, limit }: { cursor?: string; limit: number }): Promise<HistoryResponse> => {
+        return fetchHistoryBase({
+            url: `${PEANUT_API_URL}/users/history`,
+            cursor,
+            limit,
+            processEntry: (entry: HistoryEntry) => {
                 const extraData = entry.extraData ?? {}
                 let link: string = ''
                 let tokenSymbol: string = ''
                 let usdAmount: string = ''
                 switch (entry.type) {
                     case 'SEND_LINK':
                         const password = getFromLocalStorage(`sendLink::password::${entry.uuid}`)
                         const { contractVersion, depositIdx } = extraData
                         link = `${BASE_URL}/claim?c=${entry.chainId}&v=${contractVersion}&i=${depositIdx}#p=${password}`
                         const tokenDetails = getTokenDetails({
                             tokenAddress: entry.tokenAddress as Hash,
                             chainId: entry.chainId,
                         })
                         usdAmount = formatUnits(BigInt(entry.amount), tokenDetails?.decimals ?? 6)
                         tokenSymbol = tokenDetails?.symbol ?? ''
                         break
                     case 'REQUEST':
                         link = `${process.env.NEXT_PUBLIC_BASE_URL}/request/pay?id=${entry.uuid}`
                         tokenSymbol = entry.tokenSymbol
                         usdAmount = entry.amount.toString()
                         break
                     case 'DIRECT_SEND':
                         tokenSymbol = 'USDC'
                         usdAmount = entry.amount.toString()
                         break
                     case 'DEPOSIT':
                         tokenSymbol = 'USDC'
                         usdAmount = formatUnits(BigInt(entry.amount), PEANUT_WALLET_TOKEN_DECIMALS)
                         break
                     default:
                         break
                 }
                 return {
                     ...entry,
                     tokenSymbol,
                     timestamp: new Date(entry.timestamp),
                     extraData: {
                         ...extraData,
                         link,
                         usdAmount: `$${usdAmount}`,
                     },
                 }
-            }),
-        }
+            },
+        })
     }
```
</requirement> --&gt;

&lt;!-- <requirement>
[requirement refactor_fetch_functions_3/3 update_fetch_history_public]
Update the `fetchHistoryPublic` function to use the new base function.

[propose_code_change]
File: `src/hooks/useTransactionHistory.ts`

```diff
-    const fetchHistoryPublic = async ({
-        username,
-        cursor,
-        limit,
-    }: {
-        username: string
-        cursor?: string
-        limit: number
-    }): Promise<HistoryResponse> => {
-        const queryParams = new URLSearchParams()
-        if (cursor) queryParams.append('cursor', cursor)
-        if (limit) queryParams.append('limit', limit.toString())
-
-        const url = `${PEANUT_API_URL}/users/${username}/history?${queryParams.toString()}`
-
-        const response = await fetchWithSentry(url, {
-            method: 'GET',
-            headers: {
-                'Content-Type': 'application/json',
-                Authorization: `Bearer ${Cookies.get('jwt-token')}`,
-            },
-        })
-
-        if (!response.ok) {
-            throw new Error(`Failed to fetch history: ${response.statusText}`)
-        }
-
-        const data = await response.json()
-
-        // Convert ISO strings to Date objects for timestamps
-        return {
-            ...data,
-            entries: data.entries.map((entry: HistoryEntry) => {
+    const fetchHistoryPublic = async ({
+        username,
+        cursor,
+        limit,
+    }: {
+        username: string
+        cursor?: string
+        limit: number
+    }): Promise<HistoryResponse> => {
+        return fetchHistoryBase({
+            url: `${PEANUT_API_URL}/users/${username}/history`,
+            cursor,
+            limit,
+            processEntry: (entry: HistoryEntry) => {
                 const extraData = entry.extraData ?? {}
                 let link: string = ''
                 let tokenSymbol: string = ''
                 let usdAmount: string = ''
                 switch (entry.type) {
                     case 'REQUEST':
                         link = `${process.env.NEXT_PUBLIC_BASE_URL}/request/pay?id=${entry.uuid}`
                         tokenSymbol = entry.tokenSymbol
                         usdAmount = entry.amount.toString()
                         break
                     case 'DIRECT_SEND':
                         tokenSymbol = 'USDC'
                         usdAmount = entry.amount.toString()
                         break
                     default:
                         break
                 }
                 return {
                     ...entry,
                     tokenSymbol,
                     timestamp: new Date(entry.timestamp),
                     extraData: {
                         ...extraData,
                         link,
                         usdAmount: `$${usdAmount}`,
                     },
                 }
-            }),
-        }
+            },
+        })
     }
```
</requirement> --&gt;

This refactoring will extract the common logic into a `fetchHistoryBase` helper function that handles:
1. Query parameter construction
2. API request with authentication 
3. Response processing

The specialized functions will only need to provide:
- The base URL
- A callback for processing entries based on their type

This approach eliminates code duplication while preserving the specific business logic for each use case. Another agent will implement these changes when you confirm.

---

<details>
<summary>✏️ Learnings added</summary>

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

> Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure [review instructions](https://docs.coderabbit.ai/guides/review-instructions). You can manage existing learnings in the [app](https://app.coderabbit.ai/learnings).

</details>



---

<details>
<summary>🧠 Learnings used</summary>

```
Learnt from: Hugo0
PR: peanutprotocol/peanut-ui#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.
```

</details>



---

⏳ Preparing changes...

</answer></rawResChunk> -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-14T21:16:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -252,6 +270,7 @@
                                 headerTitle="Send"
                                 recipientType={parsedPaymentData?.recipient?.recipientType}
                                 type="SEND"
+                                currencyAmount={`${currencySymbol} ${currencyAmount}`}
path: src/app/[...recipient]/client.tsx
user: login: coderabbitai[bot]
body: @jjramirezn I'm glad you find the suggestion helpful! Adding the null check will prevent potential issues if currency values aren't defined.



(\\ /)
(•ᴥ•)
/ >🥜

---

<!-- This is an auto-generated comment: tweet message by coderabbit.ai -->

If you found this review helpful, would you consider giving us a shout-out on [X](https://twitter.com/intent/tweet?related=CodeRabbitAI&text=Avoid%20passing%20empty%20currency%20amounts%20to%20components%20by%20validating%20both%20currencyCode%20and%20currencyAmount%20first!%20Cleaner%2C%20safer%20UI%20rendering%20with%20%40coderabbitai%20%F0%9F%9B%A0%EF%B8%8F%F0%9F%92%A1%20Try%20it%20now!&url=https%3A%2F%2Fgithub.com%2Fpeanutprotocol%2Fpeanut-ui%2Fpull%2F852%23discussion_r2089663817)?

Thank you for using CodeRabbit!

<!-- end of auto-generated comment: tweet message by coderabbit.ai -->


<!-- <agentComponentType>chat-answer</agentComponentType> -->

<!-- <rawResChunk><answer>@jjramirezn I'm glad you find the suggestion helpful! Adding the null check will prevent potential issues if currency values aren't defined.



(\\ /)
(•ᴥ•)
/ >🥜

---

&lt;!-- This is an auto-generated comment: tweet message by coderabbit.ai --&gt;

If you found this review helpful, would you consider giving us a shout-out on [X](https://twitter.com/intent/tweet?related=CodeRabbitAI&text=Avoid%20passing%20empty%20currency%20amounts%20to%20components%20by%20validating%20both%20currencyCode%20and%20currencyAmount%20first!%20Cleaner%2C%20safer%20UI%20rendering%20with%20%40coderabbitai%20%F0%9F%9B%A0%EF%B8%8F%F0%9F%92%A1%20Try%20it%20now!&url=https%3A%2F%2Fgithub.com%2Fpeanutprotocol%2Fpeanut-ui%2Fpull%2F852%23discussion_r2089663817)?

Thank you for using CodeRabbit!

&lt;!-- end of auto-generated comment: tweet message by coderabbit.ai --&gt;

</answer></rawResChunk> -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-14T21:16:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -177,6 +248,15 @@ export function useTransactionHistory({
         })
     }
 
+    if (mode === 'public') {
+        return useQuery({
+            queryKey: ['transactions', 'public', { limit }],
+            queryFn: () => fetchHistoryPublic({ username: username!, limit }),
+            enabled,
+            staleTime: 15 * 1000, // 15 seconds
+        })
path: src/hooks/useTransactionHistory.ts
user: login: coderabbitai[bot]
body: A summary of the edits CodeRabbit can apply:

> Update the React-Query cache key in src/hooks/useTransactionHistory.ts for public mode to include the username parameter.



A plan of each step that CodeRabbit will take in order to execute the edits for each file (with inline rough draft snippets):

<details>
<summary>✏️ src/hooks/useTransactionHistory.ts</summary>

In the `if (mode === 'public')` block, locate the line containing the existing queryKey:
```ts
        queryKey: ['transactions', 'public', { limit }],
```

---

Replace that line with the updated cache key including `username`:
```diff
-        queryKey: ['transactions', 'public', { limit }],
+        queryKey: ['transactions', 'public', username, { limit }],
```

---

Save your changes and test by navigating to `/@alice` then `/@bob`, confirming that React-Query now caches each user’s public history under a distinct key.

</details>



A summary of the context that CodeRabbit is considering across the codebase:

> Locate the public‐mode query block in useTransactionHistory.ts to target the correct queryKey line for modification.

- [ ] <!-- {"checkboxId": "32599f3c-a2a5-4bb9-b887-dee90db62b97", "radioGroupId": "edit-actions-group-2089762681"} --> ✅ Create PR with these edits

- [ ] <!-- {"checkboxId": "85e7c9f1-3a23-4b8e-9f25-d7a5b234a8c1", "radioGroupId": "edit-actions-group-2089762681"} --> 📌 Create commit in current branch

- [ ] <!-- {"checkboxId": "6b3df8e2-79f5-4eb8-9d4a-c7d3a6a5f7b1", "radioGroupId": "edit-actions-group-2089762681"} --> 📋 Get copyable edits

<!-- <agentComponentType>plan-edits</agentComponentType> -->

<!-- <rawResChunk><rationale>
The user comment points out a React-Query cache key issue in the public transaction history query inside the `useTransactionHistory.ts` hook. The current query key does not include `username`, which causes cache collisions and stale data when switching between different users' public profiles.

The relevant file is `src/hooks/useTransactionHistory.ts` where the `useQuery` for mode `'public'` is defined. The fix is to add `username` to the query key array to ensure cache keys are unique per user.

No other files are directly relevant to this cache key fix.

Therefore, only `src/hooks/useTransactionHistory.ts` needs to be edited to update the query key as described.
</rationale>
<toEditFile>src/hooks/useTransactionHistory.ts</toEditFile></rawResChunk> -->

<!-- <rawResChunk><rationale>
- src/hooks/useTransactionHistory.ts: Contains the public‐mode `useQuery` call with the incomplete cache key that must include `username`.
- src/components/Home/HomeHistory.tsx: Consumes `useTransactionHistory` (with `mode='public'`) and drives the UI; its queryKey usage will surface stale data if not updated.
- src/components/Profile/components/PublicProfile.tsx: Wraps `HomeHistory` for user profiles and may implicitly rely on the stale cache key.
- src/app/[...recipient]/client.tsx: Likely prefetches or renders public transaction history on the server side; needs the same cacheKey adjustment.
- Service file defining `fetchHistoryPublic` (located via search): Verifies that the API layer supports the `username` parameter consistently.
</rationale>

<generated_script>
```shell
#!/bin/bash
# 1. Verify the hook implementation where the key needs updating
echo ">>> In useTransactionHistory.ts:"
rg -n "queryKey: *\['transactions', 'public'" src/hooks/useTransactionHistory.ts

# 2. Locate all useQuery calls referencing the 'transactions' key
echo -e "\n>>> All React-Query uses of 'transactions' cache key:"
rg -n "useQuery\(\s*{\s*queryKey.*transactions" -C1

# 3. List files explicitly specifying ['transactions', 'public'] in their queryKey
echo -e "\n>>> Files with queryKey ['transactions', 'public']:"
rg -l "queryKey: *\['transactions', 'public'"

# 4. Find consumer components invoking useTransactionHistory
echo -e "\n>>> Components calling useTransactionHistory:"
rg -l "useTransactionHistory"

# 5. Locate the source of fetchHistoryPublic to ensure it accepts username
echo -e "\n>>> Definition or references of fetchHistoryPublic:"
rg -n "fetchHistoryPublic"
```
</generated_script></rawResChunk> -->

<!-- <rawResChunk><context>
  <rationale>Locate the public‐mode query block in useTransactionHistory.ts to target the correct queryKey line for modification.</rationale>
  <generated_script>
```shell
grep -n -C1 "mode === 'public'" src/hooks/useTransactionHistory.ts
```
  </generated_script>
  <shell_result>
249-    if (mode === 'public') {
250-        return useQuery({
251-            queryKey: ['transactions', 'public', { limit }],
252-            queryFn: () => fetchHistoryPublic({ username: username!, limit }),
253-            enabled,
254-            staleTime: 15 * 1000, // 15 seconds
255-        })
256-    }
  </shell_result>
</context>

<fileAction>
  <fileActionSummary>Update the React-Query cache key in src/hooks/useTransactionHistory.ts for public mode to include the username parameter.</fileActionSummary>
  <file_name>src/hooks/useTransactionHistory.ts</file_name>
  <type>Modify</type>
  <start_line>251</start_line>
  <end_line>251</end_line>
  <steps>
    <step>
      <stepNumber>1</stepNumber>
      <generated_instruction>In the `if (mode === 'public')` block, locate the line containing the existing queryKey:
```ts
        queryKey: ['transactions', 'public', { limit }],
```</generated_instruction>
    </step>
    <step>
      <stepNumber>2</stepNumber>
      <generated_instruction>Replace that line with the updated cache key including `username`:
```diff
-        queryKey: ['transactions', 'public', { limit }],
+        queryKey: ['transactions', 'public', username, { limit }],
```</generated_instruction>
    </step>
    <step>
      <stepNumber>3</stepNumber>
      <generated_instruction>Save your changes and test by navigating to `/@alice` then `/@bob`, confirming that React-Query now caches each user’s public history under a distinct key.</generated_instruction>
    </step>
  </steps>
</fileAction></rawResChunk> -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-14T21:16:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2,36 +2,61 @@ import { PEANUT_WALLET_TOKEN } from '@/constants'
 import { tokenSelectorContext } from '@/context'
 import { useWallet } from '@/hooks/wallet/useWallet'
 import { estimateIfIsStableCoinFromPrice, formatAmountWithoutComma, formatTokenAmount } from '@/utils'
-import { useContext, useEffect, useMemo, useRef } from 'react'
+import { useContext, useEffect, useMemo, useRef, useState } from 'react'
 import Icon from '../Icon'
 
 interface TokenAmountInputProps {
     className?: string
     tokenValue: string | undefined
     setTokenValue: (tokenvalue: string | undefined) => void
+    setCurrencyAmount?: (currencyvalue: string | undefined) => void
     onSubmit?: () => void
     maxValue?: string
     disabled?: boolean
     walletBalance?: string
+    currency?: {
+        code: string
+        symbol: string
+        price: number
+    }
 }
 
 const TokenAmountInput = ({
     className,
     tokenValue,
     setTokenValue,
+    setCurrencyAmount,
     onSubmit,
     maxValue,
     disabled,
     walletBalance,
+    currency,
 }: TokenAmountInputProps) => {
     const { inputDenomination, setInputDenomination, selectedTokenData, selectedTokenAddress } =
         useContext(tokenSelectorContext)
     const inputRef = useRef<HTMLInputElement>(null)
     const inputType = useMemo(() => (window.innerWidth < 640 ? 'text' : 'number'), [])
     const { isConnected: isPeanutWallet } = useWallet()
 
-    const onChange = (tokenvalue: string) => {
-        setTokenValue(tokenvalue)
+    // Add state for currency mode (USD or custom currency)
+    const [currencyMode, setCurrencyMode] = useState<'USD' | 'CUSTOM'>(currency ? 'CUSTOM' : 'USD')
+
+    // Store display value for input field (what user sees when typing)
+    const [displayValue, setDisplayValue] = useState<string>(tokenValue || '')
+
+    const onChange = (inputValue: string) => {
+        // Update display value (what the user sees)
+        setDisplayValue(inputValue)
+
+        // 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)!
path: src/components/Global/TokenAmountInput/index.tsx
user: login: jjramirezn
body: At this point if we have currency object we have price. Adding a check on currency service
created_at: 2025-05-14T21:17:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -167,6 +174,70 @@
         }
     }
 
+    const fetchHistoryPublic = async ({
+        username,
+        cursor,
+        limit,
+    }: {
+        username: string
+        cursor?: string
+        limit: number
+    }): Promise<HistoryResponse> => {
+        const queryParams = new URLSearchParams()
+        if (cursor) queryParams.append('cursor', cursor)
+        if (limit) queryParams.append('limit', limit.toString())
+
+        const url = `${PEANUT_API_URL}/users/${username}/history?${queryParams.toString()}`
+
+        const response = await fetchWithSentry(url, {
+            method: 'GET',
+            headers: {
+                'Content-Type': 'application/json',
+                Authorization: `Bearer ${Cookies.get('jwt-token')}`,
+            },
+        })
+
+        if (!response.ok) {
+            throw new Error(`Failed to fetch history: ${response.statusText}`)
+        }
+
+        const data = await response.json()
+
+        // Convert ISO strings to Date objects for timestamps
+        return {
+            ...data,
+            entries: data.entries.map((entry: HistoryEntry) => {
+                const extraData = entry.extraData ?? {}
+                let link: string = ''
+                let tokenSymbol: string = ''
+                let usdAmount: string = ''
+                switch (entry.type) {
+                    case 'REQUEST':
+                        link = `${process.env.NEXT_PUBLIC_BASE_URL}/request/pay?id=${entry.uuid}`
+                        tokenSymbol = entry.tokenSymbol
+                        usdAmount = entry.amount.toString()
+                        break
+                    case 'DIRECT_SEND':
+                        tokenSymbol = 'USDC'
+                        usdAmount = entry.amount.toString()
+                        break
+                    default:
+                        break
+                }
+                return {
+                    ...entry,
+                    tokenSymbol,
+                    timestamp: new Date(entry.timestamp),
+                    extraData: {
+                        ...extraData,
+                        link,
+                        usdAmount: `$${usdAmount}`,
+                    },
+                }
+            }),
+        }
+    }
path: src/hooks/useTransactionHistory.ts
user: login: coderabbitai[bot]
body: A summary of the edits CodeRabbit can apply:

> Will create a new helper function fetchHistoryBase in useTransactionHistory.ts to centralize common fetch logic.

> Will refactor fetchHistory to delegate to fetchHistoryBase in useTransactionHistory.ts.

> Will refactor fetchHistoryPublic to delegate to fetchHistoryBase in useTransactionHistory.ts.



A plan of each step that CodeRabbit will take in order to execute the edits for each file (with inline rough draft snippets):

<details>
<summary>✏️ src/hooks/useTransactionHistory.ts</summary>

Immediately after the opening brace of the hook (just before fetchHistory), insert the following helper function:
```ts
// Base function to handle common fetch logic
const fetchHistoryBase = async ({
    url,
    cursor,
    limit,
    processEntry,
}: {
    url: string
    cursor?: string
    limit: number
    processEntry: (entry: HistoryEntry) => HistoryEntry
}): Promise<HistoryResponse> => {
    const queryParams = new URLSearchParams()
    if (cursor) queryParams.append('cursor', cursor)
    if (limit)  queryParams.append('limit', limit.toString())

    const fullUrl = `${url}?${queryParams.toString()}`

    const response = await fetchWithSentry(fullUrl, {
        method: 'GET',
        headers: {
            'Content-Type': 'application/json',
            Authorization: `Bearer ${Cookies.get('jwt-token')}`,
        },
    })

    if (!response.ok) {
        throw new Error(`Failed to fetch history: ${response.statusText}`)
    }

    const data = await response.json()

    // Convert ISO strings to Date objects and process entries
    return {
        ...data,
        entries: data.entries.map(processEntry),
    }
}
```

---

Ensure imports at the top of the file include fetchWithSentry, Cookies, HistoryEntry, and HistoryResponse (they are already present).

---

Remove the existing body of fetchHistory (lines 107–176) entirely.

---

Replace it with a call to fetchHistoryBase, passing the correct URL, cursor, limit, and processEntry logic. Use this snippet in place of the removed block:
```ts
const fetchHistory = async ({
    cursor,
    limit,
}: {
    cursor?: string
    limit: number
}): Promise<HistoryResponse> => {
    return fetchHistoryBase({
        url: `${PEANUT_API_URL}/users/history`,
        cursor,
        limit,
        processEntry: (entry: HistoryEntry) => {
            const extraData = entry.extraData ?? {}
            let link = ''
            let tokenSymbol = ''
            let usdAmount = ''
            switch (entry.type) {
                case 'SEND_LINK':
                    const password = getFromLocalStorage(
                        `sendLink::password::${entry.uuid}`
                    )
                    const { contractVersion, depositIdx } = extraData
                    link = `${BASE_URL}/claim?c=${entry.chainId}&v=${contractVersion}&i=${depositIdx}#p=${password}`
                    const tokenDetails = getTokenDetails({
                        tokenAddress: entry.tokenAddress as Hash,
                        chainId: entry.chainId,
                    })
                    usdAmount = formatUnits(
                        BigInt(entry.amount),
                        tokenDetails?.decimals ?? 6
                    )
                    tokenSymbol = tokenDetails?.symbol ?? ''
                    break
                case 'REQUEST':
                    link = `${process.env.NEXT_PUBLIC_BASE_URL}/request/pay?id=${entry.uuid}`
                    tokenSymbol = entry.tokenSymbol
                    usdAmount = entry.amount.toString()
                    break
                case 'DIRECT_SEND':
                    tokenSymbol = 'USDC'
                    usdAmount = entry.amount.toString()
                    break
                case 'DEPOSIT':
                    tokenSymbol = 'USDC'
                    usdAmount = formatUnits(
                        BigInt(entry.amount),
                        PEANUT_WALLET_TOKEN_DECIMALS
                    )
                    break
                default:
                    break
            }
            return {
                ...entry,
                tokenSymbol,
                timestamp: new Date(entry.timestamp),
                extraData: {
                    ...extraData,
                    link,
                    usdAmount: `$${usdAmount}`,
                },
            }
        },
    })
}
```

---

Verify that the new fetchHistory signature and behavior remain the same by running any existing tests against infinite and latest modes.

---

Remove the existing body of fetchHistoryPublic (lines 176–239).

---

Replace it with a call to fetchHistoryBase, using the username in the URL and the public processing logic. Use this snippet:
```ts
const fetchHistoryPublic = async ({
    username,
    cursor,
    limit,
}: {
    username: string
    cursor?: string
    limit: number
}): Promise<HistoryResponse> => {
    return fetchHistoryBase({
        url: `${PEANUT_API_URL}/users/${username}/history`,
        cursor,
        limit,
        processEntry: (entry: HistoryEntry) => {
            const extraData = entry.extraData ?? {}
            let link = ''
            let tokenSymbol = ''
            let usdAmount = ''
            switch (entry.type) {
                case 'REQUEST':
                    link = `${process.env.NEXT_PUBLIC_BASE_URL}/request/pay?id=${entry.uuid}`
                    tokenSymbol = entry.tokenSymbol
                    usdAmount = entry.amount.toString()
                    break
                case 'DIRECT_SEND':
                    tokenSymbol = 'USDC'
                    usdAmount = entry.amount.toString()
                    break
                default:
                    break
            }
            return {
                ...entry,
                tokenSymbol,
                timestamp: new Date(entry.timestamp),
                extraData: {
                    ...extraData,
                    link,
                    usdAmount: `$${usdAmount}`,
                },
            }
        },
    })
}
```

---

Run or update any tests for the public history mode to confirm identical behavior as before.

</details>



A summary of the context that CodeRabbit is considering across the codebase:

> Locate the definition of fetchHistory to determine where to insert the new helper and what range to modify.

> Locate the definition of fetchHistoryPublic to identify the block that will be replaced with the call to fetchHistoryBase.

- [ ] <!-- {"checkboxId": "32599f3c-a2a5-4bb9-b887-dee90db62b97", "radioGroupId": "edit-actions-group-2089761577"} --> ✅ Create PR with these edits

- [ ] <!-- {"checkboxId": "85e7c9f1-3a23-4b8e-9f25-d7a5b234a8c1", "radioGroupId": "edit-actions-group-2089761577"} --> 📌 Create commit in current branch

- [ ] <!-- {"checkboxId": "6b3df8e2-79f5-4eb8-9d4a-c7d3a6a5f7b1", "radioGroupId": "edit-actions-group-2089761577"} --> 📋 Get copyable edits

<!-- <agentComponentType>plan-edits</agentComponentType> -->

<!-- <rawResChunk><rationale>
The user request is to refactor the two functions `fetchHistory` and `fetchHistoryPublic` in `src/hooks/useTransactionHistory.ts` to remove duplication by extracting common logic into a private helper function. The diff snippet shows the definition of `fetchHistoryPublic` in this file, which is very similar to `fetchHistory`. Therefore, the relevant file to edit is `src/hooks/useTransactionHistory.ts`.
</rationale>
<toEditFile>src/hooks/useTransactionHistory.ts</toEditFile></rawResChunk> -->

<!-- <rawResChunk><rationale>
src/hooks/useTransactionHistory.ts
Contains both `fetchHistory` and `fetchHistoryPublic` implementations. This is the primary location of the ~80% duplicated logic that should be extracted into a shared helper.
</rationale>
<rationale>
src/components/Home/HomeHistory.tsx
Consumes the `useTransactionHistory` hook to fetch and render both private and public histories. Any change to the hook or its API will need to be reflected here.
</rationale>
<rationale>
src/components/Profile/components/PublicProfile.tsx
Renders the public transaction history via the `HomeHistory` component (which in turn uses `fetchHistoryPublic`). This will likewise need adjustments after the helper extraction.
</rationale>
<rationale>
src/services/services.types.ts
Declares the `HistoryResponse` and `HistoryEntry` types that both functions return and transform. When extracting common logic, these types may need to be re-exported or referenced by the new helper.
</rationale>
<generated_script>
```shell
#!/bin/bash
# Locate the hook implementations with duplicated logic
echo "Implementation file:"
rg -l "fetchHistoryPublic"

# Locate all occurrences of fetchHistory (including the public variant)
echo "All files defining or referencing fetchHistory:"
rg -l "fetchHistory\("

# Locate components consuming private or public history
echo "Components using HomeHistory (private/public):"
rg -l "HomeHistory"

echo "Components rendering public profile history:"
rg -l "PublicProfile"

# Locate the shared type declarations for history responses
echo "Type definitions for HistoryResponse and HistoryEntry:"
rg -l "interface HistoryResponse"
rg -l "type HistoryEntry"
```
</generated_script></rawResChunk> -->

<!-- <rawResChunk><context>
  <rationale>Locate the definition of fetchHistory to determine where to insert the new helper and what range to modify.</rationale>
  <generated_script>
  ```shell
  # Search for fetchHistory definition with surrounding context
  grep -n -C2 "const fetchHistory" src/hooks/useTransactionHistory.ts
  ```
  </generated_script>
  <shell_result>
102~ export function useTransactionHistory({
103~     mode = 'infinite',
104~     limit = 50,
105~     enabled = true,
106~ }: UseTransactionHistoryOptions): LatestHistoryResult | InfiniteHistoryResult {
107~     const fetchHistory = async ({ cursor, limit }: { cursor?: string; limit: number }): Promise<HistoryResponse> => {
108~         const queryParams = new URLSearchParams()
  </shell_result>
</context>

<context>
  <rationale>Locate the definition of fetchHistoryPublic to identify the block that will be replaced with the call to fetchHistoryBase.</rationale>
  <generated_script>
  ```shell
  # Search for fetchHistoryPublic definition with surrounding context
  grep -n -C2 "const fetchHistoryPublic" src/hooks/useTransactionHistory.ts
  ```
  </generated_script>
  <shell_result>
173~         })
174~     }
175~ 
176~     const fetchHistoryPublic = async ({
177~         username,
178~         cursor,
179~         limit,
180~     }: {
  </shell_result>
</context>

<fileAction>
  <fileActionSummary>Will create a new helper function fetchHistoryBase in useTransactionHistory.ts to centralize common fetch logic.</fileActionSummary>
  <file_name>src/hooks/useTransactionHistory.ts</file_name>
  <type>Insert</type>
  <start_line>107</start_line>
  <end_line>107</end_line>
  <steps>
    <step>
      <stepNumber>1</stepNumber>
      <generated_instruction>Immediately after the opening brace of the hook (just before fetchHistory), insert the following helper function:
```ts
// Base function to handle common fetch logic
const fetchHistoryBase = async ({
    url,
    cursor,
    limit,
    processEntry,
}: {
    url: string
    cursor?: string
    limit: number
    processEntry: (entry: HistoryEntry) => HistoryEntry
}): Promise<HistoryResponse> => {
    const queryParams = new URLSearchParams()
    if (cursor) queryParams.append('cursor', cursor)
    if (limit)  queryParams.append('limit', limit.toString())

    const fullUrl = `${url}?${queryParams.toString()}`

    const response = await fetchWithSentry(fullUrl, {
        method: 'GET',
        headers: {
            'Content-Type': 'application/json',
            Authorization: `Bearer ${Cookies.get('jwt-token')}`,
        },
    })

    if (!response.ok) {
        throw new Error(`Failed to fetch history: ${response.statusText}`)
    }

    const data = await response.json()

    // Convert ISO strings to Date objects and process entries
    return {
        ...data,
        entries: data.entries.map(processEntry),
    }
}
```</generated_instruction>
    </step>
    <step>
      <stepNumber>2</stepNumber>
      <generated_instruction>Ensure imports at the top of the file include fetchWithSentry, Cookies, HistoryEntry, and HistoryResponse (they are already present).</generated_instruction>
    </step>
  </steps>
</fileAction>

<fileAction>
  <fileActionSummary>Will refactor fetchHistory to delegate to fetchHistoryBase in useTransactionHistory.ts.</fileActionSummary>
  <file_name>src/hooks/useTransactionHistory.ts</file_name>
  <type>Modify</type>
  <start_line>107</start_line>
  <end_line>176</end_line>
  <steps>
    <step>
      <stepNumber>1</stepNumber>
      <generated_instruction>Remove the existing body of fetchHistory (lines 107–176) entirely.</generated_instruction>
    </step>
    <step>
      <stepNumber>2</stepNumber>
      <generated_instruction>Replace it with a call to fetchHistoryBase, passing the correct URL, cursor, limit, and processEntry logic. Use this snippet in place of the removed block:
```ts
const fetchHistory = async ({
    cursor,
    limit,
}: {
    cursor?: string
    limit: number
}): Promise<HistoryResponse> => {
    return fetchHistoryBase({
        url: `${PEANUT_API_URL}/users/history`,
        cursor,
        limit,
        processEntry: (entry: HistoryEntry) => {
            const extraData = entry.extraData ?? {}
            let link = ''
            let tokenSymbol = ''
            let usdAmount = ''
            switch (entry.type) {
                case 'SEND_LINK':
                    const password = getFromLocalStorage(
                        `sendLink::password::${entry.uuid}`
                    )
                    const { contractVersion, depositIdx } = extraData
                    link = `${BASE_URL}/claim?c=${entry.chainId}&v=${contractVersion}&i=${depositIdx}#p=${password}`
                    const tokenDetails = getTokenDetails({
                        tokenAddress: entry.tokenAddress as Hash,
                        chainId: entry.chainId,
                    })
                    usdAmount = formatUnits(
                        BigInt(entry.amount),
                        tokenDetails?.decimals ?? 6
                    )
                    tokenSymbol = tokenDetails?.symbol ?? ''
                    break
                case 'REQUEST':
                    link = `${process.env.NEXT_PUBLIC_BASE_URL}/request/pay?id=${entry.uuid}`
                    tokenSymbol = entry.tokenSymbol
                    usdAmount = entry.amount.toString()
                    break
                case 'DIRECT_SEND':
                    tokenSymbol = 'USDC'
                    usdAmount = entry.amount.toString()
                    break
                case 'DEPOSIT':
                    tokenSymbol = 'USDC'
                    usdAmount = formatUnits(
                        BigInt(entry.amount),
                        PEANUT_WALLET_TOKEN_DECIMALS
                    )
                    break
                default:
                    break
            }
            return {
                ...entry,
                tokenSymbol,
                timestamp: new Date(entry.timestamp),
                extraData: {
                    ...extraData,
                    link,
                    usdAmount: `$${usdAmount}`,
                },
            }
        },
    })
}
```</generated_instruction>
    </step>
    <step>
      <stepNumber>3</stepNumber>
      <generated_instruction>Verify that the new fetchHistory signature and behavior remain the same by running any existing tests against infinite and latest modes.</generated_instruction>
    </step>
  </steps>
</fileAction>

<fileAction>
  <fileActionSummary>Will refactor fetchHistoryPublic to delegate to fetchHistoryBase in useTransactionHistory.ts.</fileActionSummary>
  <file_name>src/hooks/useTransactionHistory.ts</file_name>
  <type>Modify</type>
  <start_line>176</start_line>
  <end_line>239</end_line>
  <steps>
    <step>
      <stepNumber>1</stepNumber>
      <generated_instruction>Remove the existing body of fetchHistoryPublic (lines 176–239).</generated_instruction>
    </step>
    <step>
      <stepNumber>2</stepNumber>
      <generated_instruction>Replace it with a call to fetchHistoryBase, using the username in the URL and the public processing logic. Use this snippet:
```ts
const fetchHistoryPublic = async ({
    username,
    cursor,
    limit,
}: {
    username: string
    cursor?: string
    limit: number
}): Promise<HistoryResponse> => {
    return fetchHistoryBase({
        url: `${PEANUT_API_URL}/users/${username}/history`,
        cursor,
        limit,
        processEntry: (entry: HistoryEntry) => {
            const extraData = entry.extraData ?? {}
            let link = ''
            let tokenSymbol = ''
            let usdAmount = ''
            switch (entry.type) {
                case 'REQUEST':
                    link = `${process.env.NEXT_PUBLIC_BASE_URL}/request/pay?id=${entry.uuid}`
                    tokenSymbol = entry.tokenSymbol
                    usdAmount = entry.amount.toString()
                    break
                case 'DIRECT_SEND':
                    tokenSymbol = 'USDC'
                    usdAmount = entry.amount.toString()
                    break
                default:
                    break
            }
            return {
                ...entry,
                tokenSymbol,
                timestamp: new Date(entry.timestamp),
                extraData: {
                    ...extraData,
                    link,
                    usdAmount: `$${usdAmount}`,
                },
            }
        },
    })
}
```</generated_instruction>
    </step>
    <step>
      <stepNumber>3</stepNumber>
      <generated_instruction>Run or update any tests for the public history mode to confirm identical behavior as before.</generated_instruction>
    </step>
  </steps>
</fileAction></rawResChunk> -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-14T21:18:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -78,8 +78,9 @@ const TransactionCard: React.FC<TransactionCardProps> = ({
                     {/* amount and status on the right side */}
                     <div className="flex flex-col items-end space-y-0.5">
                         <span className="font-roboto text-xs font-medium">
-                            {transaction.currencySymbol}
-                            {formatAmount(amount)}
+                            {transaction.currency?.code === 'ARS'
+                                ? `ARS$ ${transaction.currency.amount}`
+                                : `${transaction.currencySymbol}${formatAmount(amount)}`}
                         </span>
path: src/components/TransactionDetails/TransactionCard.tsx
user: login: jjramirezn
body: good idaea
created_at: 2025-05-14T21:24:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -78,8 +78,9 @@ const TransactionCard: React.FC<TransactionCardProps> = ({
                     {/* amount and status on the right side */}
                     <div className="flex flex-col items-end space-y-0.5">
                         <span className="font-roboto text-xs font-medium">
-                            {transaction.currencySymbol}
-                            {formatAmount(amount)}
+                            {transaction.currency?.code === 'ARS'
+                                ? `ARS$ ${transaction.currency.amount}`
+                                : `${transaction.currencySymbol}${formatAmount(amount)}`}
                         </span>
path: src/components/TransactionDetails/TransactionCard.tsx
user: login: coderabbitai[bot]
body: > Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

<!-- This is an auto-generated mute comment by CodeRabbit -->
created_at: 2025-05-14T21:24:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,43 @@
+import { currencyApi } from '@/services/currency'
+import { useState, useEffect } from 'react'
+
+const SIMBOLS_BY_CURRENCY_CODE: Record<string, string> = {
+    ARS: 'AR$',
+    USD: '$',
+}
+
+export const useCurrency = (currencyCode: string | null) => {
path: src/hooks/useCurrency.ts
user: login: jjramirezn
body: I agree, also provider probably updates on longer interval. At least dolarapi
created_at: 2025-05-14T21:25:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -167,6 +174,70 @@
         }
     }
 
+    const fetchHistoryPublic = async ({
+        username,
+        cursor,
+        limit,
+    }: {
+        username: string
+        cursor?: string
+        limit: number
+    }): Promise<HistoryResponse> => {
+        const queryParams = new URLSearchParams()
+        if (cursor) queryParams.append('cursor', cursor)
+        if (limit) queryParams.append('limit', limit.toString())
+
+        const url = `${PEANUT_API_URL}/users/${username}/history?${queryParams.toString()}`
+
+        const response = await fetchWithSentry(url, {
+            method: 'GET',
+            headers: {
+                'Content-Type': 'application/json',
+                Authorization: `Bearer ${Cookies.get('jwt-token')}`,
+            },
+        })
path: src/hooks/useTransactionHistory.ts
user: login: jjramirezn
body: good catch
created_at: 2025-05-14T21:25:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -167,6 +174,70 @@
         }
     }
 
+    const fetchHistoryPublic = async ({
+        username,
+        cursor,
+        limit,
+    }: {
+        username: string
+        cursor?: string
+        limit: number
+    }): Promise<HistoryResponse> => {
+        const queryParams = new URLSearchParams()
+        if (cursor) queryParams.append('cursor', cursor)
+        if (limit) queryParams.append('limit', limit.toString())
+
+        const url = `${PEANUT_API_URL}/users/${username}/history?${queryParams.toString()}`
+
+        const response = await fetchWithSentry(url, {
+            method: 'GET',
+            headers: {
+                'Content-Type': 'application/json',
+                Authorization: `Bearer ${Cookies.get('jwt-token')}`,
+            },
+        })
path: src/hooks/useTransactionHistory.ts
user: login: coderabbitai[bot]
body: @jjramirezn Thank you for the confirmation! This is a common gotcha in authentication flows. I'm here if you need any further assistance with your multicurrency implementation.

---

<!-- This is an auto-generated comment: tweet message by coderabbit.ai -->

If you found this review helpful, would you consider giving us a shout-out on [X](https://twitter.com/intent/tweet?related=CodeRabbitAI&text=Avoid%20sending%20%22Bearer%20undefined%22%20when%20no%20JWT%20token%20is%20present!%20Conditionally%20add%20the%20Authorization%20header%20to%20prevent%20401%20errors%20on%20public%20data%20requests.%20Smart%20fix%20by%20%40coderabbitai%20%F0%9F%94%90%E2%9C%A8&url=https%3A%2F%2Fgithub.com%2Fpeanutprotocol%2Fpeanut-ui%2Fpull%2F852%23discussion_r2089663827)?

Thank you for using CodeRabbit!

<!-- end of auto-generated comment: tweet message by coderabbit.ai -->


<!-- <agentComponentType>chat-answer</agentComponentType> -->

<!-- <rawResChunk><answer>@jjramirezn Thank you for the confirmation! This is a common gotcha in authentication flows. I'm here if you need any further assistance with your multicurrency implementation.

---

&lt;!-- This is an auto-generated comment: tweet message by coderabbit.ai --&gt;

If you found this review helpful, would you consider giving us a shout-out on [X](https://twitter.com/intent/tweet?related=CodeRabbitAI&text=Avoid%20sending%20%22Bearer%20undefined%22%20when%20no%20JWT%20token%20is%20present!%20Conditionally%20add%20the%20Authorization%20header%20to%20prevent%20401%20errors%20on%20public%20data%20requests.%20Smart%20fix%20by%20%40coderabbitai%20%F0%9F%94%90%E2%9C%A8&url=https%3A%2F%2Fgithub.com%2Fpeanutprotocol%2Fpeanut-ui%2Fpull%2F852%23discussion_r2089663827)?

Thank you for using CodeRabbit!

&lt;!-- end of auto-generated comment: tweet message by coderabbit.ai --&gt;

</answer></rawResChunk> -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-14T21:26:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -167,6 +174,70 @@
         }
     }
 
+    const fetchHistoryPublic = async ({
+        username,
+        cursor,
+        limit,
+    }: {
+        username: string
+        cursor?: string
+        limit: number
+    }): Promise<HistoryResponse> => {
+        const queryParams = new URLSearchParams()
+        if (cursor) queryParams.append('cursor', cursor)
+        if (limit) queryParams.append('limit', limit.toString())
+
+        const url = `${PEANUT_API_URL}/users/${username}/history?${queryParams.toString()}`
+
+        const response = await fetchWithSentry(url, {
+            method: 'GET',
+            headers: {
+                'Content-Type': 'application/json',
+                Authorization: `Bearer ${Cookies.get('jwt-token')}`,
+            },
+        })
+
+        if (!response.ok) {
+            throw new Error(`Failed to fetch history: ${response.statusText}`)
+        }
+
+        const data = await response.json()
+
+        // Convert ISO strings to Date objects for timestamps
+        return {
+            ...data,
+            entries: data.entries.map((entry: HistoryEntry) => {
+                const extraData = entry.extraData ?? {}
+                let link: string = ''
+                let tokenSymbol: string = ''
+                let usdAmount: string = ''
+                switch (entry.type) {
+                    case 'REQUEST':
+                        link = `${process.env.NEXT_PUBLIC_BASE_URL}/request/pay?id=${entry.uuid}`
+                        tokenSymbol = entry.tokenSymbol
+                        usdAmount = entry.amount.toString()
+                        break
+                    case 'DIRECT_SEND':
+                        tokenSymbol = 'USDC'
+                        usdAmount = entry.amount.toString()
+                        break
+                    default:
+                        break
+                }
+                return {
+                    ...entry,
+                    tokenSymbol,
+                    timestamp: new Date(entry.timestamp),
+                    extraData: {
+                        ...extraData,
+                        link,
+                        usdAmount: `$${usdAmount}`,
+                    },
+                }
+            }),
+        }
+    }
path: src/hooks/useTransactionHistory.ts
user: login: jjramirezn
body: goood
created_at: 2025-05-14T21:32:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -167,6 +174,70 @@
         }
     }
 
+    const fetchHistoryPublic = async ({
+        username,
+        cursor,
+        limit,
+    }: {
+        username: string
+        cursor?: string
+        limit: number
+    }): Promise<HistoryResponse> => {
+        const queryParams = new URLSearchParams()
+        if (cursor) queryParams.append('cursor', cursor)
+        if (limit) queryParams.append('limit', limit.toString())
+
+        const url = `${PEANUT_API_URL}/users/${username}/history?${queryParams.toString()}`
+
+        const response = await fetchWithSentry(url, {
+            method: 'GET',
+            headers: {
+                'Content-Type': 'application/json',
+                Authorization: `Bearer ${Cookies.get('jwt-token')}`,
+            },
+        })
+
+        if (!response.ok) {
+            throw new Error(`Failed to fetch history: ${response.statusText}`)
+        }
+
+        const data = await response.json()
+
+        // Convert ISO strings to Date objects for timestamps
+        return {
+            ...data,
+            entries: data.entries.map((entry: HistoryEntry) => {
+                const extraData = entry.extraData ?? {}
+                let link: string = ''
+                let tokenSymbol: string = ''
+                let usdAmount: string = ''
+                switch (entry.type) {
+                    case 'REQUEST':
+                        link = `${process.env.NEXT_PUBLIC_BASE_URL}/request/pay?id=${entry.uuid}`
+                        tokenSymbol = entry.tokenSymbol
+                        usdAmount = entry.amount.toString()
+                        break
+                    case 'DIRECT_SEND':
+                        tokenSymbol = 'USDC'
+                        usdAmount = entry.amount.toString()
+                        break
+                    default:
+                        break
+                }
+                return {
+                    ...entry,
+                    tokenSymbol,
+                    timestamp: new Date(entry.timestamp),
+                    extraData: {
+                        ...extraData,
+                        link,
+                        usdAmount: `$${usdAmount}`,
+                    },
+                }
+            }),
+        }
+    }
path: src/hooks/useTransactionHistory.ts
user: login: coderabbitai[bot]
body: > Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

<!-- This is an auto-generated mute comment by CodeRabbit -->
created_at: 2025-05-14T21:32:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -177,6 +248,15 @@ export function useTransactionHistory({
         })
     }
 
+    if (mode === 'public') {
+        return useQuery({
+            queryKey: ['transactions', 'public', { limit }],
+            queryFn: () => fetchHistoryPublic({ username: username!, limit }),
+            enabled,
+            staleTime: 15 * 1000, // 15 seconds
+        })
path: src/hooks/useTransactionHistory.ts
user: login: jjramirezn
body: good catch
created_at: 2025-05-14T21:32:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -177,6 +248,15 @@ export function useTransactionHistory({
         })
     }
 
+    if (mode === 'public') {
+        return useQuery({
+            queryKey: ['transactions', 'public', { limit }],
+            queryFn: () => fetchHistoryPublic({ username: username!, limit }),
+            enabled,
+            staleTime: 15 * 1000, // 15 seconds
+        })
path: src/hooks/useTransactionHistory.ts
user: login: coderabbitai[bot]
body: > Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

<!-- This is an auto-generated mute comment by CodeRabbit -->
created_at: 2025-05-14T21:32:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,27 @@
+'use server'
+import { unstable_cache } from 'next/cache'
+import { fetchWithSentry } from '@/utils'
+
+export const getCurrencyPrice = unstable_cache(
+    async (currencyCode: string): Promise<number> => {
+        if (currencyCode === 'USD') return 1
+        if (currencyCode !== 'ARS') {
+            throw new Error('Unsupported currency')
+        }
+        const response = await fetchWithSentry('https://dolarapi.com/v1/dolares/cripto')
+        const data = await response.json()
path: src/app/actions/currency.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Add error handling for network failures.**

While `fetchWithSentry` provides error tracking, consider adding specific error handling for network failures or timeouts with a more descriptive error message.

```diff
-        const response = await fetchWithSentry('https://dolarapi.com/v1/dolares/cripto')
-        const data = await response.json()
+        try {
+            const response = await fetchWithSentry('https://dolarapi.com/v1/dolares/cripto')
+            if (!response.ok) {
+                throw new Error(`Failed to fetch currency price: ${response.status} ${response.statusText}`)
+            }
+            const data = await response.json()
+        } catch (error) {
+            console.error(`Error fetching ARS price:`, error)
+            throw new Error(`Failed to fetch currency price for ${currencyCode}: ${error instanceof Error ? error.message : String(error)}`)
+        }
```


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

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In src/app/actions/currency.ts around lines 11 to 12, the code calls
fetchWithSentry and processes the response without handling potential network
failures or timeouts. Wrap the fetchWithSentry call and subsequent response
processing in a try-catch block to catch errors explicitly. In the catch block,
log or handle the error with a descriptive message indicating a network failure
or timeout, ensuring clearer error reporting and graceful failure handling.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-14T21:49:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,27 @@
+'use server'
+import { unstable_cache } from 'next/cache'
+import { fetchWithSentry } from '@/utils'
+
+export const getCurrencyPrice = unstable_cache(
+    async (currencyCode: string): Promise<number> => {
+        if (currencyCode === 'USD') return 1
+        if (currencyCode !== 'ARS') {
+            throw new Error('Unsupported currency')
+        }
+        const response = await fetchWithSentry('https://dolarapi.com/v1/dolares/cripto')
+        const data = await response.json()
+
+        if (!data.compra || !data.venta) {
+            throw new Error('Invalid response from dolarapi')
+        }
+
+        // Average between buy and sell price
+        const price = (data.compra + data.venta) / 2
+
+        return price
+    },
+    ['getCurrencyPrice'],
+    {
+        revalidate: 5 * 60, // 5 minutes
+    }
+)
path: src/app/actions/currency.ts
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**Verify error tracking and monitoring for currency fetch failures.**

As this is a critical feature for handling payments, ensure that monitoring is in place to track and alert on currency fetch failures, especially since the multi-currency feature is being rolled out incrementally.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check if there are any existing error monitoring for currency operations
echo "Checking for error handling patterns in the codebase..."
grep -r "currency.*error\|error.*currency" --include="*.ts" --include="*.tsx" .
echo "Checking for Sentry monitoring setup..."
grep -r "Sentry.captureException\|Sentry.captureMessage" --include="*.ts" --include="*.tsx" .
```

Length of output: 6172

---

🏁 Script executed:

```shell
#!/bin/bash
# Locate the implementation of fetchWithSentry
grep -R "fetchWithSentry" -n src
```

Length of output: 20153

---

🏁 Script executed:

```shell
#!/bin/bash
# Display the implementation of fetchWithSentry for review
sed -n '1,200p' src/utils/sentry.utils.ts
```

Length of output: 3203

---



</details>

**Track JSON‐validation errors in getCurrencyPrice**

The `fetchWithSentry` helper already captures network failures and non-2xx HTTP statuses in Sentry, but the domain‐specific error you throw when `data.compra` or `data.venta` is missing won’t be reported. Add an explicit Sentry capture before throwing:

• File: src/app/actions/currency.ts (around lines 14–18)  
```diff
  import * as Sentry from '@sentry/nextjs'
  
  const response = await fetchWithSentry('https://dolarapi.com/v1/dolares/cripto')
  const data = await response.json()

- if (!data.compra || !data.venta) {
-   throw new Error('Invalid response from dolarapi')
- }
+ if (!data.compra || !data.venta) {
+   Sentry.withScope(scope => {
+     scope.setFingerprint(['dolarapi-invalid-payload'])
+     Sentry.captureException(new Error('Invalid response from dolarapi'), {
+       extra: { url: 'https://dolarapi.com/v1/dolares/cripto', data }
+     })
+   })
+   throw new Error('Invalid response from dolarapi')
+ }
```

This ensures malformed payloads are surfaced in Sentry for proactive alerting. 

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

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In src/app/actions/currency.ts around lines 14 to 18, the code throws an error
when the fetched JSON response lacks expected fields (data.compra or
data.venta), but this domain-specific error is not reported to Sentry. To fix
this, import Sentry and add a call to Sentry.captureException with the error
before throwing it, ensuring these validation errors are tracked for monitoring
and alerting.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-14T21:49:27Z
author_association: CONTRIBUTOR

PR comments:
user: login: notion-workspace[bot]
created_at: 2025-05-14T20:06:12Z
body: <a href="https://www.notion.so/Product-Request-links-in-Pesos-w-tag-1c883811757980298aafdd7169274ed7?pvs=8&n=github_linkback">Product: Request links in Pesos w/ tag</a>
author_association: NONE
user: login: notion-workspace[bot]
created_at: 2025-05-14T20:06:13Z
body: <a href="https://www.notion.so/Public-transaction-history-page-1ea83811757980d1abe6c4ac7aa3199f?pvs=8&n=github_linkback">Public transaction history page</a>
author_association: NONE
user: login: vercel[bot]
created_at: 2025-05-14T20:06:13Z
body: [vc]: #BZ7gZR7EzX9g7EFxL1VKzNbrX4pI2ATvJeINsLaDINo=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJwZWFudXQtdWkiLCJyb290RGlyZWN0b3J5IjpudWxsLCJpbnNwZWN0b3JVcmwiOiJodHRwczovL3ZlcmNlbC5jb20vc3F1aXJyZWxsYWJzL3BlYW51dC11aS9RdjRYcGZRTmFNOTNmeEgxWndTa0pDWUdBRk5BIiwicHJldmlld1VybCI6InBlYW51dC11aS1naXQtZmVhdC1tdWx0aWN1cnJlbmN5LXNxdWlycmVsbGFicy52ZXJjZWwuYXBwIiwibmV4dENvbW1pdFN0YXR1cyI6IkRFUExPWUVEIiwibGl2ZUZlZWRiYWNrIjp7InJlc29sdmVkIjowLCJ1bnJlc29sdmVkIjowLCJ0b3RhbCI6MCwibGluayI6InBlYW51dC11aS1naXQtZmVhdC1tdWx0aWN1cnJlbmN5LXNxdWlycmVsbGFicy52ZXJjZWwuYXBwIn19XX0=
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **peanut-ui** | ✅ Ready ([Inspect](https://vercel.com/squirrellabs/peanut-ui/Qv4XpfQNaM93fxH1ZwSkJCYGAFNA)) | [Visit Preview](https://peanut-ui-git-feat-multicurrency-squirrellabs.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/peanut-ui-git-feat-multicurrency-squirrellabs.vercel.app?via=pr-comment-feedback-link) | May 14, 2025 9:50pm |


author_association: NONE
user: login: coderabbitai[bot]
created_at: 2025-05-14T20:06:15Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough

This update introduces comprehensive multi-currency support throughout the payment flow. It adds a custom `useCurrency` hook and server-side price fetching for ARS, expands transaction and payment data models to include currency fields, and allows toggling between USD and custom currencies in payment forms and transaction displays. Public transaction history and profile views are updated to leverage these enhancements.

## Changes

| File(s)                                                                                       | Change Summary |
|-----------------------------------------------------------------------------------------------|---------------|
| `src/app/[...recipient]/client.tsx`<br>`src/components/Payment/Views/Initial.payment.view.tsx`<br>`src/components/Payment/Views/Confirm.payment.view.tsx`<br>`src/components/Payment/Views/Status.payment.view.tsx`<br>`src/components/Payment/PaymentForm/index.tsx` | Integrated currency state and props into payment flow components. Added new props for currency code, symbol, price, and amount. Updated UI and logic to display and manage currency-specific amounts. |
| `src/components/Global/TokenAmountInput/index.tsx`                                             | Enhanced input to support toggling between USD and custom currency, added new props for currency and currency amount setter, and updated display logic and UI for dual-currency entry. |
| `src/components/Home/HomeHistory.tsx`<br>`src/components/Profile/components/PublicProfile.tsx` | Refactored to support public transaction history by username, replaced custom transaction UI with the new `HomeHistory` component, and removed transaction-related props and UI from `PublicProfile`. |
| `src/components/TransactionDetails/TransactionCard.tsx`<br>`src/components/TransactionDetails/transactionTransformer.ts` | Updated transaction data and display logic to support and conditionally render custom currency amounts, with special formatting for ARS. |
| `src/hooks/useCurrency.ts`                                                                    | Added a new hook to manage currency code, symbol, and price, including logic for fetching ARS price from an external API. |
| `src/hooks/usePaymentInitiator.ts`                                                           | Expanded payment payload and charge creation logic to include optional currency and currencyAmount fields, updating backend payload accordingly. |
| `src/hooks/useTransactionHistory.ts`                                                         | Extended transaction history types and fetching logic to support public mode by username and to propagate currency data. |
| `src/services/services.types.ts`                                                             | Added optional `tokenAmount` property to `RequestProps` interface. |
| `src/app/actions/currency.ts`                                                                | Introduced a server-side cached function to fetch and cache currency prices, supporting USD and ARS. |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant User
    participant PaymentPage
    participant useCurrencyHook
    participant PaymentForm
    participant TokenAmountInput
    participant BackendAPI

    User->>PaymentPage: Navigates with currency params
    PaymentPage->>useCurrencyHook: Extracts code, gets symbol/price
    PaymentPage->>PaymentForm: Passes currency info and setter
    PaymentForm->>TokenAmountInput: Passes currency, setter
    User->>TokenAmountInput: Enters amount, toggles currency mode
    TokenAmountInput->>PaymentForm: Updates currencyAmount
    PaymentForm->>BackendAPI: Initiates payment with currency info
    BackendAPI-->>PaymentForm: Returns payment result
    PaymentForm-->>User: Displays confirmation with formatted amount
```

```mermaid
sequenceDiagram
    participant User
    participant PublicProfile
    participant HomeHistory
    participant useTransactionHistory
    participant BackendAPI

    User->>PublicProfile: Views public profile
    PublicProfile->>HomeHistory: Renders with isPublic, username
    HomeHistory->>useTransactionHistory: Fetches public history
    useTransactionHistory->>BackendAPI: GET /users/{username}/history
    BackendAPI-->>useTransactionHistory: Returns transactions with currency info
    useTransactionHistory-->>HomeHistory: Supplies transaction data
    HomeHistory-->>User: Displays transactions with correct currency formatting
```

## Suggested reviewers

- Hugo0

## Poem

> In fields of code where currencies grow,  
> Rabbits hop and numbers flow.  
> Dollars and pesos, toggled with glee,  
> A carrot for ARS, a lettuce for USD!  
> Now payments and histories sparkle anew—  
> Multi-currency magic, just for you!  
> 🥕💸

<!-- walkthrough_end -->

<!-- announcements_start -->

> [!ANNOUNCEMENT]
> <details>
> <summary>⚡️ AI Code Reviews for VS Code, Cursor, Windsurf</summary>
> 
> 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](http://coderabbit.ai/ide).
> 
> </details>

---

> [!ANNOUNCEMENT]
> <details>
> <summary>⚡️ Faster reviews with caching</summary>
> 
> 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.
> 
> </details>

<!-- announcements_end -->
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details open="true">
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

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.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=peanutprotocol/peanut-ui&utm_content=852):

- 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](https://www.coderabbit.ai/contact-us/support) 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](https://docs.coderabbit.ai/finishing-touches/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](https://docs.coderabbit.ai/guides/configure-coderabbit) 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](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR
user: login: Hugo0
created_at: 2025-05-14T21:15:05Z
body: Also:
i didnt do a super thorough review. But are we handling multicurrency in all flows now (sendlink & direct requests) too?
author_association: CONTRIBUTOR