Skip to content

Comments

PRO-3781-Add_Cash#447

Merged
vignesha22 merged 27 commits intostagingfrom
feature/PRO-3781-Add_Cash
Nov 11, 2025
Merged

PRO-3781-Add_Cash#447
vignesha22 merged 27 commits intostagingfrom
feature/PRO-3781-Add_Cash

Conversation

@vignesha22
Copy link
Contributor

@vignesha22 vignesha22 commented Nov 5, 2025

Description

  • Added Add Cash button and opened the url received on a new tab
  • Added setting values to store on localStorage
  • Fixed design for mobile by removing padding

How Has This Been Tested?

  • Tested Locally

Screenshots (if appropriate):

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Summary by CodeRabbit

  • New Features

    • "Add Cash" Coinbase on‑ramp flow with loading state; cross‑chain sell support including bridge-aware previews and portfolio-aware gas estimation.
  • Bug Fixes

    • Better validation, error handling and fallbacks for on‑ramp; per‑chain transaction/hash selection fixed; initial settlement chain persisted to localStorage.
  • UI

    • Wallet actions reorganized into responsive layout, conditional wallet‑connect controls, loading indicators and new iconography; minor spacing tweaks.
  • Chores

    • Added on‑ramp proxy endpoint and dev tooling, TypeScript config updates, and dependency bumps.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 5, 2025

Walkthrough

Adds a Coinbase onramp "Add Cash" flow and UI; persists Pulse HomeScreen settings to localStorage; introduces cross‑chain sell/bridge logic and portfolio propagation; switches per‑chain userOpHash handling; adds a proxied Coinbase Pages Function, functions TypeScript config, Vite proxy and dev tooling.

Changes

Cohort / File(s) Summary
Add Cash flow & wallet UI
src/apps/pillarx-app/components/WalletPortfolioButtons/WalletPortfolioButtons.tsx
Adds isAddCashLoading state and handleAddCash flow (accountAddress validation, fetch onramp JWT from backend, best‑effort client IP, POST to proxied /api/coinbase, open onramp URL with loading/error handling); renames wallet exposure to accountAddress; conditionally renders WalletConnectDropdown; adds FrameIcon and TailSpin spinner UI.
Coinbase proxy function (Pages Function)
functions/api/coinbase/platform/v2/onramp/sessions.js
New Cloudflare Pages Function: handles OPTIONS preflight; on POST validates Authorization, forwards JSON body to Coinbase onramp sessions, returns Coinbase JSON with CORS headers; consistent error handling and 500 responses on upstream failures.
Pulse HomeScreen: persistence & wiring
src/apps/pulse/components/App/HomeScreen.tsx
Loads/saves customBuyAmounts, customSellAmounts, and selectedChainIdForSettlement to/from localStorage; coordinates initial settlement chainId with hasInitializedChainIdRef; passes portfolioTokens into buy/sell/preview flows; minor layout class removal and fallback for maxStableCoinBalance.
Pulse sell/preview & cross‑chain relay
src/apps/pulse/components/Sell/PreviewSell.tsx, src/apps/pulse/components/Sell/Sell.tsx, src/apps/pulse/hooks/useRelaySell.ts, src/apps/pulse/hooks/useGasEstimation.ts
Adds cross‑chain support and bridge path: new getBestSellOfferWithBridge and buildSellTransactionWithBridge; propagates userPortfolio through PreviewSell, gas estimation and execute flows; branches between same‑chain and bridge flows for offer selection and tx building; updates execute and offer refresh to read chainGroups keyed by chainId. Public API additions: getBestSellOfferWithBridge, buildSellTransactionWithBridge; useGasEstimation accepts userPortfolio.
SendModal: per‑chain batch/hash handling
src/components/BottomMenuModal/SendModal/SendModalBatchesTabView.tsx, src/components/BottomMenuModal/SendModal/SendModalTokensTabView.tsx
Switches userOpHash lookup from array index to object keyed by chainId (chainGroups[chainId]); derives/propagates chainIdForTxHash for WalletConnect, polling and subsequent flows to align per‑chain batching.
TypeScript & build/dev config
vite.config.js, tsconfig.json, functions/tsconfig.json, package.json
Adds Vite proxy for /api/coinbasehttps://api.cdp.coinbase.com; excludes functions from root TS project and adds functions/tsconfig.json; adds dev:functions script and wrangler dev dependency; bumps @etherspot/transaction-kit; adds rollup external for /functions/**.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI as WalletPortfolioButtons
    participant Backend as App Backend (JWT)
    participant Proxy as /api/coinbase
    participant Coinbase
    participant Browser

    User->>UI: Click "Add Cash"
    UI->>UI: Validate accountAddress
    alt valid
        UI->>Backend: GET onramp JWT
        Backend-->>UI: JWT
        UI->>UI: Resolve client IP (best-effort)
        UI->>Proxy: POST /api/coinbase (Auth: JWT, body includes destinationAddress, currencies, clientIp)
        Proxy->>Coinbase: Forward POST
        Coinbase-->>Proxy: { onramp_url }
        Proxy-->>UI: { onramp_url }
        UI->>Browser: open onramp_url in new tab
    else invalid
        UI->>User: show validation/error
    end
Loading
sequenceDiagram
    participant UI as Sell UI
    participant Preview as PreviewSell
    participant Hook as useRelaySell / useGasEstimation
    participant RelaySDK
    participant Chain as DestinationChain

    UI->>Preview: Request sell preview (sellToken, toChainId, userPortfolio)
    Preview->>Hook: getBestSellOffer or getBestSellOfferWithBridge
    Hook->>RelaySDK: buildSellTransactions or buildSellTransactionWithBridge (passes userPortfolio)
    RelaySDK-->>Hook: transactions + fee estimates
    Hook-->>Preview: SellOffer + gas estimate
    User->>UI: Confirm sell
    UI->>Hook: executeSell(..., userPortfolio)
    Hook->>RelaySDK: send transactions (returns sentBatch with chainGroups keyed by chainId)
    Hook-->>UI: userOpHash per chain for polling
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Areas needing extra attention:
    • Multi‑stage bridge transaction building and fee/approval handling in useRelaySell (buildSellTransactionWithBridge, getBestSellOfferWithBridge).
    • Correct propagation and usage of userPortfolio through PreviewSell, useGasEstimation, and execute flows.
    • Consistency of chainGroups keying (object keyed by chainId) across send/poll/WalletConnect code paths.
    • CORS/Authorization handling and error paths in functions/api/coinbase/.../sessions.js.
    • Project/tsconfig boundaries after excluding functions and adding functions/tsconfig.json.

Possibly related PRs

Suggested reviewers

  • IAmKio
  • RanaBug

Poem

🐰 I hopped through code with nimble feet,

Opened onramps where wallets and web can meet.
I bridged some chains and stored a tiny key,
Pushed a button — transactions roam free.
🥕✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'PRO-3781-Add_Cash' directly references the PR objective and primary feature (adding an Add Cash button). It is specific, concise, and clearly indicates the main change.
Description check ✅ Passed The description covers the key changes (Add Cash button, localStorage persistence, mobile design fix) and includes testing notes, but the type classification appears inconsistent with the actual changes being primarily a new feature rather than a bug fix.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/PRO-3781-Add_Cash

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f5cd39a and e041f21.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (2)
  • src/apps/pillarx-app/components/WalletPortfolioButtons/WalletPortfolioButtons.tsx (2 hunks)
  • vite.config.js (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • vite.config.js
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: RanaBug
Repo: pillarwallet/x PR: 391
File: src/apps/pulse/components/Sell/Sell.tsx:113-130
Timestamp: 2025-09-09T12:40:15.629Z
Learning: In the Pulse app Sell component, when a user changes/switches tokens, the input amount automatically resets to 0, which means liquidity validation state doesn't become stale when tokens change.
📚 Learning: 2025-11-04T14:34:00.373Z
Learnt from: RanaBug
Repo: pillarwallet/x PR: 445
File: src/containers/Authorized.tsx:63-77
Timestamp: 2025-11-04T14:34:00.373Z
Learning: In src/containers/Authorized.tsx, the tempKit useEffect intentionally excludes chainId from its dependency array because the kit is used by useWalletModeVerification to check wallet mode across all supported chains, regardless of the currently selected chainId. The chainId parameter is only used for initial kit configuration and does not affect the multi-chain verification results.

Applied to files:

  • src/apps/pillarx-app/components/WalletPortfolioButtons/WalletPortfolioButtons.tsx
📚 Learning: 2025-08-20T09:14:16.888Z
Learnt from: RanaBug
Repo: pillarwallet/x PR: 374
File: src/apps/pillarx-app/index.tsx:12-12
Timestamp: 2025-08-20T09:14:16.888Z
Learning: In this codebase, Transaction Kit providers are set up at the container level (src/containers/Authorized.tsx), not at individual app component levels. App components like src/apps/pillarx-app/index.tsx are children that consume the context through the provider tree.

Applied to files:

  • src/apps/pillarx-app/components/WalletPortfolioButtons/WalletPortfolioButtons.tsx
🧬 Code graph analysis (1)
src/apps/pillarx-app/components/WalletPortfolioButtons/WalletPortfolioButtons.tsx (2)
src/hooks/useEIP7702Upgrade.tsx (1)
  • useEIP7702Upgrade (31-188)
src/apps/pillarx-app/reducer/WalletPortfolioSlice.ts (1)
  • setIsReceiveModalOpen (134-136)
⏰ 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). (3)
  • GitHub Check: lint
  • GitHub Check: unit-tests
  • GitHub Check: build
🔇 Additional comments (2)
src/apps/pillarx-app/components/WalletPortfolioButtons/WalletPortfolioButtons.tsx (2)

48-68: Good: Early validation with clear guards.

The wallet address validation correctly checks for both presence and format using viem's isAddress helper, with appropriate early returns to prevent downstream errors.


192-209: Good: Loading state implementation.

The Add Cash button properly disables during async operations, shows a loading spinner, and prevents multiple simultaneous clicks. The disabled state styling and conditional icon rendering provide clear feedback to users.


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

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

@github-actions github-actions bot temporarily deployed to Preview (feature/PRO-3781-Add_Cash) November 5, 2025 15:03 Inactive
@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Nov 5, 2025

Deploying x with  Cloudflare Pages  Cloudflare Pages

Latest commit: e041f21
Status: ✅  Deploy successful!
Preview URL: https://cae431c2.x-e62.pages.dev
Branch Preview URL: https://feature-pro-3781-add-cash.x-e62.pages.dev

View logs

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

🧹 Nitpick comments (2)
src/apps/pulse/components/App/HomeScreen.tsx (2)

226-230: Remove debug console.logs.

These debug logs should be removed before merging to production.

Apply this diff:

   useEffect(() => {
-    console.log('Max stable coin balance chain ID:', maxStableCoinBalance);
     const storedChainId = localStorage.getItem(
       'pulse_selectedChainIdForSettlement'
     );
-    console.log('Stored chain ID for settlement:', storedChainId);

265-287: LGTM with optional suggestion.

The localStorage persistence logic is correct and includes appropriate guards. These three effects could optionally be combined into a single effect for slightly better performance, but the current implementation is clear and maintainable.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1b463ca and 8b1e4c9.

⛔ Files ignored due to path filters (2)
  • src/apps/pulse/components/App/tests/__snapshots__/AppWrapper.test.tsx.snap is excluded by !**/*.snap
  • src/apps/pulse/components/App/tests/__snapshots__/HomeScreen.test.tsx.snap is excluded by !**/*.snap
📒 Files selected for processing (2)
  • src/apps/pillarx-app/components/WalletPortfolioButtons/WalletPortfolioButtons.tsx (3 hunks)
  • src/apps/pulse/components/App/HomeScreen.tsx (7 hunks)
🧰 Additional context used
🧠 Learnings (6)
📓 Common learnings
Learnt from: RanaBug
Repo: pillarwallet/x PR: 391
File: src/apps/pulse/components/Sell/Sell.tsx:113-130
Timestamp: 2025-09-09T12:40:15.629Z
Learning: In the Pulse app Sell component, when a user changes/switches tokens, the input amount automatically resets to 0, which means liquidity validation state doesn't become stale when tokens change.
📚 Learning: 2025-11-04T14:34:00.373Z
Learnt from: RanaBug
Repo: pillarwallet/x PR: 445
File: src/containers/Authorized.tsx:63-77
Timestamp: 2025-11-04T14:34:00.373Z
Learning: In src/containers/Authorized.tsx, the tempKit useEffect intentionally excludes chainId from its dependency array because the kit is used by useWalletModeVerification to check wallet mode across all supported chains, regardless of the currently selected chainId. The chainId parameter is only used for initial kit configuration and does not affect the multi-chain verification results.

Applied to files:

  • src/apps/pillarx-app/components/WalletPortfolioButtons/WalletPortfolioButtons.tsx
  • src/apps/pulse/components/App/HomeScreen.tsx
📚 Learning: 2025-08-20T09:14:16.888Z
Learnt from: RanaBug
Repo: pillarwallet/x PR: 374
File: src/apps/pillarx-app/index.tsx:12-12
Timestamp: 2025-08-20T09:14:16.888Z
Learning: In this codebase, Transaction Kit providers are set up at the container level (src/containers/Authorized.tsx), not at individual app component levels. App components like src/apps/pillarx-app/index.tsx are children that consume the context through the provider tree.

Applied to files:

  • src/apps/pillarx-app/components/WalletPortfolioButtons/WalletPortfolioButtons.tsx
📚 Learning: 2025-09-09T12:40:15.629Z
Learnt from: RanaBug
Repo: pillarwallet/x PR: 391
File: src/apps/pulse/components/Sell/Sell.tsx:113-130
Timestamp: 2025-09-09T12:40:15.629Z
Learning: In the Pulse app Sell component, when a user changes/switches tokens, the input amount automatically resets to 0, which means liquidity validation state doesn't become stale when tokens change.

Applied to files:

  • src/apps/pulse/components/App/HomeScreen.tsx
📚 Learning: 2025-08-12T07:42:24.656Z
Learnt from: IAmKio
Repo: pillarwallet/x PR: 351
File: src/apps/pulse/utils/intent.ts:44-53
Timestamp: 2025-08-12T07:42:24.656Z
Learning: In the Pulse app's intent utilities (src/apps/pulse/utils/intent.ts), the team has chosen to use floating-point arithmetic for token amount calculations despite potential precision issues, accepting JavaScript's decimal place limitations as a valid trade-off for their use case.

Applied to files:

  • src/apps/pulse/components/App/HomeScreen.tsx
📚 Learning: 2025-03-28T09:22:22.712Z
Learnt from: RanaBug
Repo: pillarwallet/x PR: 275
File: src/apps/the-exchange/components/DropdownTokensList/DropdownTokenList.tsx:180-195
Timestamp: 2025-03-28T09:22:22.712Z
Learning: In the Exchange app, `swapTokenList` and `receiveTokenList` are derived from `searchTokenResult` when search is active, so including `searchToken` in the useEffect dependency array that uses these lists would be redundant as the lists will update when search results change.

Applied to files:

  • src/apps/pulse/components/App/HomeScreen.tsx
🧬 Code graph analysis (1)
src/apps/pillarx-app/components/WalletPortfolioButtons/WalletPortfolioButtons.tsx (2)
src/apps/pillarx-app/hooks/useReducerHooks.tsx (1)
  • useAppDispatch (5-5)
src/hooks/useEIP7702Upgrade.tsx (1)
  • useEIP7702Upgrade (31-188)
🪛 Biome (2.1.2)
src/apps/pillarx-app/components/WalletPortfolioButtons/WalletPortfolioButtons.tsx

[error] 19-19: Shouldn't redeclare 'useTransactionKit'. Consider to delete it or rename it.

'useTransactionKit' is defined here:

(lint/suspicious/noRedeclare)


[error] 121-121: This code is unreachable

(lint/correctness/noUnreachable)

⏰ 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). (3)
  • GitHub Check: unit-tests
  • GitHub Check: lint
  • GitHub Check: build
🔇 Additional comments (6)
src/apps/pillarx-app/components/WalletPortfolioButtons/WalletPortfolioButtons.tsx (1)

145-153: LGTM!

The Add Cash button is properly integrated and consistent with the existing button styling and structure.

src/apps/pulse/components/App/HomeScreen.tsx (5)

121-128: LGTM!

The localStorage initialization for custom amounts includes sensible defaults and proper error handling via JSON parsing in a try-catch context (implicit in the arrow function).


132-135: LGTM!

The localStorage initialization for settlement chain ID includes a sensible default and will be updated by the useEffect based on max stable balance.


1046-1048: Clarify the fallback balance value.

The fallback balance: 2 seems arbitrary. Should this be 0 to represent no balance, or is there a specific reason for using 2? Please clarify the intent behind this value.


956-956: LGTM!

The padding removal aligns with the PR objective to fix mobile design.


110-110: No issues found—optional type handling is correct.

The type change to optional is properly managed throughout the codebase:

  • In HomeScreen, all accesses use optional chaining (maxStableCoinBalance?.chainId) or conditional guards before accessing properties
  • The nullish coalescing fallback at lines 1046-1048 (maxStableCoinBalance ?? { chainId: 1, balance: 2 }) ensures the Buy component always receives a defined object
  • Buy component's prop type contract (non-optional) is honored—it receives a concrete object, never undefined
  • No unsafe property access exists anywhere in the codebase

@github-actions github-actions bot temporarily deployed to Preview (feature/PRO-3781-Add_Cash) November 5, 2025 15:09 Inactive
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/apps/pillarx-app/components/WalletPortfolioButtons/WalletPortfolioButtons.tsx (1)

115-147: Still need UX feedback & loading guard for Add Cash.

Lines 115-147 still leave the happy-path only; when anything fails the user gets no feedback, and the button stays enabled while the network stack is busy. This was flagged previously and is still outstanding: we need a loading state to prevent duplicate session creation and a surfaced toast/modal so the user understands the failure. Please add a scoped isAddCashLoading flag (disable the button & show spinner copy) and route errors through the app’s toast mechanism instead of silently failing.

Apply this diff:

+  const [isAddCashLoading, setIsAddCashLoading] = useState(false);
+
   const handleAddCash = async () => {
+    if (isAddCashLoading) return;
+    setIsAddCashLoading(true);
     try {
       // Validate wallet address
       if (!accountAddress) {
         throw new Error('Wallet address is not available');
       }
@@
-      window.open(onrampUrl, '_blank', 'noopener,noreferrer');
+      window.open(onrampUrl, '_blank', 'noopener,noreferrer');
     } catch (error) {
-      // TODO: Replace with toast notification or proper error UI
-      // For now, silently fail - user can try again
-      // Error is logged for debugging in development
-      if (process.env.NODE_ENV === 'development') {
-        // eslint-disable-next-line no-console
-        console.error('Error opening add cash URL:', error);
-      }
+      showToast({
+        type: 'error',
+        title: 'Add Cash failed',
+        message:
+          error instanceof Error ? error.message : 'Please try again in a moment.',
+      });
+      if (process.env.NODE_ENV === 'development') {
+        // eslint-disable-next-line no-console
+        console.error('Error opening add cash URL:', error);
+      }
+    } finally {
+      setIsAddCashLoading(false);
     }
   };
@@
-      <button
+      <button
         type="button"
         className="flex py-[9px] px-3 w-fit h-fit items-center justify-center border-x-2 border-t-2 border-b-4 rounded-[10px] border-[#121116] cursor-pointer"
-        onClick={() => handleAddCash()}
+        onClick={handleAddCash}
+        disabled={isAddCashLoading}
       >
         <div className="flex gap-2 items-center justify-center rounded-lg cursor-pointer">
-          <BodySmall>Add Cash</BodySmall>
+          <BodySmall>{isAddCashLoading ? 'Opening…' : 'Add Cash'}</BodySmall>
         </div>
       </button>
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8b1e4c9 and 0c73cc4.

📒 Files selected for processing (2)
  • src/apps/pillarx-app/components/WalletPortfolioButtons/WalletPortfolioButtons.tsx (4 hunks)
  • src/apps/pulse/components/App/HomeScreen.tsx (7 hunks)
🧰 Additional context used
🧠 Learnings (6)
📓 Common learnings
Learnt from: RanaBug
Repo: pillarwallet/x PR: 391
File: src/apps/pulse/components/Sell/Sell.tsx:113-130
Timestamp: 2025-09-09T12:40:15.629Z
Learning: In the Pulse app Sell component, when a user changes/switches tokens, the input amount automatically resets to 0, which means liquidity validation state doesn't become stale when tokens change.
📚 Learning: 2025-08-20T09:14:16.888Z
Learnt from: RanaBug
Repo: pillarwallet/x PR: 374
File: src/apps/pillarx-app/index.tsx:12-12
Timestamp: 2025-08-20T09:14:16.888Z
Learning: In this codebase, Transaction Kit providers are set up at the container level (src/containers/Authorized.tsx), not at individual app component levels. App components like src/apps/pillarx-app/index.tsx are children that consume the context through the provider tree.

Applied to files:

  • src/apps/pillarx-app/components/WalletPortfolioButtons/WalletPortfolioButtons.tsx
📚 Learning: 2025-11-04T14:34:00.373Z
Learnt from: RanaBug
Repo: pillarwallet/x PR: 445
File: src/containers/Authorized.tsx:63-77
Timestamp: 2025-11-04T14:34:00.373Z
Learning: In src/containers/Authorized.tsx, the tempKit useEffect intentionally excludes chainId from its dependency array because the kit is used by useWalletModeVerification to check wallet mode across all supported chains, regardless of the currently selected chainId. The chainId parameter is only used for initial kit configuration and does not affect the multi-chain verification results.

Applied to files:

  • src/apps/pillarx-app/components/WalletPortfolioButtons/WalletPortfolioButtons.tsx
  • src/apps/pulse/components/App/HomeScreen.tsx
📚 Learning: 2025-09-09T12:40:15.629Z
Learnt from: RanaBug
Repo: pillarwallet/x PR: 391
File: src/apps/pulse/components/Sell/Sell.tsx:113-130
Timestamp: 2025-09-09T12:40:15.629Z
Learning: In the Pulse app Sell component, when a user changes/switches tokens, the input amount automatically resets to 0, which means liquidity validation state doesn't become stale when tokens change.

Applied to files:

  • src/apps/pulse/components/App/HomeScreen.tsx
📚 Learning: 2025-08-12T07:42:24.656Z
Learnt from: IAmKio
Repo: pillarwallet/x PR: 351
File: src/apps/pulse/utils/intent.ts:44-53
Timestamp: 2025-08-12T07:42:24.656Z
Learning: In the Pulse app's intent utilities (src/apps/pulse/utils/intent.ts), the team has chosen to use floating-point arithmetic for token amount calculations despite potential precision issues, accepting JavaScript's decimal place limitations as a valid trade-off for their use case.

Applied to files:

  • src/apps/pulse/components/App/HomeScreen.tsx
📚 Learning: 2025-03-28T09:22:22.712Z
Learnt from: RanaBug
Repo: pillarwallet/x PR: 275
File: src/apps/the-exchange/components/DropdownTokensList/DropdownTokenList.tsx:180-195
Timestamp: 2025-03-28T09:22:22.712Z
Learning: In the Exchange app, `swapTokenList` and `receiveTokenList` are derived from `searchTokenResult` when search is active, so including `searchToken` in the useEffect dependency array that uses these lists would be redundant as the lists will update when search results change.

Applied to files:

  • src/apps/pulse/components/App/HomeScreen.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). (3)
  • GitHub Check: unit-tests
  • GitHub Check: lint
  • GitHub Check: build

Comment on lines +121 to 136
const [customBuyAmounts, setCustomBuyAmounts] = useState<string[]>(() => {
const stored = localStorage.getItem('pulse_customBuyAmounts');
return stored ? JSON.parse(stored) : ['10', '20', '50', '100'];
});
const [customSellAmounts, setCustomSellAmounts] = useState<string[]>(() => {
const stored = localStorage.getItem('pulse_customSellAmounts');
return stored ? JSON.parse(stored) : ['10%', '25%', '50%', '75%'];
});
const [displaySettingsMenu, setDisplaySettingsMenu] =
useState<boolean>(false);
const [selectedChainIdForSettlement, setSelectedChainIdForSettlement] =
useState<number>(maxStableCoinBalance.chainId);
useState<number>(() => {
const stored = localStorage.getItem('pulse_selectedChainIdForSettlement');
return stored ? parseInt(stored, 10) : 1; // Will be updated by useEffect once maxStableCoinBalance is calculated
});
const [payingTokens, setPayingTokens] = useState<PayingToken[]>([]);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Defensively parse persisted settings to avoid crashes.

Lines 121, 126, and 132 read from localStorage with JSON.parse / parseInt and assume happy-path data. If a user (or a previous build) wrote malformed JSON or a non-numeric chain id, JSON.parse will throw or parseInt will return NaN, which breaks the entire HomeScreen (and even propagates NaN into settlement requests). Please harden these initializers with try/catch and fall back to sane defaults.

Apply this diff:

   const [customBuyAmounts, setCustomBuyAmounts] = useState<string[]>(() => {
-    const stored = localStorage.getItem('pulse_customBuyAmounts');
-    return stored ? JSON.parse(stored) : ['10', '20', '50', '100'];
+    const stored = localStorage.getItem('pulse_customBuyAmounts');
+    if (!stored) return ['10', '20', '50', '100'];
+    try {
+      const parsed = JSON.parse(stored);
+      return Array.isArray(parsed) ? parsed : ['10', '20', '50', '100'];
+    } catch {
+      return ['10', '20', '50', '100'];
+    }
   });
   const [customSellAmounts, setCustomSellAmounts] = useState<string[]>(() => {
-    const stored = localStorage.getItem('pulse_customSellAmounts');
-    return stored ? JSON.parse(stored) : ['10%', '25%', '50%', '75%'];
+    const stored = localStorage.getItem('pulse_customSellAmounts');
+    if (!stored) return ['10%', '25%', '50%', '75%'];
+    try {
+      const parsed = JSON.parse(stored);
+      return Array.isArray(parsed) ? parsed : ['10%', '25%', '50%', '75%'];
+    } catch {
+      return ['10%', '25%', '50%', '75%'];
+    }
   });
   const [displaySettingsMenu, setDisplaySettingsMenu] =
     useState<boolean>(false);
   const [selectedChainIdForSettlement, setSelectedChainIdForSettlement] =
     useState<number>(() => {
       const stored = localStorage.getItem('pulse_selectedChainIdForSettlement');
-      return stored ? parseInt(stored, 10) : 1; // Will be updated by useEffect once maxStableCoinBalance is calculated
+      if (!stored) return 1;
+      const parsed = Number.parseInt(stored, 10);
+      return Number.isNaN(parsed) ? 1 : parsed;
     });
🤖 Prompt for AI Agents
In src/apps/pulse/components/App/HomeScreen.tsx around lines 121-136, the
localStorage initializers assume well-formed data which can throw or produce
NaN; wrap each read/parse in a try/catch and validate types: for
customBuyAmounts and customSellAmounts try to JSON.parse but on any error or if
result is not an array, fall back to the respective default arrays and coerce
entries to strings (Array.isArray check and map to String); for
selectedChainIdForSettlement read the raw value, attempt parseInt inside try,
and if the result is NaN or not a finite integer fall back to 1; ensure these
safe defaults are returned from the initializer so malformed persisted values
don’t crash the component or produce NaN chain ids.

@github-actions github-actions bot temporarily deployed to Preview (feature/PRO-3781-Add_Cash) November 5, 2025 18:24 Inactive
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)
vite.config.js (1)

32-39: Consider adding error handling and logging to the proxy configuration.

The proxy configuration is correctly set up with changeOrigin, rewrite, and secure options. However, adding error handling and logging callbacks would improve debugging during development.

Apply this diff to enhance the proxy with error handling:

     proxy: {
       '/api/coinbase': {
         target: 'https://api.cdp.coinbase.com',
         changeOrigin: true,
         rewrite: (path) => path.replace(/^\/api\/coinbase/, ''),
         secure: true,
+        configure: (proxy, options) => {
+          proxy.on('error', (err, req, res) => {
+            console.error('Proxy error:', err);
+          });
+          proxy.on('proxyReq', (proxyReq, req, res) => {
+            console.log('Proxying:', req.method, req.url, '→', proxyReq.path);
+          });
+        }
       }
     }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0c73cc4 and dd2e58e.

📒 Files selected for processing (1)
  • vite.config.js (1 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). (3)
  • GitHub Check: lint
  • GitHub Check: unit-tests
  • GitHub Check: build

@github-actions github-actions bot temporarily deployed to Preview (feature/PRO-3781-Add_Cash) November 5, 2025 18:30 Inactive
@github-actions github-actions bot temporarily deployed to Preview (feature/PRO-3781-Add_Cash) November 5, 2025 18:44 Inactive
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/apps/pillarx-app/components/WalletPortfolioButtons/WalletPortfolioButtons.tsx (1)

107-113: Check response status before parsing JSON.

Line 107 calls response.json() unconditionally, which will fail for non-2xx status codes or empty responses. When the fetch fails, the error is caught and silently suppressed (lines 116-124), leaving the user with no feedback. This was flagged in a previous review and remains unresolved.

Apply this diff:

       );

+      if (!response.ok) {
+        const errorBody = await response.text().catch(() => '');
+        throw new Error(
+          `Failed to create onramp session (${response.status}): ${errorBody}`
+        );
+      }
+
       const data = await response.json();

       const onrampUrl = data?.session?.onrampUrl;
🧹 Nitpick comments (1)
src/apps/pillarx-app/components/WalletPortfolioButtons/WalletPortfolioButtons.tsx (1)

76-85: Consider a more useful IP fallback.

While the best-effort IP lookup is reasonable, falling back to 127.0.0.1 (localhost) may not be useful for the Coinbase API, which likely needs a real IP address for compliance. Consider using an empty string or omitting the clientIp field entirely when unavailable, allowing the backend/proxy to handle it.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ac08e24 and ddb5e92.

📒 Files selected for processing (2)
  • src/apps/pillarx-app/components/WalletPortfolioButtons/WalletPortfolioButtons.tsx (4 hunks)
  • vite.config.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • vite.config.js
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-20T09:14:16.888Z
Learnt from: RanaBug
Repo: pillarwallet/x PR: 374
File: src/apps/pillarx-app/index.tsx:12-12
Timestamp: 2025-08-20T09:14:16.888Z
Learning: In this codebase, Transaction Kit providers are set up at the container level (src/containers/Authorized.tsx), not at individual app component levels. App components like src/apps/pillarx-app/index.tsx are children that consume the context through the provider tree.

Applied to files:

  • src/apps/pillarx-app/components/WalletPortfolioButtons/WalletPortfolioButtons.tsx
📚 Learning: 2025-11-04T14:34:00.373Z
Learnt from: RanaBug
Repo: pillarwallet/x PR: 445
File: src/containers/Authorized.tsx:63-77
Timestamp: 2025-11-04T14:34:00.373Z
Learning: In src/containers/Authorized.tsx, the tempKit useEffect intentionally excludes chainId from its dependency array because the kit is used by useWalletModeVerification to check wallet mode across all supported chains, regardless of the currently selected chainId. The chainId parameter is only used for initial kit configuration and does not affect the multi-chain verification results.

Applied to files:

  • src/apps/pillarx-app/components/WalletPortfolioButtons/WalletPortfolioButtons.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). (3)
  • GitHub Check: unit-tests
  • GitHub Check: lint
  • GitHub Check: build
🔇 Additional comments (4)
src/apps/pillarx-app/components/WalletPortfolioButtons/WalletPortfolioButtons.tsx (4)

7-7: LGTM! Clean import and usage.

The duplicate import issue has been resolved, and the destructuring of walletAddress as accountAddress is appropriate for the Coinbase onramp integration context.

Also applies to: 23-23


44-52: LGTM! Address validation is sound.

The validation correctly checks for the presence of the wallet address and verifies it matches the Ethereum address format.


54-73: LGTM! Token fetching handles errors appropriately.

The backend token retrieval correctly checks response.ok before parsing and validates the token exists.


140-148: LGTM! Button UI is consistent.

The Add Cash button follows the same styling pattern as the existing Receive and Upgrade buttons, maintaining visual consistency.

@github-actions github-actions bot temporarily deployed to Preview (feature/PRO-3781-Add_Cash) November 5, 2025 18:48 Inactive
@github-actions github-actions bot temporarily deployed to Preview (feature/PRO-3781-Add_Cash) November 5, 2025 18:58 Inactive
@github-actions github-actions bot temporarily deployed to Preview (feature/PRO-3781-Add_Cash) November 5, 2025 19:13 Inactive
@github-actions github-actions bot requested a deployment to Preview (feature/PRO-3781-Add_Cash) November 5, 2025 19:25 Abandoned
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

♻️ Duplicate comments (3)
vite.config.js (1)

6-6: The basicSsl plugin remains unused with https disabled.

This issue was previously flagged. The basicSsl() plugin (lines 6, 9) generates SSL certificates for HTTPS, but line 30 disables HTTPS with https: false, making the plugin serve no purpose.

Also applies to: 9-9, 30-30

src/apps/pillarx-app/components/WalletPortfolioButtons/WalletPortfolioButtons.tsx (2)

88-114: Check response.ok before parsing JSON.

This issue was previously flagged and remains unresolved. Line 106 calls response.json() unconditionally, which will throw on non-2xx responses or empty bodies, causing the error to be silently caught and logged only in development.

Apply this diff:

       const response = await fetch(
         '/api/coinbase/platform/v2/onramp/sessions',
         {
           method: 'POST',
           headers: {
             Authorization: `Bearer ${token}`,
             'Content-Type': 'application/json',
           },
           body: JSON.stringify({
             purchaseCurrency: 'USDC',
             destinationNetwork: 'base',
             destinationAddress: accountAddress,
             paymentCurrency: 'USD',
             clientIp,
           }),
         }
       );

+      if (!response.ok) {
+        const errorBody = await response.text().catch(() => '');
+        throw new Error(
+          `Failed to create onramp session (${response.status}): ${errorBody}`
+        );
+      }
+
       const data = await response.json();

       const onrampUrl = data?.session?.onrampUrl;

       if (!onrampUrl) {
         throw new Error('No URL received from Coinbase API');
       }

       window.open(onrampUrl, '_blank', 'noopener,noreferrer');

41-124: Add loading state and user feedback.

This issue was previously flagged and remains unresolved. The async operation provides no visual feedback, the button can be clicked multiple times during execution, and errors fail silently in production.

Consider:

  1. Adding a loading state to disable the button during processing
  2. Using a toast notification or modal for error feedback
  3. Showing success feedback when the URL opens
🧹 Nitpick comments (2)
functions/api/coinbase/platform/v2/onramp/sessions.js (1)

37-40: Consider tightening CORS policy.

The Access-Control-Allow-Origin: '*' header allows any origin to call this endpoint. While the endpoint still requires a valid Bearer token, consider restricting the allowed origins to your known frontend domains for defense in depth.

Example:

const allowedOrigins = [
  'https://your-app.com',
  'https://staging.your-app.com',
  'http://localhost:5173', // Vite dev server
];

const origin = context.request.headers.get('Origin');
const allowOrigin = allowedOrigins.includes(origin) ? origin : allowedOrigins[0];

// Then use allowOrigin instead of '*' in the response headers
'Access-Control-Allow-Origin': allowOrigin,

Also applies to: 58-61

src/apps/pillarx-app/components/WalletPortfolioButtons/WalletPortfolioButtons.tsx (1)

75-85: Consider server-side IP detection for reliability and privacy.

The code fetches the client IP from a third-party service (api.ipify.org). This introduces:

  • External dependency that could fail or be blocked
  • Privacy concern (client makes additional request to third party)
  • The fallback 127.0.0.1 is likely not useful to Coinbase's fraud detection

Consider detecting the client IP on your backend (in the token endpoint or the Cloudflare function) where you have access to request headers like CF-Connecting-IP or X-Forwarded-For. This would be more reliable and privacy-friendly.

Example in the Cloudflare function:

const clientIp = context.request.headers.get('CF-Connecting-IP') 
  || context.request.headers.get('X-Forwarded-For')
  || '127.0.0.1';
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 790483a and 5a312b1.

📒 Files selected for processing (3)
  • functions/api/coinbase/platform/v2/onramp/sessions.js (1 hunks)
  • src/apps/pillarx-app/components/WalletPortfolioButtons/WalletPortfolioButtons.tsx (4 hunks)
  • vite.config.js (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-20T09:14:16.888Z
Learnt from: RanaBug
Repo: pillarwallet/x PR: 374
File: src/apps/pillarx-app/index.tsx:12-12
Timestamp: 2025-08-20T09:14:16.888Z
Learning: In this codebase, Transaction Kit providers are set up at the container level (src/containers/Authorized.tsx), not at individual app component levels. App components like src/apps/pillarx-app/index.tsx are children that consume the context through the provider tree.

Applied to files:

  • src/apps/pillarx-app/components/WalletPortfolioButtons/WalletPortfolioButtons.tsx
📚 Learning: 2025-11-04T14:34:00.373Z
Learnt from: RanaBug
Repo: pillarwallet/x PR: 445
File: src/containers/Authorized.tsx:63-77
Timestamp: 2025-11-04T14:34:00.373Z
Learning: In src/containers/Authorized.tsx, the tempKit useEffect intentionally excludes chainId from its dependency array because the kit is used by useWalletModeVerification to check wallet mode across all supported chains, regardless of the currently selected chainId. The chainId parameter is only used for initial kit configuration and does not affect the multi-chain verification results.

Applied to files:

  • src/apps/pillarx-app/components/WalletPortfolioButtons/WalletPortfolioButtons.tsx
🧬 Code graph analysis (1)
src/apps/pillarx-app/components/WalletPortfolioButtons/WalletPortfolioButtons.tsx (1)
functions/api/coinbase/platform/v2/onramp/sessions.js (2)
  • response (20-27)
  • data (30-30)
⏰ 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). (3)
  • GitHub Check: lint
  • GitHub Check: unit-tests
  • GitHub Check: build
🔇 Additional comments (4)
vite.config.js (1)

32-39: LGTM on the proxy configuration.

The proxy setup correctly routes /api/coinbase requests to Coinbase's API with appropriate options: changeOrigin for host header forwarding, path rewriting to strip the prefix, and secure: true for SSL validation.

functions/api/coinbase/platform/v2/onramp/sessions.js (1)

54-64: LGTM on CORS preflight handler.

The onRequestOptions function correctly handles CORS preflight requests with a 204 status and appropriate headers. The 24-hour cache (Access-Control-Max-Age: 86400) is reasonable.

src/apps/pillarx-app/components/WalletPortfolioButtons/WalletPortfolioButtons.tsx (2)

44-52: LGTM on address validation.

The validation correctly checks for both the existence of accountAddress and its format using the standard Ethereum address regex pattern.


139-147: LGTM on button UI implementation.

The Add Cash button is styled consistently with the existing Receive and Upgrade buttons and properly wired to the handleAddCash handler.

Comment on lines 12 to 17
if (!authHeader) {
return new Response(JSON.stringify({ error: 'Missing Authorization header' }), {
status: 401,
headers: { 'Content-Type': 'application/json' },
});
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add CORS headers to 401 response.

The 401 response is missing CORS headers, which will cause the browser to block the response even though it returned correctly from the server. This makes debugging auth failures difficult for the client.

Apply this diff:

     if (!authHeader) {
       return new Response(JSON.stringify({ error: 'Missing Authorization header' }), {
         status: 401,
-        headers: { 'Content-Type': 'application/json' },
+        headers: {
+          'Content-Type': 'application/json',
+          'Access-Control-Allow-Origin': '*',
+          'Access-Control-Allow-Methods': 'POST, OPTIONS',
+          'Access-Control-Allow-Headers': 'Content-Type, Authorization',
+        },
       });
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (!authHeader) {
return new Response(JSON.stringify({ error: 'Missing Authorization header' }), {
status: 401,
headers: { 'Content-Type': 'application/json' },
});
}
if (!authHeader) {
return new Response(JSON.stringify({ error: 'Missing Authorization header' }), {
status: 401,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
},
});
}
🤖 Prompt for AI Agents
In functions/api/coinbase/platform/v2/onramp/sessions.js around lines 12 to 17,
the 401 response for a missing Authorization header lacks CORS headers; update
the returned Response to include the same CORS headers used for successful
responses (e.g., Access-Control-Allow-Origin, Access-Control-Allow-Methods,
Access-Control-Allow-Headers and any other project-standard CORS headers) so
browsers will accept the error response; ensure the headers object in the
Response contains those CORS entries and keep the JSON body and 401 status
unchanged.

Comment on lines 29 to 30
// Get the response data
const data = await response.json();
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Guard JSON parsing of Coinbase response.

Calling response.json() without checking the response status or content-type could throw if Coinbase returns an error, empty body, or non-JSON content. This will cause the catch block to return a generic 500 error, hiding the actual Coinbase error details.

Apply this diff:

-    // Get the response data
-    const data = await response.json();
+    // Get the response data
+    if (!response.ok) {
+      const errorText = await response.text().catch(() => 'Unknown error');
+      return new Response(
+        JSON.stringify({ error: `Coinbase API error (${response.status}): ${errorText}` }),
+        {
+          status: response.status,
+          headers: {
+            'Content-Type': 'application/json',
+            'Access-Control-Allow-Origin': '*',
+          },
+        }
+      );
+    }
+
+    const data = await response.json().catch(() => ({}));
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Get the response data
const data = await response.json();
// Get the response data
if (!response.ok) {
const errorText = await response.text().catch(() => 'Unknown error');
return new Response(
JSON.stringify({ error: `Coinbase API error (${response.status}): ${errorText}` }),
{
status: response.status,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
}
);
}
const data = await response.json().catch(() => ({}));

Comment on lines 42 to 50
} catch (error) {
return new Response(JSON.stringify({ error: error.message }), {
status: 500,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
});
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Avoid exposing raw error messages.

Line 43 exposes error.message directly in the response, which could leak sensitive information like internal paths, stack traces, or implementation details if an unexpected error occurs.

Apply this diff:

   } catch (error) {
-    return new Response(JSON.stringify({ error: error.message }), {
+    return new Response(JSON.stringify({ error: 'Failed to process request' }), {
       status: 500,
       headers: {
         'Content-Type': 'application/json',
         'Access-Control-Allow-Origin': '*',
       },
     });
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} catch (error) {
return new Response(JSON.stringify({ error: error.message }), {
status: 500,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
});
}
} catch (error) {
return new Response(JSON.stringify({ error: 'Failed to process request' }), {
status: 500,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
});
}
🤖 Prompt for AI Agents
In functions/api/coinbase/platform/v2/onramp/sessions.js around lines 42 to 50,
the catch block currently returns error.message to the client which can leak
sensitive implementation details; change the response to return a generic error
message (e.g., { error: "Internal server error" } or a short error code) and
keep the original error for server-side logging only (e.g., console.error or a
logger) so details are recorded but not exposed to the user; ensure the response
status and headers remain the same and the response body stays valid JSON.

Comment on lines 54 to 66
// Get token from backend API
const tokenUrl = new URL(import.meta.env.VITE_ONRAMP_JWT_URL);

const tokenResponse = await fetch(tokenUrl.toString(), {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});

if (!tokenResponse.ok) {
throw new Error('Failed to get token from backend');
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify the environment variable is documented.

The code relies on VITE_ONRAMP_JWT_URL (line 55) but this environment variable may not be documented in the project's configuration guide. Ensure it's added to .env.example or relevant documentation.

Check if the environment variable is documented:


🏁 Script executed:

#!/bin/bash
# Check for .env.example or documentation of VITE_ONRAMP_JWT_URL
fd -t f "\.env\.example|README|SETUP|CONFIG" -X grep -l "VITE_ONRAMP_JWT_URL"

Length of output: 0


🏁 Script executed:

#!/bin/bash
# 1. Search for all .env* files
echo "=== .env files ==="
fd -t f "\.env" 

# 2. Search for all VITE_ONRAMP_JWT_URL references in codebase
echo -e "\n=== VITE_ONRAMP_JWT_URL references ==="
rg -n "VITE_ONRAMP_JWT_URL" --type=ts --type=tsx --type=js --type=json

# 3. Search for environment variable documentation patterns
echo -e "\n=== Env var docs (*.md files) ==="
fd -t f "\.md$" -x grep -l "environment\|VITE_" 2>/dev/null | head -20

# 4. Check for any env example or config documentation
echo -e "\n=== Config/env related files ==="
fd -t f "\.(example|template|sample)" | head -20

Length of output: 197


🏁 Script executed:

#!/bin/bash
# 1. Search for VITE_ONRAMP_JWT_URL with corrected rg syntax
echo "=== VITE_ONRAMP_JWT_URL references ==="
rg -n "VITE_ONRAMP_JWT_URL" 

# 2. Find the WalletPortfolioButtons.tsx file
echo -e "\n=== WalletPortfolioButtons.tsx location ==="
fd -t f "WalletPortfolioButtons.tsx"

# 3. Check for vite.config files
echo -e "\n=== Vite config files ==="
fd -t f "vite\.config\.(ts|js|mts|mjs)"

# 4. Search for environment documentation in markdown/docs
echo -e "\n=== All markdown files ==="
fd -t f "\.md$" | head -30

# 5. Check root level for env documentation
echo -e "\n=== Root level files ==="
fd -maxdepth 1 -t f

Length of output: 696


🏁 Script executed:

#!/bin/bash
# 1. Check vite.config.js for env var documentation
echo "=== vite.config.js content ==="
cat vite.config.js

# 2. Check README.md for environment documentation
echo -e "\n=== README.md (first 100 lines) ==="
head -100 README.md

# 3. Search for VITE_ONRAMP_JWT_URL in all non-ignored files
echo -e "\n=== All VITE_ONRAMP_JWT_URL occurrences ==="
rg "VITE_ONRAMP_JWT_URL" -A 2 -B 2

# 4. Search for environment variable setup documentation
echo -e "\n=== Environment setup docs ==="
rg "environment|env\s*setup|VITE_" --type=md

Length of output: 3639


🏁 Script executed:

#!/bin/bash
# 1. Check .gitignore for env file patterns
echo "=== .gitignore content ==="
cat .gitignore 2>/dev/null || echo "No .gitignore found"

# 2. Use git to find .env.example (even if ignored)
echo -e "\n=== Git check for .env.example ==="
git ls-files --others --ignored --exclude-standard | grep -E "\.env"

# 3. Directly look for .env.example with any pattern
echo -e "\n=== Direct search for env example files ==="
find . -name "*env*example*" -o -name ".env.example" 2>/dev/null | head -20

# 4. Check git log to see if .env.example was ever committed
echo -e "\n=== Git history for .env files ==="
git log --all --full-history --oneline -- ".env*" 2>/dev/null | head -10

Length of output: 1110


🏁 Script executed:

#!/bin/bash
# Check .env.example contents
echo "=== .env.example content ==="
cat .env.example

# Specifically search for VITE_ONRAMP_JWT_URL in .env.example
echo -e "\n=== Search for VITE_ONRAMP_JWT_URL in .env.example ==="
grep -i "VITE_ONRAMP_JWT_URL" .env.example || echo "NOT FOUND in .env.example"

# Show all VITE_ variables documented
echo -e "\n=== All VITE_ variables in .env.example ==="
grep "VITE_" .env.example

Length of output: 1681


Add VITE_ONRAMP_JWT_URL to .env.example documentation.

The code uses VITE_ONRAMP_JWT_URL (line 55) but this environment variable is missing from .env.example. Add it to the environment configuration template so developers can properly set up their local development environment. Include a description of what the variable should contain (e.g., the URL for the onramp JWT token endpoint).

🤖 Prompt for AI Agents
In
src/apps/pillarx-app/components/WalletPortfolioButtons/WalletPortfolioButtons.tsx
around lines 54 to 66, the component reads VITE_ONRAMP_JWT_URL to fetch a JWT
token but that env var is not documented in .env.example; update .env.example to
add VITE_ONRAMP_JWT_URL with a brief description (e.g., "URL for onramp JWT
token endpoint used to fetch onramp auth tokens") and an example value
placeholder (e.g., https://api.example.com/onramp/jwt) so developers know to
configure it for local development.

@github-actions github-actions bot temporarily deployed to Preview (feature/PRO-3781-Add_Cash) November 5, 2025 19:53 Inactive
@github-actions github-actions bot temporarily deployed to Preview (feature/PRO-3781-Add_Cash) November 7, 2025 11:32 Inactive
@vignesha22
Copy link
Contributor Author

vignesha22 commented Nov 7, 2025

Sorry can i just check why we're adding Cloudflare functions in the frontend codebase before i continue the review?

yeah sure the reason for adding Cloudflare functions is that when Coinbase API was directly called we had CORS issue I tired to change the vote config to use proxy server but it does not work in production does work only on dev mode vitejs/vite#17381 so had to use redirects on Cloudflare in order to access that url on Coinbase Btw I also gave a try on Access-Cross-Control-Origin to * or specifically to our host url it didn't work

@github-actions github-actions bot temporarily deployed to Preview (feature/PRO-3781-Add_Cash) November 7, 2025 13:07 Inactive
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 (1)
src/apps/pillarx-app/components/WalletPortfolioButtons/WalletPortfolioButtons.tsx (1)

147-159: Parse JSON only after validating response status.

Line 147 calls response.json() before checking response.ok on line 152. If the Coinbase API returns a non-2xx status with an empty body (e.g., 204 No Content) or invalid JSON, the parse will throw an exception that masks the actual HTTP error. The catch block will then surface a generic parsing error instead of the meaningful API error message.

Apply this diff to check status before parsing:

       const response = await fetch(
         '/api/coinbase/platform/v2/onramp/sessions',
         {
           method: 'POST',
           headers: {
             Authorization: `Bearer ${token}`,
             'Content-Type': 'application/json',
           },
           body: JSON.stringify({
             purchaseCurrency: 'USDC',
             destinationNetwork: 'base',
             destinationAddress: accountAddress,
             paymentCurrency: 'USD',
             clientIp,
           }),
         }
       );

-      const data = await response.json();
-
-      // eslint-disable-next-line no-console
-      console.log('Coinbase API response:', { status: response.status, data });
-
       if (!response.ok) {
+        const errorBody = await response.text().catch(() => '');
-        const errorMsg = `Coinbase API error (${response.status}): ${data.error || JSON.stringify(data)}`;
+        const errorMsg = `Coinbase API error (${response.status}): ${errorBody}`;
         // eslint-disable-next-line no-console
         console.error(errorMsg);
         // eslint-disable-next-line no-alert
         alert(errorMsg);
         return;
       }
+
+      const data = await response.json();
+
+      // eslint-disable-next-line no-console
+      console.log('Coinbase API response:', { status: response.status, data });
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 331ba35 and 37fb82e.

📒 Files selected for processing (1)
  • src/apps/pillarx-app/components/WalletPortfolioButtons/WalletPortfolioButtons.tsx (2 hunks)
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: RanaBug
Repo: pillarwallet/x PR: 391
File: src/apps/pulse/components/Sell/Sell.tsx:113-130
Timestamp: 2025-09-09T12:40:15.629Z
Learning: In the Pulse app Sell component, when a user changes/switches tokens, the input amount automatically resets to 0, which means liquidity validation state doesn't become stale when tokens change.
📚 Learning: 2025-11-04T14:34:00.373Z
Learnt from: RanaBug
Repo: pillarwallet/x PR: 445
File: src/containers/Authorized.tsx:63-77
Timestamp: 2025-11-04T14:34:00.373Z
Learning: In src/containers/Authorized.tsx, the tempKit useEffect intentionally excludes chainId from its dependency array because the kit is used by useWalletModeVerification to check wallet mode across all supported chains, regardless of the currently selected chainId. The chainId parameter is only used for initial kit configuration and does not affect the multi-chain verification results.

Applied to files:

  • src/apps/pillarx-app/components/WalletPortfolioButtons/WalletPortfolioButtons.tsx
📚 Learning: 2025-08-20T09:14:16.888Z
Learnt from: RanaBug
Repo: pillarwallet/x PR: 374
File: src/apps/pillarx-app/index.tsx:12-12
Timestamp: 2025-08-20T09:14:16.888Z
Learning: In this codebase, Transaction Kit providers are set up at the container level (src/containers/Authorized.tsx), not at individual app component levels. App components like src/apps/pillarx-app/index.tsx are children that consume the context through the provider tree.

Applied to files:

  • src/apps/pillarx-app/components/WalletPortfolioButtons/WalletPortfolioButtons.tsx
🧬 Code graph analysis (1)
src/apps/pillarx-app/components/WalletPortfolioButtons/WalletPortfolioButtons.tsx (3)
src/apps/pillarx-app/hooks/useReducerHooks.tsx (1)
  • useAppDispatch (5-5)
src/hooks/useEIP7702Upgrade.tsx (1)
  • useEIP7702Upgrade (31-188)
src/apps/pillarx-app/reducer/WalletPortfolioSlice.ts (1)
  • setIsReceiveModalOpen (134-136)
⏰ 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). (3)
  • GitHub Check: unit-tests
  • GitHub Check: lint
  • GitHub Check: build
🔇 Additional comments (3)
src/apps/pillarx-app/components/WalletPortfolioButtons/WalletPortfolioButtons.tsx (3)

1-29: LGTM: Clean imports and state initialization.

The imports are well-organized, loading state is properly initialized, and the rename from walletAddress to accountAddress improves clarity.


176-186: LGTM: Proper error handling and cleanup.

The catch block appropriately logs errors and notifies the user, while the finally block ensures the loading state is always reset, preventing the button from remaining stuck in a loading state.


188-243: LGTM: Well-structured UI with proper loading states.

The two-row layout cleanly organizes the actions, and the Add Cash button properly reflects its loading state by disabling the button and displaying a spinner. Conditional rendering of the WalletConnectDropdown and Upgrade button is handled correctly.

console.log('Opening onramp URL:', onrampUrl);

// Open the URL directly - simpler approach that works on all browsers
window.open(onrampUrl, '_top', 'noreferrer');
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Reconsider _top target—user loses wallet context.

Opening the onramp URL with window.open(onrampUrl, '_top', 'noreferrer') replaces the entire current page, navigating the user away from the wallet interface. Once the user completes (or abandons) the onramp flow, they must manually navigate back. This creates friction and may confuse users.

Consider using '_blank' to open the onramp in a new tab, allowing users to keep the wallet interface accessible in the original tab.

Apply this diff to open in a new tab:

-      window.open(onrampUrl, '_top', 'noreferrer');
+      window.open(onrampUrl, '_blank', 'noopener,noreferrer');
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
window.open(onrampUrl, '_top', 'noreferrer');
window.open(onrampUrl, '_blank', 'noopener,noreferrer');
🤖 Prompt for AI Agents
In
src/apps/pillarx-app/components/WalletPortfolioButtons/WalletPortfolioButtons.tsx
around line 175, change the window.open call so it opens the onramp in a new tab
instead of replacing the current page: replace the target '_top' with '_blank'
and include 'noopener,noreferrer' in the features string (or otherwise set
window.opener = null) to avoid opening-page security/OPENER vulnerabilities
while preserving the wallet UI in the original tab.

@@ -0,0 +1,11 @@
{
Copy link
Collaborator

Choose a reason for hiding this comment

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

What is this file for excatly?

// Validate wallet address
if (!accountAddress) {
// eslint-disable-next-line no-alert
alert('Wallet address is not available');
Copy link
Collaborator

Choose a reason for hiding this comment

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

Are we sure we want to provide alert like this? Rather than error message in a UI we create?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

i will change that was debugging something

const isValidAddress = /^0x[a-fA-F0-9]{40}$/.test(accountAddress);
if (!isValidAddress) {
// eslint-disable-next-line no-alert
alert('Invalid wallet address format');
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same comment on alert here

}

// Check if address is a valid Ethereum address (0x followed by 40 hex characters)
const isValidAddress = /^0x[a-fA-F0-9]{40}$/.test(accountAddress);
Copy link
Collaborator

Choose a reason for hiding this comment

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

You can use a helper function from Viem to check if it is an address here

Copy link
Collaborator

Choose a reason for hiding this comment

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

@vignesha22 for consistency please change this

tokenResponse.statusText
);
// eslint-disable-next-line no-alert
alert(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same comment on alert, for this and the next ones

</button>
{shouldShowWalletConnectDropdown && <WalletConnectDropdown />}
</div>
<div className="flex w-full desktop:gap-x-2.5 tablet:gap-x-2.5">
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think the AddCash button should be a separate component rather than all coded here

userPortfolio
);
} else {
// eslint-disable-next-line @typescript-eslint/no-use-before-define
Copy link
Collaborator

Choose a reason for hiding this comment

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

Try not ignoring this eslint here

* Get the best sell offer with cross-chain bridge
* This calculates the final amount user will receive after:
* 1. Swapping token to USDC on same chain
* 2. Taking 1% fee from received USDC
Copy link
Collaborator

Choose a reason for hiding this comment

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

This description is incorrect, it should be taking 1% fee of the token that we sell

Copy link
Contributor Author

@vignesha22 vignesha22 Nov 7, 2025

Choose a reason for hiding this comment

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

yes since we are taking the fee as USDC on the chain they are selling the token and btw the description is right


// Step 2: Calculate USDC after first swap and fee (1%)
// User gets 99% of the USDC from the first swap
const usdcAfterFirstSwap = firstSwapOffer.tokenAmountToReceive;
Copy link
Collaborator

Choose a reason for hiding this comment

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

??? we are taking 1% from USDC not from the token we sell???

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes so now the flow is User swaps the sell token to USDC on same chain and we take 1% of the received amount and then the rest is swapped again to their destination chain

const clearError = useCallback(() => {
setError(null);
}, []);

Copy link
Collaborator

Choose a reason for hiding this comment

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

Honestly, there is so much new logic in place, different way of taking fees etc. It is very confusing and quite hard to review at this stage

@github-actions github-actions bot temporarily deployed to Preview (feature/PRO-3781-Add_Cash) November 7, 2025 16:15 Inactive
@github-actions github-actions bot requested a deployment to Preview (feature/PRO-3781-Add_Cash) November 11, 2025 10:14 Abandoned
@github-actions github-actions bot requested a deployment to Preview (feature/PRO-3781-Add_Cash) November 11, 2025 10:16 Abandoned
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
vite.config.js (1)

33-33: The basicSsl plugin remains unused with HTTPS disabled.

This contradictory configuration was previously flagged: the basicSsl() plugin (lines 6, 9) is included but https: false disables HTTPS, making the plugin serve no purpose.

🧹 Nitpick comments (1)
vite.config.js (1)

35-42: Document that this proxy is for local development only.

The proxy configuration is technically correct, but per the PR objectives, this proxy works in development but not in production. The Cloudflare function at functions/api/coinbase/... handles the production case. Consider adding a comment to clarify this limitation.

Apply this diff to add clarifying documentation:

   proxy: {
+    // Dev-only proxy for Coinbase API. Production uses Cloudflare function at /functions/api/coinbase/
     '/api/coinbase': {
       target: 'https://api.cdp.coinbase.com',
       changeOrigin: true,
       rewrite: (path) => path.replace(/^\/api\/coinbase/, ''),
       secure: true,
     },
   },
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bc40e7c and f5cd39a.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (2)
  • src/apps/pillarx-app/components/WalletPortfolioButtons/WalletPortfolioButtons.tsx (2 hunks)
  • vite.config.js (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/apps/pillarx-app/components/WalletPortfolioButtons/WalletPortfolioButtons.tsx
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: RanaBug
Repo: pillarwallet/x PR: 391
File: src/apps/pulse/components/Sell/Sell.tsx:113-130
Timestamp: 2025-09-09T12:40:15.629Z
Learning: In the Pulse app Sell component, when a user changes/switches tokens, the input amount automatically resets to 0, which means liquidity validation state doesn't become stale when tokens change.
🔇 Additional comments (1)
vite.config.js (1)

13-15: LGTM! Correct exclusion of Cloudflare functions from the build.

Excluding /functions/** prevents Cloudflare Pages functions from being bundled with the frontend code, which is the correct approach.

@github-actions github-actions bot temporarily deployed to Preview (feature/PRO-3781-Add_Cash) November 11, 2025 10:26 Inactive
kit.getEtherspotProvider().getWalletMode() !== 'delegatedEoa';

const handleAddCash = async () => {
if (isAddCashLoading) return;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we show something to the user here so they're not ghosted - just an alert telling them that it's still loading and to try again shortly

}

// Check if address is a valid Ethereum address (0x followed by 40 hex characters)
const isValidAddress = /^0x[a-fA-F0-9]{40}$/.test(accountAddress);
Copy link
Collaborator

Choose a reason for hiding this comment

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

@vignesha22 for consistency please change this

Comment on lines 177 to 192
{isAddCashLoading ? (
<>
<span className="text-white font-medium text-[14px] leading-[14px] tracking-[-0.02em] text-center align-bottom">
Add Cash
</span>
<TailSpin color="#FFFFFF" height={16} width={16} />
</>
) : (
<>
<span className="text-white font-medium text-[14px] leading-[14px] tracking-[-0.02em] text-center align-bottom">
Add Cash
</span>
<img src={FrameIcon} alt="Add Cash" width={16} height={16} />
</>
)}
</div>
Copy link
Collaborator

Choose a reason for hiding this comment

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

This inline if/else is nearly the same - could the confitional value not be just for <TailSpin />?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeah sure

vite.config.js Outdated
},
server: {
https: true,
https: false,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Keep this enabled for other developers - HTTPS should be turned on as standard

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeah sure

Copy link
Collaborator

@IAmKio IAmKio left a comment

Choose a reason for hiding this comment

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

LGTM

@vignesha22 vignesha22 merged commit c1badd1 into staging Nov 11, 2025
6 checks passed
@vignesha22 vignesha22 deleted the feature/PRO-3781-Add_Cash branch November 11, 2025 12:06
@coderabbitai coderabbitai bot mentioned this pull request Nov 12, 2025
3 tasks
@coderabbitai coderabbitai bot mentioned this pull request Nov 27, 2025
3 tasks
@coderabbitai coderabbitai bot mentioned this pull request Jan 9, 2026
3 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants