Skip to content

PRO - 3733 - Onboarding flow on Pulse#467

Merged
IAmKio merged 38 commits intostagingfrom
PRO-3733-OnboardingFlow
Dec 5, 2025
Merged

PRO - 3733 - Onboarding flow on Pulse#467
IAmKio merged 38 commits intostagingfrom
PRO-3733-OnboardingFlow

Conversation

@vignesha22
Copy link
Contributor

@vignesha22 vignesha22 commented Nov 26, 2025

Description

  • When the user doesnt have any balance on gas tank then this onboarding flow is shown and we can install modules, swap and deposit USDC into gas tank
  • Added tests for new screens and modified the existing tests to include mock gas tank paymaster as non zero to keep the existing tests as it is

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

    • Persistent onboarding flow with Welcome and Top‑up screens, multi‑step Top‑up preview, and a "Top up Gas Tank" action in Settings.
  • Background Services

    • Gas‑tank monitoring, refetching, top‑up orchestration, and gas estimation reported in native and USD; total USDC aggregation and intent/paymaster support.
  • Search / Portfolio

    • Search recognizes top‑up context and portfolio lists can include stablecoins during top‑up flows.
  • Buy/Sell UX

    • Previews validate against gas‑tank balance; sell flow simplified and onboarding synchronized.
  • Tests

    • New unit and integration tests covering onboarding, top‑up, preview and gas logic.
  • Bug Fixes / UX

    • Top‑up batches excluded from various batch lists; improved auto‑show/auto‑dismiss onboarding.

✏️ Tip: You can customize this high-level summary in your review settings.

@vignesha22 vignesha22 requested review from IAmKio and RanaBug November 26, 2025 09:29
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 26, 2025

Walkthrough

Adds a gas‑tank onboarding and top‑up flow: new onboarding UI and preview, top‑up orchestration and gas‑estimation hooks, gas‑tank/USDC balance hooks, AppWrapper→HomeScreen/Search prop wiring, PortfolioTokenList stable‑coin toggle, batch exclusions for "pulse-topup", tests, and safer paymaster URL handling. (50 words)

Changes

Cohort / File(s) Change Summary
App state & prop plumbing
\src/apps/pulse/components/App/AppWrapper.tsx``
Added local onboarding state (onboardingScreen, topupToken, isSearchingFromTopup) and forwarded onboarding/top‑up props into HomeScreen and Search.
Home screen onboarding integration
\src/apps/pulse/components/App/HomeScreen.tsx``
Extended HomeScreenProps with onboarding/top‑up props; persisted onboarding flag; integrated useGasTankBalance & useTotalUsdcBalance; auto‑preselect USDC for top‑up; added effects, handlers, onboarding render paths, and transaction finalization/refetch logic.
New onboarding UI components
\src/apps/pulse/components/Onboarding/OnboardingWelcome.tsx`, `src/apps/pulse/components/Onboarding/TopUpScreen.tsx`, `src/apps/pulse/components/Onboarding/PreviewTopUp.tsx``
Added OnboardingWelcome, TopUpScreen, and PreviewTopUp components implementing welcome card, token selection/quote/validation, multi‑step top‑up orchestration, transaction lifecycle, polling and status UI.
Onboarding tests
\src/apps/pulse/components/Onboarding/tests/*``
Added unit and integration tests for onboarding components (rendering, interactions, loading/errors, accessibility, keyboard flows, null token scenarios).
Gas‑tank & USDC hooks
\src/apps/pulse/hooks/useGasTankBalance.ts`, `src/apps/pulse/hooks/useTotalUsdcBalance.ts``
New useGasTankBalance (per‑chain + total via paymaster API) and useTotalUsdcBalance (compute total USDC across chains) hooks.
Top‑up orchestration & estimation hooks
\src/apps/pulse/hooks/useTopUp.ts`, `src/apps/pulse/hooks/useGasEstimationTopUp.ts`, `src/apps/pulse/hooks/useGasEstimationBuy.ts`, `src/apps/pulse/hooks/useGasEstimation.ts`**
New useTopUp to build/execute top‑up batches (paymaster, EIP‑7702, batching) and useGasEstimationTopUp for native estimation; extended gas estimation hooks to provide USD gasCost and paymaster‑aware estimation; added isUsingRelayBuy gating.
Intent SDK enhancements
\src/apps/pulse/hooks/useIntentSdk.ts``
Added moduleCheckError guard, async module readiness recheck, and exported getEnablePulseTradingTransactions() helper on hook return.
Search / token list changes
\src/apps/pulse/components/Search/Search.tsx`, `src/apps/pulse/components/Search/PortfolioTokenList.tsx``
Added isSearchingFromTopup?: boolean to SearchProps and forwarded it; added includeStableCoins?: boolean to PortfolioTokenListProps and conditionally include stable coins when searching from top‑up; sorting by USD balance when including stables.
Batch filtering updates
\src/components/BottomMenuModal/SendModal/SendModalBatchesTabView.tsx`, `src/providers/GlobalTransactionsBatchProvider.tsx``
Excluded batches whose names include "pulse-topup" (in addition to existing exclusions) from UI grouping and global batch counts.
Preview/Buy/Sell gas integration & Buy tweak
\src/apps/pulse/components/Buy/PreviewBuy.tsx`, `src/apps/pulse/components/Sell/PreviewSell.tsx`, `src/apps/pulse/components/Buy/Buy.tsx`, `src/apps/pulse/components/Sell/Sell.tsx``
Threaded gasTankBalance and gasCostUSD into preview flows; added paymasterDetails to batch sends and validation; added USE_RELAY_BUY early exit in Buy; removed minGasAmount logic and related messages from Sell.
Settings menu wiring
\src/apps/pulse/components/Settings/SettingsMenu.tsx``
Added onTopUp and gasTankBalance props and a "Top up Gas Tank" button that invokes onTopUp and closes the menu; displays current gasTankBalance.
Tests: App/HomeScreen
\src/apps/pulse/components/App/tests/AppWrapper.test.tsx`, `src/apps/pulse/components/App/tests/HomeScreen.test.tsx``
Mocked useGasTankBalance in tests to return non‑zero totalBalance and not loading so main UI renders reliably.
Paymaster URL safety & trimming
\src/components/BottomMenuModal/SendModal/SendModalTokensTabView.tsx`, `src/apps/pulse/components/Buy/BuyButton.tsx``
Trimmed paymaster URL and removed trailing slash when composing paymasterDetails; tightened paymasterDetails creation; cleaned duplicate import in BuyButton.
Misc tests & minor edits
\src/apps/pulse/components/Sell/tests/*`, `src/apps/pulse/components/Transaction/TransactionDetails.tsx``
Test mocks adjusted for native price/relay responses and async behavior; TransactionDetails sell amount fallback uses Number(usdAmount) when sellOffer token amount missing.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant User
    participant AppWrapper
    participant HomeScreen
    participant OnboardingWelcome
    participant TopUpScreen
    participant PreviewTopUp
    participant IntentSdk
    participant PaymasterAPI
    participant Poller

    User->>AppWrapper: open app
    AppWrapper->>HomeScreen: render (pass onboarding props)
    HomeScreen->>PaymasterAPI: fetch gas tank balance (useGasTankBalance)
    alt onboarding needed (low gas && not completed)
        HomeScreen->>OnboardingWelcome: render welcome
        User->>OnboardingWelcome: click "Top up"
        OnboardingWelcome->>HomeScreen: setOnboardingScreen("topup")
        HomeScreen->>TopUpScreen: render top‑up UI
        TopUpScreen->>IntentSdk: request quote / sell offer (if non‑USDC)
        User->>TopUpScreen: confirm -> open PreviewTopUp
        PreviewTopUp->>IntentSdk: check modules & build/send batch (useTopUp / getEnablePulseTradingTransactions)
        PreviewTopUp->>PaymasterAPI: fetch deposit transactions
        IntentSdk->>PreviewTopUp: return userOpHash
        Poller->>PreviewTopUp: poll userOp status -> update UI
        PreviewTopUp->>HomeScreen: mark onboarding complete / refetch balances
        HomeScreen->>User: render main interface
    else
        HomeScreen->>User: render main interface
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Files/areas needing extra attention:
    • src/apps/pulse/components/Onboarding/PreviewTopUp.tsx — transaction orchestration, polling, timing and error flows.
    • src/apps/pulse/hooks/useTopUp.ts — batch construction, paymaster integration, EIP‑7702 handling and cleanup.
    • src/apps/pulse/hooks/useGasEstimationTopUp.ts & related gas estimation hooks — batching/estimation, USD conversion and concurrency.
    • src/apps/pulse/components/App/HomeScreen.tsx — effect dependencies, localStorage onboarding persistence, and auto‑preselect logic.
    • src/apps/pulse/hooks/useIntentSdk.ts — async module checks and new helper surface.

Possibly related PRs

Suggested reviewers

  • RanaBug

Poem

🐇
I hop where tiny tokens sleep,
I nudge the tanks from buried deep,
"Top up" I whisper, ops begin to run,
Pollers blink, the balances hum,
Carrots counted, ledger bright — hop, job done! 🎉

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.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 - 3733 - Onboarding flow on Pulse' clearly summarizes the main change: adding an onboarding flow for the Pulse application.
Description check ✅ Passed The description provides a clear summary of changes (onboarding flow shown when gas tank is empty, enables module installation and USDC swaps), mentions testing (tested locally), and correctly identifies the change type as a new feature.
✨ 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 PRO-3733-OnboardingFlow

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.

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 (17)
src/providers/GlobalTransactionsBatchProvider.tsx (1)

86-86: Consider adding an explanatory comment for the pulse-topup exclusion.

For consistency and maintainability, consider adding a comment explaining why pulse-topup batches are excluded, similar to the existing comments for pulse-sell and pulse-buy on lines 79-81.

       // Filter out pulse-sell, pulse-buy and paymaster-batch from the count
       // - pulse-sell and pulse-buy batches are handled directly in the Pulse app
+      // - pulse-topup batches are handled directly in the Pulse onboarding flow
       // - paymaster-batch are internal and should not be displayed in batch counts
       const filteredBatches = Object.keys(batches).filter(
src/components/BottomMenuModal/SendModal/SendModalBatchesTabView.tsx (1)

82-90: Add pulse-topup filtering to the useMemo for consistency.

The groupedBatchesByChainId useMemo filters out pulse-sell and pulse-buy batches, but pulse-topup batches are only filtered during rendering (lines 747-748). For consistency and to ensure accurate batch grouping, consider adding the pulse-topup filter here as well.

     Object.entries(batches).forEach(([batchName, transactions]) => {
       if (!transactions.length) return;
       // Skip pulse-sell and pulse-buy batches - they are handled directly in the Pulse app
       if (batchName.includes('pulse-sell')) return;
       if (batchName.includes('pulse-buy')) return;
+      // Skip pulse-topup batches - they are handled directly in the Pulse onboarding flow
+      if (batchName.includes('pulse-topup')) return;
       // Skip paymaster-batch - these are internal and should not be displayed in batches view
       if (batchName === 'paymaster-batch') return;
src/apps/pulse/hooks/useGasTankBalance.ts (3)

3-6: Consider exporting ChainBalance interface for external use.

The ChainBalance interface may be useful for consumers of this hook who want to type their usage of chainBalances. Consider exporting it if needed elsewhere.


32-79: Add AbortController to prevent state updates after unmount.

The fetch operation lacks cancellation handling. If the component unmounts while a request is in-flight, calling setTotalBalance, setChainBalances, etc. will attempt to update unmounted component state.

 const fetchGasTankBalance = async () => {
+  const abortController = new AbortController();
+  
   if (!walletAddress || !paymasterUrl) {
     setTotalBalance(0);
     setChainBalances([]);
-    return;
+    return abortController;
   }

   setIsLoading(true);
   setError(null);

   try {
     const response = await fetch(
       `${paymasterUrl}/getGasTankBalance?sender=${walletAddress}`,
       {
         method: 'GET',
         headers: {
           'Content-Type': 'application/json',
         },
+        signal: abortController.signal,
       }
     );
     // ... rest of the logic
   } catch (err) {
+    if (err instanceof Error && err.name === 'AbortError') {
+      return abortController;
+    }
     // ... existing error handling
   } finally {
     setIsLoading(false);
   }
+  return abortController;
 };

Then in the useEffect, return a cleanup function that calls abortController.abort().


81-84: Consider using useCallback to avoid eslint-disable.

Wrapping fetchGasTankBalance in useCallback with [walletAddress, paymasterUrl] dependencies would eliminate the need for the eslint-disable comment and make the dependency relationship explicit.

src/apps/pulse/components/Onboarding/tests/PreviewTopUp.integration.test.tsx (2)

169-173: Duplicate test case.

This test ('renders gas fee display' at line 170) duplicates the test at lines 118-122. Consider removing this duplicate or making it test something distinct about loading states.

   describe('loading states', () => {
-    it('renders gas fee display', () => {
-      render(<PreviewTopUp {...createMockProps()} />);
-      expect(screen.getByText('Gas fee:')).toBeInTheDocument();
-    });
-
     it('renders confirm button in enabled state by default', () => {

184-189: Consider adding more error state tests.

The error handling section only verifies the component renders without errors. Consider adding tests for when useTopUp returns an error or when gasEstimationError is set in the mock.

src/apps/pulse/hooks/useGasEstimationTopUp.ts (1)

161-185: Potential stale closure when invoking estimateGasFeesRef.current.

The useEffect dependency array includes allocateAmount and sellOffer, but the effect body only checks estimateGasFeesRef.current. Since the ref is updated after the callback is created (Line 162), and the effect runs when dependencies change, this pattern is correct. However, consider adding a brief comment explaining the ref pattern for maintainability.

Additionally, userPortfolio is missing from the dependency array but is part of the estimateGasFees callback dependencies. If userPortfolio changes, the effect won't re-run, potentially causing stale estimations.

   }, [
     selectedToken,
     kit,
     amount,
     allocateAmount,
     sellOffer,
     isPaused,
     isInitialized,
+    userPortfolio,
   ]);
src/apps/pulse/hooks/useTopUp.ts (3)

95-114: Network request lacks timeout and retry logic.

The fetch call to the paymaster API has no timeout configuration. If the paymaster service is slow or unresponsive, this could leave the user waiting indefinitely.

Consider adding an AbortController with a timeout:

+      const controller = new AbortController();
+      const timeoutId = setTimeout(() => controller.abort(), 30000); // 30s timeout
+
       const response = await fetch(
         `${paymasterUrl}/getTransactionForDeposit?chainId=${chainId}&amount=${allocateAmount}`,
         {
           method: 'GET',
           headers: {
             'Content-Type': 'application/json',
           },
+          signal: controller.signal,
         }
       );
+
+      clearTimeout(timeoutId);

119-141: Paymaster API response validation could be more robust.

The response handling checks for array vs object but doesn't validate the structure of individual transaction objects (e.g., verifying to is a valid address). While the subsequent batch execution would likely fail on malformed data, explicit validation could provide clearer error messages.


304-309: buildTopUpTransactionsForEstimation is a redundant wrapper.

This function simply delegates to buildTopUpTransactions without any additional logic. Consider exporting buildTopUpTransactions directly or adding a comment explaining why the indirection exists (e.g., future extensibility).

src/apps/pulse/components/Onboarding/PreviewTopUp.tsx (1)

639-691: Step rendering uses array index as key.

Line 640 uses index as the React key for step items. Since the steps array is dynamically generated based on conditions, the order and content can change, making index-based keys potentially problematic for React's reconciliation.

Consider using a stable identifier:

-          {steps.map((step, index) => (
-            <div key={index}>
+          {steps.map((step) => (
+            <div key={step.title}>
src/apps/pulse/components/App/HomeScreen.tsx (1)

266-312: USDC preselection effect has redundant condition check.

The effect filters portfolioTokens to find USDC tokens and selects the one with the highest balance. The logic is correct, but walletPortfolioData?.result?.data?.assets check at line 272 seems unnecessary since portfolioTokens is already derived from walletPortfolioData elsewhere.

Consider simplifying:

     if (
       onboardingScreen === 'topup' &&
       totalUsdcBalance > 0 &&
-      !topupToken &&
-      walletPortfolioData?.result?.data?.assets
+      !topupToken
     ) {
src/apps/pulse/components/Onboarding/TopUpScreen.tsx (4)

59-66: isSelectedTokenUSDC function is redefined on every render.

This function is called multiple times throughout the component but is recreated on each render. Consider memoizing it with useCallback or moving the logic inline where needed.

-  const isSelectedTokenUSDC = (): boolean => {
+  const isSelectedTokenUSDC = useCallback((): boolean => {
     if (!selectedToken) return false;
     return STABLE_CURRENCIES.some(
       (stable) =>
         stable.chainId === selectedToken.chainId &&
         stable.address.toLowerCase() === selectedToken.address.toLowerCase()
     );
-  };
+  }, [selectedToken]);

137-199: Quote fetching effect lacks debouncing.

The effect fetches a new quote every time amount changes. Rapid typing could trigger many API calls. Consider adding debouncing to reduce unnecessary network requests.

Example with a simple debounce:

useEffect(() => {
  const timeoutId = setTimeout(() => {
    fetchSellOffer();
  }, 500); // 500ms debounce

  return () => clearTimeout(timeoutId);
}, [amount, selectedToken, ...]);

214-234: Unnecessary eslint-disable comment.

Line 214 has @typescript-eslint/no-use-before-define disable, but handleUsdAmountChange is defined before it's used in the JSX. This comment appears to be a leftover.

Remove the unnecessary eslint-disable:

-  // eslint-disable-next-line @typescript-eslint/no-use-before-define
   const handleUsdAmountChange = (e: React.ChangeEvent<HTMLInputElement>) => {

640-675: Button disabled logic is duplicated between disabled and style.

The same condition is used for both the disabled attribute and the background color style. Consider extracting this to a variable for DRY compliance.

+  const isButtonDisabled =
+    parseFloat(amount) <= 0 ||
+    isPortfolioLoading ||
+    portfolioTokens.length === 0 ||
+    isLoadingQuote ||
+    (!isSelectedTokenUSDC() && !sellOffer && parseFloat(amount) > 0) ||
+    !!relayError ||
+    !!error;
+
   <button
     onClick={handleTopUp}
     type="button"
     className="..."
     style={{
-      backgroundColor:
-        parseFloat(amount) <= 0 ||
-        ...
-          ? '#29292F'
-          : '#8A77FF',
+      backgroundColor: isButtonDisabled ? '#29292F' : '#8A77FF',
     }}
-    disabled={...}
+    disabled={isButtonDisabled}
   >
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 654bd55 and b474f9b.

⛔ Files ignored due to path filters (6)
  • src/apps/pulse/assets/circle-arrow-down.svg is excluded by !**/*.svg
  • src/apps/pulse/assets/gas-tank-icon.svg is excluded by !**/*.svg
  • src/apps/pulse/assets/install-7702-icon.svg is excluded by !**/*.svg
  • src/apps/pulse/assets/onboarding-gas-tank-icon.svg is excluded by !**/*.svg
  • src/apps/pulse/assets/onboarding-install-modules-icon.svg is excluded by !**/*.svg
  • src/apps/pulse/assets/onboarding-swap-icon.svg is excluded by !**/*.svg
📒 Files selected for processing (19)
  • src/apps/pulse/components/App/AppWrapper.tsx (3 hunks)
  • src/apps/pulse/components/App/HomeScreen.tsx (7 hunks)
  • src/apps/pulse/components/App/tests/AppWrapper.test.tsx (3 hunks)
  • src/apps/pulse/components/App/tests/HomeScreen.test.tsx (4 hunks)
  • src/apps/pulse/components/Onboarding/OnboardingWelcome.tsx (1 hunks)
  • src/apps/pulse/components/Onboarding/PreviewTopUp.tsx (1 hunks)
  • src/apps/pulse/components/Onboarding/TopUpScreen.tsx (1 hunks)
  • src/apps/pulse/components/Onboarding/tests/OnboardingWelcome.test.tsx (1 hunks)
  • src/apps/pulse/components/Onboarding/tests/PreviewTopUp.integration.test.tsx (1 hunks)
  • src/apps/pulse/components/Onboarding/tests/TopUpScreen.integration.test.tsx (1 hunks)
  • src/apps/pulse/components/Search/PortfolioTokenList.tsx (3 hunks)
  • src/apps/pulse/components/Search/Search.tsx (3 hunks)
  • src/apps/pulse/hooks/useGasEstimationTopUp.ts (1 hunks)
  • src/apps/pulse/hooks/useGasTankBalance.ts (1 hunks)
  • src/apps/pulse/hooks/useIntentSdk.ts (3 hunks)
  • src/apps/pulse/hooks/useTopUp.ts (1 hunks)
  • src/apps/pulse/hooks/useTotalUsdcBalance.ts (1 hunks)
  • src/components/BottomMenuModal/SendModal/SendModalBatchesTabView.tsx (1 hunks)
  • src/providers/GlobalTransactionsBatchProvider.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (7)
📓 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-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/Search/Search.tsx
  • src/apps/pulse/components/App/AppWrapper.tsx
  • src/apps/pulse/components/Search/PortfolioTokenList.tsx
  • src/apps/pulse/components/App/HomeScreen.tsx
📚 Learning: 2025-11-21T13:10:33.422Z
Learnt from: aldin4u
Repo: pillarwallet/x PR: 461
File: src/apps/pulse/components/Search/MarketList.tsx:10-15
Timestamp: 2025-11-21T13:10:33.422Z
Learning: In the Pulse app's MarketList component (src/apps/pulse/components/Search/MarketList.tsx), markets should display liquidity (not price) in the right-hand column. This is per the design specification.

Applied to files:

  • src/apps/pulse/components/Search/Search.tsx
  • src/apps/pulse/components/Search/PortfolioTokenList.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/hooks/useIntentSdk.ts
  • src/apps/pulse/hooks/useTopUp.ts
  • src/apps/pulse/hooks/useTotalUsdcBalance.ts
  • src/apps/pulse/hooks/useGasEstimationTopUp.ts
  • src/apps/pulse/components/Onboarding/TopUpScreen.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/pulse/hooks/useIntentSdk.ts
  • src/apps/pulse/components/App/AppWrapper.tsx
  • src/apps/pulse/components/App/tests/HomeScreen.test.tsx
  • src/apps/pulse/hooks/useGasTankBalance.ts
  • src/apps/pulse/components/App/tests/AppWrapper.test.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/pulse/components/App/tests/HomeScreen.test.tsx
  • src/apps/pulse/hooks/useTopUp.ts
  • src/apps/pulse/components/App/tests/AppWrapper.test.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/Onboarding/TopUpScreen.tsx
  • src/apps/pulse/components/App/HomeScreen.tsx
🧬 Code graph analysis (7)
src/apps/pulse/components/Onboarding/tests/PreviewTopUp.integration.test.tsx (1)
src/apps/pulse/components/Onboarding/PreviewTopUp.tsx (1)
  • PreviewTopUp (73-784)
src/apps/pulse/components/Onboarding/tests/OnboardingWelcome.test.tsx (1)
src/apps/pulse/components/Onboarding/OnboardingWelcome.tsx (1)
  • OnboardingWelcome (13-104)
src/apps/pulse/components/App/AppWrapper.tsx (1)
src/apps/pulse/types/tokens.ts (1)
  • SelectedToken (1-10)
src/apps/pulse/components/Onboarding/tests/TopUpScreen.integration.test.tsx (1)
src/apps/pulse/components/Onboarding/TopUpScreen.tsx (1)
  • TopUpScreen (33-679)
src/apps/pulse/hooks/useTopUp.ts (5)
src/apps/pulse/types/tokens.ts (1)
  • SelectedToken (1-10)
src/services/tokensData.ts (1)
  • Token (19-29)
src/apps/pulse/hooks/useRelaySell.ts (1)
  • useRelaySell (54-1610)
src/hooks/useTransactionDebugLogger.tsx (1)
  • useTransactionDebugLogger (1-15)
src/utils/eip7702Authorization.ts (1)
  • getEIP7702AuthorizationIfNeeded (16-95)
src/apps/pulse/components/Onboarding/TopUpScreen.tsx (6)
src/apps/pulse/types/tokens.ts (1)
  • SelectedToken (1-10)
src/services/tokensData.ts (1)
  • PortfolioToken (31-34)
src/apps/pulse/hooks/useRelaySell.ts (1)
  • useRelaySell (54-1610)
src/apps/pulse/constants/tokens.ts (1)
  • STABLE_CURRENCIES (13-15)
src/apps/pulse/components/Onboarding/PreviewTopUp.tsx (1)
  • PreviewTopUp (73-784)
src/utils/blockchain.ts (1)
  • getLogoForChainId (165-199)
src/apps/pulse/components/App/HomeScreen.tsx (7)
src/apps/pulse/types/tokens.ts (1)
  • SelectedToken (1-10)
src/apps/pulse/hooks/useGasTankBalance.ts (1)
  • useGasTankBalance (22-93)
src/apps/pulse/hooks/useTotalUsdcBalance.ts (1)
  • useTotalUsdcBalance (10-35)
src/services/tokensData.ts (1)
  • chainNameToChainIdTokensData (234-255)
src/apps/pulse/constants/tokens.ts (1)
  • STABLE_CURRENCIES (13-15)
src/apps/pulse/components/Onboarding/OnboardingWelcome.tsx (1)
  • OnboardingWelcome (13-104)
src/apps/pulse/components/Onboarding/TopUpScreen.tsx (1)
  • TopUpScreen (33-679)
⏰ 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: Cloudflare Pages: pillarx-debug
🔇 Additional comments (20)
src/apps/pulse/components/Search/PortfolioTokenList.tsx (1)

30-30: LGTM! Clean implementation of stable coin filtering.

The new includeStableCoins prop with a default value of false maintains backward compatibility while enabling the onboarding flow to include stable currencies when needed. The filtering logic is clear and correct.

Also applies to: 40-40, 53-77

src/apps/pulse/hooks/useTotalUsdcBalance.ts (1)

1-35: LGTM! Well-structured custom hook.

The implementation correctly uses useMemo to optimize the balance calculation, properly handles undefined input, and has the correct dependency array. The explicit return type interface enhances type safety.

src/apps/pulse/components/Search/Search.tsx (1)

51-51: LGTM! Clean prop threading for onboarding flow.

The isSearchingFromTopup prop is cleanly threaded through the component and semantically mapped to includeStableCoins when passed to PortfolioTokenList. The implementation integrates well with the existing search flow.

Also applies to: 77-77, 452-452

src/apps/pulse/components/App/tests/HomeScreen.test.tsx (1)

9-9: LGTM! Test setup properly updated for onboarding flow.

The test correctly mocks useGasTankBalance and adds the new onboarding-related props to mockProps. The mock configuration ensures existing tests continue to pass by returning a non-zero gas tank balance, which displays the main interface instead of the onboarding flow.

Also applies to: 33-35, 47-51, 90-93

src/apps/pulse/components/App/AppWrapper.tsx (1)

17-21: LGTM! Clean state management for onboarding flow.

The new state variables are well-typed and semantically named. The onboardingScreen uses a clear discriminated union type ('welcome' | 'topup' | null), and the props are correctly threaded to the appropriate child components.

Also applies to: 80-80, 92-96

src/apps/pulse/components/Onboarding/tests/OnboardingWelcome.test.tsx (1)

1-187: Well-structured test suite with comprehensive coverage.

The test suite covers rendering, balance formatting, button interactions, loading states, and accessibility. Good use of userEvent.setup() for async interactions and proper beforeEach cleanup.

src/apps/pulse/components/Onboarding/OnboardingWelcome.tsx (1)

79-100: LGTM - Button implementation is well-structured.

The button correctly handles loading state with spinner, disabled state, and has proper type="button" and data-testid attributes for testing.

src/apps/pulse/components/Onboarding/tests/TopUpScreen.integration.test.tsx (2)

68-162: Good test coverage for TopUpScreen.

The test suite covers component rendering, user interactions, loading states, token selection, and accessibility. The structure mirrors the other onboarding test files for consistency.


12-18: I'll verify whether the mocks for these hooks are actually incomplete and cause test failures by examining the TopUpScreen component and the actual hook implementations.
<call_capability>
<capability_name>shell

#!/bin/bash

First, find TopUpScreen.tsx to see if it uses these hooks

find . -name "TopUpScreen.tsx" -type f | head -5

</call_capability>

src/apps/pulse/hooks/useIntentSdk.ts (2)

152-181: Good addition of moduleCheckError to prevent retry loops.

The moduleCheckError state prevents repeated failed module checks, avoiding potential infinite loops or excessive API calls after an error. The integration with the useEffect dependencies is correct.


179-181: Verify transactionDebugLog stability.

The eslint-disable comment is used, but transactionDebugLog from useTransactionDebugLogger is used inside the effect. Ensure this function reference is stable (memoized) to avoid unexpected re-runs.

src/apps/pulse/hooks/useGasEstimationTopUp.ts (1)

1-35: LGTM! Clean hook initialization with proper state management.

The hook setup correctly uses refs for preventing concurrent estimations and properly initializes state for gas estimation tracking.

src/apps/pulse/hooks/useTopUp.ts (2)

70-77: Floating-point division for token amount calculation.

The token amount calculation uses floating-point arithmetic which can lead to precision issues with very small or very large token values. Based on learnings, the team has accepted this trade-off for the Pulse app.


261-277: Success path returns before setIsLoading(false) is called in finally block.

Lines 272-273 call setIsLoading(false) and return before reaching the finally block. While this works correctly, the pattern is inconsistent—the success path manually resets loading state while the error path relies on line 294. This is fine but worth noting for consistency.

src/apps/pulse/components/Onboarding/PreviewTopUp.tsx (1)

454-458: Polling interval not cleared on component unmount during active polling.

The polling setInterval at line 454 returns a cleanup function, but if isPollingActive becomes false before unmount (e.g., via closeTransactionStatus), the interval is already cleared. This is correct. However, verify that all paths that stop polling also clear any pending timeouts from updateStatusWithDelay.

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

220-223: Using useRef for localStorage-backed state.

The hasCompletedOnboardingRef pattern correctly persists onboarding completion across rerenders without causing effect re-runs. This is a valid pattern for derived state from localStorage.


240-264: Race condition between loading states and onboarding decision.

The effect depends on isGasTankLoading, walletPortfolioData, and portfolioTokens.length. However, there's a potential timing issue: if gasTankBalance is 0 initially but updates to a positive value after the effect runs, the onboarding screen might briefly flash.

The condition at line 253 (gasTankBalance > 0 || hasCompletedOnboardingRef.current) handles this by dismissing onboarding when balance becomes positive, which is correct.


1069-1092: Loading state shows onboarding welcome during initial load.

Lines 1077-1091 show OnboardingWelcome with loading state when gasTankBalance === 0 and onboarding hasn't been completed, even before portfolio data is fully loaded. This prevents flashing the main UI, which is the intended behavior per the comment.

src/apps/pulse/components/Onboarding/TopUpScreen.tsx (2)

107-135: Missing isSelectedTokenUSDC in dependency array causes stale closures.

The useEffect at line 107 uses isSelectedTokenUSDC() but doesn't include it in the dependency array. Since isSelectedTokenUSDC depends on selectedToken, and selectedToken is in the deps, this works by coincidence. However, if isSelectedTokenUSDC were memoized, this could become a bug.

The eslint-disable comment at line 134 suppresses the warning, but consider including the function explicitly if memoized.


295-342: handleTopUp validation logic is comprehensive.

The validation covers all edge cases: empty amount, no token, minimum allocation, non-USDC without quote, and balance check. This is well-structured.

<span className="text-sm font-normal text-white">
Universal Gas Tank: $
{gasTankBalance.toLocaleString('en-US', {
minimumFractionDigits: 2,
Copy link
Collaborator

Choose a reason for hiding this comment

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

why min and max are the same?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

if its 2 then it will display 2.00 like in design thats why

Copy link
Collaborator

Choose a reason for hiding this comment

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

why not doing a toFixed(2) ?

console.error(errorMessage);
// Clean up the batch on error
const { selectedToken } = params;
const batchName = `pulse-topup-batch-${selectedToken.chainId}`;
Copy link
Collaborator

Choose a reason for hiding this comment

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

I noticed that the pulse-topup-batch is used in useGasEstimation too

@cloudflare-workers-and-pages
Copy link

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

Deploying pillarx-debug with  Cloudflare Pages  Cloudflare Pages

Latest commit: f5b9058
Status: ✅  Deploy successful!
Preview URL: https://c6fccf2a.pillarx-debug.pages.dev
Branch Preview URL: https://pro-3733-onboardingflow.pillarx-debug.pages.dev

View logs

@github-actions github-actions bot temporarily deployed to Preview (PRO-3733-OnboardingFlow) November 26, 2025 12:03 Inactive
@cloudflare-workers-and-pages
Copy link

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

Deploying x with  Cloudflare Pages  Cloudflare Pages

Latest commit: f5b9058
Status: ✅  Deploy successful!
Preview URL: https://9d26d085.x-e62.pages.dev
Branch Preview URL: https://pro-3733-onboardingflow.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: 2

🧹 Nitpick comments (1)
src/apps/pulse/components/Onboarding/PreviewTopUp.tsx (1)

145-160: Avoid dummy SelectedToken when selectedToken is null

Right now the hook is called with a fabricated SelectedToken when selectedToken is null and relies on isPaused to prevent real work. It works, but it’s brittle and can hide bugs if useGasEstimationTopUp ever changes.

Consider updating useGasEstimationTopUp to accept SelectedToken | null (and early‑return when null), or only calling the hook when selectedToken is defined, instead of passing a dummy object.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b474f9b and 021a550.

📒 Files selected for processing (6)
  • src/apps/pulse/components/Onboarding/OnboardingWelcome.tsx (1 hunks)
  • src/apps/pulse/components/Onboarding/PreviewTopUp.tsx (1 hunks)
  • src/apps/pulse/components/Onboarding/TopUpScreen.tsx (1 hunks)
  • src/apps/pulse/components/Onboarding/tests/TopUpScreen.integration.test.tsx (1 hunks)
  • src/apps/pulse/hooks/useGasEstimationTopUp.ts (1 hunks)
  • src/apps/pulse/hooks/useIntentSdk.ts (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/apps/pulse/hooks/useGasEstimationTopUp.ts
  • src/apps/pulse/components/Onboarding/OnboardingWelcome.tsx
  • src/apps/pulse/components/Onboarding/tests/TopUpScreen.integration.test.tsx
🧰 Additional context used
🧠 Learnings (3)
📚 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/hooks/useIntentSdk.ts
  • src/apps/pulse/components/Onboarding/PreviewTopUp.tsx
  • src/apps/pulse/components/Onboarding/TopUpScreen.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/pulse/hooks/useIntentSdk.ts
📚 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/Onboarding/PreviewTopUp.tsx
  • src/apps/pulse/components/Onboarding/TopUpScreen.tsx
🧬 Code graph analysis (2)
src/apps/pulse/components/Onboarding/PreviewTopUp.tsx (9)
src/apps/pulse/types/types.ts (1)
  • TransactionStatusState (1-5)
src/apps/pulse/types/tokens.ts (1)
  • SelectedToken (1-10)
src/services/tokensData.ts (1)
  • PortfolioToken (31-34)
src/apps/pulse/hooks/useIntentSdk.ts (1)
  • useIntentSdk (24-230)
src/apps/pulse/hooks/useTopUp.ts (1)
  • useTopUp (30-319)
src/apps/pulse/hooks/useGasEstimationTopUp.ts (1)
  • useGasEstimationTopUp (16-196)
src/apps/pulse/constants/tokens.ts (1)
  • STABLE_CURRENCIES (13-15)
src/utils/number.tsx (2)
  • formatExponentialSmallNumber (119-149)
  • limitDigitsNumber (42-69)
src/services/userOpStatus.ts (1)
  • getUserOperationStatus (3-44)
src/apps/pulse/components/Onboarding/TopUpScreen.tsx (5)
src/apps/pulse/types/tokens.ts (1)
  • SelectedToken (1-10)
src/services/tokensData.ts (1)
  • PortfolioToken (31-34)
src/apps/pulse/hooks/useRelaySell.ts (1)
  • useRelaySell (54-1610)
src/apps/pulse/constants/tokens.ts (1)
  • STABLE_CURRENCIES (13-15)
src/utils/blockchain.ts (1)
  • getLogoForChainId (165-199)
⏰ 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). (4)
  • GitHub Check: unit-tests
  • GitHub Check: lint
  • GitHub Check: Cloudflare Pages: pillarx-debug
  • GitHub Check: build
🔇 Additional comments (1)
src/apps/pulse/hooks/useIntentSdk.ts (1)

201-214: Guard on intentSdk and payingTokens looks correct

The early return when !intentSdk || !payingTokens?.length prevents undefined chainId access and addresses the previous empty-array risk around payingTokens[0].

Comment on lines +201 to +271
let additionalTransactions: Transactions[] = [];
// Step 2: Install modules if not installed and not EIP-7702 eligible
if (!areModulesInstalled && !isEIP7702Eligible) {
try {
additionalTransactions = await getEnablePulseTradingTransactions();
} catch (moduleErr) {
console.error('Module installation failed:', moduleErr);
setSetupError('Failed to install trading modules. Please try again.');
setIsWaitingForSignature(false);
setIsLoading(false);
return;
}
}

// Step 3: Execute the top-up transaction
const userOperationHash = await executeTopUp({
selectedToken,
amount,
allocateAmount,
sellOffer,
userPortfolio,
additionalTransactions,
});

if (userOperationHash) {
setIsWaitingForSignature(false);
setIsLoading(false);

// Ensure we have a valid gas fee string for the transaction status
const gasFeeString =
gasCostNative && nativeTokenSymbol
? `≈ ${formatExponentialSmallNumber(limitDigitsNumber(parseFloat(gasCostNative)))} ${nativeTokenSymbol}`
: '≈ 0.00';

// Set transaction data and show transaction status
setUserOpHash(userOperationHash);
setTransactionGasFee(gasFeeString);
setCurrentTransactionStatus('Starting Transaction');
setErrorDetails('');
setSubmittedAt(undefined);
setPendingCompletedAt(undefined);
setBlockchainTxHash(undefined);
setHasSeenSuccess(false);
hasSeenSuccessRef.current = false;
blockchainTxHashRef.current = null;
failureGraceExpiryRef.current = null;
statusStartTime.current = Date.now();
setShowTransactionStatus(true);
setIsPollingActive(true);

// Don't clear onboarding screen - keep showing transaction status within onboarding
return;
}

throw new Error('Failed to execute top-up transaction');
} catch (err) {
console.error('Failed to execute top-up:', err);
// Check if the error is a user rejection
if (
err instanceof Error &&
err.message.includes('User rejected the request')
) {
setIsTransactionRejected(true);
setIsWaitingForSignature(false);
} else {
// Other errors will be handled by the useTopUp hook's error state
setIsWaitingForSignature(false);
}
setIsLoading(false);
}
};
Copy link
Contributor

@coderabbitai coderabbitai bot Nov 26, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Module-installation errors are effectively ignored; top-up may proceed without modules

In handleConfirm, the getEnablePulseTradingTransactions call is wrapped in a try/catch:

let additionalTransactions: Transactions[] = [];
if (!areModulesInstalled && !isEIP7702Eligible) {
  try {
    additionalTransactions = await getEnablePulseTradingTransactions();
  } catch (moduleErr) {
    // setSetupError + abort
  }
}

However, getEnablePulseTradingTransactions never throws; it logs and returns [] on failure (including when intentSdk or payingTokens are missing). That means:

  • The catch block is effectively dead.
  • When modules are not installed and the helper fails, you still proceed to executeTopUp with additionalTransactions = [], so the flow may run without module installation even though areModulesInstalled === false.

This can cause confusing failures deeper in the top-up flow instead of surfacing a clear “Failed to install trading modules” error.

Consider treating an empty result as a failure when modules are expected, e.g.:

      let additionalTransactions: Transactions[] = [];
      if (!areModulesInstalled && !isEIP7702Eligible) {
        try {
-          additionalTransactions = await getEnablePulseTradingTransactions();
+          additionalTransactions = await getEnablePulseTradingTransactions();
+          if (!additionalTransactions.length) {
+            throw new Error('No module installation transactions returned');
+          }
        } catch (moduleErr) {
          console.error('Module installation failed:', moduleErr);
          setSetupError('Failed to install trading modules. Please try again.');
          setIsWaitingForSignature(false);
          setIsLoading(false);
          return;
        }
      }

Alternatively, have getEnablePulseTradingTransactions throw on real errors instead of returning [], so the existing try/catch works as intended.

🤖 Prompt for AI Agents
In src/apps/pulse/components/Onboarding/PreviewTopUp.tsx around lines 201-271,
the code assumes getEnablePulseTradingTransactions will throw on failure but it
returns an empty array instead, so module-installation failures are ignored and
the flow proceeds; after awaiting getEnablePulseTradingTransactions, check if
areModulesInstalled was false and the returned additionalTransactions array is
empty (or missing required transactions) and treat that as an installation
failure by logging the error, calling setSetupError('Failed to install trading
modules. Please try again.'), setting setIsWaitingForSignature(false) and
setIsLoading(false) and returning early (or rethrowing) so the top-up does not
proceed without modules; alternatively, update getEnablePulseTradingTransactions
to throw on unrecoverable errors and keep the existing try/catch — choose one
approach and implement it consistently.

Copy link
Collaborator

Choose a reason for hiding this comment

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

@vignesha22 please check this one if CodeRabbit is right or not?

Copy link
Contributor

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

from the getEnablePulseTradingTransactions it would test if the modules is installed or not and then only send which are all the modules to be installed so if there is no installation needed then it would respond with empty array so this is not a problem though we can add and see why the check made was false but as far as user transaction is concerned this wont affect their flow

@github-actions github-actions bot temporarily deployed to Preview (PRO-3733-OnboardingFlow) November 27, 2025 06:43 Inactive
@vignesha22 vignesha22 requested review from IAmKio and RanaBug November 27, 2025 06:48
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/apps/pulse/hooks/useTopUp.ts (1)

285-300: Expose more specific errors where safe instead of a generic fallback

In the catch block, you log the underlying error but always surface 'Something went wrong. Please try again later' to the hook consumer. Many errors in buildTopUpTransactions already have user-friendly copy (e.g. missing paymaster URL, unsupported USDC, invalid token price), but those are hidden behind this generic message.

Consider selectively passing through known-safe messages (e.g. configuration or validation issues) to improve UX, while still masking low-level or sensitive failures.

src/apps/pulse/components/Onboarding/TopUpScreen.tsx (1)

139-189: Clear stale sellOffer on all early exits in the quote effect

In the quote-fetching effect, several early returns (!isRelayInitialized, missing portfolioToken/price) don’t reset sellOffer, so a previous quote can linger when prerequisites are no longer met and the UI will treat it as valid.

    const fetchSellOffer = async () => {
      // Only fetch for non-USDC tokens
      if (
        !selectedToken ||
        isSelectedTokenUSDC() ||
        !amount ||
        parseFloat(amount) <= 0
      ) {
        setSellOffer(null);
        return;
      }

      if (!isRelayInitialized) {
+        setSellOffer(null);
        return;
      }

      // Find the token in portfolio to get actual balance and price
      const portfolioToken = portfolioTokens.find(
        (token) =>
          token.contract.toLowerCase() === selectedToken.address.toLowerCase()
      );

      if (!portfolioToken || !portfolioToken.balance || !portfolioToken.price) {
+        setSellOffer(null);
        return;
      }

This keeps sellOffer aligned with the current token, amount, and portfolio state.

src/apps/pulse/components/Onboarding/PreviewTopUp.tsx (1)

203-215: Ensure module-installation failures don’t silently proceed

handleConfirm assumes getEnablePulseTradingTransactions will throw on failure, but if it still returns an empty array on error, the flow will continue without installing modules even when areModulesInstalled === false.

To be safe, treat an empty result as a failure when modules are expected:

      let additionalTransactions: Transactions[] = [];
      // Step 2: Install modules if not installed and not EIP-7702 eligible
      if (!areModulesInstalled && !isEIP7702Eligible) {
        try {
-          additionalTransactions = await getEnablePulseTradingTransactions();
+          additionalTransactions = await getEnablePulseTradingTransactions();
+          if (!additionalTransactions.length) {
+            throw new Error('No module installation transactions returned');
+          }
        } catch (moduleErr) {

Or ensure getEnablePulseTradingTransactions now throws on unrecoverable errors so this try/catch is effective.

🧹 Nitpick comments (4)
src/apps/pulse/hooks/useTopUp.ts (1)

60-156: Type the transactions array to avoid any and ensure consistency

transactions is currently an untyped array that mixes sell and paymaster deposit transactions. This makes it easy to push malformed objects and only discover issues at runtime when adding to the batch.

Consider introducing a dedicated type (e.g. TopUpTransaction) and using it consistently:

-interface Transactions {
-  action: string;
-  calldata: string;
-  target: string;
-}
+interface Transactions {
+  action: string;
+  calldata: string;
+  target: string;
+}
+
+interface TopUpTransaction {
+  chainId: number;
+  to: string;
+  value: string;
+  data: string;
+}
...
-      const transactions = [];
+      const transactions: TopUpTransaction[] = [];

This will also give you stronger guarantees that tx.chainId, tx.to, tx.value, and tx.data exist when batching.

src/apps/pulse/components/Onboarding/TopUpScreen.tsx (2)

92-105: Reset amount and errors on token change to avoid stale state

When selectedToken changes, you update allocateAmount but keep the previous amount, error, and sellOffer. That can leave a non-zero USD amount and old validation/quote tied to the prior token.

Consider also resetting these when the token changes:

  useEffect(() => {
    if (selectedToken && portfolioTokens.length > 0) {
      ...
      if (portfolioToken && portfolioToken.balance && portfolioToken.price) {
        const tokenBalanceUsd = portfolioToken.balance * portfolioToken.price;
        setAllocateAmount(parseFloat(tokenBalanceUsd.toFixed(2)));
      }
    }
+   setAmount('');
+   setError(null);
+   setSellOffer(null);
  }, [selectedToken, portfolioTokens]);

This mirrors the behavior in the Sell flow where inputs are cleared on token switch so liquidity/validation state doesn’t go stale. Based on learnings, this keeps onboarding consistent with existing UX.


515-541: Avoid recomputing getSelectedTokenBalance() multiple times in render

Within the wallet balance display you call getSelectedTokenBalance() up to three times, each doing a portfolio lookup. This is minor but noisy and slightly wasteful.

You can compute it once and reuse:

-                {selectedToken ? (
-                  <>
-                    {getSelectedTokenBalance().balance.toFixed(4)}{' '}
-                    {getSelectedTokenBalance().symbol}
-                    <span className="text-white/50">
-                      {' '}
-                      (${getSelectedTokenBalance().usdValue.toFixed(2)})
-                    </span>
-                  </>
-                ) : (
+                {selectedToken ? (() => {
+                  const { balance, symbol, usdValue } = getSelectedTokenBalance();
+                  return (
+                    <>
+                      {balance.toFixed(4)} {symbol}
+                      <span className="text-white/50">
+                        {' '}(${usdValue.toFixed(2)})
+                      </span>
+                    </>
+                  );
+                })() : (
                   `$${initialBalance.toFixed(2)}`
                 )}
src/apps/pulse/components/Onboarding/PreviewTopUp.tsx (1)

543-558: Clarify swap step description to avoid mixing USD and token units

The “Swap to USDC” step currently uses:

description: `$${parseFloat(amount).toFixed(2)} ${selectedToken.symbol} for ${usdcReceived.toFixed(2)} USDC`,

This reads like “$10 ETH”, which conflates USD and token units.

Consider explicitly labeling it as USD value or showing the token amount:

-        description: `$${parseFloat(amount).toFixed(2)} ${selectedToken.symbol} for ${usdcReceived.toFixed(2)} USDC`,
+        description: `$${parseFloat(amount).toFixed(2)} worth of ${selectedToken.symbol} for ${usdcReceived.toFixed(2)} USDC`,

or compute and display the token quantity similar to TopUpScreen for consistency.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 021a550 and 2635cfc.

⛔ Files ignored due to path filters (2)
  • src/apps/pulse/assets/arrow-down-onboarding.svg is excluded by !**/*.svg
  • src/apps/pulse/assets/back-arrow.svg is excluded by !**/*.svg
📒 Files selected for processing (4)
  • src/apps/pulse/components/Onboarding/PreviewTopUp.tsx (1 hunks)
  • src/apps/pulse/components/Onboarding/TopUpScreen.tsx (1 hunks)
  • src/apps/pulse/hooks/useGasTankBalance.ts (1 hunks)
  • src/apps/pulse/hooks/useTopUp.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/apps/pulse/hooks/useGasTankBalance.ts
🧰 Additional context used
🧠 Learnings (3)
📚 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/Onboarding/PreviewTopUp.tsx
  • src/apps/pulse/components/Onboarding/TopUpScreen.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/Onboarding/PreviewTopUp.tsx
  • src/apps/pulse/components/Onboarding/TopUpScreen.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/pulse/hooks/useTopUp.ts
🧬 Code graph analysis (1)
src/apps/pulse/components/Onboarding/TopUpScreen.tsx (5)
src/apps/pulse/types/tokens.ts (1)
  • SelectedToken (1-10)
src/services/tokensData.ts (1)
  • PortfolioToken (31-34)
src/apps/pulse/constants/tokens.ts (1)
  • STABLE_CURRENCIES (13-15)
src/apps/pulse/components/Onboarding/PreviewTopUp.tsx (1)
  • PreviewTopUp (75-775)
src/utils/blockchain.ts (1)
  • getLogoForChainId (165-199)
⏰ 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). (4)
  • GitHub Check: lint
  • GitHub Check: unit-tests
  • GitHub Check: build
  • GitHub Check: Cloudflare Pages: pillarx-debug

Comment on lines +561 to +571
// Step 4: Deposit to Gas Tank
const depositAmount = isSelectedTokenUSDC()
? parseFloat(amount)
: allocateAmount;
steps.push({
icon: (
<img src={OnboardingGasTankIcon} alt="Gas Tank" className="w-9 h-9" />
),
title: 'Deposit to Gas Tank',
description: `$${depositAmount.toFixed(2)} USDC into Gas Tank`,
bgColor: 'bg-[rgba(255,54,108,0.1)]',
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

Use allocateAmount for USDC deposit step to reflect actual top-up

For USDC tokens, the deposit step uses parseFloat(amount) while the actual on-chain deposit uses allocateAmount (which factors in the USDC price). If USDC deviates from $1, the previewed $X USDC into Gas Tank may not match the real deposit.

-    const depositAmount = isSelectedTokenUSDC()
-      ? parseFloat(amount)
-      : allocateAmount;
+    const depositAmount = allocateAmount;

This keeps the preview aligned with the amount actually sent to the gas tank for both USDC and non-USDC paths.

📝 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
// Step 4: Deposit to Gas Tank
const depositAmount = isSelectedTokenUSDC()
? parseFloat(amount)
: allocateAmount;
steps.push({
icon: (
<img src={OnboardingGasTankIcon} alt="Gas Tank" className="w-9 h-9" />
),
title: 'Deposit to Gas Tank',
description: `$${depositAmount.toFixed(2)} USDC into Gas Tank`,
bgColor: 'bg-[rgba(255,54,108,0.1)]',
// Step 4: Deposit to Gas Tank
const depositAmount = allocateAmount;
steps.push({
icon: (
<img src={OnboardingGasTankIcon} alt="Gas Tank" className="w-9 h-9" />
),
title: 'Deposit to Gas Tank',
description: `$${depositAmount.toFixed(2)} USDC into Gas Tank`,
bgColor: 'bg-[rgba(255,54,108,0.1)]',
🤖 Prompt for AI Agents
In src/apps/pulse/components/Onboarding/PreviewTopUp.tsx around lines 561 to
571, the depositAmount is set to parseFloat(amount) when USDC is selected which
causes the preview to mismatch the actual on-chain deposit that uses
allocateAmount; update the assignment so the USDC branch uses allocateAmount
(i.e., replace the ternary to use allocateAmount for USDC instead of
parseFloat(amount) or simply set depositAmount = allocateAmount) so the
previewed “$X USDC into Gas Tank” matches the real deposit.

@vignesha22 vignesha22 self-assigned this Nov 27, 2025
@github-actions github-actions bot temporarily deployed to Preview (PRO-3733-OnboardingFlow) November 27, 2025 09:41 Inactive
@github-actions github-actions bot temporarily deployed to Preview (PRO-3733-OnboardingFlow) November 27, 2025 09:41 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 (2)
src/apps/pulse/components/Onboarding/PreviewTopUp.tsx (2)

203-215: Module-installation errors are still effectively ignored.

The getEnablePulseTradingTransactions function returns an empty array on failure rather than throwing. This means the catch block (lines 208-214) is effectively unreachable, and when modules are not installed but the helper fails, the flow proceeds with additionalTransactions = [].

Consider treating an empty result as a failure when modules are expected:

      let additionalTransactions: Transactions[] = [];
      // Step 2: Install modules if not installed and not EIP-7702 eligible
      if (!areModulesInstalled && !isEIP7702Eligible) {
        try {
          additionalTransactions = await getEnablePulseTradingTransactions();
+          if (additionalTransactions.length === 0) {
+            throw new Error('No module installation transactions returned');
+          }
        } catch (moduleErr) {
          console.error('Module installation failed:', moduleErr);
          setSetupError('Failed to install trading modules. Please try again.');
          setIsWaitingForSignature(false);
          setIsLoading(false);
          return;
        }
      }

561-564: Preview may mismatch actual deposit for USDC tokens.

For USDC tokens, depositAmount uses parseFloat(amount) while the actual on-chain deposit uses allocateAmount. If USDC deviates from $1, the previewed amount won't match the real deposit.

Consider using allocateAmount consistently:

    // Step 4: Deposit to Gas Tank
-    const depositAmount = isSelectedTokenUSDC()
-      ? parseFloat(amount)
-      : allocateAmount;
+    const depositAmount = allocateAmount;
🧹 Nitpick comments (3)
src/apps/pulse/components/Onboarding/PreviewTopUp.tsx (1)

652-653: Consider using stable keys for step items.

Using array index as key (key={index}) is acceptable here since steps are computed fresh each render, but consider using a more descriptive key based on step title for better debugging in React DevTools:

-          {steps.map((step, index) => (
-            <div key={index}>
+          {steps.map((step, index) => (
+            <div key={step.title}>
src/apps/pulse/components/App/HomeScreen.tsx (2)

568-570: Unnecessary dependency in showTransactionStatus callback.

setOnboardingScreen was added to the dependency array, but it's not used within the showTransactionStatus callback body. This could cause unnecessary re-creation of the callback. If this was added to satisfy a lint rule, consider if it's actually needed.

  // eslint-disable-next-line react-hooks/exhaustive-deps
-    [sellToken, tokenAmount, sellOffer, isBuy, setOnboardingScreen]
+    [sellToken, tokenAmount, sellOffer, isBuy]

247-247: Consider extracting magic number for onboarding threshold.

The value 2 (gas tank balance threshold) is hardcoded in multiple places (lines 247, 249, 1076). Consider extracting it to a constant for maintainability.

// In constants file or at top of component
const MIN_GAS_TANK_BALANCE_FOR_TRADING = 2;

// Then use throughout
if (gasTankBalance < MIN_GAS_TANK_BALANCE_FOR_TRADING && onboardingScreen === null) {

Also applies to: 1076-1076

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2635cfc and 8b8ca52.

📒 Files selected for processing (2)
  • src/apps/pulse/components/App/HomeScreen.tsx (7 hunks)
  • src/apps/pulse/components/Onboarding/PreviewTopUp.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (5)
📚 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/Onboarding/PreviewTopUp.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/Onboarding/PreviewTopUp.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/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
📚 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/pulse/components/App/HomeScreen.tsx
🧬 Code graph analysis (1)
src/apps/pulse/components/App/HomeScreen.tsx (7)
src/apps/pulse/types/tokens.ts (1)
  • SelectedToken (1-10)
src/apps/pulse/hooks/useGasTankBalance.ts (1)
  • useGasTankBalance (22-98)
src/apps/pulse/hooks/useTotalUsdcBalance.ts (1)
  • useTotalUsdcBalance (10-35)
src/services/tokensData.ts (1)
  • chainNameToChainIdTokensData (234-255)
src/apps/pulse/constants/tokens.ts (1)
  • STABLE_CURRENCIES (13-15)
src/apps/pulse/components/Onboarding/OnboardingWelcome.tsx (1)
  • OnboardingWelcome (13-102)
src/apps/pulse/components/Onboarding/TopUpScreen.tsx (1)
  • TopUpScreen (34-670)
⏰ 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: Cloudflare Pages: pillarx-debug
  • GitHub Check: build
🔇 Additional comments (12)
src/apps/pulse/components/Onboarding/PreviewTopUp.tsx (5)

1-43: LGTM!

Imports and interface definitions are well-organized with clear separation of concerns (hooks, types, utils, constants, components, services, assets).


44-73: LGTM!

Type definitions are well-structured. The UserOpStatus type covers all expected status values, and the props interface correctly types optional parameters.


117-130: Verify actualBal and totalRaw hardcoded values.

The actualBal and totalRaw are hardcoded to '0' with a comment that balance is managed in the parent. Confirm that useIntentSdk doesn't rely on these values for any calculations or validations that could affect the top-up flow.


388-395: LGTM!

The cleanup effect properly clears the failure timeout on unmount, addressing the potential memory leak concern.


397-485: LGTM!

The polling effect is well-implemented with proper cleanup. The use of refs (hasSeenSuccessRef, blockchainTxHashRef, failureGraceExpiryRef) correctly avoids stale closure issues, and the intentional exclusion of certain dependencies is documented.

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

37-38: LGTM!

New imports for onboarding components, hooks, and constants are properly organized and follow the existing file structure.

Also applies to: 46-47, 53-54


97-119: LGTM!

The extended props interface and destructuring are well-typed and follow the existing component patterns.


220-237: LGTM!

The onboarding state management is well-implemented. Using a ref for hasCompletedOnboarding with localStorage persistence is appropriate for this use case, and the new balance hooks are cleanly integrated.


240-261: Onboarding may repeatedly trigger after completion.

The effect shows onboarding when gasTankBalance < 2 regardless of whether the user previously completed it. Per the discussion in past comments, this appears intentional ("should repeat when gas tank balance is less than $2 in order to top up again").

However, hasCompletedOnboardingRef is set in markOnboardingComplete but not checked here. This means the ref and localStorage persistence at lines 1118-1120 don't prevent re-triggering. If the current behavior is intentional, consider removing the unused persistence logic or documenting the intent more clearly.


263-309: LGTM!

The USDC token preselection logic is well-implemented. It correctly filters stable currencies, finds the one with the highest USD balance, and converts to the expected format. The guard at line 281 prevents the reduce from failing on an empty array.


311-326: LGTM!

The token sync effect and navigation handlers are clean and focused. The sync correctly resets the isSearchingFromTopup flag after token selection.


1103-1124: LGTM!

The TopUpScreen integration is well-implemented. The setSearching callback correctly configures the search mode for top-up, and markOnboardingComplete properly persists the completion state.

@github-actions github-actions bot temporarily deployed to Preview (PRO-3733-OnboardingFlow) November 27, 2025 10:24 Inactive
@github-actions github-actions bot temporarily deployed to Preview (PRO-3733-OnboardingFlow) November 27, 2025 15:47 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/pulse/components/Onboarding/PreviewTopUp.tsx (1)

520-585: Deposit-step text can drift from actual top-up amount (re-iterating prior comment)

For the “Deposit to Gas Tank” step, depositAmount is:

const depositAmount = isSelectedTokenUSDC()
  ? parseFloat(amount)
  : allocateAmount;

In the non‑USDC path, on‑chain deposit uses allocateAmount (from the quote), so the step description matches reality. For USDC, allocateAmount is derived from amount and the USDC price in TopUpScreen; if USDC ≠ $1.00, parseFloat(amount) and allocateAmount diverge, so the preview text may not reflect the actual deposit.

You can keep the preview consistent with the executed top‑up by always using allocateAmount:

-    const depositAmount = isSelectedTokenUSDC()
-      ? parseFloat(amount)
-      : allocateAmount;
+    const depositAmount = allocateAmount;

This reuses the same normalized “USDC to deposit” value for both display and execution.

🧹 Nitpick comments (2)
src/apps/pulse/components/Onboarding/tests/OnboardingWelcome.test.tsx (1)

7-187: Solid coverage; only minor test ergonomics nits

The suite exercises layout, formatting, loading state, interactions, and accessibility quite well and aligns with the component implementation. If you find these tests growing further, consider extracting a small renderOnboardingWelcome(overrides?) helper so you don’t repeat render(<OnboardingWelcome {...mockProps} />) and so per-test prop overrides are clearer, but this is optional and not blocking.

src/apps/pulse/components/Onboarding/TopUpScreen.tsx (1)

91-105: Consider resetting amount / validation when token changes

When selectedToken changes you recompute allocateAmount from the new portfolio balance, but amount and error remain from the previous token. If the new token has a lower USD balance, the UI will immediately show “Insufficient balance” or similar until the user clears the field.

Consider also resetting amount (and clearing error) in this effect when the token changes to avoid carrying over stale inputs/validation between tokens, similar to how the Sell flow resets the amount on token switch. Based on learnings, this keeps validation state in sync with the active token.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8b8ca52 and f849555.

📒 Files selected for processing (4)
  • src/apps/pulse/components/Onboarding/PreviewTopUp.tsx (1 hunks)
  • src/apps/pulse/components/Onboarding/TopUpScreen.tsx (1 hunks)
  • src/apps/pulse/components/Onboarding/tests/OnboardingWelcome.test.tsx (1 hunks)
  • src/apps/pulse/hooks/useTopUp.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/apps/pulse/hooks/useTopUp.ts
🧰 Additional context used
🧠 Learnings (2)
📚 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/Onboarding/PreviewTopUp.tsx
  • src/apps/pulse/components/Onboarding/TopUpScreen.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/Onboarding/PreviewTopUp.tsx
  • src/apps/pulse/components/Onboarding/TopUpScreen.tsx
🧬 Code graph analysis (1)
src/apps/pulse/components/Onboarding/tests/OnboardingWelcome.test.tsx (1)
src/apps/pulse/components/Onboarding/OnboardingWelcome.tsx (1)
  • OnboardingWelcome (13-102)
⏰ 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). (4)
  • GitHub Check: unit-tests
  • GitHub Check: lint
  • GitHub Check: build
  • GitHub Check: Cloudflare Pages: pillarx-debug
🔇 Additional comments (4)
src/apps/pulse/components/Onboarding/TopUpScreen.tsx (2)

139-205: Nice: stale sellOffer cleared on all early exits

The sell-offer effect now clears sellOffer and isLoadingQuote on every early return (no token, USDC, no amount, !isRelayInitialized, missing portfolioToken or price). That prevents a stale quote from sticking around when prerequisites aren’t met and addresses the earlier concern about outdated offers driving UI state.


639-668: I'm unable to complete the verification due to a repository clone failure. Let me explain what I need to verify this review comment properly:

Information needed to verify the review comment:

  1. Confirm useRelaySell usage scope: Does useRelaySell hook only execute/set errors when a non-USDC token is selected? Or does it run unconditionally?

  2. Token switching behavior: When a user switches from a non-USDC token (with a relayError set) to USDC, does the relayError state automatically reset? (The learnings show that in Sell.tsx, input amount resets when tokens change—need to know if TopUpScreen has similar token-switching cleanup)

  3. Current button disabled logic: Verify that the actual code at lines 639-668 matches the snippet provided in the review comment.

  4. Error state lifecycle: When/where is relayError cleared? Is it:

    • Cleared when token selection changes?
    • Only cleared when a new quote is successfully fetched?
    • Never cleared until component unmounts?

What I can assess from the provided context:

  • The logical concern in the review comment is sound: if relayError is set by a non-USDC flow, it shouldn't block a pure-USDC transaction.
  • The suggested fix is architecturally correct: gating relayError within !isSelectedTokenUSDC() condition prevents stale error state from blocking USDC flows.
  • The learnings support this pattern: similar token-switching logic exists in Sell.tsx.

Would you be able to provide:

  • Access to the repository, or
  • Confirmation of the behavior patterns above, or
  • Output from reading the TopUpScreen.tsx file directly?

This will allow me to complete the verification and generate the final rewritten review comment.

src/apps/pulse/components/Onboarding/PreviewTopUp.tsx (2)

141-163: Gas estimation guard now avoids non‑null assertion risk

Passing a dummy token object to useGasEstimationTopUp and gating with isPaused: isWaitingForSignature || isLoading || !selectedToken removes the previous selectedToken! non‑null assertion and keeps the hook from running until real data is available. This is a clean way to satisfy the hook’s typing without risking a runtime crash.


294-405: Timeout and polling cleanup look robust

updateStatusWithDelay now tracks the normal transition timeout via normalTimeoutIdRef, and the cleanup effect cancels both failure and normal timeouts on unmount. Combined with the isPollingActive guard and interval cleanup in the polling effect, this should avoid status updates after unmount and matches the earlier review feedback.

Comment on lines 221 to 251
const handleUsdAmountChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { value } = e.target;
// Only allow numbers and decimals
if (value === '' || /^\d*\.?\d*$/.test(value)) {
// Get selected token balance in USD
const { usdValue } = getSelectedTokenBalance();
const numValue = parseFloat(value) || 0;

// Don't allow more than the token's USD balance
if (numValue > usdValue && value !== '') {
setError(
`Amount cannot exceed your balance of $${usdValue.toFixed(2)}`
);
return;
}

setAmount(value);
setError(null); // Clear error when user types
}
};

// eslint-disable-next-line @typescript-eslint/no-use-before-define
const handleQuickAmountClick = (item: string) => {
if (item === 'MAX') {
// Use selected token's USD balance as max
const { usdValue } = getSelectedTokenBalance();
setAmount(usdValue.toFixed(2));
} else {
setAmount(item);
}
};
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

Manual input blocked when no token is selected (UX inconsistency)

handleUsdAmountChange compares against getSelectedTokenBalance().usdValue. When no token is selected this is 0, so any non-empty positive input immediately trips the “Amount cannot exceed your balance of $0.00” path and effectively prevents typing, while the quick-amount buttons happily set a positive amount that is later rejected in handleTopUp.

If you want users to be able to type an amount before choosing a token, consider skipping the numValue > usdValue check when !selectedToken, or switching the error in that case to “Please select a token” so the message matches what’s actually wrong.

🤖 Prompt for AI Agents
In src/apps/pulse/components/Onboarding/TopUpScreen.tsx around lines 221 to 251,
the USD input handler compares against getSelectedTokenBalance().usdValue which
is 0 when no token is selected, blocking any positive manual input; update
handleUsdAmountChange to first check whether a token is selected (e.g. if
(!selectedToken) { setError('Please select a token'); setAmount(value); return;
} OR skip the usdValue check when selectedToken is falsy) so typing is not
rejected, and also guard handleQuickAmountClick MAX handling to only use
usdValue when selectedToken exists (otherwise set an appropriate "Please select
a token" error or prevent setting the amount). Ensure setError is cleared
appropriately when a token is selected and input is valid.

- Ensure closeTransactionStatus properly closes the onboarding flow on transaction success by calling setOnboardingScreen(null)
- For failed transactions, return to preview screen via onBack() to allow retry
- Fix OnboardingWelcome test to match actual description text without "and enable trading" suffix

✅ Build passes
✅ All onboarding tests pass (42 tests)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@github-actions github-actions bot temporarily deployed to Preview (PRO-3733-OnboardingFlow) December 4, 2025 12:02 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/pulse/components/Onboarding/PreviewTopUp.tsx (1)

587-599: Previewed deposit amount for USDC can diverge from actual on-chain top-up

depositAmount uses parseFloat(amount) for USDC but allocateAmount for non‑USDC, while the actual deposit logic (in the hooks) uses allocateAmount for both. If USDC deviates from $1, the “$X USDC into Gas Tank” preview may not match the real deposit.

Since the team is already comfortable with float arithmetic for these flows, aligning the preview with allocateAmount is a small but important UX fix. Based on learnings, …

-    const depositAmount = isSelectedTokenUSDC()
-      ? parseFloat(amount)
-      : allocateAmount;
+    const depositAmount = allocateAmount;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4bf798a and f924fa9.

📒 Files selected for processing (2)
  • src/apps/pulse/components/Onboarding/PreviewTopUp.tsx (1 hunks)
  • src/apps/pulse/components/Onboarding/tests/OnboardingWelcome.test.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/apps/pulse/components/Onboarding/tests/OnboardingWelcome.test.tsx
🧰 Additional context used
🧠 Learnings (3)
📚 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/Onboarding/PreviewTopUp.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/Onboarding/PreviewTopUp.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/pulse/components/Onboarding/PreviewTopUp.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). (2)
  • GitHub Check: build
  • GitHub Check: Cloudflare Pages: pillarx-debug
🔇 Additional comments (1)
src/apps/pulse/components/Onboarding/PreviewTopUp.tsx (1)

420-501: Transaction polling and status mapping look solid

The UserOpStatusTransactionStatusState mapping plus the polling loop and timing guards (min display time, failure grace window, hasSeenSuccessRef, cleanup of intervals/timeouts) are well thought out and should handle most backend status quirks gracefully.

@github-actions github-actions bot temporarily deployed to Preview (PRO-3733-OnboardingFlow) December 4, 2025 12:11 Inactive
@github-actions github-actions bot temporarily deployed to Preview (PRO-3733-OnboardingFlow) December 4, 2025 12:41 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/pulse/components/App/HomeScreen.tsx (1)

639-639: Unused dependency in callback.

The showTransactionStatus callback (lines 595-640) doesn't call setOnboardingScreen, so it shouldn't be in the dependency array at line 639. This was flagged in a previous review but appears to still be present.

Apply this diff:

   [sellToken, tokenAmount, sellOffer, isBuy, setOnboardingScreen]
+  [sellToken, tokenAmount, sellOffer, isBuy]

You can also remove the eslint-disable comment on line 638 if this is the only violation.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1bb4ee1 and a0f435b.

📒 Files selected for processing (1)
  • src/apps/pulse/components/App/HomeScreen.tsx (11 hunks)
🧰 Additional context used
🧠 Learnings (5)
📚 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-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/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
📚 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-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/pulse/components/App/HomeScreen.tsx
🧬 Code graph analysis (1)
src/apps/pulse/components/App/HomeScreen.tsx (4)
src/apps/pulse/hooks/useGasTankBalance.ts (1)
  • useGasTankBalance (22-99)
src/apps/pulse/hooks/useTotalUsdcBalance.ts (1)
  • useTotalUsdcBalance (10-35)
src/apps/pulse/components/Onboarding/OnboardingWelcome.tsx (1)
  • OnboardingWelcome (20-128)
src/apps/pulse/components/Onboarding/TopUpScreen.tsx (1)
  • TopUpScreen (39-706)
⏰ 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). (2)
  • GitHub Check: build
  • GitHub Check: Cloudflare Pages: pillarx-debug
🔇 Additional comments (3)
src/apps/pulse/components/App/HomeScreen.tsx (3)

652-666: Good transaction cleanup logic.

The conditional reset of buyToken based on USE_RELAY_BUY and unconditional reset of sellToken makes sense for different transaction flows. Refetching the gas tank balance after successful transactions ensures the onboarding state stays accurate.


1153-1190: Clean onboarding screen rendering.

The conditional rendering of OnboardingWelcome and TopUpScreen is well-structured. The distinction between onboarding initiated from settings (showCloseButton={isTopUpFromSettings}) versus automatic onboarding is a good UX detail.


267-285: Onboarding flow logic is well-implemented.

The three effects work together to create a smooth onboarding experience:

  1. Show welcome screen when gas tank balance is insufficient (lines 267-285)
  2. Auto-dismiss with a 3-second delay when balance is restored (lines 287-302)
  3. Reset the completion flag when balance drops below threshold to re-trigger onboarding (lines 304-318)

The dependency arrays are complete and the logic handles edge cases appropriately.

Also applies to: 287-302, 304-318

Comment on lines +320 to +368
// Preselect max USDC token when topup screen is shown and totalUsdcBalance > 0
useEffect(() => {
if (
onboardingScreen === 'topup' &&
totalUsdcBalance > 0 &&
!topupToken &&
walletPortfolioData?.result?.data?.assets
) {
// Find all USDC tokens from the portfolio
const usdcTokens = portfolioTokens.filter((token) => {
const chainId = chainNameToChainIdTokensData(token.blockchain);
return STABLE_CURRENCIES.some(
(stable) =>
stable.chainId === chainId &&
stable.address.toLowerCase() === token.contract.toLowerCase()
);
});

if (usdcTokens.length > 0) {
// Find the USDC token with the highest USD balance
const maxUsdcToken = usdcTokens.reduce((max, token) => {
const currentBalance = (token.price || 0) * (token.balance || 0);
const maxBalance = (max.price || 0) * (max.balance || 0);
return currentBalance > maxBalance ? token : max;
});

if ((maxUsdcToken.balance ?? 0) > 2) {
// Convert to SelectedToken format
setTopupToken({
name: maxUsdcToken.name,
symbol: maxUsdcToken.symbol,
logo: maxUsdcToken.logo,
usdValue: String(maxUsdcToken.price || 0),
dailyPriceChange: 0,
chainId: chainNameToChainIdTokensData(maxUsdcToken.blockchain),
decimals: maxUsdcToken.decimals,
address: maxUsdcToken.contract,
});
}
}
}
}, [
onboardingScreen,
totalUsdcBalance,
topupToken,
walletPortfolioData,
portfolioTokens,
setTopupToken,
]);
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

Potential type mismatch with chainId.

At line 354, chainNameToChainIdTokensData(maxUsdcToken.blockchain) returns number | undefined (based on the function signature in relevant snippets), but SelectedToken.chainId expects number. If the blockchain name is unrecognized, this could cause a type error.

Consider adding a type guard or default value:

+        const chainId = chainNameToChainIdTokensData(maxUsdcToken.blockchain);
+        if (!chainId) {
+          return; // Skip if chainId cannot be determined
+        }
+
         if ((maxUsdcToken.balance ?? 0) > 2) {
           // Convert to SelectedToken format
           setTopupToken({
             name: maxUsdcToken.name,
             symbol: maxUsdcToken.symbol,
             logo: maxUsdcToken.logo,
             usdValue: String(maxUsdcToken.price || 0),
             dailyPriceChange: 0,
-            chainId: chainNameToChainIdTokensData(maxUsdcToken.blockchain),
+            chainId,
             decimals: maxUsdcToken.decimals,
             address: maxUsdcToken.contract,
           });
         }
🤖 Prompt for AI Agents
In src/apps/pulse/components/App/HomeScreen.tsx around lines 320 to 368, the
call to chainNameToChainIdTokensData(maxUsdcToken.blockchain) can return number
| undefined but SelectedToken.chainId expects number; guard against undefined by
resolving chainId into a local const, check if it's a valid number before
calling setTopupToken, and either skip selecting that token (return/continue) or
provide a safe default numeric chainId; ensure types align by narrowing the
union so that setTopupToken always receives a numeric chainId.

@github-actions github-actions bot temporarily deployed to Preview (PRO-3733-OnboardingFlow) December 4, 2025 14:25 Inactive
@github-actions github-actions bot temporarily deployed to Preview (PRO-3733-OnboardingFlow) December 4, 2025 14:29 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: 3

♻️ Duplicate comments (3)
src/apps/pulse/components/App/HomeScreen.tsx (2)

638-639: Remove unused setOnboardingScreen from showTransactionStatus dependencies.

showTransactionStatus does not reference setOnboardingScreen, but it’s included in the useCallback dependency array:

const showTransactionStatus = useCallback(
  (userOperationHash: string, gasFee?: string) => {
    // ...
  },
  // eslint-disable-next-line react-hooks/exhaustive-deps
-  [sellToken, tokenAmount, sellOffer, isBuy, setOnboardingScreen]
+  [sellToken, tokenAmount, sellOffer, isBuy]
);

Keeping unused dependencies makes the hook signature misleading and can cause unnecessary invalidations if the setter identity ever changes. Since setOnboardingScreen isn’t used, it’s safe to drop it and then also remove the eslint-disable comment if no longer needed.


316-354: Guard chainNameToChainIdTokensData when setting topupToken.chainId.

In the USDC preselection effect, chainNameToChainIdTokensData is used both for filtering and to populate SelectedToken.chainId:

const chainId = chainNameToChainIdTokensData(token.blockchain);
// ...
chainId: chainNameToChainIdTokensData(maxUsdcToken.blockchain),

Since chainNameToChainIdTokensData can return number | undefined, an unrecognized blockchain name would yield undefined, which violates the SelectedToken.chainId: number contract and can leak into downstream logic.

Consider narrowing before setting topupToken:

      const usdcTokens = portfolioTokens.filter((token) => {
-        const chainId = chainNameToChainIdTokensData(token.blockchain);
-        return STABLE_CURRENCIES.some(
+        const chainId = chainNameToChainIdTokensData(token.blockchain);
+        if (!chainId) return false;
+        return STABLE_CURRENCIES.some(
           (stable) =>
             stable.chainId === chainId &&
             stable.address.toLowerCase() === token.contract.toLowerCase()
        );
      });
      // ...
-        if ((maxUsdcToken.balance ?? 0) > 2) {
-          // Convert to SelectedToken format
-          setTopupToken({
+        const chainId = chainNameToChainIdTokensData(maxUsdcToken.blockchain);
+        if (!chainId || (maxUsdcToken.balance ?? 0) <= 2) {
+          return;
+        }
+
+        // Convert to SelectedToken format
+        setTopupToken({
           name: maxUsdcToken.name,
           symbol: maxUsdcToken.symbol,
           logo: maxUsdcToken.logo,
           usdValue: String(maxUsdcToken.price || 0),
           dailyPriceChange: 0,
-          chainId: chainNameToChainIdTokensData(maxUsdcToken.blockchain),
+          chainId,
           decimals: maxUsdcToken.decimals,
           address: maxUsdcToken.contract,
         });
-        }

This mirrors a previous suggestion and keeps SelectedToken typing consistent.

src/apps/pulse/components/Onboarding/TopUpScreen.tsx (1)

242-262: Manual USD input is effectively blocked when no token is selected.

handleUsdAmountChange always compares against getSelectedTokenBalance().usdValue, which is 0 when selectedToken is null. Any positive input then triggers the “Amount cannot exceed your balance of $0.00” error and prevents typing, while quick-amount buttons can still set a positive amount. This was previously flagged and remains.

Consider skipping the balance check when no token is selected or switching the error to “Please select a token” in that case, e.g.:

  const handleUsdAmountChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const { value } = e.target;
    // Only allow numbers and decimals
    if (value === '' || /^\d*\.?\d*$/.test(value)) {
-     // Get selected token balance in USD
-     const { usdValue } = getSelectedTokenBalance();
-     const numValue = parseFloat(value) || 0;
-
-     // Don't allow more than the token's USD balance
-     if (numValue > usdValue && value !== '') {
-       setError(
-         `Amount cannot exceed your balance of $${usdValue.toFixed(2)}`
-       );
-       return;
-     }
+     const { usdValue } = getSelectedTokenBalance();
+     const numValue = parseFloat(value) || 0;
+
+     if (!selectedToken && value !== '') {
+       setAmount(value);
+       setError('Please select a token');
+       return;
+     }
+
+     // Don't allow more than the token's USD balance
+     if (numValue > usdValue && value !== '') {
+       setError(
+         `Amount cannot exceed your balance of $${usdValue.toFixed(2)}`
+       );
+       return;
+     }

This keeps the UX consistent and avoids blocking typing when no token is selected.

🧹 Nitpick comments (3)
src/apps/pulse/components/Onboarding/TopUpScreen.tsx (1)

65-69: Remove or gate debug console.log for showCloseButton.

The useEffect at Lines 65–69 only logs showCloseButton and has no functional role. This should be removed or wrapped in a dev-only flag to avoid noisy console output in production.

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

248-265: Effect to “hide onboarding” is effectively a no-op.

The effect:

useEffect(() => {
  if (
    (buyToken || sellToken || previewBuy || previewSell) &&
    onboardingScreen === null
  ) {
    setOnboardingScreen(null);
  }
}, [...]);

only calls setOnboardingScreen(null) when onboardingScreen is already null, so it doesn’t actually hide anything.

Either remove this effect, or change the condition to act when onboarding is showing, e.g.:

-  if (
-    (buyToken || sellToken || previewBuy || previewSell) &&
-    onboardingScreen === null
-  ) {
-    setOnboardingScreen(null);
-  }
+  if (
+    (buyToken || sellToken || previewBuy || previewSell) &&
+    onboardingScreen !== null
+  ) {
+    setOnboardingScreen(null);
+  }

to match the comment “Immediately hide onboarding when a token is selected or preview opens.”


375-385: Avoid permanent console.log noise for onboarding/top-up handlers.

handleShowTopUp and handleBackToWelcome log messages on every invocation:

console.log('[HomeScreen] handleShowTopUp called with fromSettings:', fromSettings);
console.log('[HomeScreen] handleBackToWelcome called - resetting isTopUpFromSettings to false');

These are useful during development but noisy in production. Consider removing them or gating behind a debug flag (e.g. a feature flag or NODE_ENV !== 'production').

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a0f435b and 72b4a74.

📒 Files selected for processing (3)
  • src/apps/pulse/components/App/HomeScreen.tsx (11 hunks)
  • src/apps/pulse/components/Onboarding/OnboardingWelcome.tsx (1 hunks)
  • src/apps/pulse/components/Onboarding/TopUpScreen.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/apps/pulse/components/Onboarding/OnboardingWelcome.tsx
🧰 Additional context used
🧠 Learnings (6)
📚 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/Onboarding/TopUpScreen.tsx
  • 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/Onboarding/TopUpScreen.tsx
  • src/apps/pulse/components/App/HomeScreen.tsx
📚 Learning: 2025-11-21T13:10:33.422Z
Learnt from: aldin4u
Repo: pillarwallet/x PR: 461
File: src/apps/pulse/components/Search/MarketList.tsx:10-15
Timestamp: 2025-11-21T13:10:33.422Z
Learning: In the Pulse app's MarketList component (src/apps/pulse/components/Search/MarketList.tsx), markets should display liquidity (not price) in the right-hand column. This is per the design specification.

Applied to files:

  • src/apps/pulse/components/Onboarding/TopUpScreen.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/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
📚 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/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). (2)
  • GitHub Check: build
  • GitHub Check: Cloudflare Pages: pillarx-debug
🔇 Additional comments (3)
src/apps/pulse/components/App/HomeScreen.tsx (3)

651-665: Token reset and gas tank refetch on transaction completion look correct.

In closeTransactionStatus, resetting buyToken/sellToken only on 'Transaction Complete' and then calling refetchGasTankBalance() is a good integration point:

  • It avoids clearing tokens on failed/cancelled transactions.
  • It ensures gas tank onboarding/gating reacts promptly to successful top-ups and other transactions that might modify gas credit.

No changes needed here.


1151-1188: Onboarding render branches are wired coherently.

The renderPreview branches for onboardingScreen === 'welcome' and 'topup' correctly:

  • Pass gas/onboarding data (gasTankBalance, isGasTankLoading, maxStableCoinBalance) into OnboardingWelcome.
  • Route onComplete to handleShowTopUp, with fromSettings explicitly flagged when invoked from SettingsMenu.
  • Provide TopUpScreen with initialBalance={totalUsdcBalance}, portfolioTokens, onboarding setters, and showCloseButton={isTopUpFromSettings} to differentiate settings-triggered flows.

This arrangement matches the described feature behavior and keeps onboarding concerns local to renderPreview.


1274-1284: Settings “Top Up” entry point is correctly integrated.

Wiring onTopUp={() => handleShowTopUp(true)} with gasTankBalance into SettingsMenu cleanly exposes the same onboarding/top-up flow from settings without duplicating logic. This is a sensible reuse of the existing handlers.

Comment on lines +265 to +276
const handleQuickAmountClick = (item: string) => {
const percentage = parseInt(item.replace('%', ''), 10);
const { usdValue } = getSelectedTokenBalance();

if (usdValue > 0) {
const calculatedAmount = (usdValue * percentage) / 100;
// Round to 2 decimals
setAmount(calculatedAmount.toFixed(2));
} else {
setAmount('0');
}
};
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

Quick-amount buttons should also clear any existing error state.

handleQuickAmountClick updates amount based on the selected percentage but never clears error. If the user previously hit a validation error (e.g. exceeded balance via manual input), the error persists and the Top Up button remains disabled even after picking a valid quick amount.

Add setError(null) after setting the amount:

  const handleQuickAmountClick = (item: string) => {
    const percentage = parseInt(item.replace('%', ''), 10);
    const { usdValue } = getSelectedTokenBalance();

    if (usdValue > 0) {
      const calculatedAmount = (usdValue * percentage) / 100;
      // Round to 2 decimals
-     setAmount(calculatedAmount.toFixed(2));
+     setAmount(calculatedAmount.toFixed(2));
+     setError(null);
    } else {
      setAmount('0');
+     setError(null);
    }
  };
📝 Committable suggestion

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

Suggested change
const handleQuickAmountClick = (item: string) => {
const percentage = parseInt(item.replace('%', ''), 10);
const { usdValue } = getSelectedTokenBalance();
if (usdValue > 0) {
const calculatedAmount = (usdValue * percentage) / 100;
// Round to 2 decimals
setAmount(calculatedAmount.toFixed(2));
} else {
setAmount('0');
}
};
const handleQuickAmountClick = (item: string) => {
const percentage = parseInt(item.replace('%', ''), 10);
const { usdValue } = getSelectedTokenBalance();
if (usdValue > 0) {
const calculatedAmount = (usdValue * percentage) / 100;
// Round to 2 decimals
setAmount(calculatedAmount.toFixed(2));
setError(null);
} else {
setAmount('0');
setError(null);
}
};
🤖 Prompt for AI Agents
In src/apps/pulse/components/Onboarding/TopUpScreen.tsx around lines 265 to 276,
handleQuickAmountClick sets the calculated amount but does not clear any
existing validation error, so a prior error can persist and keep the Top Up
button disabled; after setting the amount (both when using
calculatedAmount.toFixed(2) and when setting '0'), call setError(null) to clear
the error state so the UI re-validates and enables the button when the new
amount is valid.

Comment on lines +279 to +325
const handleAllocateAmountChange = (
e: React.ChangeEvent<HTMLInputElement>
) => {
const { value } = e.target;
// Only allow numbers and decimals
if (value === '' || /^\d*\.?\d*$/.test(value)) {
const numValue = parseFloat(value) || 0;

// For non-USDC tokens, max is the received USDC amount from sell offer
let maxAmount: number;
if (!isSelectedTokenUSDC() && sellOffer) {
maxAmount = sellOffer.tokenAmountToReceive;
} else {
maxAmount = parseFloat(amount) || 0;
}

// Don't allow more than the max amount
if (numValue <= maxAmount || value === '') {
setAllocateAmount(numValue);
setError(null); // Clear error when user types
} else {
setError(`Allocate amount cannot exceed ${maxAmount.toFixed(2)} USDC`);
}
}
};

const handleAllocateDecrease = () => {
const minAmount = 2; // Minimum 2 USDC for gas tank
if (allocateAmount > minAmount) {
setAllocateAmount(Math.max(minAmount, allocateAmount - 1));
}
};

const handleAllocateIncrease = () => {
// For non-USDC tokens, max is the received USDC amount from sell offer
let maxAmount: number;
if (!isSelectedTokenUSDC() && sellOffer) {
maxAmount = sellOffer.tokenAmountToReceive;
} else {
maxAmount = parseFloat(amount) || 0;
}

if (allocateAmount < maxAmount) {
setAllocateAmount(Math.min(maxAmount, allocateAmount + 1));
}
};

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

“Minimum 2 USDC” comment is not enforced in validation.

handleAllocateDecrease enforces a minimum of 2 USDC only when using the button, but:

  • handleAllocateAmountChange allows manual values < 2 with no error.
  • handleTopUp does not enforce any minimum on allocateAmount or amount.

Given the inline comment “Minimum 2 USDC for gas tank”, either:

  • Enforce a true minimum (for non‑USDC flows) in handleAllocateAmountChange / handleTopUp, or
  • Adjust the comment to avoid implying a protocol constraint that isn’t actually enforced.

Example enforcing a minimum for non‑USDC flows:

  const handleAllocateAmountChange = (
    e: React.ChangeEvent<HTMLInputElement>
  ) => {
    const { value } = e.target;
    // Only allow numbers and decimals
    if (value === '' || /^\d*\.?\d*$/.test(value)) {
      const numValue = parseFloat(value) || 0;
+     const minAmount = 2;

      // For non-USDC tokens, max is the received USDC amount from sell offer
      let maxAmount: number;
      if (!isSelectedTokenUSDC() && sellOffer) {
        maxAmount = sellOffer.tokenAmountToReceive;
      } else {
        maxAmount = parseFloat(amount) || 0;
      }

-     // Don't allow more than the max amount
-     if (numValue <= maxAmount || value === '') {
+     // Don't allow less than min or more than max (when applicable)
+     if ((value === '' || numValue >= minAmount) && numValue <= maxAmount) {
        setAllocateAmount(numValue);
        setError(null); // Clear error when user types
      } else {
-       setError(`Allocate amount cannot exceed ${maxAmount.toFixed(2)} USDC`);
+       if (numValue < minAmount) {
+         setError(`Allocate amount must be at least ${minAmount.toFixed(2)} USDC`);
+       } else {
+         setError(`Allocate amount cannot exceed ${maxAmount.toFixed(2)} USDC`);
+       }
      }
    }
  };

Or, if no true min is required, update the comment to avoid confusion.

📝 Committable suggestion

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

Suggested change
const handleAllocateAmountChange = (
e: React.ChangeEvent<HTMLInputElement>
) => {
const { value } = e.target;
// Only allow numbers and decimals
if (value === '' || /^\d*\.?\d*$/.test(value)) {
const numValue = parseFloat(value) || 0;
// For non-USDC tokens, max is the received USDC amount from sell offer
let maxAmount: number;
if (!isSelectedTokenUSDC() && sellOffer) {
maxAmount = sellOffer.tokenAmountToReceive;
} else {
maxAmount = parseFloat(amount) || 0;
}
// Don't allow more than the max amount
if (numValue <= maxAmount || value === '') {
setAllocateAmount(numValue);
setError(null); // Clear error when user types
} else {
setError(`Allocate amount cannot exceed ${maxAmount.toFixed(2)} USDC`);
}
}
};
const handleAllocateDecrease = () => {
const minAmount = 2; // Minimum 2 USDC for gas tank
if (allocateAmount > minAmount) {
setAllocateAmount(Math.max(minAmount, allocateAmount - 1));
}
};
const handleAllocateIncrease = () => {
// For non-USDC tokens, max is the received USDC amount from sell offer
let maxAmount: number;
if (!isSelectedTokenUSDC() && sellOffer) {
maxAmount = sellOffer.tokenAmountToReceive;
} else {
maxAmount = parseFloat(amount) || 0;
}
if (allocateAmount < maxAmount) {
setAllocateAmount(Math.min(maxAmount, allocateAmount + 1));
}
};
const handleAllocateAmountChange = (
e: React.ChangeEvent<HTMLInputElement>
) => {
const { value } = e.target;
// Only allow numbers and decimals
if (value === '' || /^\d*\.?\d*$/.test(value)) {
const numValue = parseFloat(value) || 0;
const minAmount = 2;
// For non-USDC tokens, max is the received USDC amount from sell offer
let maxAmount: number;
if (!isSelectedTokenUSDC() && sellOffer) {
maxAmount = sellOffer.tokenAmountToReceive;
} else {
maxAmount = parseFloat(amount) || 0;
}
// Don't allow less than min or more than max (when applicable)
if ((value === '' || numValue >= minAmount) && numValue <= maxAmount) {
setAllocateAmount(numValue);
setError(null); // Clear error when user types
} else {
if (numValue < minAmount) {
setError(`Allocate amount must be at least ${minAmount.toFixed(2)} USDC`);
} else {
setError(`Allocate amount cannot exceed ${maxAmount.toFixed(2)} USDC`);
}
}
}
};
const handleAllocateDecrease = () => {
const minAmount = 2; // Minimum 2 USDC for gas tank
if (allocateAmount > minAmount) {
setAllocateAmount(Math.max(minAmount, allocateAmount - 1));
}
};
const handleAllocateIncrease = () => {
// For non-USDC tokens, max is the received USDC amount from sell offer
let maxAmount: number;
if (!isSelectedTokenUSDC() && sellOffer) {
maxAmount = sellOffer.tokenAmountToReceive;
} else {
maxAmount = parseFloat(amount) || 0;
}
if (allocateAmount < maxAmount) {
setAllocateAmount(Math.min(maxAmount, allocateAmount + 1));
}
};
🤖 Prompt for AI Agents
In src/apps/pulse/components/Onboarding/TopUpScreen.tsx around lines 279-325,
the comment "Minimum 2 USDC for gas tank" is misleading because manual input and
handleTopUp allow values < 2; enforce the minimum consistently: define a
minAmount (2) where you compute maxAmount, then in handleAllocateAmountChange
clamp or reject values below minAmount (setAllocateAmount to minAmount or
setError describing the minimum) and in handleAllocateIncrease/Decrease keep
current behavior but ensure they respect minAmount; additionally add a
validation in handleTopUp to block/normalize allocateAmount < minAmount before
submitting (or update the comment if you decide not to enforce a protocol
minimum). Ensure error messages and clamping only apply for non‑USDC flows if
that restriction is intended.

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 (4)
src/apps/pulse/components/Onboarding/TopUpScreen.tsx (2)

273-305: Minimum 2 USDC constraint is only enforced on the decrease button.

The comment at line 301 states "Minimum 2 USDC for gas tank", but this is only enforced in handleAllocateDecrease. Manual input via handleAllocateAmountChange and the final handleTopUp validation allow values below 2 USDC.

Either enforce the minimum consistently across all input paths, or remove/update the comment to avoid implying a protocol constraint that isn't enforced.

   const handleAllocateAmountChange = (
     e: React.ChangeEvent<HTMLInputElement>
   ) => {
     const { value } = e.target;
     // Only allow numbers and decimals
     if (value === '' || /^\d*\.?\d*$/.test(value)) {
       const numValue = parseFloat(value) || 0;
+      const minAmount = 2; // Minimum 2 USDC for gas tank
 
       // For non-USDC tokens, max is the received USDC amount from sell offer
       let maxAmount: number;
       if (!isSelectedTokenUSDC() && sellOffer) {
         maxAmount = sellOffer.tokenAmountToReceive;
       } else {
         maxAmount = parseFloat(amount) || 0;
       }
 
       // Don't allow more than the max amount
-      if (numValue <= maxAmount || value === '') {
+      if ((numValue >= minAmount || value === '') && numValue <= maxAmount) {
         setAllocateAmount(numValue);
         setError(null);
+      } else if (numValue < minAmount && value !== '') {
+        setError(`Allocate amount must be at least ${minAmount} USDC`);
       } else {
         setError(`Allocate amount cannot exceed ${maxAmount.toFixed(2)} USDC`);
       }
     }
   };

259-271: Quick-amount buttons should clear any existing error state.

handleQuickAmountClick updates amount but never clears error. If the user previously hit a validation error, the error persists and the Top Up button remains disabled even after picking a valid quick amount.

   const handleQuickAmountClick = (item: string) => {
     const percentage = parseInt(item.replace('%', ''), 10);
     const { usdValue } = getSelectedTokenBalance();
 
     if (usdValue > 0) {
       const calculatedAmount = (usdValue * percentage) / 100;
       // Round to 2 decimals
       setAmount(calculatedAmount.toFixed(2));
+      setError(null);
     } else {
       setAmount('0');
+      setError(null);
     }
   };
src/apps/pulse/components/App/HomeScreen.tsx (2)

304-314: hasCompletedOnboarding flag can oscillate between true/false while balance stays ≥ 2.

When gasTankBalance >= 2 and !hasCompletedOnboardingRef.current is true, the flag is set to true. On the next effect run (still with gasTankBalance >= 2), !hasCompletedOnboardingRef.current is now false, so the else branch executes and resets the flag to false.

This causes the localStorage and ref to flip on every balance change, potentially re-triggering onboarding incorrectly.

   useEffect(() => {
-    if (gasTankBalance >= 2 && !hasCompletedOnboardingRef.current) {
-      hasCompletedOnboardingRef.current = true;
-      localStorage.setItem('hasCompletedOnboarding', 'true');
-    } else {
-      hasCompletedOnboardingRef.current = false;
-      localStorage.setItem('hasCompletedOnboarding', 'false');
+    if (gasTankBalance >= 2) {
+      if (!hasCompletedOnboardingRef.current) {
+        hasCompletedOnboardingRef.current = true;
+        localStorage.setItem('hasCompletedOnboarding', 'true');
+      }
+    } else {
+      if (hasCompletedOnboardingRef.current) {
+        hasCompletedOnboardingRef.current = false;
+        localStorage.setItem('hasCompletedOnboarding', 'false');
+      }
     }
   }, [gasTankBalance]);

342-354: Guard against invalid chainId when preselecting USDC token.

chainNameToChainIdTokensData returns 0 for unrecognized blockchain names. Setting chainId: 0 on the SelectedToken may cause downstream issues if a valid chain ID is expected.

+        const chainId = chainNameToChainIdTokensData(maxUsdcToken.blockchain);
+        if (!chainId) {
+          return; // Skip if chainId cannot be determined
+        }
+
         if ((maxUsdcToken.balance ?? 0) > 2) {
           // Convert to SelectedToken format
           setTopupToken({
             name: maxUsdcToken.name,
             symbol: maxUsdcToken.symbol,
             logo: maxUsdcToken.logo,
             usdValue: String(maxUsdcToken.price || 0),
             dailyPriceChange: 0,
-            chainId: chainNameToChainIdTokensData(maxUsdcToken.blockchain),
+            chainId,
             decimals: maxUsdcToken.decimals,
             address: maxUsdcToken.contract,
           });
         }
🧹 Nitpick comments (4)
src/apps/pulse/components/Onboarding/TopUpScreen.tsx (2)

224-235: Consider reusing chainIdToChainNameTokensData instead of duplicating chain name mapping.

This local getChainName function duplicates the chain ID to name mapping that already exists in tokensData.ts. The implementations also differ slightly (e.g., this returns "BSC" while the external function returns "BNB Smart Chain (BEP20)").

-  // Get chain name from chain ID
-  const getChainName = (chainId: number): string => {
-    const chainNames: { [key: number]: string } = {
-      1: 'Ethereum',
-      137: 'Polygon',
-      10: 'Optimism',
-      42161: 'Arbitrum',
-      8453: 'Base',
-      56: 'BSC',
-    };
-    return chainNames[chainId] || 'Unknown';
-  };

Then use chainIdToChainNameTokensData(selectedToken.chainId) at line 519. If you need shortened names for UI purposes, consider adding a display variant to the existing utility.


321-361: Consider adding minimum allocation validation in handleTopUp.

The validation checks for valid amount, selected token, and balance constraints, but doesn't validate that allocateAmount meets the minimum 2 USDC requirement for non-USDC tokens before navigating to preview. This could allow proceeding with invalid allocations if the user manually enters a value below 2.

     // Additional validation for non-USDC tokens
     if (!isSelectedTokenUSDC()) {
       if (!sellOffer) {
         return;
       }
 
+      if (allocateAmount < 2) {
+        setError('Allocate amount must be at least 2 USDC');
+        return;
+      }
+
       const maxAllocateAmount = sellOffer.tokenAmountToReceive;
       if (allocateAmount > maxAllocateAmount) {
         setError(
           `Allocate amount cannot exceed ${maxAllocateAmount.toFixed(2)} USDC`
         );
         return;
       }
     }
src/apps/pulse/components/App/HomeScreen.tsx (2)

250-264: This effect is a no-op and can be removed.

The condition onboardingScreen === null means setOnboardingScreen(null) is called when it's already null. This effect doesn't change any state.

If the intent was to hide onboarding when a token/preview is active and onboarding is currently showing, the condition should check for non-null onboarding states:

   useEffect(() => {
     if (
-      (buyToken || sellToken || previewBuy || previewSell) &&
-      onboardingScreen === null
+      (buyToken || sellToken || previewBuy || previewSell) &&
+      onboardingScreen !== null
     ) {
       setOnboardingScreen(null);
     }
   }, [

However, the original past review comment indicated this was intentionally gated to not interfere with active onboarding flows, so this may have been addressed by design. Please verify the intended behavior.


636-636: Remove unused setOnboardingScreen from dependency array.

setOnboardingScreen is not called within showTransactionStatus (lines 592-634). Removing it avoids unnecessary callback invalidations.

     // eslint-disable-next-line react-hooks/exhaustive-deps
-    [sellToken, tokenAmount, sellOffer, isBuy, setOnboardingScreen]
+    [sellToken, tokenAmount, sellOffer, isBuy]
   );
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 72b4a74 and a5e1bbe.

📒 Files selected for processing (2)
  • src/apps/pulse/components/App/HomeScreen.tsx (11 hunks)
  • src/apps/pulse/components/Onboarding/TopUpScreen.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (5)
📚 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/Onboarding/TopUpScreen.tsx
  • 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/Onboarding/TopUpScreen.tsx
  • src/apps/pulse/components/App/HomeScreen.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/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
📚 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/pulse/components/App/HomeScreen.tsx
🧬 Code graph analysis (2)
src/apps/pulse/components/Onboarding/TopUpScreen.tsx (5)
src/apps/pulse/types/tokens.ts (1)
  • SelectedToken (1-10)
src/services/tokensData.ts (2)
  • PortfolioToken (31-34)
  • chainIdToChainNameTokensData (210-231)
src/apps/pulse/constants/tokens.ts (1)
  • STABLE_CURRENCIES (13-15)
src/apps/pulse/components/Onboarding/PreviewTopUp.tsx (1)
  • PreviewTopUp (75-815)
src/utils/blockchain.ts (1)
  • getLogoForChainId (165-199)
src/apps/pulse/components/App/HomeScreen.tsx (7)
src/apps/pulse/types/tokens.ts (1)
  • SelectedToken (1-10)
src/apps/pulse/hooks/useGasTankBalance.ts (1)
  • useGasTankBalance (22-99)
src/apps/pulse/hooks/useTotalUsdcBalance.ts (1)
  • useTotalUsdcBalance (10-35)
src/services/tokensData.ts (1)
  • chainNameToChainIdTokensData (234-255)
src/apps/pulse/constants/tokens.ts (1)
  • STABLE_CURRENCIES (13-15)
src/apps/pulse/components/Onboarding/OnboardingWelcome.tsx (1)
  • OnboardingWelcome (20-128)
src/apps/pulse/components/Onboarding/TopUpScreen.tsx (1)
  • TopUpScreen (39-706)
⏰ 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: Cloudflare Pages: pillarx-debug
🔇 Additional comments (11)
src/apps/pulse/components/Onboarding/TopUpScreen.tsx (5)

1-63: LGTM on imports, interface, and initial setup.

The imports are well-organized, the props interface is clearly defined, and the initial state setup follows React conventions with appropriate defaults.


65-94: LGTM on helper functions.

isSelectedTokenUSDC and getSelectedTokenBalance are correctly implemented with proper null checks and fallback values.


152-222: Stale sellOffer handling is now addressed.

The effect now properly clears sellOffer and isLoadingQuote on all early-exit branches, preventing stale quote data from persisting when prerequisites aren't met.


367-427: LGTM on button text logic.

The getButtonText function handles all states appropriately: loading states, USDC vs non-USDC tokens, and swap quote display.


669-702: LGTM on button disabled logic.

The disabled condition correctly accounts for all validation states: empty/zero amount, loading states, missing sell offer for non-USDC tokens, and error conditions.

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

37-55: LGTM on new imports.

The onboarding-related imports are well-organized and correctly categorized by their purpose (components, hooks, constants).


96-121: LGTM on extended props and destructuring.

The new onboarding-related props are properly typed and destructured.


240-246: LGTM on onboarding data hooks.

The useGasTankBalance and useTotalUsdcBalance hooks are correctly integrated with proper null handling for accountAddress.


648-663: LGTM on post-transaction cleanup.

The logic correctly resets tokens after successful transaction completion and refetches gas tank balance. The conditional reset based on USE_RELAY_BUY and transaction type is appropriate.


1149-1186: LGTM on onboarding screen rendering.

The renderPreview function correctly handles the onboarding flow states and passes appropriate props to OnboardingWelcome and TopUpScreen components.


1279-1280: LGTM on settings menu integration.

The top-up action from settings is properly wired with handleShowTopUp(true) and the gas tank balance is passed for display.

@github-actions github-actions bot requested a deployment to Preview (PRO-3733-OnboardingFlow) December 4, 2025 14:38 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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/apps/pulse/components/Sell/Sell.tsx (1)

86-112: Critical: Undefined variable portfolioTokens will cause runtime error.

The portfolioTokens variable is referenced in the useMemo (line 87) but is not defined anywhere in the component—it's not in props, state, or returned from any hook. Additionally, getChainId (line 92) and MobulaChainNames (line 92) are used but never imported. This will cause a ReferenceError at runtime and break the PnL calculation feature.

According to the AI summary, portfolioTokens was removed from the props as part of this refactor, but the dependent code was not updated.

You need to either:

  1. Remove this useMemo block and the useTokenPnL hook usage (lines 86-112) if portfolio-based PnL is no longer needed, OR
  2. Restore the portfolioTokens prop and add the missing imports for getChainId and MobulaChainNames

Apply this diff if removing portfolio-based PnL:

-  // Find matching portfolio token to get balance and price
-  const portfolioToken = useMemo(() => {
-    if (!token || !portfolioTokens || portfolioTokens.length === 0) return null;
-
-    return portfolioTokens.find(
-      (pt) =>
-        pt.contract.toLowerCase() === token.address.toLowerCase() &&
-        Number(getChainId(pt.blockchain as MobulaChainNames)) === token.chainId
-    );
-  }, [token, portfolioTokens]);
-
-  // Calculate PnL for selected token with proper balance and price
-  const { pnl, isLoading: isPnLLoading } = useTokenPnL(
-    token && accountAddress && portfolioToken
-      ? {
-          token: {
-            contract: token.address || '',
-            symbol: token.symbol,
-            decimals: token.decimals || 18,
-            balance: portfolioToken.balance || 0,
-            price: portfolioToken.price || 0,
-          },
-          transactionsData,
-          walletAddress: accountAddress,
-          chainId: token.chainId,
-        }
-      : null
-  );
+  // Calculate PnL for selected token
+  const { pnl, isLoading: isPnLLoading } = useTokenPnL(
+    token && accountAddress
+      ? {
+          token: {
+            contract: token.address || '',
+            symbol: token.symbol,
+            decimals: token.decimals || 18,
+            balance: tokenBalance || 0,
+            price: parseFloat(token.usdValue) || 0,
+          },
+          transactionsData,
+          walletAddress: accountAddress,
+          chainId: token.chainId,
+        }
+      : null
+  );

Also remove the unused import:

-import { PortfolioToken } from '../../../../services/tokensData';
♻️ Duplicate comments (2)
src/apps/pulse/components/App/HomeScreen.tsx (2)

304-314: Onboarding completion flag oscillates on every balance update.

The current logic in this effect will flip the hasCompletedOnboarding flag between true and false whenever gasTankBalance changes, even when the balance remains above the threshold:

  1. First update: gasTankBalance = 2.5, ref.current = false → sets ref.current = true
  2. Next update: gasTankBalance = 2.6, ref.current = true → hits else branch → sets ref.current = false

This was flagged in a previous review but appears unresolved.

Apply this diff to fix the oscillation:

 useEffect(() => {
-  if (gasTankBalance >= 2 && !hasCompletedOnboardingRef.current) {
-    hasCompletedOnboardingRef.current = true;
-    localStorage.setItem('hasCompletedOnboarding', 'true');
-  } else {
-    hasCompletedOnboardingRef.current = false;
-    localStorage.setItem('hasCompletedOnboarding', 'false');
-  }
+  if (gasTankBalance >= 2) {
+    if (!hasCompletedOnboardingRef.current) {
+      hasCompletedOnboardingRef.current = true;
+      localStorage.setItem('hasCompletedOnboarding', 'true');
+    }
+  } else if (gasTankBalance < 2) {
+    if (hasCompletedOnboardingRef.current) {
+      hasCompletedOnboardingRef.current = false;
+      localStorage.setItem('hasCompletedOnboarding', 'false');
+    }
+  }
 }, [gasTankBalance]);

316-364: Guard against invalid chainId when preselecting USDC.

At line 350, chainNameToChainIdTokensData(maxUsdcToken.blockchain) can return 0 if the blockchain name is unrecognized. Setting chainId: 0 creates an invalid SelectedToken that may cause downstream errors in swap/transaction logic.

Apply this diff to validate the chainId before selecting the token:

       if (usdcTokens.length > 0) {
         // Find the USDC token with the highest USD balance
         const maxUsdcToken = usdcTokens.reduce((max, token) => {
           const currentBalance = (token.price || 0) * (token.balance || 0);
           const maxBalance = (max.price || 0) * (max.balance || 0);
           return currentBalance > maxBalance ? token : max;
         });
 
+        const chainId = chainNameToChainIdTokensData(maxUsdcToken.blockchain);
+        
+        // Only select token if we have a valid chainId and sufficient balance
-        if ((maxUsdcToken.balance ?? 0) > 2) {
+        if ((maxUsdcToken.balance ?? 0) > 2 && chainId > 0) {
           // Convert to SelectedToken format
           setTopupToken({
             name: maxUsdcToken.name,
             symbol: maxUsdcToken.symbol,
             logo: maxUsdcToken.logo,
             usdValue: String(maxUsdcToken.price || 0),
             dailyPriceChange: 0,
-            chainId: chainNameToChainIdTokensData(maxUsdcToken.blockchain),
+            chainId,
             decimals: maxUsdcToken.decimals,
             address: maxUsdcToken.contract,
           });
         }
       }
🧹 Nitpick comments (1)
src/apps/pulse/components/App/HomeScreen.tsx (1)

636-636: Remove unused setOnboardingScreen from dependency array.

The showTransactionStatus callback (lines 592-637) does not reference setOnboardingScreen anywhere in its body, so it should not be in the dependency array.

Apply this diff:

     },
     // eslint-disable-next-line react-hooks/exhaustive-deps
-    [sellToken, tokenAmount, sellOffer, isBuy, setOnboardingScreen]
+    [sellToken, tokenAmount, sellOffer, isBuy]
   );
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a5e1bbe and e9bdaed.

📒 Files selected for processing (8)
  • src/apps/pulse/components/App/HomeScreen.tsx (11 hunks)
  • src/apps/pulse/components/App/tests/AppWrapper.test.tsx (3 hunks)
  • src/apps/pulse/components/App/tests/HomeScreen.test.tsx (4 hunks)
  • src/apps/pulse/components/Buy/Buy.tsx (2 hunks)
  • src/apps/pulse/components/Buy/BuyButton.tsx (1 hunks)
  • src/apps/pulse/components/Search/PortfolioTokenList.tsx (3 hunks)
  • src/apps/pulse/components/Search/Search.tsx (3 hunks)
  • src/apps/pulse/components/Sell/Sell.tsx (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
  • src/apps/pulse/components/Search/PortfolioTokenList.tsx
  • src/apps/pulse/components/App/tests/AppWrapper.test.tsx
  • src/apps/pulse/components/Buy/Buy.tsx
  • src/apps/pulse/components/App/tests/HomeScreen.test.tsx
  • src/apps/pulse/components/Search/Search.tsx
  • src/apps/pulse/components/Buy/BuyButton.tsx
🧰 Additional context used
🧠 Learnings (6)
📚 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/Sell/Sell.tsx
  • src/apps/pulse/components/App/HomeScreen.tsx
📚 Learning: 2025-11-21T13:10:33.422Z
Learnt from: aldin4u
Repo: pillarwallet/x PR: 461
File: src/apps/pulse/components/Search/MarketList.tsx:10-15
Timestamp: 2025-11-21T13:10:33.422Z
Learning: In the Pulse app's MarketList component (src/apps/pulse/components/Search/MarketList.tsx), markets should display liquidity (not price) in the right-hand column. This is per the design specification.

Applied to files:

  • src/apps/pulse/components/Sell/Sell.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/Sell/Sell.tsx
  • 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/Sell/Sell.tsx
  • src/apps/pulse/components/App/HomeScreen.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/pulse/components/Sell/Sell.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/pulse/components/App/HomeScreen.tsx
🧬 Code graph analysis (1)
src/apps/pulse/components/App/HomeScreen.tsx (7)
src/apps/pulse/types/tokens.ts (1)
  • SelectedToken (1-10)
src/apps/pulse/hooks/useGasTankBalance.ts (1)
  • useGasTankBalance (22-99)
src/apps/pulse/hooks/useTotalUsdcBalance.ts (1)
  • useTotalUsdcBalance (10-35)
src/services/tokensData.ts (1)
  • chainNameToChainIdTokensData (234-255)
src/apps/pulse/constants/tokens.ts (1)
  • STABLE_CURRENCIES (13-15)
src/apps/pulse/components/Onboarding/OnboardingWelcome.tsx (1)
  • OnboardingWelcome (20-128)
src/apps/pulse/components/Onboarding/TopUpScreen.tsx (1)
  • TopUpScreen (39-706)
⏰ 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). (4)
  • GitHub Check: unit-tests
  • GitHub Check: lint
  • GitHub Check: build
  • GitHub Check: Cloudflare Pages: pillarx-debug
🔇 Additional comments (4)
src/apps/pulse/components/Sell/Sell.tsx (2)

476-493: LGTM: Error handling simplified correctly.

The removal of minGasAmount checks and simplification of the error message aligns with the PR's refactoring goals. The error banner now appropriately shows when there's insufficient token balance or a relay error, without the native token balance requirement.


586-596: LGTM: SellButton prop correctly simplified.

The notEnoughLiquidity prop now correctly passes only the token balance check, consistent with the removal of gas/native token requirements from this component.

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

649-663: LGTM! Token reset and balance refetch logic.

The transaction completion handling correctly:

  • Resets tokens after successful transactions (respecting the USE_RELAY_BUY flag for buy flows)
  • Refetches gas tank balance to update UI after deposits

1150-1186: LGTM! Onboarding UI integration.

The onboarding screen rendering is well-structured:

  • Welcome screen properly passes balance data and handlers
  • TopUp screen correctly configures search mode (sell-only with stable coins included)
  • Intentional design to defer marking onboarding complete until balance validation confirms the deposit

@github-actions github-actions bot temporarily deployed to Preview (PRO-3733-OnboardingFlow) December 4, 2025 15:02 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: 0

♻️ Duplicate comments (3)
src/apps/pulse/components/Buy/BuyButton.tsx (1)

6-6: Move getChainName import back under the utils section

Previous feedback on this line asked for getChainName to live with the other utils imports; please move this import down under the // utils block to align with that convention and close out the earlier comment.

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

638-638: Remove unused setOnboardingScreen from dependency array.

The showTransactionStatus callback (lines 594–639) does not call setOnboardingScreen, so it should not be in the dependency array at line 638. This was noted in a previous review and marked as addressed, but the dependency is still present.

Apply this diff:

     // eslint-disable-next-line react-hooks/exhaustive-deps
-    [sellToken, tokenAmount, sellOffer, isBuy, setOnboardingScreen]
+    [sellToken, tokenAmount, sellOffer, isBuy]

318-366: Guard against invalid chain ID when preselecting USDC.

At line 352, chainNameToChainIdTokensData returns 0 for unrecognized blockchain names. Setting chainId: 0 in SelectedToken may cause issues in downstream code expecting valid chain IDs (e.g., RPC calls, chain-specific logic).

Apply this diff to guard against invalid chain IDs:

+        const chainId = chainNameToChainIdTokensData(maxUsdcToken.blockchain);
+        if (!chainId || chainId === 0) {
+          // Skip tokens with unrecognized blockchains
+          return;
+        }
+
         if ((maxUsdcToken.balance ?? 0) > 2) {
           // Convert to SelectedToken format
           setTopupToken({
             name: maxUsdcToken.name,
             symbol: maxUsdcToken.symbol,
             logo: maxUsdcToken.logo,
             usdValue: String(maxUsdcToken.price || 0),
             dailyPriceChange: 0,
-            chainId: chainNameToChainIdTokensData(maxUsdcToken.blockchain),
+            chainId,
             decimals: maxUsdcToken.decimals,
             address: maxUsdcToken.contract,
           });
         }

This ensures only tokens with recognized blockchains are preselected for top-up.

🧹 Nitpick comments (2)
src/apps/pulse/components/Search/PortfolioTokenList.tsx (1)

89-95: Consider removing the redundant initial sort.

The sort by USD value at lines 91–95 is immediately overwritten by the main sorting logic at lines 106–138, which applies sortConfig (price/balance/pnl). This initial sort adds unnecessary computation without affecting the final output.

Apply this diff to remove the redundant sort:

-    let tokens = convertPortfolioAPIResponseToToken(walletPortfolioData)
-      .filter((token) => includeStableCoins || !isStableCurrency(token))
-      .sort((a: Token, b: Token) => {
-        const balanceUSDA = (a.price || 0) * (a.balance || 0);
-        const balanceUSDB = (b.price || 0) * (b.balance || 0);
-        return balanceUSDB - balanceUSDA; // Sort by highest USD value first
-      });
+    let tokens = convertPortfolioAPIResponseToToken(walletPortfolioData)
+      .filter((token) => includeStableCoins || !isStableCurrency(token));

The final sort order is determined by the sorting logic at lines 106–138 based on sortConfig, making this early sort redundant.

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

250-264: No-op effect can be removed.

The effect sets onboardingScreen to null only when it's already null (line 253), making it a no-op. This check was likely added to prevent unmounting during the onboarding flow, but the result is that the effect serves no purpose.

Apply this diff to remove the no-op effect:

-  // Immediately hide onboarding when a token is selected or preview opens
-  // But only if not in an active onboarding flow (welcome or topup screen)
-  useEffect(() => {
-    if (
-      (buyToken || sellToken || previewBuy || previewSell) &&
-      onboardingScreen === null
-    ) {
-      setOnboardingScreen(null);
-    }
-  }, [
-    buyToken,
-    sellToken,
-    previewBuy,
-    previewSell,
-    onboardingScreen,
-    setOnboardingScreen,
-  ]);
-

The onboarding visibility is already managed by other effects (lines 267–285 and 287–302), making this effect redundant.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e9bdaed and 0f756d6.

📒 Files selected for processing (6)
  • src/apps/pulse/components/App/HomeScreen.tsx (11 hunks)
  • src/apps/pulse/components/App/tests/AppWrapper.test.tsx (3 hunks)
  • src/apps/pulse/components/App/tests/HomeScreen.test.tsx (4 hunks)
  • src/apps/pulse/components/Buy/BuyButton.tsx (1 hunks)
  • src/apps/pulse/components/Search/PortfolioTokenList.tsx (4 hunks)
  • src/apps/pulse/components/Sell/Sell.tsx (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/apps/pulse/components/Sell/Sell.tsx
  • src/apps/pulse/components/App/tests/HomeScreen.test.tsx
🧰 Additional context used
🧠 Learnings (6)
📚 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/pulse/components/App/tests/AppWrapper.test.tsx
  • src/apps/pulse/components/App/HomeScreen.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/pulse/components/App/tests/AppWrapper.test.tsx
  • src/apps/pulse/components/App/HomeScreen.tsx
  • src/apps/pulse/components/Buy/BuyButton.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-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
  • src/apps/pulse/components/Buy/BuyButton.tsx
  • src/apps/pulse/components/Search/PortfolioTokenList.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-11-21T13:10:33.422Z
Learnt from: aldin4u
Repo: pillarwallet/x PR: 461
File: src/apps/pulse/components/Search/MarketList.tsx:10-15
Timestamp: 2025-11-21T13:10:33.422Z
Learning: In the Pulse app's MarketList component (src/apps/pulse/components/Search/MarketList.tsx), markets should display liquidity (not price) in the right-hand column. This is per the design specification.

Applied to files:

  • src/apps/pulse/components/Search/PortfolioTokenList.tsx
🧬 Code graph analysis (2)
src/apps/pulse/components/App/HomeScreen.tsx (7)
src/apps/pulse/types/tokens.ts (1)
  • SelectedToken (1-10)
src/apps/pulse/hooks/useGasTankBalance.ts (1)
  • useGasTankBalance (22-99)
src/apps/pulse/hooks/useTotalUsdcBalance.ts (1)
  • useTotalUsdcBalance (10-35)
src/services/tokensData.ts (1)
  • chainNameToChainIdTokensData (234-255)
src/apps/pulse/constants/tokens.ts (1)
  • STABLE_CURRENCIES (13-15)
src/apps/pulse/components/Onboarding/OnboardingWelcome.tsx (1)
  • OnboardingWelcome (20-128)
src/apps/pulse/components/Onboarding/TopUpScreen.tsx (1)
  • TopUpScreen (39-706)
src/apps/pulse/components/Search/PortfolioTokenList.tsx (2)
src/services/pillarXApiWalletPortfolio.ts (1)
  • convertPortfolioAPIResponseToToken (27-61)
src/services/tokensData.ts (1)
  • Token (19-29)
⏰ 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). (4)
  • GitHub Check: unit-tests
  • GitHub Check: lint
  • GitHub Check: build
  • GitHub Check: Cloudflare Pages: pillarx-debug
🔇 Additional comments (15)
src/apps/pulse/components/Search/PortfolioTokenList.tsx (2)

38-38: LGTM: Clean prop addition with sensible default.

The new includeStableCoins prop with a default value of false preserves the existing behavior while enabling stable coin visibility when needed during onboarding flows.

Also applies to: 49-49


152-158: LGTM: Dependency array is complete and accurate.

All dependencies used within getFilteredPortfolioTokens are correctly included in the dependency array, including the newly added includeStableCoins.

src/apps/pulse/components/App/tests/AppWrapper.test.tsx (2)

11-11: LGTM: Mock setup follows established patterns.

The import and mock configuration for useGasTankBalance is consistent with other hooks mocked in this test file.

Also applies to: 37-39


130-133: LGTM: Mock values ensure main interface rendering.

Setting totalBalance: 100 (non-zero) ensures the main interface is rendered rather than the onboarding flow, which aligns with the test objectives for AppWrapper.

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

19-19: LGTM: Imports and prop additions are well-organized.

The new imports for onboarding components, gas tank hooks, and stable currencies are correctly integrated. The extended HomeScreenProps interface cleanly adds the necessary onboarding state management props.

Also applies to: 36-37, 45-46, 54-55, 96-102


223-246: LGTM: State initialization includes persistence and proper hook usage.

The onboarding completion tracking via localStorage ensures persistence across sessions, and the gas tank/USDC balance hooks are correctly initialized with appropriate parameters.


267-285: LGTM: Welcome screen trigger logic is correct.

The effect correctly shows the welcome screen only when the gas tank balance is insufficient (< 2) and portfolio data is available, addressing the requirement that onboarding should appear when the user has no gas tank balance.


287-302: LGTM: Auto-dismiss logic provides good UX.

The 3-second delay before auto-dismissing the welcome screen when balance reaches the threshold gives users visual feedback that their top-up was successful, with proper timeout cleanup.


304-316: LGTM: Onboarding completion flag is now managed correctly.

The revised logic prevents unnecessary flag toggling by only updating when the state actually changes, addressing the previous concern about the flag flipping between true/false on every balance update.


368-375: LGTM: Token synchronization for top-up flow is correct.

The effect properly syncs the selected token from search to the top-up flow and resets the search-from-top-up flag after selection.


377-385: LGTM: Navigation handlers for onboarding flow are clean.

The handlers correctly manage navigation between onboarding screens and track whether top-up was initiated from settings vs. the welcome flow.


651-665: LGTM: Transaction completion handling is correct.

The logic appropriately resets tokens after successful transactions and refetches the gas tank balance to reflect the updated state, with proper conditional handling for Relay Buy mode.


1152-1188: LGTM: Onboarding screen rendering is well-integrated.

Both OnboardingWelcome and TopUpScreen are rendered with appropriate props, and the setSearching callback correctly configures the search mode for token selection during top-up.


1206-1206: LGTM: Gas tank balance propagated to preview components.

Passing gasTankBalance to both preview components enables them to display accurate gas tank information during transaction previews.

Also applies to: 1226-1226


1281-1282: LGTM: Settings menu integration with top-up flow.

The settings menu now has access to the top-up handler and gas tank balance, enabling users to top up their gas tank from the settings screen.

@github-actions github-actions bot temporarily deployed to Preview (PRO-3733-OnboardingFlow) December 5, 2025 02:39 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: 0

🧹 Nitpick comments (2)
src/apps/pulse/components/Transaction/TransactionDetails.tsx (1)

178-180: Consider renaming tokenAmountToReceive to clarify it represents USDC, not token units.

The variable name is misleading—despite its name suggesting a token quantity, tokenAmountToReceive consistently represents the USDC amount received from token swaps. This is evident from usage like "Selling ${amount} ${token.symbol} for ${sellOffer.tokenAmountToReceive.toFixed(2)} USDC" (useRelaySell.ts line 921) and assignment to usdcAmount (PreviewSell.tsx line 563). Renaming to usdcAmountToReceive would improve code clarity without affecting functionality.

src/apps/pulse/components/Sell/tests/PreviewSell.test.tsx (1)

167-174: Consider making the fetch mock more specific.

The global fetch mock intercepts all fetch calls and returns { priceUSD: 1.5 } regardless of the URL. If PreviewSell or its dependencies make other fetch calls expecting different responses, this could lead to test failures or false positives.

Consider validating the URL and returning appropriate responses:

-    // Mock fetch for native price endpoint
-    global.fetch = vi.fn(() =>
-      Promise.resolve({
-        ok: true,
-        json: () => Promise.resolve({ priceUSD: 1.5 }),
-      } as any)
-    );
+    // Mock fetch for native price endpoint
+    global.fetch = vi.fn((url: string) => {
+      if (url.includes('native-price') || url.includes('price')) {
+        return Promise.resolve({
+          ok: true,
+          json: () => Promise.resolve({ priceUSD: 1.5 }),
+        } as any);
+      }
+      return Promise.reject(new Error(`Unexpected fetch to ${url}`));
+    });
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0f756d6 and 0d67371.

📒 Files selected for processing (2)
  • src/apps/pulse/components/Sell/tests/PreviewSell.test.tsx (3 hunks)
  • src/apps/pulse/components/Transaction/TransactionDetails.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 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/Transaction/TransactionDetails.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/Transaction/TransactionDetails.tsx
📚 Learning: 2025-11-21T13:10:33.422Z
Learnt from: aldin4u
Repo: pillarwallet/x PR: 461
File: src/apps/pulse/components/Search/MarketList.tsx:10-15
Timestamp: 2025-11-21T13:10:33.422Z
Learning: In the Pulse app's MarketList component (src/apps/pulse/components/Search/MarketList.tsx), markets should display liquidity (not price) in the right-hand column. This is per the design specification.

Applied to files:

  • src/apps/pulse/components/Transaction/TransactionDetails.tsx
🧬 Code graph analysis (1)
src/apps/pulse/components/Transaction/TransactionDetails.tsx (1)
src/utils/number.tsx (1)
  • limitDigitsNumber (42-69)
⏰ 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). (4)
  • GitHub Check: unit-tests
  • GitHub Check: lint
  • GitHub Check: build
  • GitHub Check: Cloudflare Pages: pillarx-debug
🔇 Additional comments (3)
src/apps/pulse/components/Sell/tests/PreviewSell.test.tsx (3)

176-178: LGTM!

Adding afterEach cleanup with vi.restoreAllMocks() is excellent test hygiene. This ensures each test runs in isolation and prevents mock pollution between tests.


245-252: LGTM!

Simulating asynchronous behavior with a delay in the executeSell mock makes the test more realistic and better validates async state transitions (e.g., showing the "Please open your wallet" message).


66-72: The test mock structure correctly reflects the component implementation.

The chainGroups structure change from array to object keyed by chain ID is intentional and correctly implemented. The component explicitly accesses it as sentBatch?.chainGroups?.[sellToken.chainId]?.userOpHash (line 475), and the test mock at lines 66-72 matches this pattern with chainGroups: { 1: { userOpHash: '0x...' } }. The totalCost field at batch level is also correctly placed and used to convert gas costs from wei to USD (lines 487-490).

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)
src/apps/pulse/components/Sell/tests/PreviewSell.test.tsx (1)

174-182: Consider adding test coverage for fetch error scenarios.

The global fetch mock only covers the success case (returning priceUSD: 1.5). If the PreviewSell component has error handling for failed price fetches, consider adding test cases to verify that behavior.

Example test case structure:

it('handles native price fetch failure gracefully', async () => {
  global.fetch = vi.fn(() =>
    Promise.reject(new Error('Network error'))
  );
  
  renderWithProviders();
  
  // Assert appropriate error handling or fallback behavior
});
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0d67371 and f5b9058.

📒 Files selected for processing (1)
  • src/apps/pulse/components/Sell/tests/PreviewSell.test.tsx (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). (4)
  • GitHub Check: lint
  • GitHub Check: unit-tests
  • GitHub Check: build
  • GitHub Check: Cloudflare Pages: pillarx-debug
🔇 Additional comments (4)
src/apps/pulse/components/Sell/tests/PreviewSell.test.tsx (4)

153-159: LGTM! Good test isolation practice.

Stubbing the paymaster URL in the test suite ensures predictable behavior and aligns with the PR's paymaster configuration changes.


184-186: LGTM! Improved test cleanup.

Using vi.restoreAllMocks() ensures thorough cleanup of all mocks between tests, including the newly added global fetch mock.


253-260: LGTM! Better async behavior simulation.

Adding a delay before resolving the transaction hash more accurately simulates real-world async transaction execution and helps identify potential race conditions.


66-72: Chaingroups and totalCost structure in mock data is correct and consistent.

The mock data correctly reflects the actual implementation: chainGroups is an object keyed by chainId (not an array), and totalCost is properly placed at the batch level. This structure is consistently used across the codebase in useGasEstimation hooks, PreviewBuy, PreviewSell, and SendModal components, all accessing these fields with the expected patterns (chainGroups?.[chainId]?.userOpHash and sentBatch?.totalCost).

@IAmKio IAmKio merged commit 3dc25b4 into staging Dec 5, 2025
7 checks passed
@IAmKio IAmKio deleted the PRO-3733-OnboardingFlow branch December 5, 2025 11:42
@coderabbitai coderabbitai bot mentioned this pull request Jan 5, 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.

4 participants