Skip to content

Distinguish wallet balance from in-market account balance in the trade panel#2147

Merged
dcccrypto merged 3 commits intodcccrypto:mainfrom
0x-SquidSol:feat/wallet-vs-account-balance
May 8, 2026
Merged

Distinguish wallet balance from in-market account balance in the trade panel#2147
dcccrypto merged 3 commits intodcccrypto:mainfrom
0x-SquidSol:feat/wallet-vs-account-balance

Conversation

@0x-SquidSol
Copy link
Copy Markdown
Contributor

@0x-SquidSol 0x-SquidSol commented May 7, 2026

Summary

Two pieces of data were being conflated in the trade panel UI:

  1. The user's Phantom (or other wallet) USDC balance — what they have available to deposit
  2. The user's deposited capital on this market's slab account — what they can actually trade

Previously the top of the trade panel showed "ACCOUNT 0 USDC" and the Size row showed "Bal: 0 USDC" — both reading from the same in-market capital figure (with a fallback to wallet balance when no account existed). Users had no way to tell what they had in their wallet vs what was on-chain in the market.

Changes

Surface Before After
Top bar (DepositTrigger) `ACCOUNT 0 USDC` (label dim, value bright) — value was account capital `Wallet Balance: X USDC` (label and value same brightness) — value is wallet ATA balance
Size row (TradeForm) `Bal: 0 USDC` — value was capital with fallback to wallet balance when no account `Account Bal: 0 USDC` — value is always capital ("0" pre-account, truthful)

New hook `useWalletAtaBalance` fetches the user's associated token account balance via `getTokenAccountBalance`. One-shot fetch on dependency change — no polling.

Why

The relabel makes the relationship between the two figures explicit:

  • Wallet Balance at the top tells the user how much they have available to deposit.
  • Account Bal in the Size row tells them what's already in the market for trading.

Pre-account users now see `Account Bal: 0 USDC` (truthful — they haven't deposited anything yet) instead of the previous fallback to wallet balance, which was misleading because the same number now appears above as Wallet Balance.

Test plan

  • Full app suite: 2277 passing, 0 failures
  • TradeForm tests still passing — they don't assert on the specific label text
  • Manual smoke: connect wallet with USDC → top bar shows actual Phantom USDC, Size row shows 0 (no account yet)
  • Manual smoke: deposit some USDC → wallet balance decreases, account balance increases, both visible
  • Manual smoke: pre-account user sees both rows informatively (no "squint to read" issue)

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • Improvements

    • Deposit header now displays the wallet-available balance for clearer funding visibility.
    • Balance label in trade inputs updated to "Account Bal" for clarity.
  • Bug Fixes

    • Deposit trigger header corrected to reflect wallet-available funds rather than already-deposited capital.
    • Balance fallbacks standardized to show "0" when account data is unavailable.

Two pieces of data were being conflated in the trade panel UI:

  1. The user's Phantom (or other wallet) USDC balance — what they
     have available to deposit.
  2. The user's deposited capital on this market's slab account —
     what they can actually trade with.

Previously the top of the trade panel showed "ACCOUNT 0 USDC" and
the Size row showed "Bal: 0 USDC" — both reading from the same
in-market capital figure (with a fallback to wallet balance when
no account existed). Users had no way to tell what they had in
their wallet vs what was on-chain in the market without doing
mental math.

Changes:

  app/hooks/useWalletAtaBalance.ts (new)
    Small hook that fetches the user's associated token account
    balance for a given mint via getTokenAccountBalance. Returns
    `{ balance, decimals }` or null when the wallet is
    disconnected, the mint is null, or the ATA doesn't exist yet.
    One-shot fetch on dependency change — no polling, callers can
    rebuild on slab capital changes if they want refresh-after-
    deposit semantics.

  DepositTrigger.tsx — top bar
    Renamed "ACCOUNT" to "Wallet Balance:" and switched the value
    from `capital` to the wallet's ATA balance via the new hook.
    Label and value now share the same brightness (--text), font,
    and weight so the whole "Wallet Balance: X USDC" line reads
    as one unit. Drops the small uppercase-tracking label that was
    at --text-dim and forced users to squint to read which balance
    they were looking at.

  TradeForm.tsx — Size row
    Renamed "Bal:" to "Account Bal:" so the label distinguishes
    itself from the wallet-balance line above. Drops the
    conditional fallback to walletAtaBalance when no userAccount
    exists — pre-account users now see "Account Bal: 0 USDC"
    (truthful: they have nothing deposited) rather than the
    misleading wallet balance which already shows above. The
    in-component walletAtaBalance state is retained for the
    margin / CTA logic that still depends on it.

Tests: 2277 app tests passing.
@0x-SquidSol 0x-SquidSol requested a review from dcccrypto as a code owner May 7, 2026 16:02
@vercel
Copy link
Copy Markdown

vercel Bot commented May 7, 2026

@0x-SquidSol is attempting to deploy a commit to the Khubair Nasir's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 7, 2026

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2d753a73-0211-4103-8f94-b12ec36f9f67

📥 Commits

Reviewing files that changed from the base of the PR and between 88ca23f and c76ac23.

📒 Files selected for processing (3)
  • app/components/trade/DepositTrigger.tsx
  • app/components/trade/TradeForm.tsx
  • app/hooks/useWalletAtaBalance.ts

📝 Walkthrough

Walkthrough

This PR adds a new useWalletAtaBalance hook to fetch wallet-held ATA balance and decimals, wires it into DepositTrigger to show wallet-available balance in the collapsed header (with fallbacks), and updates TradeForm's balance label and missing-account fallback to show "Account Bal" and "0".

Changes

Wallet ATA Balance Tracking

Layer / File(s) Summary
Hook Contract & Types
app/hooks/useWalletAtaBalance.ts
Exports WalletAtaBalance interface with `balance: bigint
Hook Implementation
app/hooks/useWalletAtaBalance.ts
Adds useWalletAtaBalance(mint, refreshTrigger?): derives wallet publicKey and connection, computes ATA via getAssociatedTokenAddressSync, fetches token account balance via connection.getTokenAccountBalance, maps RPC amount to BigInt and decimals when present, handles missing inputs/errors by returning nulls, and cancels state updates after unmount or dependency changes.
DepositTrigger Integration
app/components/trade/DepositTrigger.tsx
Imports and calls useWalletAtaBalance, derives displayBalance (fallback 0n) and displayDecimals (fallback collateral decimals), adds comment clarifying the header shows wallet-available funds, and renders the collapsed header as "Wallet Balance" using formatted display values instead of capital.
TradeForm Balance Label & Fallback
app/components/trade/TradeForm.tsx
Renames balance header from "Bal" to "Account Bal" and changes missing-user-account fallback to always display "0".

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 I hopped to the chain to peek and see,
The ATA balance shining back at me,
No hidden capital in the fold,
The wallet's numbers now are told,
Small paws, big clarity — balance set free.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Distinguish wallet balance from in-market account balance in the trade panel' clearly and specifically describes the main change: separating the display of wallet balance from account balance in the UI components.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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 unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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
Copy Markdown

@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)
app/components/trade/TradeForm.tsx (1)

94-100: ⚖️ Poor tradeoff

TradeForm duplicates the ATA fetch that useWalletAtaBalance now encapsulates

TradeForm has its own inline useEffect (lines 394–415) that calls connection.getTokenAccountBalance for exactly the same reason as the new hook — fetching wallet ATA balance and on-chain decimals. This means two separate RPC calls are fired for the same account whenever both components are mounted. Migrating TradeForm to useWalletAtaBalance (adding decimals to its return value is already done) would remove the duplication.

♻️ Sketch of the migration
-  const [onChainDecimals, setOnChainDecimals] = useState<number | null>(null);
-  const decimals = onChainDecimals ?? tokenMeta?.decimals ?? 6;
-  const [walletAtaBalance, setWalletAtaBalance] = useState<bigint | null>(null);
+  const { balance: walletAtaBalance, decimals: hookDecimals } =
+    useWalletAtaBalance(mktConfig?.collateralMint ?? null);
+  const decimals = hookDecimals ?? tokenMeta?.decimals ?? 6;

Then remove the inline useEffect at lines 394–415.

Also applies to: 394-415

🤖 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 `@app/components/trade/TradeForm.tsx` around lines 94 - 100, TradeForm
currently duplicates RPC logic by calling connection.getTokenAccountBalance in
its inline useEffect; replace that logic with the new useWalletAtaBalance hook:
import and call useWalletAtaBalance(...) and use the returned {balance,
decimals} (or equivalent names provided by the hook) instead of maintaining
local state variables walletAtaBalance/onChainDecimals and setters
(setWalletAtaBalance, setOnChainDecimals); remove the inline useEffect that
performs getTokenAccountBalance and any related local state initialization so
TradeForm relies solely on the hook-provided values.
🤖 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 `@app/hooks/useWalletAtaBalance.ts`:
- Around line 43-50: The current branch checks info.value.amount truthiness and
when it's an empty string (""), the else resets both balance and decimals to
null, discarding valid decimals; update the check in useWalletAtaBalance (the if
using info.value.amount and the setState calls) to only attempt BigInt when
amount is non-empty and not null/undefined (e.g., amount !== "" && amount !=
null) and in the else branch preserve decimals by setting decimals:
info.value.decimals !== undefined ? info.value.decimals : null while leaving
balance null; ensure you reference info.value.amount, info.value.decimals, and
the setState call when making this change.
- Around line 32-60: The hook useWalletAtaBalance currently only fetches once on
changes to publicKey, mint, or connection, causing stale balances after
deposits/withdraws; modify useWalletAtaBalance to accept an optional
refreshTrigger (e.g., a number or boolean) parameter and include it in the
useEffect dependency array so callers can toggle refreshTrigger to force a
re-fetch; ensure the effect still computes ata via getAssociatedTokenAddressSync
and calls connection.getTokenAccountBalance, preserves the cancelled flag logic,
and updates setState the same way when the refreshTrigger changes.

---

Nitpick comments:
In `@app/components/trade/TradeForm.tsx`:
- Around line 94-100: TradeForm currently duplicates RPC logic by calling
connection.getTokenAccountBalance in its inline useEffect; replace that logic
with the new useWalletAtaBalance hook: import and call useWalletAtaBalance(...)
and use the returned {balance, decimals} (or equivalent names provided by the
hook) instead of maintaining local state variables
walletAtaBalance/onChainDecimals and setters (setWalletAtaBalance,
setOnChainDecimals); remove the inline useEffect that performs
getTokenAccountBalance and any related local state initialization so TradeForm
relies solely on the hook-provided values.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0c1fd79e-8b65-45c0-998e-8438a83a4197

📥 Commits

Reviewing files that changed from the base of the PR and between e651e93 and 88ca23f.

📒 Files selected for processing (3)
  • app/components/trade/DepositTrigger.tsx
  • app/components/trade/TradeForm.tsx
  • app/hooks/useWalletAtaBalance.ts

Comment thread app/hooks/useWalletAtaBalance.ts Outdated
Comment thread app/hooks/useWalletAtaBalance.ts Outdated
0x-SquidSol and others added 2 commits May 7, 2026 12:12
…cimals on empty-amount edge case

Both findings from the CodeRabbit pass on PR dcccrypto#2147.

  1. (Major) Wallet balance went stale after every deposit / withdraw.
     useWalletAtaBalance only re-fetched on publicKey / mint /
     connection changes — none of which fire when the user deposits
     via the inline DepositWithdrawCard. So a deposit decreased the
     on-chain ATA balance but the bar kept showing the pre-deposit
     number until the page reloaded.

     Fix: hook now accepts an optional `refreshTrigger` arg and
     includes it in the effect's deps. DepositTrigger passes the
     in-market `capital` value as the trigger — capital changes on
     every settled deposit / withdraw / trade-fee event, which is
     exactly when the wallet ATA balance also changes on-chain.
     Capital also changes on trade settlement (fees), which causes
     an unnecessary re-fetch, but ATA fetches are cheap single RPC
     calls and the simpler "any capital change = refresh" trigger
     beats the alternative of plumbing explicit deposit/withdraw
     event signals through the component tree.

  2. (Minor) The else branch in the success path of the fetch
     reset both balance AND decimals to null when info.value.amount
     was empty (an unusual malformed-RPC case), discarding any
     usable info.value.decimals from the same response. Now we
     compute on-chain decimals once at the top of the success
     block and preserve them across both branches.

Tests: 11 TradeForm tests passing.
@dcccrypto dcccrypto merged commit 1f69b6d into dcccrypto:main May 8, 2026
6 of 10 checks passed
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.

2 participants