Repository navigation
Read vault and wallet USDC balances from Soroban instead of hardcoded values #1313
Description
Summary
The balances users base funding decisions on are made up. The Billing page, the deposit modal and the Dashboard all show a vault balance of 284.62 and a wallet balance of 1260.5 USDC whatever the chain says. After a deposit, the UI adds the amount locally instead of reading the new balance back. This issue reads both balances from Stellar testnet through read-only Soroban simulation, and makes loading, error and stale states explicit.
Background and evidence
src/App.tsxline 354 setsuseState(284.62)forvaultBalance, and line 355 setsuseState(1260.5)forwalletBalance.- These values feed:
- the Billing vault cards (lines 692–704)
- the modal balance tiles (lines 899–908)
- the Max button (
handleMax, line 532) - the "exceeds wallet balance" validation (line 434)
DashboardPage(line 664)
- After a confirmed deposit, line 578 sets the balance to
startingBalance + approvedAmount, which is an optimistic local sum that is never reconciled with the chain. src/pages/DashboardOverview.tsxline 94 hardcodes the subtitle "Freighter Stellar Wallet (Public Net)" even though the app targets testnet.- The contract interface (Callora-Contracts
contracts/vault/src/lib.rs):balance()(line 1231) returns the tracked vault balance in stroops (7 decimals).get_usdc_token()(line 1261) returns the USDC SAC address. On testnet this isCAC7CFZ42L3LTTZCI4C3XI6UF7AHS2PYTNJ5QEG2PSLRGDGMWRQOZOT5(docs/DEPLOYMENTS.md).- The SAC's
balance(id)gives the wallet's USDC balance.
Scope
In scope
- New module
src/services/vaultReads.tswith:readVaultBalance(): Promise<bigint>, which simulatesbalance()onSTELLAR_VAULT_CONTRACT_ID.readUsdcToken(): Promise<string>, which simulatesget_usdc_token()and caches the result for the session.readWalletUsdcBalance(address): Promise<bigint>, which simulates SACbalance(address).- All three use
scValToNative, and stroops stay asbigintuntil formatting.
- Hook
useVaultBalances()returning{ vault, wallet, status: "idle" | "loading" | "ready" | "error", error, refresh, updatedAt }.- The wallet read only runs once a Freighter address is known.
- Requests are aborted or ignored when they are superseded.
- Replace the hardcoded state in
App.tsxwith the hook.- Billing cards, modal tiles, Max and validation use real values.
- While loading, show skeletons that do not shift layout.
- On error, show an inline message with a Retry button, and never show
0as if it were real.
- Read back after deposit. After
submitVaultDepositresolves, callrefresh()and show the chain value. Use the optimistic value only as a labelled "pending refresh" placeholder until the read returns. - Dashboard. Pass the same values to
DashboardPageand correct the network label usingSTELLAR_NETWORK.
Out of scope
- Billing history, deposit preflight and trustline handling (separate issues).
- Backend
/api/vault/balanceintegration.
Design constraints
- Money formatting must be exact. Convert stroops to a display string without going through
Numberfor values above 2^53, and reuse or extendsrc/utils/format.ts. - Make no network calls when no vault contract is configured. Show a "not configured" state instead.
- Simulation needs a source account. Use the connected address when there is one. Otherwise build the read with a valid, fixed, well-formed account and document why it is safe (simulation only, never signed).
- Debounce refreshes so opening and closing the modal does not flood the RPC. Allow at most one in-flight read per contract.
- WCAG 2.1 AA: balance updates are announced via a polite live region, and loading skeletons carry
aria-busy.
Test plan
Add src/services/vaultReads.test.ts, and extend src/App.deposit.test.tsx and src/components/Dashboard.test.tsx as needed.
| # | Scenario | Expected |
|---|---|---|
| 1 | balance() returns 5000000000 |
UI shows 500.00 USDC |
| 2 | Very large i128 (> 2^53 stroops) | formatted exactly, with no float rounding |
| 3 | Wallet not connected | wallet tile shows "Connect wallet", with no SAC call |
| 4 | RPC error | inline error and Retry; no 284.62 or 0 shown |
| 5 | Deposit confirmed | refresh() called; displayed vault value equals the mocked post-deposit balance() and not the local sum |
| 6 | Amount > real wallet balance | validation message appears, driven by the chain value |
| 7 | Max button | fills the real wallet balance, truncated to 7 decimals |
| 8 | Superseded read | an older response resolving after a newer one does not overwrite it |
| 9 | Not configured | no RPC calls; "Vault not configured" state |
Regression proof. Row 5 fails on main, where line 578 computes the sum locally.
Acceptance criteria
-
grep -n "284.62\|1260.5" src/App.tsxreturns nothing. - Billing, the deposit modal and the Dashboard show the same chain-sourced values.
-
npx tsc -p tsconfig.json --noEmit,npm run buildandnpx vitest runpass. -
npm run test:coverageshows at least 90% line coverage forvaultReads.tsand the new hook. -
docs/Deposit-Flow.mddescribes the read-back.
Getting started
git checkout -b feat/onchain-vault-balances
npm ci
npx vitest run src/App.deposit.test.tsx
# read-only sanity check against the deployed vault:
stellar contract invoke --id CBJZLUKQBWDMZPKNJAVONZ7GCCWADSETRXWOPN5Z3XWAUNI4F2R7XDYQ --network testnet --send=no -- balancePR requirements
- Link this issue.
- Include before and after screenshots of Billing and the Dashboard at 375px and 1280px.
- Include a short screen recording of a real testnet deposit followed by the balance refresh.
- Paste the test and coverage output, and keep the diff focused.
- Example commit:
feat(vault): read vault and wallet balances via Soroban simulation.
Priority rationale
High. Wrong balances on a funding screen erode trust directly and can mislead users about money. The work spans Soroban simulation, i128 precision, async race handling and three surfaces that share state.
Activity
AugistineCreates commentedon Oct 18, 2026
@AugistineCreates has applied to work on this issue as part of the Stellar Wave Program's 10th wave.
ℹ️ Repo Maintainers: To accept this application, review their application or assign @AugistineCreates to this issue.
Adejoke46 commentedon Oct 18, 2026
@Adejoke46 has applied to work on this issue as part of the Stellar Wave Program's 10th wave.
ℹ️ Repo Maintainers: To accept this application, review their application or assign @Adejoke46 to this issue.
IFEANYIBRIGHT commentedon Oct 18, 2026
@IFEANYIBRIGHT has applied to work on this issue as part of the Stellar Wave Program's 10th wave.
ℹ️ Repo Maintainers: To accept this application, review their application or assign @IFEANYIBRIGHT to this issue.
raskenite commentedon Oct 18, 2026
@raskenite has applied to work on this issue as part of the Stellar Wave Program's 10th wave.
ℹ️ Repo Maintainers: To accept this application, review their application or assign @raskenite to this issue.
drips-wave commentedon Oct 19, 2026
Congratulations, @IFEANYIBRIGHT! 🎉 Your application was accepted by the repo's maintainers, and the issue is due on October 22, 2026.
🧑💻 @IFEANYIBRIGHT: Please resolve the issue such that the repo's maintainers have enough time to review your contribution before the due date. You'll earn Points for completing the issue on-time, which will make you eligible for a share of the Stellar Wave Program's reward pool.
Warning
When opening a PR, please link it to this issue to ensure it gets tracked accurately. Points are awarded when this issue is marked as completed by the maintainer.
🤠 Repo maintainers: Please keep an eye on the contributor's progress and review their work before the due date. You can manage this issue, including adjusting its complexity and points, here.
🌊 Happy Wave 🌊
grantfox-oss commentedon Oct 21, 2026
🎉 This issue has been marked as completed on GrantFox!
@IFEANYIBRIGHT's PR #1358 was approved and merged by @greatest0fallt1me.
🏆 @IFEANYIBRIGHT: You earned 35 FoxPoints for this contribution! Your current tier: Explorer (666 total points). Track your full progress on GrantFox.
👏 Great work, @IFEANYIBRIGHT! Keep contributing to CalloraOrg.
drips-wave commentedon Oct 21, 2026
This issue has been completed by @IFEANYIBRIGHT as part of the Stellar Wave Program's 10th Wave 🥳
😎 @IFEANYIBRIGHT: You earned 100 Points for completing this issue! Once the Wave's rewards are finalized, you'll be eligible for a percentage of the Wave's reward pool based on the percentage of total points you've earned. Learn more here. You can also Leave a review to share your experience working on this issue.
🧑💻 Repo maintainers: How'd the contributor do? Leave a review to share your experience working with them.