Skip to content

feat(solana): load token balances#7850

Open
shoom3301 wants to merge 43 commits into
developfrom
solana/web-2
Open

feat(solana): load token balances#7850
shoom3301 wants to merge 43 commits into
developfrom
solana/web-2

Conversation

@shoom3301

@shoom3301 shoom3301 commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes: https://linear.app/cowswap/issue/FE-305/fetch-token-balances-from-blockchain

There is a new usePersistSolanaBalancesViaWebCalls which is similar to usePersistBalancesViaWebCalls but for Solana.
It uses @solana/web3.js.connection.getMultipleAccountsInfo() to load balances and @solana/spl-token to work with Solana tokens.

Essentially, it should work the same as balance loading in EVM chains:

  • PriorityTokensUpdater which updates currently selected tokens and tokens of pending orders every 8 seconds
  • BalancesAndAllowancesUpdater which updates all tokens every 31 seconds
image

To Test

  1. It should load balance in Solana network
  2. It should update balances in UI when they are changing in the account

Summary by CodeRabbit

  • New Features

    • Added Solana token balance fetching, caching, persistence, and refresh support (including Token-2022).
    • Updated non-EVM RPC defaults and improved wallet reconnection using stored connector state.
  • Bug Fixes

    • Improved balance display for connected wallets on non-EVM networks.
    • Prevented permit loading on unsupported chains (non-EVM).
  • Tests

    • Added coverage for Solana token balance fetching, RPC batching, caching, persistence hooks, and Token-2022 behavior.

@shoom3301 shoom3301 self-assigned this Jul 14, 2026
@vercel

vercel Bot commented Jul 14, 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 Jul 17, 2026 3:47pm
explorer-dev Ready Ready Preview Jul 17, 2026 3:47pm
storybook Ready Ready Preview Jul 17, 2026 3:47pm
swap-dev Ready Ready Preview Jul 17, 2026 3:47pm
widget-configurator Ready Ready Preview Jul 17, 2026 3:47pm
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
cosmos Ignored Ignored Jul 17, 2026 3:47pm
sdk-tools Ignored Ignored Preview Jul 17, 2026 3:47pm

Request Review

@coderabbitai

coderabbitai Bot commented Jul 14, 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: 64db4531-cf73-4b12-8243-67b25d071a9c

📥 Commits

Reviewing files that changed from the base of the PR and between 49150e3 and a228233.

📒 Files selected for processing (3)
  • apps/cowswap-frontend/src/modules/tokensList/pure/TokenListItem/index.tsx
  • libs/balances-and-allowances/src/hooks/usePersistSolanaBalancesViaWebCalls.ts
  • libs/balances-and-allowances/src/services/fetchSolanaTokenBalances.ts
💤 Files with no reviewable changes (1)
  • apps/cowswap-frontend/src/modules/tokensList/pure/TokenListItem/index.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • libs/balances-and-allowances/src/services/fetchSolanaTokenBalances.ts
  • libs/balances-and-allowances/src/hooks/usePersistSolanaBalancesViaWebCalls.ts

Walkthrough

The PR adds Solana SPL and Token-2022 balance fetching, persistence, caching, network configuration, and frontend integration. It also replaces several EVM-only guards and adds explicit return types to combined-balance components.

Changes

Solana balance support

Layer / File(s) Summary
Network and token contracts
libs/common-const/src/networks.ts, libs/common-const/src/types.ts, libs/common-const/src/types.test.ts, libs/balances-and-allowances/package.json
RPC configuration now covers target chains, and Token-2022 metadata is normalized through a shared tag and helper.
Solana balance service
libs/balances-and-allowances/src/services/fetchSolanaTokenBalances.*
SPL and Token-2022 associated accounts are derived, fetched in batches of 100, decoded, and returned in input order with zero defaults for unavailable balances.
Solana persistence hook
libs/balances-and-allowances/src/hooks/usePersist*
Non-EVM balance persistence uses a React Query hook that updates balance state, timestamps, loading, and error state; tests cover batching, native SOL exclusion, and Token-2022 handling.
Address-keyed balance cache
libs/balances-and-allowances/src/hooks/useSwrConfigWithPauseForNetwork.ts, libs/balances-and-allowances/src/updaters/BalancesCacheUpdater*
Pause timestamps and balance cache reads and writes use standardized address keys, including case-sensitive Solana accounts.
Frontend chain integration
apps/cowswap-frontend/src/modules/..., libs/wallet/src/wagmi/config.ts, apps/cowswap-frontend/vite.config.mts, apps/cowswap-frontend/package.json
Permit loading and token balance display support non-EVM chains, wallet connector detection uses AppKit storage, and Solana AppKit support is added.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

Suggested labels: Balances

Suggested reviewers: alfetopito, elena-zh, fairlighteth, danziger

Sequence Diagram(s)

sequenceDiagram
  participant Wallet
  participant BalanceHook
  participant SolanaRPC
  participant BalanceAtom
  Wallet->>BalanceHook: provide Solana account and token state
  BalanceHook->>SolanaRPC: fetch associated token accounts
  SolanaRPC-->>BalanceHook: return account information
  BalanceHook->>BalanceAtom: persist decoded balances and timestamps
Loading

Poem

I’m a rabbit with tokens to chart,
SPL balances now play their part.
Through batched RPCs they hop in a row,
Token-2022 tells the right path to go.
Cache keys stay crisp, case and all—
Solana balances answer the call! 🐇

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed Clear and related to the main Solana balance-loading change.
Description check ✅ Passed Includes Summary and To Test, with concrete Solana balance-loading details; Background is omitted but not critical.
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.
✨ 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 solana/web-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.

@shoom3301
shoom3301 marked this pull request as ready for review July 14, 2026 13:33
@shoom3301
shoom3301 requested review from a team July 14, 2026 13:33

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

Hey @shoom3301 , no, it is not working: I can see only native balance

Image

Same happens on other chains in this PR when being connected to MM and change chain using chain selector in sell/buy token lists

Image

@fairlighteth
fairlighteth self-requested a review July 14, 2026 15:10

@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 23m): Solana token-program and address handling need fixes

Finding: [BLOCKING] Resolve the token program before deriving ATAs

  • Location: fetchSolanaTokenBalances.ts:33-50
  • Both ATA derivation and unpackAccount are hard-coded to TOKEN_PROGRAM_ID. The program ID is part of the ATA seed, and unpackAccount verifies the account owner against it.
  • The enabled-by-default Solana token list already contains Token-2022 mints. For example, PYUSD is owned by TOKEN_2022_PROGRAM_ID. The current code derives the wrong ATA and records 0n when that address is absent, so held Token-2022 balances appear as zero.

Suggested fix

  • Resolve each mint’s owner program and carry either TOKEN_PROGRAM_ID or TOKEN_2022_PROGRAM_ID through both ATA derivation and unpacking. Alternatively, query the owner’s accounts under both programs.
  • Add service-level coverage for a Token-2022 mint. The current test mocks both SPL-token functions, so it cannot catch incorrect program selection.

Finding: [BLOCKING] Preserve Solana account keys and remove non-null assertions

Suggested fix

  • Guard the query function or use React Query’s skipToken instead of non-null assertions.
  • Key the timestamp, pause state, and balance cache consistently with getAddressKey(account).
  • Add a regression test using a mixed-case Solana account through update and cache restoration.

Finding: [NON-BLOCKING] Isolate malformed mint entries from the batch

  • Location: fetchSolanaTokenBalances.ts:33
  • The token-list sanitizer only checks the base58 character set and length. For example, 'z'.repeat(44) passes that check but decodes to 33 bytes, so new PublicKey(mint) throws.
  • Because every ATA is constructed before the RPC request, one malformed remote or widget-list entry rejects balance loading for the entire token list.

Suggested fix

  • Validate that entries decode to 32-byte public keys at the token-list boundary, and defensively isolate invalid entries in this service.
  • Add a test proving one bad mint cannot prevent valid balances from loading.
Review scope and related context
  • The existing changes-requested review already reports the end-to-end “only native balance” failure and other-chain selector regression, so those findings are not repeated.
  • There is no author response or resolved thread to recheck, and the head remains 2a8d3ee.
  • Test, lint, typecheck, and agent-harness checks pass. The Cypress failure reproduces on the base PR, so it is not attributed to this change.
  • After the runtime issue is fixed, the PR test plan should explicitly cover legacy SPL tokens, Token-2022 tokens, and account/chain switching.
🤖 Prompt for AI agents
Verify these findings against the current PR head and keep fixes scoped:

1. Make Solana balance loading program-aware for both the legacy Token Program and Token-2022, using the resolved program consistently for ATA derivation and account unpacking.
2. Remove the new non-null assertions and replace every Solana-exposed account-key lowercasing operation with getAddressKey.
3. Prevent one malformed mint from rejecting the complete balance request.
4. Add focused tests for Token-2022, mixed-case Solana account cache keys, and malformed-mint isolation.

Preserve existing EVM behavior and run targeted test, lint, and typecheck verification.

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

Comment thread libs/balances-and-allowances/src/hooks/usePersistSolanaBalancesViaWebCalls.ts Outdated
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 15, 2026

Copy link
Copy Markdown

Deploying swap-dev with  Cloudflare Pages  Cloudflare Pages

Latest commit: a228233
Status: ✅  Deploy successful!
Preview URL: https://605f57d1.swap-dev-5u6.pages.dev
Branch Preview URL: https://solana-web-2.swap-dev-5u6.pages.dev

View logs

@shoom3301

Copy link
Copy Markdown
Collaborator Author

@elena-zh fixed, it works now.
@fairlighteth thanks! Fixed all the issues.

@limitofzero

Copy link
Copy Markdown
Contributor
image LGTM, works as expected

@elena-zh

Copy link
Copy Markdown
Contributor

Hey @shoom3301 , great! Balances can be loaded now.
However! I've found 2 issues:

  1. Allowances are not loaded on the Tokens page. Should it be addressed in this PR?
allowances
  1. The app is crashed on Solana on the Account proxy page. Should I report it as a separate issue or it can be addressed in the current PR?
acc proxy

Thanks

@shoom3301

Copy link
Copy Markdown
Collaborator Author

@elena-zh thanks!

  1. Allowances is out of this PR scope, will be addressed in the Future
  2. Account proxy page is out of this PR scope, will be addressed in the Future. Yes please, add it to Linear project

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

@linear-code

linear-code Bot commented Jul 17, 2026

Copy link
Copy Markdown

FE-305

Base automatically changed from solana/web-1 to develop July 17, 2026 15:19

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 5

🧹 Nitpick comments (1)
libs/common-const/src/networks.ts (1)

47-55: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Update the type cast to reflect non-EVM networks.

Since RPC_URL_ENVS now contains keys for NonEvmChains.SOLANA and NonEvmChains.BITCOIN, casting its keys to EvmChains[] is semantically inaccurate. Use TargetChainId[] instead.

♻️ Proposed refactor
 export const RPC_URLS: Record<TargetChainId, HttpsString> = (
-  Object.keys(RPC_URL_ENVS) as unknown as EvmChains[]
+  Object.keys(RPC_URL_ENVS) as unknown as TargetChainId[]
 ).reduce(
   (acc, chainId) => {
     acc[Number(chainId) as TargetChainId] = getRpcUrl(Number(chainId) as TargetChainId)
     return acc
   },
   {} as Record<TargetChainId, HttpsString>,
 )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@libs/common-const/src/networks.ts` around lines 47 - 55, Update the
Object.keys(RPC_URL_ENVS) cast in the RPC_URLS initialization to TargetChainId[]
instead of EvmChains[], while preserving the existing reduce logic and
TargetChainId indexing.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/cowswap-frontend/src/modules/tokensList/pure/TokenListItem/index.tsx`:
- Around line 111-114: Update the stale comment above shouldShowBalances to
reflect that Solana balances are supported, and verify which bridge-only
destination chains support balance fetching. Keep shouldShowBalances enabled
only for supported chains, preserving the existing behavior that avoids
indefinite loading skeletons for unsupported destinations such as Bitcoin.

In
`@libs/balances-and-allowances/src/hooks/usePersistSolanaBalancesViaWebCalls.ts`:
- Around line 61-68: Update the useQuery options in
usePersistSolanaBalancesViaWebCalls to forward params.query?.refetchOnMount,
matching the EVM hook’s behavior and preserving caller control over refetching
on mount.
- Around line 136-153: The buildSolanaTokenMints function incorrectly defaults
unlisted mints to the classic SPL program. Resolve each missing mint’s token
program from its on-chain mint account owner, or query both classic and
Token-2022 ATA variants, while preserving the existing tokensByAddress metadata
path; add a regression test covering an unlisted Token-2022 mint and its real
balance.

In `@libs/balances-and-allowances/src/services/fetchSolanaTokenBalances.ts`:
- Around line 73-79: Update the ataInfos iteration in fetchSolanaTokenBalances
to catch TokenInvalidAccountOwnerError and TokenInvalidAccountSizeError from
unpackAccount, leaving the corresponding balances[index] at its default zero
value when either occurs. Preserve successful unpacking and balance assignment,
and avoid allowing malformed ATA data to abort processing of other accounts.

In `@libs/wallet/src/wagmi/config.ts`:
- Around line 82-87: Wrap the top-level localStorage access in the
hasRecentConnector initialization with try...catch, including both getItem calls
and the existing undefined guard. On a synchronous storage access failure such
as SecurityError, fall back to false so module evaluation continues without
crashing; preserve the current true result when either connector key is
available.

---

Nitpick comments:
In `@libs/common-const/src/networks.ts`:
- Around line 47-55: Update the Object.keys(RPC_URL_ENVS) cast in the RPC_URLS
initialization to TargetChainId[] instead of EvmChains[], while preserving the
existing reduce logic and TargetChainId indexing.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 04ec6e69-5fc5-4e43-b838-04dddcdcbf62

📥 Commits

Reviewing files that changed from the base of the PR and between 989a735 and 49150e3.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (20)
  • apps/cowswap-frontend/package.json
  • apps/cowswap-frontend/src/modules/combinedBalances/hooks/useTokensBalancesCombined.ts
  • apps/cowswap-frontend/src/modules/combinedBalances/updater/BalancesCombinedUpdater.tsx
  • apps/cowswap-frontend/src/modules/permit/hooks/usePermitInfo.ts
  • apps/cowswap-frontend/src/modules/tokensList/pure/TokenListItem/index.tsx
  • apps/cowswap-frontend/vite.config.mts
  • libs/balances-and-allowances/package.json
  • libs/balances-and-allowances/src/hooks/usePersistBalancesViaWebCalls.test.tsx
  • libs/balances-and-allowances/src/hooks/usePersistBalancesViaWebCalls.ts
  • libs/balances-and-allowances/src/hooks/usePersistSolanaBalancesViaWebCalls.test.tsx
  • libs/balances-and-allowances/src/hooks/usePersistSolanaBalancesViaWebCalls.ts
  • libs/balances-and-allowances/src/hooks/useSwrConfigWithPauseForNetwork.ts
  • libs/balances-and-allowances/src/services/fetchSolanaTokenBalances.test.ts
  • libs/balances-and-allowances/src/services/fetchSolanaTokenBalances.ts
  • libs/balances-and-allowances/src/updaters/BalancesCacheUpdater.test.tsx
  • libs/balances-and-allowances/src/updaters/BalancesCacheUpdater.tsx
  • libs/common-const/src/networks.ts
  • libs/common-const/src/types.test.ts
  • libs/common-const/src/types.ts
  • libs/wallet/src/wagmi/config.ts

Comment thread apps/cowswap-frontend/src/modules/tokensList/pure/TokenListItem/index.tsx Outdated
Comment thread libs/wallet/src/wagmi/config.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants