Skip to content

[TASK-18801] fix: refresh balance on all balance-affecting websocket history entries#1765

Open
jjramirezn wants to merge 1 commit intodevfrom
fix/balance-refresh-after-transaction
Open

[TASK-18801] fix: refresh balance on all balance-affecting websocket history entries#1765
jjramirezn wants to merge 1 commit intodevfrom
fix/balance-refresh-after-transaction

Conversation

@jjramirezn
Copy link
Contributor

Previously only DIRECT_SEND and completed REQUEST triggered a balance refetch. Added explicit list of balance-affecting transaction types: DEPOSIT, WITHDRAW, BRIDGE_OFFRAMP/ONRAMP, MANTECA_OFFRAMP/ONRAMP, SEND_LINK, BANK_SEND_LINK_CLAIM, and PERK_REWARD.

Non-balance-affecting types (e.g. CASHOUT, QR payments with separate flows) are excluded. REQUEST still requires COMPLETED status.

Previously only DIRECT_SEND and completed REQUEST triggered a balance
refetch. Added explicit list of balance-affecting transaction types:
DEPOSIT, WITHDRAW, BRIDGE_OFFRAMP/ONRAMP, MANTECA_OFFRAMP/ONRAMP,
SEND_LINK, BANK_SEND_LINK_CLAIM, and PERK_REWARD.

Non-balance-affecting types (e.g. CASHOUT, QR payments with separate
flows) are excluded. REQUEST still requires COMPLETED status.
@notion-workspace
Copy link

@vercel
Copy link

vercel bot commented Mar 18, 2026

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

Project Deployment Actions Updated (UTC)
peanut-wallet Ready Ready Preview, Comment Mar 18, 2026 9:19pm

Request Review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 18, 2026

Walkthrough

The PR modifies the balance refresh trigger in HomeHistory's WebSocket onHistoryEntry handler by introducing a BALANCE_AFFECTING_TYPES list that defines which transaction types should refresh the balance, replacing the previous narrower condition.

Changes

Cohort / File(s) Summary
Balance Refresh Logic
src/components/Home/HomeHistory.tsx
Expanded balance refresh trigger by introducing BALANCE_AFFECTING_TYPES constant and updated fetchBalance condition to trigger when entry type matches the list or when REQUEST entries complete, replacing previous DIRECT_SEND-focused logic.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: expanding balance refresh triggers to cover all balance-affecting transaction types, as implemented in the changeset.
Description check ✅ Passed The description is directly related to the changeset, clearly explaining the previous behavior, new transaction types added, and exclusions—matching the implementation details.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/balance-refresh-after-transaction
📝 Coding Plan
  • Generate coding plan for human review comments

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

@jjramirezn
Copy link
Contributor Author

@coderabbitai review in depth.

Evaluate:

  • risk of introducing new bugs and breaking existing functionality
  • performance impact and latency
  • code quality and DRY

Also, write a very concise testing guide for QA

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 18, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

Let me gather the code changes for this PR first.


🏁 Script executed:

#!/bin/bash
# Get the PR diff
gh pr view 1765 --repo peanutprotocol/peanut-ui --json files,additions,deletions,title,body

# Get the PR diff
gh pr diff 1765 --repo peanutprotocol/peanut-ui

Length of output: 2446


Good. Let me now look at the full file and the EHistoryEntryType enum for deeper context.


🏁 Script executed:

#!/bin/bash
# Find and read the full HomeHistory.tsx file
fd HomeHistory.tsx --exec cat {}

# Find EHistoryEntryType enum definition
rg -n "EHistoryEntryType" --type ts -C 2 | head -120

# Find the type definition file
rg -rn "enum EHistoryEntryType" --type ts -A 30

Length of output: 31023


Let me check a couple more things — the HistoryEntry type definition and fetchBalance implementation.


🏁 Script executed:

#!/bin/bash
# Check HistoryEntry type definition for status field
rg -n "HistoryEntry" src/utils/history.utils.ts -A 40 | head -80
rg -n "HistoryEntry" src/hooks/useTransactionHistory.ts -A 40 | head -80

# Check fetchBalance implementation
rg -n "fetchBalance" --type ts -C 3 | head -80

# Check if there's debounce / rate limiting on fetchBalance
rg -n "fetchBalance" src/hooks/wallet/useWallet.ts -C 5

Length of output: 13825


@jjramirezn Here is an in-depth evaluation of this PR:


🔍 In-Depth Review: HomeHistory.tsx — Balance Refresh on WebSocket Entries

Summary of Change

The PR expands the set of transaction types that trigger fetchBalance() on incoming WebSocket history entries, moving from a narrow allowlist (DIRECT_SEND, REQUEST+COMPLETED) to a broader one covering deposits, withdrawals, bridge flows, send links, etc.


🐛 Bug Risks & Correctness Issues

1. ⚠️ No Status Filter for Balance-Affecting Types (Medium Risk)

The old code only triggered fetchBalance() for REQUEST when COMPLETED. The new code triggers it for all listed types regardless of status — including PENDING, FAILED, STARTING, AWAITING_FUNDS, etc. (EHistoryStatus has 22+ states).

// Current: fetches balance even on FAILED or PENDING DEPOSIT/WITHDRAW
if (isBalanceAffecting || (entry.type === EHistoryEntryType.REQUEST && isCompleted)) {
    fetchBalance()
}

A WITHDRAW in PENDING state will fire a refetch before funds have moved. This is likely harmless (the balance shown won't be wrong, just unchanged), but it is a spurious network call. If the intent is to show the updated balance after the transaction completes, the check should arguably be:

const isBalanceAffecting = BALANCE_AFFECTING_TYPES.includes(entry.type) && isCompleted

This is a design call — please confirm whether refreshing on non-terminal statuses is intentional.

2. ⚠️ BRIDGE_GUEST_OFFRAMP Is Absent from the List (Medium Risk)

EHistoryEntryType.BRIDGE_GUEST_OFFRAMP exists in the enum and is handled elsewhere in the codebase (e.g., history.utils.ts line 286). It is not included in BALANCE_AFFECTING_TYPES. If a logged-in user can be a party to a guest offramp that affects their balance, this is a gap. Please confirm this exclusion is intentional.

3. 🟡 !entry.status in isCompleted Changes Behavior for REQUEST (Low-Medium Risk)

const isCompleted = !entry.status || entry.status.toUpperCase() === 'COMPLETED'

The !entry.status branch is new. If a REQUEST entry arrives via WebSocket with no status field, it will now be treated as completed and trigger fetchBalance(). The EHistoryStatus enum doesn't include undefined, so this edge case depends entirely on how the WS server serializes entries with no status. If this is an intentional null-safety guard, it's reasonable — but it's a silent behavioral change worth documenting.


⚡ Performance & Latency

4. No Debounce on fetchBalance() (Low Risk)

fetchBalance is a thin wrapper around TanStack Query's refetchBalance(). There is no debounce or coalescing logic:

// useWallet.ts
const fetchBalance = useCallback(async () => {
    if (!isAddressReady) { ... return }
    await refetchBalance()
}, [isAddressReady, refetchBalance])

If a burst of WebSocket messages arrives rapidly (e.g., a bridge flow with multiple status transitions), multiple parallel balance fetches will be fired. TanStack Query deduplicates concurrent identical queries within a render cycle, but since these calls originate from an async callback (not a render), deduplication is not guaranteed. This is low risk in practice but worth a debounce or a useRef flag if the WS is chatty.

5. BALANCE_AFFECTING_TYPES Recreated on Every Callback Invocation (Minor)

The constant is defined inside the useCallback body, so it is recreated fresh on every WebSocket event:

onHistoryEntry: useCallback(
    (entry: HistoryEntry) => {
        const BALANCE_AFFECTING_TYPES: string[] = [ ... ] // ← recreated every call

Since the array is fixed and never depends on component state, it should be hoisted to module scope:

// At module level, outside the component
const BALANCE_AFFECTING_TYPES: EHistoryEntryType[] = [
    EHistoryEntryType.DIRECT_SEND,
    EHistoryEntryType.DEPOSIT,
    // ...
]

This avoids unnecessary allocations and makes the intent clearer.


🧹 Code Quality & DRY

6. Loose Typing (string[] instead of EHistoryEntryType[]) (Minor)

const BALANCE_AFFECTING_TYPES: string[] = [
    EHistoryEntryType.DIRECT_SEND,
    ...
]

EHistoryEntryType values are string literals. Typing the array as string[] loses TypeScript's ability to catch a typo or a removed enum member at compile time. It should be typed as EHistoryEntryType[] (or HistoryEntryType[]), which is especially important since the enum comment says "do not change the order, keep synced with backend."


✅ What's Correct

  • REQUEST still correctly requires COMPLETED status before triggering refresh.
  • CASHOUT, MANTECA_QR_PAYMENT, SIMPLEFI_QR_PAYMENT are correctly excluded (they have separate flows per the PR description).
  • fetchBalance has an isAddressReady guard that prevents spurious calls if the wallet isn't ready.
  • useCallback dependency on [fetchBalance] is correct.
  • The overall pattern is sound and is a net improvement over the previous narrow allowlist.

🔧 Suggested Fix (consolidated)

// Module-level constant — outside the component
const BALANCE_AFFECTING_TYPES: EHistoryEntryType[] = [
    EHistoryEntryType.DIRECT_SEND,
    EHistoryEntryType.DEPOSIT,
    EHistoryEntryType.WITHDRAW,
    EHistoryEntryType.BRIDGE_OFFRAMP,
    EHistoryEntryType.BRIDGE_ONRAMP,
    EHistoryEntryType.MANTECA_OFFRAMP,
    EHistoryEntryType.MANTECA_ONRAMP,
    EHistoryEntryType.SEND_LINK,
    EHistoryEntryType.BANK_SEND_LINK_CLAIM,
    EHistoryEntryType.PERK_REWARD,
    // confirm: EHistoryEntryType.BRIDGE_GUEST_OFFRAMP,
]

// Inside the component's useCallback:
onHistoryEntry: useCallback(
    (entry: HistoryEntry) => {
        const isCompleted = entry.status?.toUpperCase() === 'COMPLETED'
        const isBalanceAffecting = BALANCE_AFFECTING_TYPES.includes(entry.type as EHistoryEntryType)
        // Consider: && isCompleted for the balance-affecting types too

        if (isBalanceAffecting || (entry.type === EHistoryEntryType.REQUEST && isCompleted)) {
            fetchBalance()
        }
    },
    [fetchBalance]
),

🧪 QA Testing Guide

Scenario Expected Behavior
Make a deposit (on-chain) Balance updates in the Home screen in real time
Make a withdrawal Balance decrements in real time
Complete a Bridge on-ramp Balance updates after WS event received
Complete a Bridge off-ramp Balance updates after WS event received
Complete a Manteca on/off-ramp Balance updates after WS event received
Send a link Balance decrements in real time
Claim a bank send link (BANK_SEND_LINK_CLAIM) Balance updates after claim
Receive a PERK_REWARD Balance updates in real time
Send a REQUEST (status = NEW) Balance does NOT update
Complete a REQUEST (status = COMPLETED) Balance does update
Initiate a CASHOUT Balance does NOT update via WS (separate flow)
QR payment (MANTECA_QR_PAYMENT / SIMPLEFI_QR_PAYMENT) Balance does NOT update via WS
Rapid burst of WS events (simulate multiple status changes) No duplicate/hung balance fetch states
Balance fetch while wallet address not ready Gracefully skipped (check console for warning)

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

Learnt from: Hugo0
Repo: peanutprotocol/peanut-ui PR: 594
File: src/components/Request/Pay/Views/Initial.view.tsx:188-213
Timestamp: 2024-12-31T15:08:15.640Z
Learning: Always resolve coderabbit comments when reviewing code, since they can contain useful suggestions.

Learnt from: kushagrasarathe
Repo: peanutprotocol/peanut-ui PR: 0
File: :0-0
Timestamp: 2026-03-04T18:32:51.654Z
Learning: In peanutprotocol/peanut-ui, Bridge-specific add-money and withdraw flows (`src/app/(mobile-ui)/add-money/[country]/bank/page.tsx`, `src/components/AddWithdraw/AddWithdrawCountriesList.tsx`, `src/components/Claim/Link/views/BankFlowManager.view.tsx`) have been migrated (PR `#1713`) to use the unified `isUserKycApproved` from `useKycStatus()` hook instead of per-component `liveKycStatus` + WebSocket state. The migration is complete; no more direct `bridgeKycStatus === 'approved'` checks in those files.

Learnt from: kushagrasarathe
Repo: peanutprotocol/peanut-ui PR: 1693
File: src/app/(mobile-ui)/add-money/[country]/bank/page.tsx:206-207
Timestamp: 2026-03-04T18:35:17.673Z
Learning: In Bridge-specific add-money and withdraw flows (e.g., src/app/(mobile-ui)/add-money/[country]/bank/page.tsx), liveKycStatus from WebSocket tracks real-time bridge KYC status changes, and bridgeKycStatus is the persisted status. These flows use direct bridgeKycStatus === 'approved' checks instead of the unified isUserKycVerified helper. Migrating to the unified verifier requires careful testing of WebSocket-based real-time status tracking and is deferred to follow-up work.

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/Home/HomeHistory.tsx`:
- Around line 69-73: isCompleted currently treats missing status as completed
(const isCompleted = !entry.status || entry.status.toUpperCase() ===
'COMPLETED'), which causes REQUEST entries with no status to trigger
fetchBalance; change the logic so isCompleted is true only when status
explicitly equals 'COMPLETED' (e.g., use entry.status &&
entry.status.toUpperCase() === 'COMPLETED' or check entry.status?.toUpperCase()
=== 'COMPLETED') and keep the existing condition that calls fetchBalance for
BALANCE_AFFECTING_TYPES or when entry.type === EHistoryEntryType.REQUEST &&
isCompleted, so only REQUEST entries with explicit COMPLETED status invoke
fetchBalance.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: aefb2bf8-4fd0-4403-b18d-7d50f939197d

📥 Commits

Reviewing files that changed from the base of the PR and between 22b4c5b and a754eaa.

📒 Files selected for processing (1)
  • src/components/Home/HomeHistory.tsx

Comment on lines +69 to 73
const isCompleted = !entry.status || entry.status.toUpperCase() === 'COMPLETED'
const isBalanceAffecting = BALANCE_AFFECTING_TYPES.includes(entry.type)

if (isBalanceAffecting || (entry.type === EHistoryEntryType.REQUEST && isCompleted)) {
fetchBalance()
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

REQUEST completion check is too permissive.

At Line 69, isCompleted treats a missing status as completed (!entry.status), so REQUEST entries without status can still call fetchBalance at Line 72. The requirement is to refresh only when REQUEST is explicitly COMPLETED.

Proposed fix
-                const isCompleted = !entry.status || entry.status.toUpperCase() === 'COMPLETED'
+                const isCompleted = entry.status?.toUpperCase() === 'COMPLETED'
                 const isBalanceAffecting = BALANCE_AFFECTING_TYPES.includes(entry.type)

                 if (isBalanceAffecting || (entry.type === EHistoryEntryType.REQUEST && isCompleted)) {
                     fetchBalance()
                 }
📝 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 isCompleted = !entry.status || entry.status.toUpperCase() === 'COMPLETED'
const isBalanceAffecting = BALANCE_AFFECTING_TYPES.includes(entry.type)
if (isBalanceAffecting || (entry.type === EHistoryEntryType.REQUEST && isCompleted)) {
fetchBalance()
const isCompleted = entry.status?.toUpperCase() === 'COMPLETED'
const isBalanceAffecting = BALANCE_AFFECTING_TYPES.includes(entry.type)
if (isBalanceAffecting || (entry.type === EHistoryEntryType.REQUEST && isCompleted)) {
fetchBalance()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/Home/HomeHistory.tsx` around lines 69 - 73, isCompleted
currently treats missing status as completed (const isCompleted = !entry.status
|| entry.status.toUpperCase() === 'COMPLETED'), which causes REQUEST entries
with no status to trigger fetchBalance; change the logic so isCompleted is true
only when status explicitly equals 'COMPLETED' (e.g., use entry.status &&
entry.status.toUpperCase() === 'COMPLETED' or check entry.status?.toUpperCase()
=== 'COMPLETED') and keep the existing condition that calls fetchBalance for
BALANCE_AFFECTING_TYPES or when entry.type === EHistoryEntryType.REQUEST &&
isCompleted, so only REQUEST entries with explicit COMPLETED status invoke
fetchBalance.

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.

1 participant