Skip to content

feat: add balancec-watcher updater [pr 2 - connection logic] - #7640

Merged
limitofzero merged 26 commits into
developfrom
feat/balance-watcher-updater-2
Jun 19, 2026
Merged

feat: add balancec-watcher updater [pr 2 - connection logic]#7640
limitofzero merged 26 commits into
developfrom
feat/balance-watcher-updater-2

Conversation

@limitofzero

@limitofzero limitofzero commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Summary

Wires the cowswap-frontend to the new balances-watcher SSE service, behind the LaunchDarkly flag isBwEnabled.

When the flag is on, balances are pushed in real time over an EventSource and the existing multicall pipeline (priority tokens + full token-list polling) is bypassed. When off, nothing changes — the multicall path stays as is.

Note on the native token (ETH / xDAI / MATIC / BNB): the watcher service does not emit native balances, so we keep polling them via a single eth_getBalance RPC call (wagmi useBalance), refetched every 11 seconds.
This means the native balance can lag up to 11s behind the on-chain state — that's an explicit trade-off because I reused current implementation, but if it's crucial - I can rework it.

Out of scope (planned for follow-up):

  • Fallback to multicall when the SSE stream terminates with an unrecoverable error.
  • if the FE tries to send more than 1500 tokens (token lists tokens + custom tokens) - it returns the error. Because it's the only second pr - I decided to not implement fallback on it, it will be implemented further when we decide what to do here)

To Test

  1. Open the swap widget and switch on isBwEnabled flag.
  • Network tab shows a single POST /sessions followed by an open EventSource to the balances-watcher endpoint - and it returns 200 (OK)
  • Balances of imported tokens / tokens from enabled lists are visible
  • try to swap - balances should be updated very fast, about 0-2s after order execution
  • if user switch on token list/or add custom token - there should be another /sessions call with new token list or custom token in parameters(body), the balance of these tokens should be updated too

Summary by CodeRabbit

Release Notes

  • New Features
    • Added a real-time balances watcher updater to keep wallet balances in sync via live balance events.
    • Added an updater to write the connected wallet’s native token balance into state.
    • Added hooks to gather enabled token list URLs and derive per-chain custom tokens.
  • Bug Fixes
    • Improved watcher session start/stop logic, including safer handling for empty token sources, non-EVM chains, and race conditions.
  • Tests
    • Added comprehensive coverage for watcher session lifecycle, event merging/error handling, and native/custom token behavior.
  • Chores
    • Updated public exports to make the new watcher updater available.

@vercel

vercel Bot commented Jun 10, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
cowfi Ready Ready Preview Jun 19, 2026 11:33am
storybook Ready Ready Preview Jun 19, 2026 11:33am
widget-configurator Ready Ready Preview Jun 19, 2026 11:33am
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
cosmos Ignored Ignored Jun 19, 2026 11:33am
sdk-tools Ignored Ignored Preview Jun 19, 2026 11:33am

Request Review

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: c71f95af-fc42-4172-8179-2358aa0c77c2

📥 Commits

Reviewing files that changed from the base of the PR and between b86e1c8 and 4dda8df.

📒 Files selected for processing (2)
  • libs/balances-and-allowances/src/hooks/useBalancesWatcherSession.test.tsx
  • libs/balances-and-allowances/src/hooks/useBalancesWatcherSession.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • libs/balances-and-allowances/src/hooks/useBalancesWatcherSession.ts
  • libs/balances-and-allowances/src/hooks/useBalancesWatcherSession.test.tsx

Walkthrough

Adds an SSE-driven balances watcher mode to the balances-and-allowances library. New hooks collect enabled token list URLs and per-chain custom token addresses, a session hook manages the SSE lifecycle with stale-update cancellation and error handling, and a NativeTokenBalanceUpdater writes native balances to Jotai state. A new BalancesWatcherUpdater component composes these pieces and is wired into CommonPriorityBalancesAndAllowancesUpdater behind a isBwEnabled feature flag.

Changes

SSE Balances Watcher Mode

Layer / File(s) Summary
Token input hooks
libs/balances-and-allowances/src/hooks/useEnabledTokensListsUrls.ts, libs/balances-and-allowances/src/hooks/useCustomTokensForChain.ts, libs/balances-and-allowances/src/hooks/useCustomTokensForChain.test.tsx
useEnabledTokensListsUrls returns sorted enabled token list URLs; useCustomTokensForChain returns memoized sorted normalized address keys for user-added tokens on a given chain. Tests cover filtering, normalization, sorting, and empty inputs.
NativeTokenBalanceUpdater
libs/balances-and-allowances/src/updaters/NativeTokenBalanceUpdater.tsx, libs/balances-and-allowances/src/updaters/NativeTokenBalanceUpdater.test.tsx
New component reads the native token balance via useNativeTokenBalance and writes it into balancesAtom keyed by the chain's native token address. Tests verify no write before data, correct key usage, prop forwarding, and rerender updates.
useBalancesWatcherSession hook
libs/balances-and-allowances/src/hooks/useBalancesWatcherSession.ts, libs/balances-and-allowances/src/hooks/useBalancesWatcherSession.test.tsx
Implements the SSE session lifecycle: guards on account/EVM chain/non-empty tokens, creates a session, subscribes to events, merges balance diffs into balancesAtom with bigint conversion and address normalization, surfaces only terminal errors, and closes subscriptions on cleanup. Tests cover guard conditions, session payload shape, diff merging, terminal vs. non-terminal errors, session rejection, unmount cleanup, and a race-guard for stale chainId changes.
BalancesWatcherUpdater component and export
libs/balances-and-allowances/src/updaters/BalancesWatcherUpdater.tsx, libs/balances-and-allowances/src/index.ts
New component composes the two input hooks and useBalancesWatcherSession, then renders BalancesResetUpdater, BalancesCacheUpdater, and NativeTokenBalanceUpdater. Re-exported from the library's public index.
Feature-flag short-circuit
apps/cowswap-frontend/src/modules/balancesAndAllowances/updaters/CommonPriorityBalancesAndAllowancesUpdater.tsx
Imports BalancesWatcherUpdater, reads isBwEnabled from useFeatureFlags, and early-returns the watcher when enabled instead of the existing combined priority updaters.

Sequence Diagram(s)

sequenceDiagram
  rect rgba(100, 149, 237, 0.5)
    note over CommonPriorityBalancesAndAllowancesUpdater: Feature flag active
    CommonPriorityBalancesAndAllowancesUpdater->>BalancesWatcherUpdater: render(account, chainId)
  end
  BalancesWatcherUpdater->>useEnabledTokensListsUrls: get enabled list URLs
  BalancesWatcherUpdater->>useCustomTokensForChain: get custom token addresses
  BalancesWatcherUpdater->>useBalancesWatcherSession: start(account, chainId, listUrls, customTokens)
  useBalancesWatcherSession->>createBalancesWatcherSession: POST session
  createBalancesWatcherSession-->>useBalancesWatcherSession: sessionId
  useBalancesWatcherSession->>subscribeToBalancesEvents: open SSE stream
  subscribeToBalancesEvents-->>useBalancesWatcherSession: balance diff event
  useBalancesWatcherSession->>balancesAtom: merge update (BigInt, normalized addresses)
  BalancesWatcherUpdater->>NativeTokenBalanceUpdater: render(account, chainId)
  NativeTokenBalanceUpdater->>useNativeTokenBalance: fetch native balance
  NativeTokenBalanceUpdater->>balancesAtom: write native token balance
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • cowprotocol/cowswap#7551: Both PRs modify CommonPriorityBalancesAndAllowancesUpdater.tsx to change the updater mounting flow (retrieved PR removes legacy BFF gating, while this PR adds a useFeatureFlags-controlled early render of BalancesWatcherUpdater).
  • cowprotocol/cowswap#7587: The new NativeTokenBalanceUpdater relies on useNativeTokenBalance(account, chainId), and the retrieved PR changes that hook to disable balance queries on Solana chains, directly impacting the native balance flow introduced here.

Suggested reviewers

  • elena-zh
  • Danziger

🐇 Hop hop, the balances stream in live,
No more polling just to stay alive!
SSE whispers each token's new weight,
Big integers normalized, sorted just great.
A flag flips, the watcher takes the lead —
This bunny approves at lightning speed! ⚡

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title contains a typo ('balancec-watcher' instead of 'balances-watcher') and is cryptic with '[pr 2 - connection logic]', making it unclear what the main change accomplishes. Revise to: 'feat: add balances-watcher updater and SSE integration' or similar, removing the bracketed suffix for clarity.
Docstring Coverage ⚠️ Warning Docstring coverage is 26.32% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description check ✅ Passed The PR description covers summary, testing steps, and background context well. Most required sections are present with sufficient detail about functionality, configuration, and known limitations.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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 feat/balance-watcher-updater-2

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.

@limitofzero limitofzero changed the title feat: add bw updater feat: add balancec-watcher updater Jun 10, 2026
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 10, 2026

Copy link
Copy Markdown

Deploying swap-dev with  Cloudflare Pages  Cloudflare Pages

Latest commit: 4dda8df
Status: ✅  Deploy successful!
Preview URL: https://7524de8d.swap-dev-5u6.pages.dev
Branch Preview URL: https://feat-balance-watcher-updater.swap-dev-5u6.pages.dev

View logs

…-watcher-updater-2

# Conflicts:
#	libs/balances-and-allowances/src/balancesWatcher/subscribeToBalancesEvents.test.ts
@vercel
vercel Bot temporarily deployed to Preview – storybook June 10, 2026 18:24 Inactive
@vercel
vercel Bot temporarily deployed to Preview – widget-configurator June 10, 2026 18:24 Inactive
@vercel
vercel Bot temporarily deployed to Preview – cowfi June 10, 2026 18:26 Inactive
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 10, 2026

Copy link
Copy Markdown

Deploying explorer-dev with  Cloudflare Pages  Cloudflare Pages

Latest commit: 4dda8df
Status: ✅  Deploy successful!
Preview URL: https://f591fe1e.explorer-dev-dxz.pages.dev
Branch Preview URL: https://feat-balance-watcher-updater.explorer-dev-dxz.pages.dev

View logs

@vercel
vercel Bot temporarily deployed to Preview – storybook June 10, 2026 18:37 Inactive
@vercel
vercel Bot temporarily deployed to Preview – widget-configurator June 10, 2026 18:38 Inactive
@vercel
vercel Bot temporarily deployed to Preview – cowfi June 10, 2026 18:38 Inactive
Base automatically changed from feat/add-balances-watcher-interfaces to develop June 11, 2026 18:18
@vercel
vercel Bot temporarily deployed to Preview – storybook June 16, 2026 11:05 Inactive
@vercel
vercel Bot temporarily deployed to Preview – widget-configurator June 16, 2026 11:05 Inactive
@vercel
vercel Bot temporarily deployed to Preview – cowfi June 16, 2026 11:06 Inactive
@vercel
vercel Bot temporarily deployed to Preview – storybook June 16, 2026 19:15 Inactive
@vercel
vercel Bot temporarily deployed to Preview – widget-configurator June 16, 2026 19:15 Inactive
@limitofzero
limitofzero requested a review from elena-zh June 18, 2026 15:20

@elena-zh elena-zh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thank you, approved with issues to be addressed separately!

@vercel
vercel Bot temporarily deployed to Preview – storybook June 18, 2026 21:44 Inactive
@vercel
vercel Bot temporarily deployed to Preview – widget-configurator June 18, 2026 21:44 Inactive
@vercel
vercel Bot temporarily deployed to Preview – cowfi June 18, 2026 21:46 Inactive
@limitofzero

Copy link
Copy Markdown
Contributor Author

AI Review (Codex GPT-5, worked 5m): watcher failures can leave swaps stuck on balance loading
Finding: Handle watcher errors before the first snapshot as a completed balance load failure

  • Location: libs/balances-and-allowances/src/hooks/useBalancesWatcherSession.ts:75 and apps/cowswap-frontend/src/modules/tradeFormValidation/hooks/useTradeFormValidationContext.ts:111
  • When isBwEnabled is true, the PR bypasses the RPC updater and relies on the watcher path for EVM chains.
  • If createBalancesWatcherSession rejects or the SSE stream terminates before the first balance_update, the hook sets error and isLoading: false, but leaves hasFirstLoad: false.
  • The validation context then computes isBalancesLoading: !hasFirstLoad || isBalancesLoading, so the UI stays in BalancesLoading instead of surfacing BalancesNotLoaded. This matches the current Cypress failures on the PR head where swap flows never find #do-trade-button.

Suggested fix

  • Make the error path stop the first-load gate, or make validation treat a balance error as not loading. For example, terminal watcher/create-session failures should transition to a state that renders BalancesNotLoaded rather than permanent BalancesLoading.
  • Add coverage for both createSession rejection and terminal SSE error before the first snapshot, asserting the downstream validation sees a not-loaded/error state rather than loading forever.

Review scope and related context

fixed, thank you

@vercel
vercel Bot temporarily deployed to Preview – storybook June 19, 2026 08:58 Inactive
@vercel
vercel Bot temporarily deployed to Preview – widget-configurator June 19, 2026 08:59 Inactive
@vercel
vercel Bot temporarily deployed to Preview – cowfi June 19, 2026 09:00 Inactive

@fairlighteth fairlighteth left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ AI Review (Codex GPT-5, worked 4m): empty watcher token set can keep balances loading forever

Finding: Mark watcher mode loaded when there are no ERC20 tokens to subscribe to

  • Location: libs/balances-and-allowances/src/hooks/useBalancesWatcherSession.ts:43
  • When isBwEnabled is on, BalancesWatcherUpdater replaces the RPC updater for EVM chains.
  • If a connected user has no enabled token-list URLs and no custom tokens, useBalancesWatcherSession returns before creating a session and never updates balancesAtom.
  • useTradeFormValidationContext still treats !hasFirstLoad as loading, so the swap UI can stay in BalancesLoading forever even though NativeTokenBalanceUpdater may separately write the native balance.

Suggested fix

  • In the empty tokensListsUrls + customTokens case, transition balancesAtom to a completed empty-load state for the current chainId (hasFirstLoad: true, isLoading: false, error: null) instead of returning without touching state.
  • Add a focused test for a connected EVM account with both arrays empty, asserting the watcher does not create a session and the first-load gate is closed.
Review scope and related context

Already covered or rechecked, so not repeated as new findings:

  • The previous AI finding about pre-snapshot watcher failures is fixed on the current head: terminal SSE errors and createSession rejection now call applyTerminalError, which sets hasFirstLoad: true.
  • The toLowerCase() test normalization issue is resolved with getAddressKey.
  • The updater-vs-atom architecture discussion is already active in Danziger's review threads.
  • CodeRabbit already raised the non-blocking point that legacy hooks still run before the watcher-mode return.

Local targeted tests were not runnable in this checkout because dependencies are not installed (node_modules / Nx / Jest missing). GitHub Test/Lint/Typecheck/Cypress were still pending when checked.

🤖 Prompt for AI agents
Verify this finding against the current PR head. Fix only if still valid, keep the change minimal, and validate with a focused test.

Context:
- `CommonPriorityBalancesAndAllowancesUpdater` renders `BalancesWatcherUpdater` for EVM chains when `isBwEnabled` is true.
- `useBalancesWatcherSession` returns early when `tokensListsUrls.length === 0 && customTokens.length === 0`.
- That early return does not set `hasFirstLoad: true`.
- `useTradeFormValidationContext` treats `!hasFirstLoad` as balances loading.
- Expected behavior: connected EVM watcher mode with no ERC20 tokens to subscribe to should complete an empty balances load instead of leaving the swap form stuck in loading.

Generated using the pr-review skill from the CoW Protocol skills repo.

@fairlighteth fairlighteth left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Security Review (Codex GPT-5, worked 1m): no verified security findings

Review completed. I found no verified security findings worth posting.

Review scope and related context
  • Checked: balances-watcher session creation, SSE URL construction, malformed balance_update handling, terminal/non-terminal error handling, stale session cleanup, token-list/custom-token data sent to the watcher, and workflow env wiring for REACT_APP_BALANCES_WATCHER_BASE_URL.
  • Source: code inspection only, plus current GitHub PR checks.
  • Lifecycle: no verified-vulnerability findings.
  • Existing comments: the remaining issue I found earlier is availability/UX (hasFirstLoad stays false with an empty tracked token set), not a verified security vulnerability.
  • CI context: lint, tests, typecheck, agent harness, Socket checks, and deploy previews pass on current head; Cypress was still pending when checked.
  • Confidence limits: I did not inspect the balances-watcher backend, so backend-side handling of user-enabled token-list URLs is outside this frontend PR review.

Generated using the security-review skill from the CoW Protocol skills repo.

@fairlighteth

Copy link
Copy Markdown
Contributor

@limitofzero one more new finding #7640 (review) please review if applicable or not.

@vercel
vercel Bot temporarily deployed to Preview – storybook June 19, 2026 11:13 Inactive
@vercel
vercel Bot temporarily deployed to Preview – widget-configurator June 19, 2026 11:13 Inactive
@vercel
vercel Bot temporarily deployed to Preview – cowfi June 19, 2026 11:14 Inactive
@limitofzero

Copy link
Copy Markdown
Contributor Author

@limitofzero one more new finding #7640 (review) please review if applicable or not.

thank you, fixed

…ol/cowswap into feat/balance-watcher-updater-2
@vercel
vercel Bot temporarily deployed to Preview – storybook June 19, 2026 11:32 Inactive
@vercel
vercel Bot temporarily deployed to Preview – widget-configurator June 19, 2026 11:32 Inactive
@vercel
vercel Bot temporarily deployed to Preview – cowfi June 19, 2026 11:33 Inactive

@fairlighteth fairlighteth left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

AI Review (Codex GPT-5, worked 1m): follow-up addressed

Rechecked

  • Author response: @limitofzero said the empty watcher token-set finding was fixed.
  • Code path: libs/balances-and-allowances/src/hooks/useBalancesWatcherSession.ts:43.
  • Test coverage: libs/balances-and-allowances/src/hooks/useBalancesWatcherSession.test.tsx:110.

Result: Fixed. The empty token-list/custom-token case now skips session creation but still closes the first-load gate with hasFirstLoad: true, isLoading: false, error: null, and the current chainId.

🤖 Verification notes for AI agents
Verify the prior finding against current code only. Confirm whether the author's claimed fix addresses the specific failure mode, and avoid reopening broader or unrelated issues.

Prior finding:
- Connected EVM watcher mode with no enabled token-list URLs and no custom tokens returned before updating balancesAtom, leaving useTradeFormValidationContext stuck in BalancesLoading.

Current verification:
- useBalancesWatcherSession now calls applyEmptyLoad(state, chainId) before returning from the empty arrays branch.
- The focused test asserts no session is created and hasFirstLoad is true, isLoading is false, error is null, and chainId is set.

Generated using the pr-review skill from the CoW Protocol skills repo.

@fairlighteth
fairlighteth self-requested a review June 19, 2026 11:42

@fairlighteth fairlighteth left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Image

@limitofzero
limitofzero merged commit 5a38bec into develop Jun 19, 2026
19 of 20 checks passed
@limitofzero
limitofzero deleted the feat/balance-watcher-updater-2 branch June 19, 2026 13:42
@github-actions github-actions Bot locked and limited conversation to collaborators Jun 19, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants