Skip to content

Conversation

Zishan-7
Copy link
Contributor

Contributes to TASK-13598

Copy link

Integrate Daimo pay

Copy link

vercel bot commented Aug 18, 2025

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

Project Deployment Preview Comments Updated (UTC)
peanut-wallet Ready Ready Preview Comment Aug 25, 2025 3:36pm

Copy link
Contributor

coderabbitai bot commented Aug 18, 2025

Walkthrough

Adds 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

Cohort / File(s) Summary
Package & Env
package.json, .env.example
Added @daimo/pay; bumped @wagmi/core, wagmi, viem; updated resolutions (including ox); added NEXT_PUBLIC_DAIMO_APP_ID to example env.
Provider / Networks / Theme
src/config/wagmi.config.tsx, src/constants/daimo.consts.ts
Wrapped app with DaimoPayProvider using DAIMO_THEME; added linea, worldchain, celo to exported networks; added DAIMO_THEME constant.
Daimo Button & Exports
src/components/Global/DaimoPayButton/index.tsx, src/components/index.ts
New DaimoPayButton component wrapping Daimo SDK Custom flow; exported via components index.
Payment Hook & Form Integration
src/hooks/usePaymentInitiator.ts, src/components/Payment/PaymentForm/index.tsx
Added initiateDaimoPayment, completeDaimoPayment, and exposed setLoadingStep in usePaymentInitiator; integrated Daimo lifecycle into PaymentForm with new callbacks, error handling, and gating.
Add-Money Crypto Pages
src/app/(mobile-ui)/add-money/crypto/page.tsx, src/app/(mobile-ui)/add-money/crypto/direct/page.tsx
Reworked flows to prefer QR/wallet-backed Daimo flow: QR-first default, revised defaults for token/network selection, removed Redux reset/pre-fill logic in direct flow, added local wallet state and success view, improved loading/back-nav behavior.
Drawer & Selection UIs
src/components/AddMoney/components/CryptoMethodDrawer.tsx, src/components/AddWithdraw/AddWithdrawRouterView.tsx, src/components/AddWithdraw/AddWithdrawCountriesList.tsx, src/components/Common/ActionList.tsx
Added CryptoMethodDrawer with risk modal and navigation; intercept crypto-add to open drawer in add flows; rendered drawer in router and countries list; filtered ACTION_METHODS for request flow.
Token Selector & Assets
src/components/Global/TokenSelector/TokenSelector.consts.ts, src/assets/chains/index.ts
Added network exclusions in token selector constants; exported OTHER_CHAINS_ICON.
API Action
src/app/actions/users.ts
Added trackDaimoDepositTransactionHash(txHash, payerAddress) which POSTs transaction/payer to /users/track-transaction.
Misc HTML
public/game/peanut-game.html
Minor syntactic refactor combining assignments with comma operator; no behavior change.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • kushagrasarathe
  • jjramirezn

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 Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/integrate-daimo

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

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

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

Support

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

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

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

CodeRabbit Configuration File (.coderabbit.yaml)

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

Status, Documentation and Community

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🔭 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 in node_modules/@daimo/pay. Update your package.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 via source.path

Before switching to source.path, we discovered the Trust Wallet entry in CRYPTO_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 centralized path 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 object

Lock 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: setisPaymentSuccesssetIsPaymentSuccess 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 or show() 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 to toUnits

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 and isAmountValid logic at every toUnits 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.

📥 Commits

Reviewing files that changed from the base of the PR and between d4bcddf and 8746b07.

⛔ 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 fine

Adding Daimo Pay SDK aligns with the new provider usage. No issues spotted here.

src/components/AddWithdraw/AddWithdrawRouterView.tsx (1)

228-235: Verify crypto route existence

I couldn’t locate any page.tsx under the crypto 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: LGTM

The new initial state aligns with the redesigned flow and avoids stale defaults.


71-73: Back from QR to source selection for exchanges: LGTM

This simplifies navigation and matches the updated UX.

src/config/wagmi.config.tsx (1)

82-84: Provider composition looks correct

Wrapping 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 both toAddress and toToken with getAddress is solid and helps prevent checksum/address issues.

@Zishan-7 Zishan-7 changed the title DEAFT : Integrate Daimo Pay DRAFT : Integrate Daimo Pay Aug 18, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (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.

📥 Commits

Reviewing files that changed from the base of the PR and between 8746b07 and b1f31bf.

⛔ 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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
public/game/peanut-game.html (1)

1004-1106: Prefer explicit statements over nested comma-expression for clarity

Wrapping 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.

📥 Commits

Reviewing files that changed from the base of the PR and between b1f31bf and 77a5d03.

📒 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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Nitpick comments (1)
src/components/Payment/PaymentForm/index.tsx (1)

589-599: App ID fallback throws at render time — consider early validation

Throwing 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 77a5d03 and fc2322e.

📒 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 needs

Providing 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 flows

Commenting 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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (3)
src/components/Payment/PaymentForm/index.tsx (3)

600-603: Avoid unhandled promise in onPaymentCompleted

Prefixing 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 UI

If 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 address

getAddress(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 tree

Throwing 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 flag

Long 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.

📥 Commits

Reviewing files that changed from the base of the PR and between fc2322e and 1047c84.

📒 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 prop

Passing a human-readable decimal string without commas matches the SDK’s expected format. LGTM.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (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 the return.

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 selectedSource

Instead of indexing into CRYPTO_EXCHANGES by position—which can easily break if the array order changes—lookup the “other-exchanges” source by its id (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 a string, 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 1047c84 and 7fe6042.

⛔ 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 chains

Confirmed that:

  • src/assets/index.ts includes export * from './chains'.
  • src/assets/chains/index.ts exports OTHER_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 asset

Your next.config.js currently only permits HTTP hosts via a wildcard. Since the USDC logo is served over HTTPS from assets.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 confirmed

The /withdraw/crypto route is implemented under src/app/(mobile-ui)/withdraw/crypto/, so using router.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 with

id: 'crypto',
type: 'crypto',
path: 'crypto',

in src/components/AddMoney/consts/index.ts, and that recent methods carry this same id when stored and compared in handleMethodSelected (method.id === 'crypto').
Additionally, the “all methods” view doesn’t rely on method.id to open the drawer; it uses CountryList’s onCryptoClick 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 exists

Verified that the page component for the direct wallet route is present at src/app/(mobile-ui)/add-money/crypto/direct/page.tsx, so the router.push('/add-money/crypto/direct') navigation is valid.

• Location to note:

  • src/app/(mobile-ui)/add-money/crypto/direct/page.tsx

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (3)
src/components/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.

📥 Commits

Reviewing files that changed from the base of the PR and between 31557c2 and 0b3f720.

📒 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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ 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 if crypto-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 named setisDrawerOpen (lowercase “i”) but you’re passing setIsDrawerOpen 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 via next/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 the open 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 0b3f720 and 5cdad60.

📒 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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ 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 webhook

A 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 prop

The 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.example

This 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 address

DaimoPayButton 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 explicit readonly number[] type is redundant. Also, a Set<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 handling

To simplify the exclusion check and avoid unnecessary array scans, convert networksToExclude to a Set<number> and then:

  • Use Set.has(...) instead of Array.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 of worldchain is valid and won’t break the build.
  • src/config/wagmi.config.tsx already exports a networks array that includes linea, worldchain, and celo.

Short‐term: safe to keep your direct imports from viem/chains.
Mid‐term: optional refactor – you can export a TOKEN_SELECTOR_EXCLUDED_CHAIN_IDS (e.g. [linea.id, worldchain.id, celo.id]) from wagmi.config.tsx for reuse in TokenSelector.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 barrel

export * 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 API

Avoid 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: setIsPaymentSuccess

The 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 imports

Required 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'>) => void

If 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 path

If 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: useDaimoPayUI

It’s not referenced in this file and will fail lint/build under noUnusedLocals.


559-582: Validate Daimo payerAddress and type the completion handler param

Don’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.

📥 Commits

Reviewing files that changed from the base of the PR and between 5cdad60 and d4939b7.

📒 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 path

Conditionally showing “Send” vs “Add funds” CTAs based on balance and wallet state looks solid.


907-909: Surfacing Daimo validation errors is a nice touch

Hooking onValidationError into ErrorAlert keeps the UX coherent with your existing error pattern.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ 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.tsx

If 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 required

Note: 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 to text() 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 stack

Re-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 early

A 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: consider recordDeposit or saveDepositRecord

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 selection

Using 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.

📥 Commits

Reviewing files that changed from the base of the PR and between d4939b7 and 92e9063.

📒 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 errors

Most actions in this module return { data?, error? } instead of throwing. This function throws on error. If onPaymentCompleted (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)?

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
src/app/(mobile-ui)/add-money/crypto/direct/page.tsx (2)

3-13: Type the Daimo completion event and add a light guard

Use 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 prop

The 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 invocation

You’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 JWT

We 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 bodies

Message 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-wrapping

Re-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 consistency

Follow 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 update

Optional 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 cents

You’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.

📥 Commits

Reviewing files that changed from the base of the PR and between 92e9063 and 7a21e90.

📒 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?

@Zishan-7 Zishan-7 merged commit b8e0b30 into peanut-wallet-dev Aug 25, 2025
6 of 7 checks passed
@Zishan-7 Zishan-7 deleted the feat/integrate-daimo branch August 25, 2025 15:41
@coderabbitai coderabbitai bot mentioned this pull request Aug 26, 2025
This was referenced Sep 3, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants