-
Notifications
You must be signed in to change notification settings - Fork 13
Integrate Daimo Pay #1104
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Integrate Daimo Pay #1104
Conversation
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughAdds Daimo Pay integration: new SDK dependency, provider/theme and env var, button component, hook methods, and UI wiring. Reworks add-money crypto flows (QR-first and drawer), introduces CryptoMethodDrawer and Daimo-specific UI, expands supported networks, adds DAIMO_THEME, and a tracking API call. Minor public/game HTML syntax change. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ Finishing Touches
🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🔭 Outside diff range comments (2)
package.json (1)
135-137
: Allowlist @daimo in Jest’s transformIgnorePatterns
The Daimo SDK is ESM and is imported in:
src/config/wagmi.config.tsx
(line 21)src/app/(mobile-ui)/add-money/crypto/direct/page.tsx
(line 9)Without adding
@daimo
to the allowlist, Jest will choke on the ESM syntax innode_modules/@daimo/pay
. Update yourpackage.json
as follows:"jest": { // … "transformIgnorePatterns": [ - "node_modules/(?!(@wagmi|wagmi|viem|@viem|@squirrel-labs)/)" + "node_modules/(?!(@wagmi|wagmi|viem|@viem|@squirrel-labs|@daimo)/)" ], // … }This change ensures Jest transforms the Daimo SDK modules and prevents test failures when importing
@daimo/pay
.src/app/(mobile-ui)/add-money/crypto/page.tsx (1)
135-138
: Fix incorrect Trust Wallet path & centralize routing viasource.path
Before switching to
source.path
, we discovered the Trust Wallet entry inCRYPTO_WALLETS
has the wrong URL. Please apply these updates:• In
src/components/AddMoney/consts/index.ts
, correct the Trust Wallet path:{ id: 'trust-wallet', name: 'Trust Wallet', type: 'wallet', icon: TRUST_WALLET_LOGO, - path: '/add-money/crypto/rainbow', + path: '/add-money/crypto/trust-wallet', },• In
src/app/(mobile-ui)/add-money/crypto/page.tsx
, leverage the centralizedpath
field:- <CryptoSourceListCard - sources={CRYPTO_WALLETS} - onItemClick={() => router.push('/add-money/crypto/direct')} - /> + <CryptoSourceListCard + sources={CRYPTO_WALLETS} + onItemClick={(source) => router.push(source.path)} + />This keeps routing DRY and future‐proof.
🧹 Nitpick comments (7)
src/constants/daimo.consts.ts (1)
1-2
: Type-safety and immutability for theme objectLock down the theme object to prevent accidental mutation and preserve literal types.
Apply this:
-export const DAIMO_THEME = { +export const DAIMO_THEME = { /* ... */ -} +} as const satisfies Record<string, string>Also applies to: 100-102
src/app/(mobile-ui)/add-money/crypto/direct/page.tsx (6)
31-31
: Remove stray curly brace in key prop.There’s an extra
}
at the end of the key string.- key={`success-add-money}`} + key="success-add-money"
20-20
: Align setter name with React conventions (camelCase).Nit:
setisPaymentSuccess
→setIsPaymentSuccess
improves readability and follows common camelCase setter naming.- const [isPaymentSuccess, setisPaymentSuccess] = useState(false) + const [isPaymentSuccess, setIsPaymentSuccess] = useState(false) @@ - setisPaymentSuccess(true) + setIsPaymentSuccess(true)Also applies to: 82-82
86-90
: Add basic error handling around Daimo UI launch.If
resetPayment
orshow()
throws, the click handler will silently fail. Log or surface an error.- <Button - onClick={async () => { - await resetPaymentAmount() - show() - }} + <Button + onClick={async () => { + try { + await resetPaymentAmount() + show() + } catch (err) { + console.error('Failed to start Daimo Pay flow', err) + } + }}
70-76
: Compute Daimo appId once; avoid inline throw in JSX.Minor readability nit: hoist the env read so JSX stays declarative. Still fail fast if missing.
- appId={ - process.env.NEXT_PUBLIC_DAIMO_APP_ID ?? - (() => { - throw new Error('Daimo APP ID is required') - })() - } + appId={daimoAppId}Add this near your other hooks:
const daimoAppId = process.env.NEXT_PUBLIC_DAIMO_APP_ID ?? (() => { throw new Error('Daimo APP ID is required') })()
68-69
: Optional UX: show a connect CTA when no wallet is connected.Right now the Daimo button disappears entirely if
address
is falsy. Consider showing a lightweight “Connect wallet to continue” CTA to guide the user.I can wire this up to your existing connect flow if you point me to the preferred connect component.
Also applies to: 102-102
22-26
: Normalize and validate the input amount once; pass human-readable decimals directly totoUnits
Both
resetPayment({ toUnits })
and<DaimoPayButton.Custom>
expect a precise decimal string (e.g."10.5"
or"10.50"
), not base-unit integers. To avoid duplicated logic and accidental divergence, strip formatting and check validity once, then reuse:@@ - const { resetPayment } = useDaimoPayUI() - const [isPaymentSuccess, setisPaymentSuccess] = useState(false) + const { resetPayment } = useDaimoPayUI() + const [isPaymentSuccess, setisPaymentSuccess] = useState(false) + // clean and validate the token amount once + const normalizedAmount = inputTokenAmount.replace(/,/g, '').trim() + const isAmountValid = normalizedAmount !== '' && Number(normalizedAmount) > 0 @@ - await resetPayment({ - toUnits: inputTokenAmount.replace(/,/g, ''), - }) + await resetPayment({ + toUnits: normalizedAmount, + }) @@ - toUnits={inputTokenAmount.replace(/,/g, '')} + toUnits={normalizedAmount} @@ - disabled={inputTokenAmount.length == 0} + disabled={!isAmountValid}Apply the same
normalizedAmount
andisAmountValid
logic at everytoUnits
call site to keep formatting and validation consistent.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (1)
pnpm-lock.yaml
is excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (6)
package.json
(3 hunks)src/app/(mobile-ui)/add-money/crypto/direct/page.tsx
(1 hunks)src/app/(mobile-ui)/add-money/crypto/page.tsx
(2 hunks)src/components/AddWithdraw/AddWithdrawRouterView.tsx
(1 hunks)src/config/wagmi.config.tsx
(3 hunks)src/constants/daimo.consts.ts
(1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-05-22T15:38:48.586Z
Learnt from: kushagrasarathe
PR: peanutprotocol/peanut-ui#869
File: src/app/(mobile-ui)/withdraw/page.tsx:82-88
Timestamp: 2025-05-22T15:38:48.586Z
Learning: The country-specific withdrawal route exists at src/app/(mobile-ui)/withdraw/[...country]/page.tsx and renders the AddWithdrawCountriesList component with flow="withdraw".
Applied to files:
src/app/(mobile-ui)/add-money/crypto/page.tsx
src/components/AddWithdraw/AddWithdrawRouterView.tsx
📚 Learning: 2024-10-08T20:13:42.967Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#413
File: src/context/tokenSelector.context.tsx:118-123
Timestamp: 2024-10-08T20:13:42.967Z
Learning: In the `TokenContextProvider` component within `src/context/tokenSelector.context.tsx`, in the TypeScript React application, when data changes and before calling `fetchAndSetTokenPrice`, it is necessary to reset `selectedTokenData`, `selectedTokenPrice`, `selectedTokenDecimals`, and `inputDenomination` to discard stale data.
Applied to files:
src/app/(mobile-ui)/add-money/crypto/page.tsx
📚 Learning: 2024-12-02T17:19:18.532Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#551
File: src/components/Request/Create/Views/Initial.view.tsx:151-156
Timestamp: 2024-12-02T17:19:18.532Z
Learning: In the `InitialView` component at `src/components/Request/Create/Views/Initial.view.tsx`, when setting the default chain and token in the `useEffect` triggered by `isPeanutWallet`, it's acceptable to omit the setters from the dependency array and not include additional error handling for invalid defaults.
Applied to files:
src/app/(mobile-ui)/add-money/crypto/page.tsx
🧬 Code Graph Analysis (3)
src/app/(mobile-ui)/add-money/crypto/page.tsx (1)
src/components/AddMoney/consts/index.ts (1)
CryptoSource
(7-14)
src/config/wagmi.config.tsx (3)
src/utils/__mocks__/wagmi.ts (1)
mainnet
(2-14)src/constants/daimo.consts.ts (1)
DAIMO_THEME
(1-102)src/config/justaname.config.tsx (1)
JustaNameContext
(7-28)
src/app/(mobile-ui)/add-money/crypto/direct/page.tsx (3)
src/hooks/wallet/useWallet.ts (1)
useWallet
(21-124)src/constants/zerodev.consts.ts (1)
PEANUT_WALLET_TOKEN
(20-20)src/components/0_Bruddle/Button.tsx (1)
Button
(76-267)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Deploy-Preview
🔇 Additional comments (6)
package.json (1)
29-29
: Dependency addition for @daimo/pay looks fineAdding Daimo Pay SDK aligns with the new provider usage. No issues spotted here.
src/components/AddWithdraw/AddWithdrawRouterView.tsx (1)
228-235
: Verify crypto route existenceI couldn’t locate any
page.tsx
under thecrypto
subfolders for either flow, so please confirm that the following routes exist (or adjust the paths accordingly) before applying the refactoring:
src/app/**/add-money/crypto/direct/page.tsx
src/app/**/add-money/crypto/page.tsx
src/app/**/withdraw-money/crypto/direct/page.tsx
src/app/**/withdraw-money/crypto/page.tsx
Once the routes are in place, you can simplify the click handler as suggested:
- onCryptoClick={() => { - let cryptoPath = '' - if (flow === 'add') { - cryptoPath = `${baseRoute}/crypto/direct` - } else { - cryptoPath = `${baseRoute}/crypto` - } - router.push(cryptoPath) - }} + onCryptoClick={() => + router.push(flow === 'add' ? `${baseRoute}/crypto/direct` : `${baseRoute}/crypto`) + }src/app/(mobile-ui)/add-money/crypto/page.tsx (2)
19-21
: Starting at source selection with no default source: LGTMThe new initial state aligns with the redesigned flow and avoids stale defaults.
71-73
: Back from QR to source selection for exchanges: LGTMThis simplifies navigation and matches the updated UX.
src/config/wagmi.config.tsx (1)
82-84
: Provider composition looks correctWrapping JustaNameContext within DaimoPayProvider (inside Wagmi/QueryClient) is appropriate for hook access and theming.
src/app/(mobile-ui)/add-money/crypto/direct/page.tsx (1)
77-81
: Good: explicit chain, checksummed addresses.Using
arbitrum.id
and normalizing bothtoAddress
andtoToken
withgetAddress
is solid and helps prevent checksum/address issues.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (1)
package.json (1)
157-161
: Ensure “resolutions” are honored by your package manager; otherwise use overrides.
- Yarn: use "resolutions" (what you have).
- pnpm: use "pnpm.overrides".
- npm: use "overrides".
If you’re not on Yarn, the "ox": "0.6.5" constraint (and others) may be ignored.
Please confirm the package manager. If pnpm or npm, I can provide the exact JSON changes to migrate to overrides.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (1)
pnpm-lock.yaml
is excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (1)
package.json
(4 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Deploy-Preview
🔇 Additional comments (2)
package.json (2)
29-29
: Verify @daimo/pay@^1.15.0 peerDependencies
@daimo/pay@^1.15.0 requires:
- wagmi: “2.x”
- viem: “>=2.22.0”
Please confirm your project’s versions satisfy both.
- package.json ▶ ensure your wagmi version is within the 2.x range
- package.json/lockfile ▶ ensure viem is >= 2.22.0
If you’re not on wagmi 2.x or viem >= 2.22.0, adopt Option A to bump them accordingly.
82-83
: Compatibility Confirmed: [email protected] → viem ^2.22.0
[email protected] declares a peerDependency on viem “2.x”, and your pinned ^2.22.0 falls within that range—no changes needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
public/game/peanut-game.html (1)
1004-1106
: Prefer explicit statements over nested comma-expression for clarityWrapping the two assignments in a double-parenthesized comma-expression adds no functional value and reduces readability. Explicit statements are clearer and less error-prone for future edits/tools.
No behavior change; this is a readability refactor.
Apply this diff:
-;((Obstacle.MAX_OBSTACLE_LENGTH = 3), - (Obstacle.prototype = { +;Obstacle.MAX_OBSTACLE_LENGTH = 3; +Obstacle.prototype = { @@ - })) + })
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
public/game/peanut-game.html
(2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Deploy-Preview
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
🧹 Nitpick comments (1)
src/components/Payment/PaymentForm/index.tsx (1)
589-599
: App ID fallback throws at render time — consider early validationThrowing inside props is okay but makes the entire component tree crash for guests when misconfigured. Optionally assert the env var once (e.g., at module init) and render a disabled button with a clear message if missing.
If you want, I can push a small guard that logs to Sentry and disables the Daimo button when NEXT_PUBLIC_DAIMO_APP_ID is unset.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (4)
src/app/(mobile-ui)/add-money/crypto/direct/page.tsx
(1 hunks)src/components/AddWithdraw/AddWithdrawRouterView.tsx
(2 hunks)src/components/Payment/PaymentForm/index.tsx
(8 hunks)src/hooks/usePaymentInitiator.ts
(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- src/components/AddWithdraw/AddWithdrawRouterView.tsx
- src/app/(mobile-ui)/add-money/crypto/direct/page.tsx
🧰 Additional context used
🧠 Learnings (6)
📚 Learning: 2024-10-07T13:42:00.443Z
Learnt from: Hugo0
PR: peanutprotocol/peanut-ui#422
File: src/components/Request/Pay/Pay.tsx:103-111
Timestamp: 2024-10-07T13:42:00.443Z
Learning: When the token price cannot be fetched in `src/components/Request/Pay/Pay.tsx` within the `PayRequestLink` component, set `tokenPriceData.price` to 0 to ensure the UI remains functional. Since Squid uses their own price engine for x-chain fulfillment transactions, this approach will not affect the transaction computation.
Applied to files:
src/components/Payment/PaymentForm/index.tsx
📚 Learning: 2024-12-02T17:19:18.532Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#551
File: src/components/Request/Create/Views/Initial.view.tsx:151-156
Timestamp: 2024-12-02T17:19:18.532Z
Learning: In the `InitialView` component at `src/components/Request/Create/Views/Initial.view.tsx`, when setting the default chain and token in the `useEffect` triggered by `isPeanutWallet`, it's acceptable to omit the setters from the dependency array and not include additional error handling for invalid defaults.
Applied to files:
src/components/Payment/PaymentForm/index.tsx
📚 Learning: 2024-10-08T20:13:42.967Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#413
File: src/context/tokenSelector.context.tsx:118-123
Timestamp: 2024-10-08T20:13:42.967Z
Learning: In the `TokenContextProvider` component within `src/context/tokenSelector.context.tsx`, in the TypeScript React application, when data changes and before calling `fetchAndSetTokenPrice`, it is necessary to reset `selectedTokenData`, `selectedTokenPrice`, `selectedTokenDecimals`, and `inputDenomination` to discard stale data.
Applied to files:
src/components/Payment/PaymentForm/index.tsx
📚 Learning: 2025-06-18T19:56:55.443Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#919
File: src/components/Withdraw/views/Initial.withdraw.view.tsx:87-87
Timestamp: 2025-06-18T19:56:55.443Z
Learning: In withdraw flows for Peanut Wallet, the PeanutActionDetailsCard should always display "USDC" as the token symbol because it shows the amount being withdrawn from the Peanut Wallet (which holds USDC), regardless of the destination token/chain selected by the user. The TokenSelector is used for choosing the withdrawal destination, not the source display.
Applied to files:
src/components/Payment/PaymentForm/index.tsx
📚 Learning: 2024-10-08T20:13:42.967Z
Learnt from: Hugo0
PR: peanutprotocol/peanut-ui#413
File: src/components/Request/Pay/Views/Initial.view.tsx:71-72
Timestamp: 2024-10-08T20:13:42.967Z
Learning: In `src/components/Request/Pay/Views/Initial.view.tsx`, it's acceptable to use the `!` operator in TypeScript to assert that `selectedTokenData` is not `null` or `undefined`, and potential runtime errors from accessing its properties without checks can be disregarded.
Applied to files:
src/components/Payment/PaymentForm/index.tsx
📚 Learning: 2024-10-11T01:14:15.489Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#424
File: src/components/Global/TokenSelector/TokenSelector.tsx:197-211
Timestamp: 2024-10-11T01:14:15.489Z
Learning: In `src/components/Global/TokenSelector/TokenSelector.tsx`, when the calculation within functions like `byChainAndText` is not computationally expensive, it's acceptable to avoid using `useCallback` for memoization.
Applied to files:
src/components/Payment/PaymentForm/index.tsx
🧬 Code Graph Analysis (2)
src/hooks/usePaymentInitiator.ts (3)
src/services/services.types.ts (1)
TRequestChargeResponse
(171-206)src/services/charges.ts (1)
chargesApi
(6-94)src/redux/slices/payment-slice.ts (1)
paymentActions
(73-73)
src/components/Payment/PaymentForm/index.tsx (4)
src/hooks/usePaymentInitiator.ts (2)
usePaymentInitiator
(80-826)InitiatePaymentPayload
(52-68)src/redux/slices/payment-slice.ts (1)
paymentActions
(73-73)src/utils/general.utils.ts (1)
areEvmAddressesEqual
(445-452)src/constants/zerodev.consts.ts (1)
PEANUT_WALLET_TOKEN
(20-20)
🔇 Additional comments (2)
src/hooks/usePaymentInitiator.ts (1)
805-805
: Exposing setLoadingStep is useful and consistent with UI needsProviding setLoadingStep allows the UI (e.g., Daimo UI close handlers) to reset the loading state. This is aligned with how the PaymentForm integrates the Daimo button.
src/components/Payment/PaymentForm/index.tsx (1)
811-823
: Confirm removing TokenSelector doesn’t break non-Daimo external-wallet flowsCommenting out the TokenSelector means some non-Peanut, non-Daimo flows may no longer have a way to pick chain/token unless these are always provided by the request or fixed by the flow. Please confirm this is intentional for all routes that render this form.
If this wasn’t intended, I can help conditionally render TokenSelector only when:
- user is signed-in but not using Peanut Wallet, and
- a request doesn’t specify chain/token.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (3)
src/components/Payment/PaymentForm/index.tsx (3)
600-603
: Avoid unhandled promise in onPaymentCompletedPrefixing the async call with void makes it explicit we’re intentionally not awaiting inside the callback and silences unhandled promise warnings.
- onPaymentCompleted={(e) => { - handleCompleteDaimoPayment(e) - }} + onPaymentCompleted={(e) => { + void handleCompleteDaimoPayment(e) + }}
453-521
: Wrap Daimo initiation in try/catch to surface errors to the UIIf initiateDaimoPayment rejects, the error will bubble and users won’t get a friendly message. Wrap the await call and dispatch a Redux error using the shared ErrorHandler.
- const result = await initiateDaimoPayment(payload) - - if (result.status === 'Charge Created') { - console.log('Charge created!!') - } else if (result.status === 'Error') { - console.error('Payment initiation failed:', result) - } else { - console.warn('Unexpected status from usePaymentInitiator:', result.status) - } + try { + const result = await initiateDaimoPayment(payload) + if (result.status === 'Charge Created') { + console.log('Charge created!!') + } else if (result.status === 'Error') { + console.error('Payment initiation failed:', result) + dispatch(paymentActions.setError(result.error || 'Failed to initiate payment')) + } else { + console.warn('Unexpected status from usePaymentInitiator:', result.status) + } + } catch (err) { + const errorString = ErrorHandler(err) + dispatch(paymentActions.setError(errorString)) + }
588-625
: Guard Daimo button rendering to avoid getAddress throwing on missing/invalid recipient addressgetAddress(recipient.resolvedAddress) will throw if resolvedAddress is absent. Render the DaimoPayButton only when a resolved address exists.
- <DaimoPayButton.Custom + {recipient?.resolvedAddress && ( + <DaimoPayButton.Custom appId={ process.env.NEXT_PUBLIC_DAIMO_APP_ID ?? (() => { throw new Error('Daimo APP ID is required') })() } intent="Deposit" toChain={arbitrum.id} toUnits={inputTokenAmount.replace(/,/g, '')} toAddress={getAddress(recipient.resolvedAddress)} toToken={getAddress(PEANUT_WALLET_TOKEN)} // USDC on arbitrum onPaymentCompleted={(e) => { - handleCompleteDaimoPayment(e) + void handleCompleteDaimoPayment(e) }} closeOnSuccess onClose={() => { setLoadingStep('Idle') }} > {({ show }) => ( <Button loading={isProcessing} variant="primary-soft" shadowSize="4" onClick={async () => { await handleInitiateDaimoPayment() await resetPayment({ toUnits: inputTokenAmount.replace(/,/g, ''), }) show() }} className="w-full" > Pay with Daimo </Button> )} - </DaimoPayButton.Custom> + </DaimoPayButton.Custom> + )}
🧹 Nitpick comments (2)
src/components/Payment/PaymentForm/index.tsx (2)
588-599
: Fail-fast if Daimo APP ID is missing without crashing the entire render treeThrowing in render will crash the whole subtree. Consider surfacing a disabled button with an inline error instead of throwing.
Example approach inside this block:
- If !process.env.NEXT_PUBLIC_DAIMO_APP_ID, render a disabled “Pay with Daimo (Unavailable)” button and optionally show an ErrorAlert or tooltip. This avoids a hard crash in misconfigured environments.
809-827
: Commented-out TokenSelector blocks: consider removing or gating with a feature flagLong commented blocks quickly rot. If Daimo fully owns token selection in this flow, either remove these sections or gate with a runtime feature flag (e.g., ENABLE_DAIMO_TOKEN_SELECTOR=false) so intent is explicit.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
src/components/Payment/PaymentForm/index.tsx
(8 hunks)
🧰 Additional context used
🧠 Learnings (18)
📚 Learning: 2024-11-18T21:36:11.486Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#535
File: src/components/Claim/Claim.tsx:142-146
Timestamp: 2024-11-18T21:36:11.486Z
Learning: In `src/components/Claim/Claim.tsx`, external calls like token price fetching and cross-chain details retrieval are already encapsulated within existing `try...catch` blocks, so additional error handling may be unnecessary.
Applied to files:
src/components/Payment/PaymentForm/index.tsx
📚 Learning: 2025-06-22T16:10:53.167Z
Learnt from: kushagrasarathe
PR: peanutprotocol/peanut-ui#915
File: src/hooks/useKycFlow.ts:96-124
Timestamp: 2025-06-22T16:10:53.167Z
Learning: The `initiateKyc` function in `src/app/actions/users.ts` already includes comprehensive error handling with try-catch blocks and returns structured responses with either `{ data }` or `{ error }` fields, so additional try-catch blocks around its usage are not needed.
Applied to files:
src/components/Payment/PaymentForm/index.tsx
📚 Learning: 2024-10-08T20:13:42.967Z
Learnt from: Hugo0
PR: peanutprotocol/peanut-ui#422
File: src/components/Request/Pay/Pay.tsx:113-123
Timestamp: 2024-10-08T20:13:42.967Z
Learning: In the `PayRequestLink` component (`src/components/Request/Pay/Pay.tsx`), when resolving ENS names, handle errors by displaying an appropriate error message to the user if the ENS cannot be resolved.
Applied to files:
src/components/Payment/PaymentForm/index.tsx
📚 Learning: 2024-12-11T10:13:22.806Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#564
File: src/components/Request/Pay/Views/Initial.view.tsx:430-430
Timestamp: 2024-12-11T10:13:22.806Z
Learning: In the React TypeScript file `src/components/Request/Pay/Views/Initial.view.tsx`, when reviewing the `InitialView` component, do not flag potential issues with using non-null assertion `!` on the `slippagePercentage` variable, as handling undefined values in this context is considered out of scope.
Applied to files:
src/components/Payment/PaymentForm/index.tsx
📚 Learning: 2025-05-23T19:26:58.220Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#873
File: src/components/Withdraw/views/Initial.withdraw.view.tsx:95-95
Timestamp: 2025-05-23T19:26:58.220Z
Learning: The GeneralRecipientInput component supports username validation and resolution through the validateAndResolveRecipient function in src/lib/validation/recipient.ts. The function automatically detects usernames (inputs that don't contain '.' for ENS and don't start with '0x' for addresses), validates them via API HEAD request, fetches user data, and resolves them to Ethereum addresses from the user's PEANUT_WALLET account.
Applied to files:
src/components/Payment/PaymentForm/index.tsx
📚 Learning: 2024-10-04T13:10:49.199Z
Learnt from: Hugo0
PR: peanutprotocol/peanut-ui#413
File: src/components/Request/Pay/Views/Initial.view.tsx:71-72
Timestamp: 2024-10-04T13:10:49.199Z
Learning: In `src/components/Request/Pay/Views/Initial.view.tsx`, it's acceptable to use the `!` operator in TypeScript to assert that `selectedTokenData` is not `null` or `undefined`, and potential runtime errors from accessing its properties without checks can be disregarded.
Applied to files:
src/components/Payment/PaymentForm/index.tsx
📚 Learning: 2024-10-23T09:38:27.670Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#469
File: src/app/request/pay/page.tsx:32-64
Timestamp: 2024-10-23T09:38:27.670Z
Learning: In `src/app/request/pay/page.tsx`, if `linkRes` is not OK in the `generateMetadata` function, the desired behavior is to use the standard title and preview image without throwing an error.
Applied to files:
src/components/Payment/PaymentForm/index.tsx
📚 Learning: 2024-10-07T15:28:25.280Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#422
File: src/components/Request/Pay/Views/Initial.view.tsx:76-78
Timestamp: 2024-10-07T15:28:25.280Z
Learning: In `src/components/Request/Pay/Views/Initial.view.tsx`, both `txFee` and `utils.formatTokenAmount(estimatedGasCost, 3)` return strings, ensuring consistent return types for `calculatedFee`.
Applied to files:
src/components/Payment/PaymentForm/index.tsx
📚 Learning: 2024-10-07T15:25:45.170Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#422
File: src/components/Request/Pay/Views/Initial.view.tsx:76-78
Timestamp: 2024-10-07T15:25:45.170Z
Learning: In `src/components/Request/Pay/Views/Initial.view.tsx`, both `txFee` and `utils.formatTokenAmount(...)` return strings, ensuring that `calculatedFee` consistently returns a string without the need for additional type conversion.
Applied to files:
src/components/Payment/PaymentForm/index.tsx
📚 Learning: 2024-10-29T12:19:41.968Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#495
File: src/components/Global/TokenAmountInput/index.tsx:23-30
Timestamp: 2024-10-29T12:19:41.968Z
Learning: In the `TokenAmountInput` component (`src/components/Global/TokenAmountInput/index.tsx`), when the 'Max' button is clicked, we intentionally set the input denomination to 'TOKEN' because we are setting the value as token.
Applied to files:
src/components/Payment/PaymentForm/index.tsx
📚 Learning: 2024-10-29T12:20:47.207Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#495
File: src/components/Create/Link/Input.view.tsx:244-248
Timestamp: 2024-10-29T12:20:47.207Z
Learning: In the `TokenAmountInput` component within `src/components/Global/TokenAmountInput/index.tsx`, when `balance` is undefined, the `maxValue` prop should be set to an empty string `''`.
Applied to files:
src/components/Payment/PaymentForm/index.tsx
📚 Learning: 2024-12-02T17:21:45.515Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#551
File: src/context/walletContext/walletContext.tsx:87-88
Timestamp: 2024-12-02T17:21:45.515Z
Learning: When converting `totalBalance` (in USD) to a `BigInt` balance in `src/context/walletContext/walletContext.tsx`, multiplying by `1e6` is intentional to maintain compatibility with USDC's 6 decimal places. The application displays only 2 decimal places, so this level of precision is sufficient.
Applied to files:
src/components/Payment/PaymentForm/index.tsx
📚 Learning: 2025-05-19T19:40:43.138Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#868
File: src/components/Payment/PaymentForm/index.tsx:284-293
Timestamp: 2025-05-19T19:40:43.138Z
Learning: When converting between USD and token amounts, always check if the token price (divisor) is valid and non-zero before performing the division to prevent Infinity, NaN, or errors. Implementing validation like `if (!tokenPrice || isNaN(tokenPrice) || tokenPrice === 0)` before division operations is crucial for handling cases where price data might be unavailable.
Applied to files:
src/components/Payment/PaymentForm/index.tsx
📚 Learning: 2024-10-07T13:42:00.443Z
Learnt from: Hugo0
PR: peanutprotocol/peanut-ui#422
File: src/components/Request/Pay/Pay.tsx:103-111
Timestamp: 2024-10-07T13:42:00.443Z
Learning: When the token price cannot be fetched in `src/components/Request/Pay/Pay.tsx` within the `PayRequestLink` component, set `tokenPriceData.price` to 0 to ensure the UI remains functional. Since Squid uses their own price engine for x-chain fulfillment transactions, this approach will not affect the transaction computation.
Applied to files:
src/components/Payment/PaymentForm/index.tsx
📚 Learning: 2024-12-02T17:19:18.532Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#551
File: src/components/Request/Create/Views/Initial.view.tsx:151-156
Timestamp: 2024-12-02T17:19:18.532Z
Learning: In the `InitialView` component at `src/components/Request/Create/Views/Initial.view.tsx`, when setting the default chain and token in the `useEffect` triggered by `isPeanutWallet`, it's acceptable to omit the setters from the dependency array and not include additional error handling for invalid defaults.
Applied to files:
src/components/Payment/PaymentForm/index.tsx
📚 Learning: 2024-10-08T20:13:42.967Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#413
File: src/context/tokenSelector.context.tsx:118-123
Timestamp: 2024-10-08T20:13:42.967Z
Learning: In the `TokenContextProvider` component within `src/context/tokenSelector.context.tsx`, in the TypeScript React application, when data changes and before calling `fetchAndSetTokenPrice`, it is necessary to reset `selectedTokenData`, `selectedTokenPrice`, `selectedTokenDecimals`, and `inputDenomination` to discard stale data.
Applied to files:
src/components/Payment/PaymentForm/index.tsx
📚 Learning: 2025-06-18T19:56:55.443Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#919
File: src/components/Withdraw/views/Initial.withdraw.view.tsx:87-87
Timestamp: 2025-06-18T19:56:55.443Z
Learning: In withdraw flows for Peanut Wallet, the PeanutActionDetailsCard should always display "USDC" as the token symbol because it shows the amount being withdrawn from the Peanut Wallet (which holds USDC), regardless of the destination token/chain selected by the user. The TokenSelector is used for choosing the withdrawal destination, not the source display.
Applied to files:
src/components/Payment/PaymentForm/index.tsx
📚 Learning: 2024-10-11T01:14:15.489Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#424
File: src/components/Global/TokenSelector/TokenSelector.tsx:197-211
Timestamp: 2024-10-11T01:14:15.489Z
Learning: In `src/components/Global/TokenSelector/TokenSelector.tsx`, when the calculation within functions like `byChainAndText` is not computationally expensive, it's acceptable to avoid using `useCallback` for memoization.
Applied to files:
src/components/Payment/PaymentForm/index.tsx
🧬 Code Graph Analysis (1)
src/components/Payment/PaymentForm/index.tsx (5)
src/hooks/usePaymentInitiator.ts (2)
usePaymentInitiator
(80-826)InitiatePaymentPayload
(52-68)src/redux/slices/payment-slice.ts (1)
paymentActions
(73-73)src/utils/general.utils.ts (1)
areEvmAddressesEqual
(445-452)src/components/0_Bruddle/Button.tsx (1)
Button
(76-267)src/constants/zerodev.consts.ts (1)
PEANUT_WALLET_TOKEN
(20-20)
🔇 Additional comments (1)
src/components/Payment/PaymentForm/index.tsx (1)
595-600
: Correct usage of Daimo’s toUnits propPassing a human-readable decimal string without commas matches the SDK’s expected format. LGTM.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (10)
src/assets/chains/index.ts (1)
5-5
: Name vs. UI label mismatch (chains vs. tokens).The UI copy says “Other Tokens,” while the symbol is named OTHER_CHAINS_ICON. Consider aligning names or adding an alias to avoid confusion when scanning code.
Apply either of the following small tweaks:
Option A — add an alias export:
export { default as OTHER_CHAINS_ICON } from './other-chains.svg' +export { default as OTHER_TOKENS_ICON } from './other-chains.svg'
Option B — keep as-is and ensure alt/text copy consistently says “Other Chains.”
src/components/AddMoney/components/CryptoMethodDrawer.tsx (4)
16-16
: Prevent Drawer from closing only when the risk modal is open, without dropping the handler.Passing
undefined
to onOpenChange when the modal is visible can lead to inconsistent behavior. Always provide a handler and early-return when the modal is shown.- <Drawer open={isDrawerOpen} onOpenChange={showRiskModal ? undefined : closeDrawer}> + <Drawer + open={isDrawerOpen} + onOpenChange={(open) => { + if (showRiskModal) return + if (!open) closeDrawer() + }} + >
67-67
: Fix incorrect alt text.This image represents “Other Tokens,” not “Arbitrum.”
- <Image src={OTHER_CHAINS_ICON} alt="Arbitrum" width={60} height={60} /> + <Image src={OTHER_CHAINS_ICON} alt="Other Tokens" width={60} height={60} />
21-24
: Clickable Card should be keyboard-accessible.Add button semantics so users can activate via keyboard. Minimal change below preserves styling.
- <Card - onClick={() => setShowRiskModal(true)} - className={'cursor-pointer px-4 py-2 hover:bg-gray-50'} - > + <Card + role="button" + tabIndex={0} + onKeyDown={(e) => (e.key === 'Enter' || e.key === ' ') && setShowRiskModal(true)} + onClick={() => setShowRiskModal(true)} + className="cursor-pointer px-4 py-2 hover:bg-gray-50" + >- <Card - onClick={() => router.push('/add-money/crypto/direct')} - className={'cursor-pointer px-4 py-2 hover:bg-gray-50'} - > + <Card + role="button" + tabIndex={0} + onKeyDown={(e) => (e.key === 'Enter' || e.key === ' ') && router.push('/add-money/crypto/direct')} + onClick={() => router.push('/add-money/crypto/direct')} + className="cursor-pointer px-4 py-2 hover:bg-gray-50" + >Also applies to: 55-58
85-87
: Close Drawer before navigating to the crypto page (avoids UI flicker and stale state).The route change will unmount this view, but closing explicitly prevents transient overlap.
- <Slider onValueChange={(v) => v && router.push('/add-money/crypto')} /> + <Slider + onValueChange={(v) => { + if (v) { + setShowRiskModal(false) + closeDrawer() + router.push('/add-money/crypto') + } + }} + />src/components/AddWithdraw/AddWithdrawRouterView.tsx (3)
43-44
: Local Drawer state is appropriate; consider hoisting once to avoid duplicate instances.You render CryptoMethodDrawer in two mutually exclusive branches. It works, but a single instance at the component root would simplify state/props.
A light refactor: always render one
<CryptoMethodDrawer>
at the bottom of the component and remove duplicates from branches.
207-207
: Avoid duplicate Drawer renders in different branches.Same component and props appear here and in the “all methods” view. Prefer a single instance to reduce risk of z-index/focus traps getting out of sync in future edits.
- <CryptoMethodDrawer isDrawerOpen={isDrawerOpen} closeDrawer={() => setIsDrawerOpen(false)} /> + {/* Rendered once at root; see bottom of component */}…and keep only the instance at Lines 245-246 (or vice versa).
123-126
: Recent methods update skips when opening Drawer for crypto: confirm desired.Since you
return
after opening the Drawer, the crypto method won’t be added to recents. If intentional, ignore; if not, move the recents update before thereturn
.src/app/(mobile-ui)/add-money/crypto/page.tsx (2)
82-89
: router.back() from QR: confirm navigation expectations.Using history back from QR means users return to the previous page (often the Add/Withdraw view with the Drawer). If you intend to return to in-page selection steps when they arrived via deep link, consider falling back to
setCurrentStep('networkSelection')
when there’s no prior history entry for this route.Example guarded fallback:
- const handleBackToNetworkSelectionFromQR = () => { - router.back() - } + const handleBackToNetworkSelectionFromQR = () => { + if (typeof window !== 'undefined' && window.history?.length > 1) { + router.back() + } else { + setCurrentStep(selectedSource?.type === 'exchange' ? 'sourceSelection' : 'networkSelection') + } + }
27-34
: Use a stable key for the default selectedSourceInstead of indexing into
CRYPTO_EXCHANGES
by position—which can easily break if the array order changes—lookup the “other-exchanges” source by itsid
(it’s present in the list) and fall back to the first entry:- const [selectedSource, setSelectedSource] = useState<CryptoSource | null>(CRYPTO_EXCHANGES[3]) + const [selectedSource, setSelectedSource] = useState<CryptoSource | null>( + CRYPTO_EXCHANGES.find((s) => s.id === 'other-exchanges') ?? CRYPTO_EXCHANGES[0] + )No change is needed for the network’s
chainId
literal—NetworkConfig.chainId
is typed as astring
, so'42161'
is correct.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (1)
src/assets/chains/other-chains.svg
is excluded by!**/*.svg
📒 Files selected for processing (4)
src/app/(mobile-ui)/add-money/crypto/page.tsx
(4 hunks)src/assets/chains/index.ts
(1 hunks)src/components/AddMoney/components/CryptoMethodDrawer.tsx
(1 hunks)src/components/AddWithdraw/AddWithdrawRouterView.tsx
(5 hunks)
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-05-22T15:38:48.586Z
Learnt from: kushagrasarathe
PR: peanutprotocol/peanut-ui#869
File: src/app/(mobile-ui)/withdraw/page.tsx:82-88
Timestamp: 2025-05-22T15:38:48.586Z
Learning: The country-specific withdrawal route exists at src/app/(mobile-ui)/withdraw/[...country]/page.tsx and renders the AddWithdrawCountriesList component with flow="withdraw".
Applied to files:
src/components/AddWithdraw/AddWithdrawRouterView.tsx
📚 Learning: 2024-12-02T17:19:18.532Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#551
File: src/components/Request/Create/Views/Initial.view.tsx:151-156
Timestamp: 2024-12-02T17:19:18.532Z
Learning: In the `InitialView` component at `src/components/Request/Create/Views/Initial.view.tsx`, when setting the default chain and token in the `useEffect` triggered by `isPeanutWallet`, it's acceptable to omit the setters from the dependency array and not include additional error handling for invalid defaults.
Applied to files:
src/app/(mobile-ui)/add-money/crypto/page.tsx
📚 Learning: 2024-10-08T20:13:42.967Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#413
File: src/context/tokenSelector.context.tsx:118-123
Timestamp: 2024-10-08T20:13:42.967Z
Learning: In the `TokenContextProvider` component within `src/context/tokenSelector.context.tsx`, in the TypeScript React application, when data changes and before calling `fetchAndSetTokenPrice`, it is necessary to reset `selectedTokenData`, `selectedTokenPrice`, `selectedTokenDecimals`, and `inputDenomination` to discard stale data.
Applied to files:
src/app/(mobile-ui)/add-money/crypto/page.tsx
📚 Learning: 2025-06-18T19:56:55.443Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#919
File: src/components/Withdraw/views/Initial.withdraw.view.tsx:87-87
Timestamp: 2025-06-18T19:56:55.443Z
Learning: In withdraw flows for Peanut Wallet, the PeanutActionDetailsCard should always display "USDC" as the token symbol because it shows the amount being withdrawn from the Peanut Wallet (which holds USDC), regardless of the destination token/chain selected by the user. The TokenSelector is used for choosing the withdrawal destination, not the source display.
Applied to files:
src/app/(mobile-ui)/add-money/crypto/page.tsx
🧬 Code graph analysis (2)
src/components/AddMoney/components/CryptoMethodDrawer.tsx (3)
src/components/Global/Drawer/index.tsx (2)
Drawer
(81-81)DrawerContent
(86-86)src/components/0_Bruddle/Card.tsx (1)
Card
(72-72)src/components/Slider/index.tsx (1)
Slider
(86-86)
src/app/(mobile-ui)/add-money/crypto/page.tsx (3)
src/components/AddMoney/consts/index.ts (4)
CryptoSource
(7-14)CRYPTO_EXCHANGES
(63-92)CryptoToken
(16-21)DEPOSIT_CRYPTO_TOKENS
(24-61)src/components/AddMoney/views/NetworkSelection.view.tsx (1)
SelectedNetwork
(14-14)src/components/Global/PeanutLoading/index.tsx (1)
PeanutLoading
(4-19)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Deploy-Preview
🔇 Additional comments (11)
src/assets/chains/index.ts (2)
5-5
: New OTHER_CHAINS_ICON export looks fine.Re-export is consistent with the existing pattern for chain icons.
1-7
: Assets barrel correctly re-exports chainsConfirmed that:
src/assets/index.ts
includesexport * from './chains'
.src/assets/chains/index.ts
exportsOTHER_CHAINS_ICON
from./other-chains.svg
.- The file
src/assets/chains/other-chains.svg
exists at the expected path.No further changes needed.
src/components/AddMoney/components/CryptoMethodDrawer.tsx (2)
72-91
: Risk modal copy and UX: good clarity and flow.Clear warning, non-dismissive modal with slider confirmation is appropriate for reducing mis-sends.
43-51
: Configure Next/Image to allow the Coingecko HTTPS assetYour
next.config.js
currently only permits HTTP hosts via a wildcard. Since the USDC logo is served over HTTPS fromassets.coingecko.com
, you must explicitly allow that domain/protocol. Otherwise the<Image>
component will error at build or runtime.Please update your
images
config in next.config.js (around lines 27–31) to include one of the following fixes:• Add an HTTPS remotePattern for Coingecko:
images: { remotePatterns: [ { protocol: 'http', hostname: '*', - // … }, + { + protocol: 'https', + hostname: 'assets.coingecko.com', + // adjust pathname to match your URL structure + pathname: '/coins/images/**', + }, ], },• Or (simpler) add the domain to
images.domains
:images: { + domains: ['assets.coingecko.com'], remotePatterns: [ { protocol: 'http', hostname: '*', pathname: '/**' }, ], },
If modifying the Next.js config isn’t desirable, switch that one
<Image>
to a plain<img>
tag to avoid needing a remote entry:- <Image + <img className="absolute bottom-0 left-3" src="https://assets.coingecko.com/coins/images/6319/small/USD_Coin_icon.png" alt="USDC" width={20} height={20} />src/components/AddWithdraw/AddWithdrawRouterView.tsx (4)
236-241
: onCryptoClick behavior by flow: looks correct.Add flow opens Drawer; withdraw keeps old navigation. Good split without breaking withdraw UX.
228-244
: Back navigation logic: solid defaults.The onPrev handler correctly collapses “all methods” view before navigating away; no issues spotted.
239-241
: Withdraw crypto route confirmedThe
/withdraw/crypto
route is implemented undersrc/app/(mobile-ui)/withdraw/crypto/
, so usingrouter.push(\
${baseRoute}/crypto`)` will not 404.
88-91
: Crypto drawer logic is correct and consistent—no changes needed.I’ve confirmed that the only
DepositMethod
entry for crypto is defined withid: 'crypto', type: 'crypto', path: 'crypto',in
src/components/AddMoney/consts/index.ts
, and that recent methods carry this sameid
when stored and compared inhandleMethodSelected
(method.id === 'crypto').
Additionally, the “all methods” view doesn’t rely onmethod.id
to open the drawer; it usesCountryList
’sonCryptoClick
callback, so there’s no missing branch in that flow.src/app/(mobile-ui)/add-money/crypto/page.tsx (3)
96-123
: Risk modal logic and copy: clear and consistent.Good reuse of the modal with token/network-specific title and slider confirmation.
126-128
: Loading guard for wallet address is correct.Showing PeanutLoading until
peanutWalletAddress
is available avoids rendering a blank/incomplete QR.
150-156
: Confirmed/add-money/crypto/direct
route existsVerified that the page component for the direct wallet route is present at
src/app/(mobile-ui)/add-money/crypto/direct/page.tsx
, so therouter.push('/add-money/crypto/direct')
navigation is valid.• Location to note:
- src/app/(mobile-ui)/add-money/crypto/direct/page.tsx
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (3)
src/components/AddWithdraw/AddWithdrawCountriesList.tsx (3)
31-31
: Use alias import for consistency; optionally code‑split the drawer.
- Prefer the "@/…" alias to match the rest of the file and avoid brittle relative paths.
- Optional: dynamically import the drawer to shave JS from the initial render of this page.
Apply this diff for path consistency:
-import CryptoMethodDrawer from '../AddMoney/components/CryptoMethodDrawer' +import CryptoMethodDrawer from '@/components/AddMoney/components/CryptoMethodDrawer'Optional dynamic import (add alongside the imports):
import dynamic from 'next/dynamic' const CryptoMethodDrawer = dynamic( () => import('@/components/AddMoney/components/CryptoMethodDrawer'), { ssr: false } )
51-51
: Name the state specifically; consider closing the drawer on back/path change.
- Rename for clarity and future-proofing if more drawers land here.
- Optional UX: close the drawer if the user taps the header back or navigates away.
Apply this diff to clarify naming:
- const [isDrawerOpen, setIsDrawerOpen] = useState(false) + const [cryptoDrawerOpen, setCryptoDrawerOpen] = useState(false)Then update its uses below (see my separate diffs in later comments).
Optional snippets:
- Close on header back (inside NavHeader onPrev):
if (flow === 'add' && cryptoDrawerOpen) { setCryptoDrawerOpen(false) return }
- Close on route change:
import { usePathname } from 'next/navigation' const pathname = usePathname() useEffect(() => { setCryptoDrawerOpen(false) }, [pathname])
341-343
: Mount the drawer only when open (optional); update names if you rename state.
- You can avoid keeping an off-screen drawer mounted by gating on the open flag. This is minor and mostly a DOM/paint optimization.
- Also shows the updated names if you adopt the rename above.
Apply one of the following diffs:
A) Keep current behavior but update names (if you renamed state):
-{flow === 'add' && ( - <CryptoMethodDrawer isDrawerOpen={isDrawerOpen} closeDrawer={() => setIsDrawerOpen(false)} /> -)} +{flow === 'add' && ( + <CryptoMethodDrawer isDrawerOpen={cryptoDrawerOpen} closeDrawer={() => setCryptoDrawerOpen(false)} /> +)}B) Additionally mount only when open:
-{flow === 'add' && ( - <CryptoMethodDrawer isDrawerOpen={isDrawerOpen} closeDrawer={() => setIsDrawerOpen(false)} /> -)} +{flow === 'add' && isDrawerOpen && ( + <CryptoMethodDrawer isDrawerOpen={isDrawerOpen} closeDrawer={() => setIsDrawerOpen(false)} /> +)}If you also implement dynamic import (suggested above), the drawer will be fetched only when needed, further reducing initial JS.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
src/components/AddWithdraw/AddWithdrawCountriesList.tsx
(4 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-05-22T15:38:48.586Z
Learnt from: kushagrasarathe
PR: peanutprotocol/peanut-ui#869
File: src/app/(mobile-ui)/withdraw/page.tsx:82-88
Timestamp: 2025-05-22T15:38:48.586Z
Learning: The country-specific withdrawal route exists at src/app/(mobile-ui)/withdraw/[...country]/page.tsx and renders the AddWithdrawCountriesList component with flow="withdraw".
Applied to files:
src/components/AddWithdraw/AddWithdrawCountriesList.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Deploy-Preview
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (1)
src/components/AddWithdraw/AddWithdrawCountriesList.tsx (1)
297-305
: Fix crypto-add click being gated behind method.path (duplicate of prior feedback).Currently the crypto-add branch sits inside the
method.path
guard, so ifcrypto-add
has no path the click is a no-op. Move the crypto check before the path check.onClick={() => { - if (flow === 'withdraw') { - handleWithdrawMethodClick(method) - } else if (method.path) { - if (method.id === 'crypto-add') { - setIsDrawerOpen(true) - return - } - router.push(method.path) - } + if (flow === 'withdraw') { + handleWithdrawMethodClick(method) + return + } + if (method.id === 'crypto-add') { + setIsDrawerOpen(true) + return + } + if (method.path) { + router.push(method.path) + } }}
🧹 Nitpick comments (7)
src/components/AddWithdraw/AddWithdrawCountriesList.tsx (3)
341-347
: Unify prop casing: setisDrawerOpen → setIsDrawerOpen.The prop in
CryptoMethodDrawer
is namedsetisDrawerOpen
(lowercase “i”) but you’re passingsetIsDrawerOpen
from here. It works, but the API is inconsistent and easy to misread.Apply together with the paired change in CryptoMethodDrawer.tsx:
<CryptoMethodDrawer - isDrawerOpen={isDrawerOpen} - setisDrawerOpen={setIsDrawerOpen} - closeDrawer={() => setIsDrawerOpen(false)} + isDrawerOpen={isDrawerOpen} + setIsDrawerOpen={setIsDrawerOpen} + closeDrawer={() => setIsDrawerOpen(false)} />
31-31
: Consider lazy-loading the drawer to trim initial bundle for this page.
CryptoMethodDrawer
is only used in the add-flow and after a click. Deferring it vianext/dynamic
can reduce initial JS.-import CryptoMethodDrawer from '../AddMoney/components/CryptoMethodDrawer' +import dynamic from 'next/dynamic' +const CryptoMethodDrawer = dynamic(() => import('../AddMoney/components/CryptoMethodDrawer'), { ssr: false })
51-51
: Optional: make state name self-descriptive.If there are or will be other drawers on this screen, rename to
cryptoDrawerOpen
for clarity.-const [isDrawerOpen, setIsDrawerOpen] = useState(false) +const [cryptoDrawerOpen, setCryptoDrawerOpen] = useState(false) @@ - if (method.id === 'crypto-add') { - setIsDrawerOpen(true) + if (method.id === 'crypto-add') { + setCryptoDrawerOpen(true) return } @@ - isDrawerOpen={isDrawerOpen} - setisDrawerOpen={setIsDrawerOpen} - closeDrawer={() => setIsDrawerOpen(false)} + isDrawerOpen={cryptoDrawerOpen} + setIsDrawerOpen={setCryptoDrawerOpen} + closeDrawer={() => setCryptoDrawerOpen(false)}Also applies to: 300-303, 343-345
src/components/AddMoney/components/CryptoMethodDrawer.tsx (4)
12-20
: Normalize setter casing: setisDrawerOpen → setIsDrawerOpen (and update callsites).Standard camelCase improves readability and matches usage in the parent.
-const CryptoMethodDrawer = ({ - isDrawerOpen, - setisDrawerOpen, - closeDrawer, -}: { - isDrawerOpen: boolean - setisDrawerOpen: Dispatch<SetStateAction<boolean>> - closeDrawer: () => void -}) => { +const CryptoMethodDrawer = ({ + isDrawerOpen, + setIsDrawerOpen, + closeDrawer, +}: { + isDrawerOpen: boolean + setIsDrawerOpen: Dispatch<SetStateAction<boolean>> + closeDrawer: () => void +}) => { @@ - setisDrawerOpen(false) + setIsDrawerOpen(false) @@ - setShowRiskModal(false) - setisDrawerOpen(true) + setShowRiskModal(false) + setIsDrawerOpen(true)Note: Apply the paired prop rename in AddWithdrawCountriesList.tsx where this component is used.
Also applies to: 33-35, 90-91
26-27
: Consider making onOpenChange controlled instead of always closing.Passing
closeDrawer
ignores theopen
arg. Prefer syncing to the emitted state; this also lets the drawer open via an external trigger if needed.-<Drawer open={isDrawerOpen} onOpenChange={showRiskModal ? undefined : closeDrawer}> +<Drawer + open={isDrawerOpen} + onOpenChange={showRiskModal ? undefined : (open) => setIsDrawerOpen(open)} +>If you adopt this, you can drop the
closeDrawer
prop from the component API entirely.
80-80
: Correct alt text for accessibility.The “Other Tokens” card uses
alt="Arbitrum"
.-<Image src={OTHER_CHAINS_ICON} alt="Arbitrum" width={60} height={60} /> +<Image src={OTHER_CHAINS_ICON} alt="Other chains" width={60} height={60} />
21-23
: Prefetch the target route when the risk modal opens to reduce perceived latency.Small UX boost: prefetch the crypto route while the risk modal is visible.
-import React, { Dispatch, SetStateAction, useState } from 'react' +import React, { Dispatch, SetStateAction, useEffect, useState } from 'react' @@ const [showRiskModal, setShowRiskModal] = useState(false) +useEffect(() => { + if (showRiskModal) { + // Next.js app router supports router.prefetch + router.prefetch('/add-money/crypto').catch(() => {}) + } +}, [showRiskModal, router])Also applies to: 24-24
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (3)
src/components/AddMoney/components/CryptoMethodDrawer.tsx
(1 hunks)src/components/AddWithdraw/AddWithdrawCountriesList.tsx
(4 hunks)src/components/AddWithdraw/AddWithdrawRouterView.tsx
(5 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/components/AddWithdraw/AddWithdrawRouterView.tsx
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-05-22T15:38:48.586Z
Learnt from: kushagrasarathe
PR: peanutprotocol/peanut-ui#869
File: src/app/(mobile-ui)/withdraw/page.tsx:82-88
Timestamp: 2025-05-22T15:38:48.586Z
Learning: The country-specific withdrawal route exists at src/app/(mobile-ui)/withdraw/[...country]/page.tsx and renders the AddWithdrawCountriesList component with flow="withdraw".
Applied to files:
src/components/AddWithdraw/AddWithdrawCountriesList.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Deploy-Preview
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (4)
src/app/(mobile-ui)/add-money/crypto/direct/page.tsx (2)
23-25
: Polling via fixed sleep is brittle; consider a poll-with-timeout or webhookA hard-coded 6s delay can be flaky under variable indexing times. Prefer a short poll with timeout/backoff, or rely on a webhook/signal from your backend to confirm ingestion.
58-58
: Remove stray brace in key propThe rendered key becomes "success-add-money}". Remove the extra "}".
- key={`success-add-money}`} + key={`success-add-money`}src/components/Global/DaimoPayButton/index.tsx (1)
98-102
: Ensure NEXT_PUBLIC_DAIMO_APP_ID is present in env.exampleThis component hard-requires the client env var. Add it to env.example (and README) to prevent runtime surprises.
src/components/Payment/PaymentForm/index.tsx (1)
634-651
: Guard Daimo button rendering on a valid recipient addressDaimoPayButton internally normalizes the address; pass it only when you have a valid one to avoid runtime throws.
- const daimoButton = () => { - return ( - <DaimoPayButton + const daimoButton = () => { + return recipient?.resolvedAddress ? ( + <DaimoPayButton amount={inputTokenAmount} toAddress={recipient.resolvedAddress} onPaymentCompleted={handleCompleteDaimoPayment} onClose={() => setLoadingStep('Idle')} onBeforeShow={handleInitiateDaimoPayment} variant="primary-soft" loading={isProcessing} minAmount={0.1} maxAmount={4000} onValidationError={setDaimoError} > Pay using exchange or wallet </DaimoPayButton> - ) + ) : null }
🧹 Nitpick comments (11)
src/components/Global/TokenSelector/TokenSelector.consts.ts (3)
72-73
: Replace array with Set and remove redundant typing.
as const
plus an explicitreadonly number[]
type is redundant. Also, aSet<number>
gives O(1) lookups and cleaner intent for membership checks.Apply:
-const networksToExclude: readonly number[] = [celo.id, linea.id, worldchain.id] as const +const networksToExclude = new Set<number>([celo.id, linea.id, worldchain.id])Optional: add a short comment explaining why these chains are excluded (e.g., temporary block while Daimo/Squid support lands) to prevent future regressions.
75-77
: Refactor networksToExclude into a Set and streamline ID handlingTo simplify the exclusion check and avoid unnecessary array scans, convert
networksToExclude
to aSet<number>
and then:
- Use
Set.has(...)
instead ofArray.includes(...)
for O(1) lookups.- Normalize IDs consistently with
String(...)
rather than calling.toString()
on the ID.- Document your choice of coercion (number-only vs. CAIP-2 strings) or introduce a helper if you may ever encounter CAIP-2 IDs.
Suggested changes:
--- src/components/Global/TokenSelector/TokenSelector.consts.ts @@ diff - // networks to exclude from the token selector - const networksToExclude: readonly number[] = [celo.id, linea.id, worldchain.id] as const + // networks to exclude from the token selector + const networksToExclude = new Set<number>([celo.id, linea.id, worldchain.id] as const) export const TOKEN_SELECTOR_SUPPORTED_NETWORK_IDS = networks - .filter((network) => !networksToExclude.includes(Number(network.id))) - .map((network) => network.id.toString()) + .filter((network) => !networksToExclude.has(Number(network.id))) + .map((network) => String(network.id))If you ever need to handle CAIP-2 IDs (e.g.
"eip155:1"
), introduce a small utility:// normalize chain IDs to plain numbers const toChainIdString = (id: number | string) => typeof id === 'number' ? String(id) : id.replace(/^eip155:/, ''); // then: .map((network) => toChainIdString(network.id))
4-4
: Confirmed viem version & central network config has exclusions
- package.json pins
viem@^2.22.0
, so our import ofworldchain
is valid and won’t break the build.src/config/wagmi.config.tsx
already exports anetworks
array that includeslinea
,worldchain
, andcelo
.Short‐term: safe to keep your direct imports from
viem/chains
.
Mid‐term: optional refactor – you can export aTOKEN_SELECTOR_EXCLUDED_CHAIN_IDS
(e.g.[linea.id, worldchain.id, celo.id]
) fromwagmi.config.tsx
for reuse inTokenSelector.consts.ts
.Let me know if you’d like assistance drafting that follow-up patch.
src/components/index.ts (1)
4-4
: Also export a default alias for DaimoPayButton from the barrelexport * does not re-export default. If consumers import from the central barrel, provide a named alias for the default too.
export * from './Global/DaimoPayButton' +export { default as DaimoPayButton } from './Global/DaimoPayButton'
src/app/(mobile-ui)/add-money/crypto/direct/page.tsx (3)
21-49
: Type the Daimo event and guard missing/invalid fields before calling the APIAvoid any and send a canonicalized payerAddress only when present/valid. Also prefer text fallback when the error response isn’t JSON.
- const onPaymentCompleted = async (e: any) => { + const onPaymentCompleted = async (e: DaimoPayEvent<'payment_completed'>) => { setIsUpdatingDepositStatus(true) // 6 second delay to ensure the payment is indexed in the backend await new Promise((resolve) => setTimeout(resolve, 6000)) - // Update the depositor address via API try { + const txHash = e?.txHash + const payer = e?.payment?.source?.payerAddress + if (!txHash || !payer) { + console.warn('Missing txHash or payerAddress from Daimo event', e) + return + } const response = await fetch('/api/deposit', { method: 'PUT', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ - txHash: e.txHash, - payerAddress: e.payment.source?.payerAddress, + txHash, + payerAddress: getAddress(payer), }), }) if (!response.ok) { - const errorData = await response.json() - console.error('Failed to update depositor address:', errorData) + const msg = await response.text().catch(() => 'Unknown error') + console.error('Failed to update depositor address:', msg) } } catch (error) { console.error('Error updating depositor address:', error) } finally { setIsUpdatingDepositStatus(false) - setisPaymentSuccess(true) + setIsPaymentSuccess(true) } }Add the missing imports near the top:
import type { DaimoPayEvent } from '@daimo/pay' import { getAddress } from 'viem'
17-17
: Fix setter casing for state: setIsPaymentSuccessThe setter is misspelled (lowercase i). Align with React conventions and your variable name for readability.
- const [isPaymentSuccess, setisPaymentSuccess] = useState(false) + const [isPaymentSuccess, setIsPaymentSuccess] = useState(false)Also applies to: 47-47
3-11
: Add the event type import alongside your other importsRequired for the onPaymentCompleted typing change.
import type { DaimoPayEvent } from '@daimo/pay' import { getAddress } from 'viem'src/components/Global/DaimoPayButton/index.tsx (2)
33-34
: Strongly type onPaymentCompleted using the SDK event (or a local shape)Avoid any to catch regressions early. If the package exports DaimoPayEvent, use it; otherwise define a minimal local type.
-import { DaimoPayButton as DaimoPayButtonSDK, useDaimoPayUI } from '@daimo/pay' +import { DaimoPayButton as DaimoPayButtonSDK, useDaimoPayUI } from '@daimo/pay' +import type { DaimoPayEvent } from '@daimo/pay' @@ - /** Callback when payment is completed */ - onPaymentCompleted: (paymentResponse: any) => void + /** Callback when payment is completed */ + onPaymentCompleted: (paymentResponse: DaimoPayEvent<'payment_completed'>) => voidIf DaimoPayEvent isn’t exported in your installed version, define:
type DaimoPaymentCompleted = { txHash?: string payment?: { source?: { chainId?: number; payerAddress?: string } } }and use DaimoPaymentCompleted instead.
Also applies to: 6-6
110-112
: Pre-validate toAddress to avoid getAddress throwing in the render pathIf an invalid address slips through, getAddress throws and crashes the tree. Validate in handleClick and short-circuit with a validation error instead.
Example addition inside handleClick, before resetPayment:
import { isAddress } from 'viem' ... if (!isAddress(toAddress)) { onValidationError?.('Invalid recipient address.') return false }src/components/Payment/PaymentForm/index.tsx (2)
38-38
: Remove unused import: useDaimoPayUIIt’s not referenced in this file and will fail lint/build under noUnusedLocals.
559-582
: Validate Daimo payerAddress and type the completion handler paramDon’t trust the SDK payload blindly; ensure required fields exist and canonicalize the address before completing.
- const handleCompleteDaimoPayment = useCallback( - async (daimoPaymentResponse: any) => { + const handleCompleteDaimoPayment = useCallback( + async (daimoPaymentResponse: { + txHash?: string + payment?: { source?: { chainId?: number; payerAddress?: string } } + }) => { console.log('handleCompleteDaimoPayment called') if (chargeDetails) { - const result = await completeDaimoPayment({ - chargeDetails: chargeDetails, - txHash: daimoPaymentResponse.txHash as string, - sourceChainId: daimoPaymentResponse.payment.source.chainId, - payerAddress: daimoPaymentResponse.payment.source.payerAddress, - }) + const txHash = daimoPaymentResponse?.txHash + const payer = daimoPaymentResponse?.payment?.source?.payerAddress + if (!txHash || !payer || !isAddress(payer)) { + console.warn('Missing/invalid txHash or payerAddress from Daimo response', daimoPaymentResponse) + return + } + const result = await completeDaimoPayment({ + chargeDetails: chargeDetails, + txHash, + sourceChainId: daimoPaymentResponse?.payment?.source?.chainId as number, + payerAddress: getAddress(payer), + })Add the imports at the top of this file:
import { getAddress, isAddress } from 'viem'
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (5)
src/app/(mobile-ui)/add-money/crypto/direct/page.tsx
(1 hunks)src/components/Global/DaimoPayButton/index.tsx
(1 hunks)src/components/Global/TokenSelector/TokenSelector.consts.ts
(2 hunks)src/components/Payment/PaymentForm/index.tsx
(10 hunks)src/components/index.ts
(1 hunks)
🧰 Additional context used
🧠 Learnings (25)
📚 Learning: 2025-08-22T07:28:32.260Z
Learnt from: Zishan-7
PR: peanutprotocol/peanut-ui#1104
File: src/components/Payment/PaymentForm/index.tsx:522-545
Timestamp: 2025-08-22T07:28:32.260Z
Learning: In `src/components/Payment/PaymentForm/index.tsx`, the `handleCompleteDaimoPayment` function is only for updating payment status in the backend after a successful Daimo payment. Payment success/failure is handled by Daimo itself, so try/catch error handling and error display are not needed for backend sync failures - users shouldn't see errors if payment succeeded but database update failed.
Applied to files:
src/components/Global/DaimoPayButton/index.tsx
src/components/Payment/PaymentForm/index.tsx
src/app/(mobile-ui)/add-money/crypto/direct/page.tsx
📚 Learning: 2024-11-18T21:36:11.486Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#535
File: src/components/Claim/Claim.tsx:142-146
Timestamp: 2024-11-18T21:36:11.486Z
Learning: In `src/components/Claim/Claim.tsx`, external calls like token price fetching and cross-chain details retrieval are already encapsulated within existing `try...catch` blocks, so additional error handling may be unnecessary.
Applied to files:
src/components/Payment/PaymentForm/index.tsx
📚 Learning: 2025-06-22T16:10:53.167Z
Learnt from: kushagrasarathe
PR: peanutprotocol/peanut-ui#915
File: src/hooks/useKycFlow.ts:96-124
Timestamp: 2025-06-22T16:10:53.167Z
Learning: The `initiateKyc` function in `src/app/actions/users.ts` already includes comprehensive error handling with try-catch blocks and returns structured responses with either `{ data }` or `{ error }` fields, so additional try-catch blocks around its usage are not needed.
Applied to files:
src/components/Payment/PaymentForm/index.tsx
📚 Learning: 2024-10-08T20:13:42.967Z
Learnt from: Hugo0
PR: peanutprotocol/peanut-ui#422
File: src/components/Request/Pay/Pay.tsx:113-123
Timestamp: 2024-10-08T20:13:42.967Z
Learning: In the `PayRequestLink` component (`src/components/Request/Pay/Pay.tsx`), when resolving ENS names, handle errors by displaying an appropriate error message to the user if the ENS cannot be resolved.
Applied to files:
src/components/Payment/PaymentForm/index.tsx
📚 Learning: 2024-12-11T10:13:22.806Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#564
File: src/components/Request/Pay/Views/Initial.view.tsx:430-430
Timestamp: 2024-12-11T10:13:22.806Z
Learning: In the React TypeScript file `src/components/Request/Pay/Views/Initial.view.tsx`, when reviewing the `InitialView` component, do not flag potential issues with using non-null assertion `!` on the `slippagePercentage` variable, as handling undefined values in this context is considered out of scope.
Applied to files:
src/components/Payment/PaymentForm/index.tsx
📚 Learning: 2025-05-23T19:26:58.220Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#873
File: src/components/Withdraw/views/Initial.withdraw.view.tsx:95-95
Timestamp: 2025-05-23T19:26:58.220Z
Learning: The GeneralRecipientInput component supports username validation and resolution through the validateAndResolveRecipient function in src/lib/validation/recipient.ts. The function automatically detects usernames (inputs that don't contain '.' for ENS and don't start with '0x' for addresses), validates them via API HEAD request, fetches user data, and resolves them to Ethereum addresses from the user's PEANUT_WALLET account.
Applied to files:
src/components/Payment/PaymentForm/index.tsx
📚 Learning: 2025-08-22T07:25:59.258Z
Learnt from: Zishan-7
PR: peanutprotocol/peanut-ui#1104
File: src/components/Payment/PaymentForm/index.tsx:596-600
Timestamp: 2025-08-22T07:25:59.258Z
Learning: The `TokenAmountInput` component in `src/components/Global/TokenAmountInput/` always returns decimal strings (e.g., "1,234.56"), not base units. When passing these values to external APIs like Daimo's `toUnits` prop, simply stripping commas with `.replace(/,/g, '')` is sufficient.
Applied to files:
src/components/Payment/PaymentForm/index.tsx
src/app/(mobile-ui)/add-money/crypto/direct/page.tsx
📚 Learning: 2024-10-07T15:28:25.280Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#422
File: src/components/Request/Pay/Views/Initial.view.tsx:76-78
Timestamp: 2024-10-07T15:28:25.280Z
Learning: In `src/components/Request/Pay/Views/Initial.view.tsx`, both `txFee` and `utils.formatTokenAmount(estimatedGasCost, 3)` return strings, ensuring consistent return types for `calculatedFee`.
Applied to files:
src/components/Payment/PaymentForm/index.tsx
src/app/(mobile-ui)/add-money/crypto/direct/page.tsx
📚 Learning: 2024-10-07T15:25:45.170Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#422
File: src/components/Request/Pay/Views/Initial.view.tsx:76-78
Timestamp: 2024-10-07T15:25:45.170Z
Learning: In `src/components/Request/Pay/Views/Initial.view.tsx`, both `txFee` and `utils.formatTokenAmount(...)` return strings, ensuring that `calculatedFee` consistently returns a string without the need for additional type conversion.
Applied to files:
src/components/Payment/PaymentForm/index.tsx
📚 Learning: 2024-10-29T12:19:41.968Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#495
File: src/components/Global/TokenAmountInput/index.tsx:23-30
Timestamp: 2024-10-29T12:19:41.968Z
Learning: In the `TokenAmountInput` component (`src/components/Global/TokenAmountInput/index.tsx`), when the 'Max' button is clicked, we intentionally set the input denomination to 'TOKEN' because we are setting the value as token.
Applied to files:
src/components/Payment/PaymentForm/index.tsx
src/app/(mobile-ui)/add-money/crypto/direct/page.tsx
📚 Learning: 2024-10-29T12:20:47.207Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#495
File: src/components/Create/Link/Input.view.tsx:244-248
Timestamp: 2024-10-29T12:20:47.207Z
Learning: In the `TokenAmountInput` component within `src/components/Global/TokenAmountInput/index.tsx`, when `balance` is undefined, the `maxValue` prop should be set to an empty string `''`.
Applied to files:
src/components/Payment/PaymentForm/index.tsx
src/app/(mobile-ui)/add-money/crypto/direct/page.tsx
📚 Learning: 2024-12-02T17:21:45.515Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#551
File: src/context/walletContext/walletContext.tsx:87-88
Timestamp: 2024-12-02T17:21:45.515Z
Learning: When converting `totalBalance` (in USD) to a `BigInt` balance in `src/context/walletContext/walletContext.tsx`, multiplying by `1e6` is intentional to maintain compatibility with USDC's 6 decimal places. The application displays only 2 decimal places, so this level of precision is sufficient.
Applied to files:
src/components/Payment/PaymentForm/index.tsx
src/app/(mobile-ui)/add-money/crypto/direct/page.tsx
📚 Learning: 2025-05-19T19:40:43.138Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#868
File: src/components/Payment/PaymentForm/index.tsx:284-293
Timestamp: 2025-05-19T19:40:43.138Z
Learning: When converting between USD and token amounts, always check if the token price (divisor) is valid and non-zero before performing the division to prevent Infinity, NaN, or errors. Implementing validation like `if (!tokenPrice || isNaN(tokenPrice) || tokenPrice === 0)` before division operations is crucial for handling cases where price data might be unavailable.
Applied to files:
src/components/Payment/PaymentForm/index.tsx
📚 Learning: 2024-10-29T14:44:08.745Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#495
File: src/components/Cashout/Components/Initial.view.tsx:194-198
Timestamp: 2024-10-29T14:44:08.745Z
Learning: Using a fixed 6 decimal places for `floorFixed` is acceptable for token amounts in this codebase, even if tokens have varying decimal places.
Applied to files:
src/components/Payment/PaymentForm/index.tsx
src/app/(mobile-ui)/add-money/crypto/direct/page.tsx
📚 Learning: 2024-10-08T20:13:42.967Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#413
File: src/context/tokenSelector.context.tsx:118-123
Timestamp: 2024-10-08T20:13:42.967Z
Learning: In the `TokenContextProvider` component within `src/context/tokenSelector.context.tsx`, in the TypeScript React application, when data changes and before calling `fetchAndSetTokenPrice`, it is necessary to reset `selectedTokenData`, `selectedTokenPrice`, `selectedTokenDecimals`, and `inputDenomination` to discard stale data.
Applied to files:
src/components/Payment/PaymentForm/index.tsx
📚 Learning: 2024-10-08T20:13:42.967Z
Learnt from: Hugo0
PR: peanutprotocol/peanut-ui#422
File: src/components/Request/Pay/Pay.tsx:103-111
Timestamp: 2024-10-08T20:13:42.967Z
Learning: When the token price cannot be fetched in `src/components/Request/Pay/Pay.tsx` within the `PayRequestLink` component, set `tokenPriceData.price` to 0 to ensure the UI remains functional. Since Squid uses their own price engine for x-chain fulfillment transactions, this approach will not affect the transaction computation.
Applied to files:
src/components/Payment/PaymentForm/index.tsx
📚 Learning: 2024-10-07T15:50:29.173Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#422
File: src/components/Request/Pay/Pay.consts.ts:34-34
Timestamp: 2024-10-07T15:50:29.173Z
Learning: In `src/components/Request/Pay` components, the `tokenPrice` property in the `IPayScreenProps` interface is only relevant to these views. Other components using `IPayScreenProps` do not need to handle `tokenPriceData` when it's updated in these components.
Applied to files:
src/components/Payment/PaymentForm/index.tsx
📚 Learning: 2024-10-08T20:13:42.967Z
Learnt from: Hugo0
PR: peanutprotocol/peanut-ui#413
File: src/components/Request/Pay/Views/Initial.view.tsx:71-72
Timestamp: 2024-10-08T20:13:42.967Z
Learning: In `src/components/Request/Pay/Views/Initial.view.tsx`, it's acceptable to use the `!` operator in TypeScript to assert that `selectedTokenData` is not `null` or `undefined`, and potential runtime errors from accessing its properties without checks can be disregarded.
Applied to files:
src/components/Payment/PaymentForm/index.tsx
📚 Learning: 2024-12-02T17:19:18.532Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#551
File: src/components/Request/Create/Views/Initial.view.tsx:151-156
Timestamp: 2024-12-02T17:19:18.532Z
Learning: In the `InitialView` component at `src/components/Request/Create/Views/Initial.view.tsx`, when setting the default chain and token in the `useEffect` triggered by `isPeanutWallet`, it's acceptable to omit the setters from the dependency array and not include additional error handling for invalid defaults.
Applied to files:
src/components/Payment/PaymentForm/index.tsx
📚 Learning: 2025-06-18T19:56:55.443Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#919
File: src/components/Withdraw/views/Initial.withdraw.view.tsx:87-87
Timestamp: 2025-06-18T19:56:55.443Z
Learning: In withdraw flows for Peanut Wallet, the PeanutActionDetailsCard should always display "USDC" as the token symbol because it shows the amount being withdrawn from the Peanut Wallet (which holds USDC), regardless of the destination token/chain selected by the user. The TokenSelector is used for choosing the withdrawal destination, not the source display.
Applied to files:
src/components/Payment/PaymentForm/index.tsx
📚 Learning: 2024-10-11T01:14:15.489Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#424
File: src/components/Global/TokenSelector/TokenSelector.tsx:197-211
Timestamp: 2024-10-11T01:14:15.489Z
Learning: In `src/components/Global/TokenSelector/TokenSelector.tsx`, when the calculation within functions like `byChainAndText` is not computationally expensive, it's acceptable to avoid using `useCallback` for memoization.
Applied to files:
src/components/Payment/PaymentForm/index.tsx
📚 Learning: 2025-05-22T15:38:48.586Z
Learnt from: kushagrasarathe
PR: peanutprotocol/peanut-ui#869
File: src/app/(mobile-ui)/withdraw/page.tsx:82-88
Timestamp: 2025-05-22T15:38:48.586Z
Learning: The country-specific withdrawal route exists at src/app/(mobile-ui)/withdraw/[...country]/page.tsx and renders the AddWithdrawCountriesList component with flow="withdraw".
Applied to files:
src/app/(mobile-ui)/add-money/crypto/direct/page.tsx
📚 Learning: 2024-10-08T20:13:42.967Z
Learnt from: Hugo0
PR: peanutprotocol/peanut-ui#420
File: src/components/Offramp/Offramp.consts.ts:27-28
Timestamp: 2024-10-08T20:13:42.967Z
Learning: In `src/components/Offramp/Offramp.consts.ts`, the `MIN_CASHOUT_LIMIT` is set to $10 because smaller amounts are impractical due to approximately $1 fee per cashout.
Applied to files:
src/app/(mobile-ui)/add-money/crypto/direct/page.tsx
📚 Learning: 2025-07-07T20:22:11.092Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#958
File: src/app/actions/tokens.ts:266-266
Timestamp: 2025-07-07T20:22:11.092Z
Learning: In `src/app/actions/tokens.ts`, within the `fetchWalletBalances` function, using the non-null assertion operator `!` on `process.env.MOBULA_API_KEY!` is intentional and correct, and should not be flagged for replacement with explicit validation.
Applied to files:
src/app/(mobile-ui)/add-money/crypto/direct/page.tsx
📚 Learning: 2024-10-23T09:38:27.670Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#469
File: src/app/request/pay/page.tsx:32-64
Timestamp: 2024-10-23T09:38:27.670Z
Learning: In `src/app/request/pay/page.tsx`, if `linkRes` is not OK in the `generateMetadata` function, the desired behavior is to use the standard title and preview image without throwing an error.
Applied to files:
src/app/(mobile-ui)/add-money/crypto/direct/page.tsx
🧬 Code graph analysis (4)
src/components/Global/TokenSelector/TokenSelector.consts.ts (1)
src/config/wagmi.config.tsx (1)
networks
(38-41)
src/components/Global/DaimoPayButton/index.tsx (2)
src/components/Global/Icons/Icon.tsx (1)
IconName
(62-120)src/constants/zerodev.consts.ts (1)
PEANUT_WALLET_TOKEN
(20-20)
src/components/Payment/PaymentForm/index.tsx (3)
src/hooks/usePaymentInitiator.ts (2)
usePaymentInitiator
(80-829)InitiatePaymentPayload
(52-68)src/utils/general.utils.ts (1)
areEvmAddressesEqual
(520-527)src/components/Global/DaimoPayButton/index.tsx (1)
DaimoPayButton
(46-137)
src/app/(mobile-ui)/add-money/crypto/direct/page.tsx (3)
src/hooks/wallet/useWallet.ts (1)
useWallet
(21-124)src/components/Global/PeanutLoading/index.tsx (1)
PeanutLoading
(4-19)src/components/Global/DaimoPayButton/index.tsx (1)
DaimoPayButton
(46-137)
🔇 Additional comments (2)
src/components/Payment/PaymentForm/index.tsx (2)
867-881
: Good UX gating for Peanut Wallet pathConditionally showing “Send” vs “Add funds” CTAs based on balance and wallet state looks solid.
907-909
: Surfacing Daimo validation errors is a nice touchHooking onValidationError into ErrorAlert keeps the UX coherent with your existing error pattern.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (3)
src/app/(mobile-ui)/add-money/crypto/page.tsx (3)
2-2
: Good move: centralized assets and constants; addresses past feedback.Using ARBITRUM_ICON and PEANUT_WALLET_CHAIN + adding PeanutLoading aligns with our constants-driven approach and removes hard-coded values. This directly addresses the earlier request to rely on constants (e.g., PEANUT_*), not literals.
Also applies to: 16-16, 18-18
37-41
: Type safety: chainId as string and iconUrl shape may drift from expectations.
- chainId: Using PEANUT_WALLET_CHAIN.id.toString() may conflict with places where chainId is treated as a number/hex (see retrieved learning about passing numeric id to explorers). Keep it numeric unless SelectedNetwork explicitly requires string.
- iconUrl: If SelectedNetwork.iconUrl expects a URL string, ARBITRUM_ICON may need .src depending on how the asset is typed (StaticImageData vs string). If the component accepts StaticImageData, current code is fine.
Run to confirm the types:
#!/bin/bash # Check SelectedNetwork typing and how iconUrl is used/rendered rg -n -C3 "export (type|interface) SelectedNetwork" src rg -n -C3 "iconUrl" src/components/AddMoney/views/NetworkSelection.view.tsxIf SelectedNetwork expects a numeric chainId or string URL, consider:
- chainId: PEANUT_WALLET_CHAIN.id.toString(), - name: PEANUT_WALLET_CHAIN.name, - iconUrl: ARBITRUM_ICON, + chainId: PEANUT_WALLET_CHAIN.id, // keep numeric if that's our convention + name: PEANUT_WALLET_CHAIN.name, + iconUrl: ARBITRUM_ICON, // or ARBITRUM_ICON.src if a string is requiredNote: referencing past learning where chainId is stored/used as numeric for explorer URLs.
146-147
: Pass router.back directly; avoid wrapping.No need for an arrow that just calls back; pass the function reference.
- onBack={() => router.back()} + onBack={router.back}
🧹 Nitpick comments (6)
src/app/actions/users.ts (4)
141-148
: Harden error parsing when the backend returns non-JSON bodies
response.json()
will throw if the server responds with text or empty body. Fall back totext()
to preserve the error message.- const responseJson = await response.json() - if (!response.ok) { - throw new Error( - responseJson.message || - responseJson.error || - `Failed to save deposit address with status: ${response.status}` - ) - } + let parsed: any + try { + parsed = await response.json() + } catch { + parsed = await response.text() + } + if (!response.ok) { + const msg = + (parsed && (parsed.message || parsed.error)) || + `Failed to save deposit address with status: ${response.status}` + throw new Error(msg) + } - return { data: responseJson } + return { data: parsed }
150-152
: Preserve original error instance and stackRe-wrapping the caught error loses stack/context. Prefer rethrowing the original
Error
.- } catch (e: any) { - throw new Error(e.message || e.toString() || 'An unexpected error occurred') - } + } catch (e: unknown) { + if (e instanceof Error) throw e + throw new Error(String(e) || 'An unexpected error occurred') + }
125-128
: Validate txHash and payerAddress formats earlyA quick format check avoids bad requests and makes issues obvious at the call site. For EVM chains:
if (!txHash || !payerAddress) { throw new Error('Missing required fields: txHash and payerAddress') } + // Basic EVM format validation + const isTxHash = /^0x[a-fA-F0-9]{64}$/.test(txHash) + const isAddress = /^0x[a-fA-F0-9]{40}$/.test(payerAddress) + if (!isTxHash || !isAddress) { + throw new Error('Invalid format for txHash or payerAddress') + }
123-123
: Naming nit: considerrecordDeposit
orsaveDepositRecord
The function posts a “deposit history” record, not an “address” itself. A more precise name helps future maintainers discover it.
src/app/(mobile-ui)/add-money/crypto/page.tsx (2)
133-136
: Allow QR when depositAddress is provided, even if wallet isn't connected.If this page is opened with a known depositAddress prop (e.g., deep link), we can render immediately instead of blocking on wallet connection. This improves resilience when ZeroDev readiness lags.
Apply this diff:
- if (!isConnected) { - return <PeanutLoading /> - } + if (!isConnected && !depositAddress) { + return <PeanutLoading /> + }
35-35
: Avoid magic index for default crypto source selectionUsing
CRYPTO_EXCHANGES[3]
is brittle: the item at index 3 may change if the array is reordered. Currently, index 3 corresponds to the'sol'
source, so it’s better to look up by the'sol'
identifier with a safe fallback.File:
src/app/(mobile-ui)/add-money/crypto/page.tsx
(line 35)Replace:
- const [selectedSource, setSelectedSource] = useState<CryptoSource | null>(CRYPTO_EXCHANGES[3]) + // Use identifier-based lookup instead of a hardcoded index + const DEFAULT_SOURCE_ID = 'sol' + const initialSource = + CRYPTO_EXCHANGES.find((s) => s.id === DEFAULT_SOURCE_ID) ?? CRYPTO_EXCHANGES[0] + const [selectedSource, setSelectedSource] = useState<CryptoSource | null>(initialSource)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (3)
src/app/(mobile-ui)/add-money/crypto/direct/page.tsx
(1 hunks)src/app/(mobile-ui)/add-money/crypto/page.tsx
(3 hunks)src/app/actions/users.ts
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/app/(mobile-ui)/add-money/crypto/direct/page.tsx
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-05-05T20:43:40.198Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#830
File: src/components/Global/ListItemView/index.tsx:186-191
Timestamp: 2025-05-05T20:43:40.198Z
Learning: In the Peanut UI codebase, chainId is stored as a numeric or hex ID in the HistoryEntry object, not as a human-readable name string. When using it with explorer links, it should be passed directly to getExplorerUrl() as a number, rather than comparing it against chain.name in the supportedPeanutChains array.
Applied to files:
src/app/(mobile-ui)/add-money/crypto/page.tsx
📚 Learning: 2024-12-02T17:19:18.532Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#551
File: src/components/Request/Create/Views/Initial.view.tsx:151-156
Timestamp: 2024-12-02T17:19:18.532Z
Learning: In the `InitialView` component at `src/components/Request/Create/Views/Initial.view.tsx`, when setting the default chain and token in the `useEffect` triggered by `isPeanutWallet`, it's acceptable to omit the setters from the dependency array and not include additional error handling for invalid defaults.
Applied to files:
src/app/(mobile-ui)/add-money/crypto/page.tsx
📚 Learning: 2024-10-08T20:13:42.967Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#413
File: src/context/tokenSelector.context.tsx:118-123
Timestamp: 2024-10-08T20:13:42.967Z
Learning: In the `TokenContextProvider` component within `src/context/tokenSelector.context.tsx`, in the TypeScript React application, when data changes and before calling `fetchAndSetTokenPrice`, it is necessary to reset `selectedTokenData`, `selectedTokenPrice`, `selectedTokenDecimals`, and `inputDenomination` to discard stale data.
Applied to files:
src/app/(mobile-ui)/add-money/crypto/page.tsx
🧬 Code graph analysis (2)
src/app/actions/users.ts (2)
src/utils/sentry.utils.ts (1)
fetchWithSentry
(11-89)src/constants/general.consts.ts (1)
PEANUT_API_URL
(45-49)
src/app/(mobile-ui)/add-money/crypto/page.tsx (5)
src/hooks/wallet/useWallet.ts (1)
useWallet
(21-124)src/components/AddMoney/consts/index.ts (2)
CryptoToken
(16-21)DEPOSIT_CRYPTO_TOKENS
(24-61)src/constants/zerodev.consts.ts (1)
PEANUT_WALLET_CHAIN
(18-18)src/components/Global/PeanutLoading/index.tsx (1)
PeanutLoading
(4-19)src/components/AddMoney/views/CryptoDepositQR.view.tsx (1)
CryptoDepositQR
(17-65)
🔇 Additional comments (3)
src/app/actions/users.ts (1)
123-153
: Align error-handling contract with other actions or confirm callers expect thrown errorsMost actions in this module return
{ data?, error? }
instead of throwing. This function throws on error. IfonPaymentCompleted
(and future callers) rely on exceptions, that’s fine; otherwise, consider standardizing to the{ error }
pattern to reduce surprises.Follow-up: confirm the consumer(s) handle thrown errors, or decide to standardize and I can prepare a cohesive refactor across actions.
src/app/(mobile-ui)/add-money/crypto/page.tsx (2)
4-10
: Imports look consistent with usage.Bringing DEPOSIT_CRYPTO_TOKENS into this page matches the new default-selection behavior below. No issues.
34-34
: QR-first default skips risk-gate—confirm this is intended.Setting currentStep to 'qrScreen' displays the QR without requiring a prior risk acknowledgment. Previously, the risk modal was encountered after network selection. If product requires explicit risk acceptance before exposing a deposit address, add a gate here; otherwise, this is fine as a UX simplification.
Would you like me to wire the risk slider acceptance as a prerequisite for the initial QR (e.g., show the modal on first entry unless previously accepted)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (2)
src/app/(mobile-ui)/add-money/crypto/direct/page.tsx (2)
3-13
: Type the Daimo completion event and add a light guardUse the SDK’s typed event instead of any; add a minimal field guard to avoid surprising runtime errors if SDK payload changes. This mirrors an earlier suggestion.
Apply:
import ErrorAlert from '@/components/Global/ErrorAlert' import NavHeader from '@/components/Global/NavHeader' import PeanutLoading from '@/components/Global/PeanutLoading' import TokenAmountInput from '@/components/Global/TokenAmountInput' import DaimoPayButton from '@/components/Global/DaimoPayButton' import DirectSuccessView from '@/components/Payment/Views/Status.payment.view' import { useWallet } from '@/hooks/wallet/useWallet' import { useRouter } from 'next/navigation' import { useState } from 'react' import { trackDaimoDepositTransactionHash } from '@/app/actions/users' +import { DaimoPayEvent } from '@daimo/pay' @@ - const onPaymentCompleted = async (e: any) => { + const onPaymentCompleted = async (e: DaimoPayEvent<'payment_completed'>) => { setIsUpdatingDepositStatus(true) // Save deposit txn hash in the backend to track the user's deposit try { - await trackDaimoDepositTransactionHash(e.txHash, e.payment.source.payerAddress) + const txHash = e?.txHash + const payerAddress = e?.payment?.source?.payerAddress + if (!txHash || !payerAddress) { + console.warn('Missing txHash or payerAddress in Daimo event') + return + } + await trackDaimoDepositTransactionHash(txHash, payerAddress) } catch (error) { - console.error('Error updating depositor address:', error) + console.error('Error tracking Daimo deposit transaction:', error) } finally { setIsUpdatingDepositStatus(false) - setisPaymentSuccess(true) + setIsPaymentSuccess(true) } }Also applies to: 22-34
41-49
: Remove stray brace in key propThe key currently renders as "success-add-money}". Drop the extra brace.
Apply:
- key={`success-add-money}`} + key={`success-add-money`}
🧹 Nitpick comments (7)
src/app/actions/users.ts (4)
123-153
: Make this a function-scoped Server Action to ensure safe client invocationYou’re importing and calling this from a 'use client' page. Adding a function-scoped "use server" directive makes it an explicit Server Action and avoids accidental refactors breaking client calls across Next versions.
Apply:
export async function trackDaimoDepositTransactionHash(txHash: string, payerAddress: string): Promise<{ data?: any }> { + 'use server' try { if (!txHash || !payerAddress) { throw new Error('Missing required fields: txHash and payerAddress') }
129-139
: Auth model: verify whether /users/track-transaction requires JWTWe have a learning that /users/history/deposit does not require JWT (only api-key). This is a different endpoint (/users/track-transaction). If it’s protected, include the JWT; otherwise, keep as-is. Propose optional header-add that won’t fail when JWT is absent.
Apply:
- const response = await fetchWithSentry(`${PEANUT_API_URL}/users/track-transaction`, { + // Optionally attach JWT if available (won't break if endpoint is public) + const cookieStore = cookies() + const jwtToken = (await cookieStore).get('jwt-token')?.value + const response = await fetchWithSentry(`${PEANUT_API_URL}/users/track-transaction`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'api-key': API_KEY, + ...(jwtToken ? { Authorization: `Bearer ${jwtToken}` } : {}), }, body: JSON.stringify({ txHash, payerAddress, }), })If you confirm it’s public, feel free to skip this change. I used the earlier learning to avoid re-introducing unnecessary auth.
141-149
: Align error message with endpoint intent and tolerate non-JSON error bodiesMessage references “save deposit address” but the endpoint is “track-transaction”. Also, response bodies might not always be JSON; wrap parsing on the error path.
Apply:
- const responseJson = await response.json() - if (!response.ok) { - throw new Error( - responseJson.message || - responseJson.error || - `Failed to save deposit address with status: ${response.status}` - ) - } + let responseJson: any + try { + responseJson = await response.json() + } catch { + responseJson = null + } + if (!response.ok) { + const msg = + responseJson?.message || + responseJson?.error || + `Failed to track transaction with status: ${response.status}` + throw new Error(msg) + }
150-152
: Preserve original error instead of re-wrappingRe-wrapping drops stack and cause. Re-throw existing Error and only wrap unknowns.
Apply:
- } catch (e: any) { - throw new Error(e.message || e.toString() || 'An unexpected error occurred') - } + } catch (e: unknown) { + if (e instanceof Error) throw e + throw new Error(String(e) || 'An unexpected error occurred') + }src/app/(mobile-ui)/add-money/crypto/direct/page.tsx (3)
18-19
: Fix setter casing: use setIsPaymentSuccess for consistencyFollow React convention for state setters; avoids inconsistent naming.
Apply:
- const [isPaymentSuccess, setisPaymentSuccess] = useState(false) + const [isPaymentSuccess, setIsPaymentSuccess] = useState(false) @@ - setisPaymentSuccess(true) + setIsPaymentSuccess(true) @@ - if (isPaymentSuccess) { + if (isPaymentSuccess) { return ( <DirectSuccessView - key={`success-add-money}`} + key={`success-add-money`} headerTitle={'Add Money'} type="SEND" currencyAmount={`$${inputTokenAmount}`} isWithdrawFlow={false} redirectTo={'/add-money'} /> ) }Also applies to: 32-33, 40-51
36-38
: Consider full-screen loading overlay during status updateOptional UX tweak: PeanutLoading supports coverFullScreen. Using it prevents accidental clicks during the short sync period.
Apply:
- return <PeanutLoading /> + return <PeanutLoading coverFullScreen />
80-94
: Amount validation in DaimoPayButton uses floats — prefer integer centsYou’re passing minAmount/maxAmount (0.1/4000). The DaimoPayButton currently validates with parseFloat and direct comparisons. To avoid precision edge-cases (e.g., 0.1 vs 0.1000000000001), switch validation there to integer cents.
Apply in src/components/Global/DaimoPayButton/index.tsx:
- const formattedAmount = parseFloat(amount.replace(/,/g, '')) - - // Validate amount range if specified - if (minAmount !== undefined && formattedAmount < minAmount) { - onValidationError?.(`Minimum deposit is $${minAmount.toFixed(2)}.`) - return false - } - - if (maxAmount !== undefined && formattedAmount > maxAmount) { - onValidationError?.(`Maximum deposit is $${maxAmount.toFixed(2)}.`) - return false - } + const cleaned = amount.replace(/,/g, '') + const parsed = Number.parseFloat(cleaned) + const cents = Math.round(parsed * 100) + if (!Number.isFinite(parsed)) { + onValidationError?.('Enter a valid amount.') + return false + } + if (minAmount !== undefined && cents < Math.round(minAmount * 100)) { + onValidationError?.(`Minimum deposit is $${minAmount.toFixed(2)}.`) + return false + } + if (maxAmount !== undefined && cents > Math.round(maxAmount * 100)) { + onValidationError?.(`Maximum deposit is $${maxAmount.toFixed(2)}.`) + return false + }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (3)
src/app/(mobile-ui)/add-money/crypto/direct/page.tsx
(1 hunks)src/app/actions/users.ts
(1 hunks)src/constants/daimo.consts.ts
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/constants/daimo.consts.ts
🧰 Additional context used
🧠 Learnings (12)
📚 Learning: 2025-08-25T09:26:14.537Z
Learnt from: Zishan-7
PR: peanutprotocol/peanut-ui#1104
File: src/app/actions/users.ts:123-139
Timestamp: 2025-08-25T09:26:14.537Z
Learning: The `/users/history/deposit` API endpoint in the Peanut UI codebase does not require JWT authentication, only the API key header is needed.
Applied to files:
src/app/actions/users.ts
📚 Learning: 2025-08-22T07:28:32.260Z
Learnt from: Zishan-7
PR: peanutprotocol/peanut-ui#1104
File: src/components/Payment/PaymentForm/index.tsx:522-545
Timestamp: 2025-08-22T07:28:32.260Z
Learning: In `src/components/Payment/PaymentForm/index.tsx`, the `handleCompleteDaimoPayment` function is only for updating payment status in the backend after a successful Daimo payment. Payment success/failure is handled by Daimo itself, so try/catch error handling and error display are not needed for backend sync failures - users shouldn't see errors if payment succeeded but database update failed.
Applied to files:
src/app/(mobile-ui)/add-money/crypto/direct/page.tsx
📚 Learning: 2024-10-08T20:13:42.967Z
Learnt from: Hugo0
PR: peanutprotocol/peanut-ui#420
File: src/components/Offramp/Offramp.consts.ts:27-28
Timestamp: 2024-10-08T20:13:42.967Z
Learning: In `src/components/Offramp/Offramp.consts.ts`, the `MIN_CASHOUT_LIMIT` is set to $10 because smaller amounts are impractical due to approximately $1 fee per cashout.
Applied to files:
src/app/(mobile-ui)/add-money/crypto/direct/page.tsx
📚 Learning: 2025-08-22T07:25:59.258Z
Learnt from: Zishan-7
PR: peanutprotocol/peanut-ui#1104
File: src/components/Payment/PaymentForm/index.tsx:596-600
Timestamp: 2025-08-22T07:25:59.258Z
Learning: The `TokenAmountInput` component in `src/components/Global/TokenAmountInput/` always returns decimal strings (e.g., "1,234.56"), not base units. When passing these values to external APIs like Daimo's `toUnits` prop, simply stripping commas with `.replace(/,/g, '')` is sufficient.
Applied to files:
src/app/(mobile-ui)/add-money/crypto/direct/page.tsx
📚 Learning: 2024-10-29T14:44:08.745Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#495
File: src/components/Cashout/Components/Initial.view.tsx:194-198
Timestamp: 2024-10-29T14:44:08.745Z
Learning: Using a fixed 6 decimal places for `floorFixed` is acceptable for token amounts in this codebase, even if tokens have varying decimal places.
Applied to files:
src/app/(mobile-ui)/add-money/crypto/direct/page.tsx
📚 Learning: 2024-10-29T12:20:47.207Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#495
File: src/components/Create/Link/Input.view.tsx:244-248
Timestamp: 2024-10-29T12:20:47.207Z
Learning: In the `TokenAmountInput` component within `src/components/Global/TokenAmountInput/index.tsx`, when `balance` is undefined, the `maxValue` prop should be set to an empty string `''`.
Applied to files:
src/app/(mobile-ui)/add-money/crypto/direct/page.tsx
📚 Learning: 2024-10-29T12:19:41.968Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#495
File: src/components/Global/TokenAmountInput/index.tsx:23-30
Timestamp: 2024-10-29T12:19:41.968Z
Learning: In the `TokenAmountInput` component (`src/components/Global/TokenAmountInput/index.tsx`), when the 'Max' button is clicked, we intentionally set the input denomination to 'TOKEN' because we are setting the value as token.
Applied to files:
src/app/(mobile-ui)/add-money/crypto/direct/page.tsx
📚 Learning: 2024-10-07T15:28:25.280Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#422
File: src/components/Request/Pay/Views/Initial.view.tsx:76-78
Timestamp: 2024-10-07T15:28:25.280Z
Learning: In `src/components/Request/Pay/Views/Initial.view.tsx`, both `txFee` and `utils.formatTokenAmount(estimatedGasCost, 3)` return strings, ensuring consistent return types for `calculatedFee`.
Applied to files:
src/app/(mobile-ui)/add-money/crypto/direct/page.tsx
📚 Learning: 2024-12-02T17:21:45.515Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#551
File: src/context/walletContext/walletContext.tsx:87-88
Timestamp: 2024-12-02T17:21:45.515Z
Learning: When converting `totalBalance` (in USD) to a `BigInt` balance in `src/context/walletContext/walletContext.tsx`, multiplying by `1e6` is intentional to maintain compatibility with USDC's 6 decimal places. The application displays only 2 decimal places, so this level of precision is sufficient.
Applied to files:
src/app/(mobile-ui)/add-money/crypto/direct/page.tsx
📚 Learning: 2025-07-07T20:22:11.092Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#958
File: src/app/actions/tokens.ts:266-266
Timestamp: 2025-07-07T20:22:11.092Z
Learning: In `src/app/actions/tokens.ts`, within the `fetchWalletBalances` function, using the non-null assertion operator `!` on `process.env.MOBULA_API_KEY!` is intentional and correct, and should not be flagged for replacement with explicit validation.
Applied to files:
src/app/(mobile-ui)/add-money/crypto/direct/page.tsx
📚 Learning: 2025-05-22T15:38:48.586Z
Learnt from: kushagrasarathe
PR: peanutprotocol/peanut-ui#869
File: src/app/(mobile-ui)/withdraw/page.tsx:82-88
Timestamp: 2025-05-22T15:38:48.586Z
Learning: The country-specific withdrawal route exists at src/app/(mobile-ui)/withdraw/[...country]/page.tsx and renders the AddWithdrawCountriesList component with flow="withdraw".
Applied to files:
src/app/(mobile-ui)/add-money/crypto/direct/page.tsx
📚 Learning: 2024-10-23T09:38:27.670Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#469
File: src/app/request/pay/page.tsx:32-64
Timestamp: 2024-10-23T09:38:27.670Z
Learning: In `src/app/request/pay/page.tsx`, if `linkRes` is not OK in the `generateMetadata` function, the desired behavior is to use the standard title and preview image without throwing an error.
Applied to files:
src/app/(mobile-ui)/add-money/crypto/direct/page.tsx
🧬 Code graph analysis (2)
src/app/actions/users.ts (2)
src/utils/sentry.utils.ts (1)
fetchWithSentry
(11-89)src/constants/general.consts.ts (1)
PEANUT_API_URL
(45-49)
src/app/(mobile-ui)/add-money/crypto/direct/page.tsx (4)
src/hooks/wallet/useWallet.ts (1)
useWallet
(21-124)src/app/actions/users.ts (1)
trackDaimoDepositTransactionHash
(123-153)src/components/Global/PeanutLoading/index.tsx (1)
PeanutLoading
(4-19)src/components/Global/DaimoPayButton/index.tsx (1)
DaimoPayButton
(46-137)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Deploy-Preview
🔇 Additional comments (1)
src/app/(mobile-ui)/add-money/crypto/direct/page.tsx (1)
12-12
: Confirm importing a Server Action into a Client component (and ensure it’s marked correctly)Directly calling an action from a 'use client' file is fine if the function is an explicit Server Action. Please ensure the function itself has a "use server" directive (see my comment in users.ts). If your Next version or config balks at module-level "use server", switch to an internal API route.
Would you prefer I draft a small /api/users/track-transaction route so this page performs a simple fetch without relying on Server Actions?
Contributes to TASK-13598