Skip to content

fix(wallet): resolve balance from the configured network's Horizon - #19

Merged
ibrahimmosouf-png merged 1 commit into
OrbitChainLabs:mainfrom
Degentle12:fix/issue-8-wallet-balance-network
Aug 23, 2026
Merged

fix(wallet): resolve balance from the configured network's Horizon#19
ibrahimmosouf-png merged 1 commit into
OrbitChainLabs:mainfrom
Degentle12:fix/issue-8-wallet-balance-network

Conversation

@Degentle12

Copy link
Copy Markdown
Contributor

Summary

Closes #8

The wallet store fetched balances from a hardcoded Horizon testnet URL, so mainnet-configured wallets always showed a zero (or wrong-network) balance. The balance lookup now resolves the Horizon base URL from the configured network (NEXT_PUBLIC_STELLAR_NETWORK, with NEXT_PUBLIC_STELLAR_HORIZON_URL as an override), distinguishes a Horizon 404 (a brand-new account — a valid zero) from real lookup failures, and keeps the balance fresh on connect and on a 15-second interval.

The key design decision: the fetch logic lives in a new pure module (lib/stellar/balance.ts) with no app imports, so the failure semantics and network selection are unit-testable with mocked fetch responses; the store is a thin glue layer that owns the refresh lifecycle.

Why

Before this change, store/walletStore.ts hit https://horizon-testnet.stellar.org/accounts/<address> unconditionally, bypassing the repo's network-aware infrastructure (lib/stellar/config.ts HORIZON_URLS/getStellarConfig, lib/env.ts network validation). Consequences:

  • With NEXT_PUBLIC_STELLAR_NETWORK=mainnet, balance lookups still went to testnet Horizon, returning 404 (rendered as '0.0000000') or an unrelated testnet balance.
  • Every failure — 404 or network error — was collapsed into a silently wrong '0.0000000', so users couldn't tell an empty account from a lookup failure.
  • components/Header.tsx duplicated the same hardcoded testnet fetch with its own 15s interval, so there were two competing implementations of the same bug.

What was built

File What it contains
lib/stellar/balance.ts New pure module: fetchNativeBalance(address, horizonUrl) and parseNativeBalance(data). Horizon 404 returns ZERO_BALANCE with no error (new account); network failures and non-404 HTTP errors return balance: null plus a human-readable error. Trailing slashes on the base URL are tolerated. No app imports, so it runs under Node's test runner.
lib/stellar/config.ts New resolveHorizonUrl(): honors NEXT_PUBLIC_STELLAR_HORIZON_URL when set, otherwise maps NEXT_PUBLIC_STELLAR_NETWORK (testnet/mainnet/futurenet) to the module's well-known HORIZON_URLS, where the env's mainnet maps to the public network.
store/walletStore.ts connect now fetches the balance immediately and starts a 15s refresh interval; disconnect stops the timer; new refreshBalance() action; new balanceError state field set on lookup failures (previous balance is preserved) instead of writing a fake zero.
types/index.ts `WalletState.balanceError: string
components/Header.tsx Removed the duplicate hardcoded testnet fetch + its own interval; the wallet store now owns balance fetching and refresh.
components/WalletDropdown.tsx Shows "Balance unavailable" when balanceError is set, instead of rendering a silently wrong 0.00 XLM.
tests/walletBalance.test.ts 11 unit tests with mocked fetch: native balance, no native asset, 404, network error, non-404 HTTP error, trailing-slash URL handling, testnet URL resolution, mainnet URL resolution, NEXT_PUBLIC_STELLAR_HORIZON_URL override, end-to-end mainnet fetch, and parseNativeBalance fallback.
package.json Adds "test": "node --test \"tests/**/*.test.ts\"" (Node's built-in runner, zero new dependencies).
tsconfig.json "target": "ES2020" (was unset, defaulting to ES3) and "allowImportingTsExtensions": true (so tests can import .ts modules under Node's native TypeScript support; safe with noEmit).

The tests exercise the exact failure semantics in the acceptance criteria: a 404 is a zero, a network error is an error, and the mainnet vs testnet URL selection is asserted end to end.

Integration changes outside store/

  • lib/server/adminStore.ts, lib/server/draftStore.ts, lib/server/campaignDeployer.ts — pre-existing type errors on main (12 total) that made npm run type-check and npm run build fail before any wallet change. These are the same mechanical fixes as in PR fix(env): reconcile env schema with reads and fail fast at boot #18 (issue Environment contract is drifting: lib/env.ts schema omits variables read elsewhere and assertEnv is never invoked #9): noUncheckedIndexedAccess spread guards in the stores, and a port of campaignDeployer.ts from the removed @stellar/stellar-sdk v11 SorobanRpc/scval API to the v14 rpc/nativeToScVal/xdr API (symbol map keys preserved). Included here only because the required quality gates cannot pass without them; identical content in both PRs merges cleanly regardless of order.
  • tsconfig.json — target/extension settings above, required for the new tests and for the BigInt literals in campaignDeployer.ts.

No other files were modified.

Acceptance criteria coverage

  • A connected wallet's balance is fetched from the Horizon URL of the configured network, not a hardcoded URL. (lib/stellar/balance.ts + resolveHorizonUrl() in lib/stellar/config.ts; verified by tests/walletBalance.test.ts — URL selection tests)
  • On mainnet configuration the balance reflects the mainnet account; switching NEXT_PUBLIC_STELLAR_NETWORK changes the lookup target. (resolveHorizonUrl() maps mainnethttps://horizon.stellar.org; tests/walletBalance.test.ts — "mainnet configuration resolves the mainnet Horizon URL" and "mainnet configuration fetches from the mainnet Horizon URL")
  • A brand-new account shows zero with no error; a Horizon failure surfaces as an error rather than a silently wrong balance. (fetchNativeBalance 404 → ZERO_BALANCE/no error; network/HTTP errors → error + balanceError in the store, surfaced in WalletDropdown; tests/walletBalance.test.ts — "404 from Horizon…", "network failure…", "non-404 HTTP error…")
  • Balance refreshes on reconnect and at least once after connect without user action. (store/walletStore.ts — immediate fetch on connect + 15s interval, stopped on disconnect; reconnecting restarts both)
  • Tests cover the fetch with mocked responses: native balance, no native asset, 404, network error, and mainnet vs testnet URL selection. (tests/walletBalance.test.ts — 11 tests, all scenarios listed)

Test plan

  • npm test — 11/11 passing (11 new tests)
  • npm run type-check — no errors
  • npm run lint — no warnings or errors
  • npm run build — succeeds

Env vars / Notes

No new environment variables. Behavior of existing variables:

  • NEXT_PUBLIC_STELLAR_NETWORK — now controls the balance lookup target (testnethttps://horizon-testnet.stellar.org, mainnethttps://horizon.stellar.org, futurenethttps://horizon-futurenet.stellar.org).
  • NEXT_PUBLIC_STELLAR_HORIZON_URL — optional override that wins over the network mapping when set.

Notes:

  • The refresh interval (15s) matches the cadence the previous Header.tsx implementation used, now owned by the store so it survives page navigation and stops cleanly on disconnect.
  • On a failed refresh, the last known balance is preserved and balanceError is set; the dropdown shows "Balance unavailable" rather than a fake zero.
  • No data migrations involved.

The wallet store fetched balances from a hardcoded Horizon testnet URL,
so mainnet wallets always displayed a zero balance. Resolve the Horizon
base URL from the configured network (NEXT_PUBLIC_STELLAR_NETWORK with a
NEXT_PUBLIC_STELLAR_HORIZON_URL override) via a new resolveHorizonUrl()
in lib/stellar/config.ts, and move the fetch into a pure
lib/stellar/balance.ts module that distinguishes Horizon 404 (a valid
zero balance for a new account) from real lookup failures, which now
surface as a balanceError instead of a silently wrong zero. Balance
refreshes on connect and every 15s; the duplicate hardcoded fetch in
Header.tsx is removed and the wallet store owns the refresh lifecycle.
Adds unit tests with mocked fetch covering native balance, no native
asset, 404, network error, non-404 HTTP error, and mainnet vs testnet
URL selection. Also fixes pre-existing type errors in adminStore,
draftStore, and campaignDeployer that blocked the required type-check
and build gates (identical to PR OrbitChainLabs#18).
@Degentle12
Degentle12 force-pushed the fix/issue-8-wallet-balance-network branch from 0e7a676 to 5ad8e89 Compare August 23, 2026 16:39
nasalehj added a commit to nasalehj/OrbitChain-Web that referenced this pull request Aug 23, 2026
The repository had no test infrastructure and no CI: package.json had no
test script, no test files existed, and .github/workflows did not exist,
so pure logic (Stellar formatting/validation/error mapping, the cache
manager, the auth and UI stores, image uploads) had zero regression
protection. Introduce Vitest as the runner (TS-native, zero-config unit
tests, built-in jsdom for browser-API tests, and a path to React Testing
Library for future component tests), add a test script, and write 76
unit tests across the seven modules the issue lists. Add a GitHub
Actions workflow that runs type-check, lint, test, and build on push and
pull request with no required secrets, and document npm test in the
README. Also fixes pre-existing type errors in adminStore, draftStore,
and campaignDeployer that blocked the required type-check and build
gates (identical to PRs OrbitChainLabs#18 and OrbitChainLabs#19).

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

LGTM

@ibrahimmosouf-png
ibrahimmosouf-png merged commit 402e644 into OrbitChainLabs:main Aug 23, 2026
nasalehj added a commit to nasalehj/OrbitChain-Web that referenced this pull request Aug 23, 2026
The repository had no test infrastructure and no CI: package.json had no
test script, no test files existed, and .github/workflows did not exist,
so pure logic (Stellar formatting/validation/error mapping, the cache
manager, the auth and UI stores, image uploads) had zero regression
protection. Introduce Vitest as the runner (TS-native, zero-config unit
tests, built-in jsdom for browser-API tests, and a path to React Testing
Library for future component tests), add a test script, and write 76
unit tests across the seven modules the issue lists. Add a GitHub
Actions workflow that runs type-check, lint, test, and build on push and
pull request with no required secrets, and document npm test in the
README. Also fixes pre-existing type errors in adminStore, draftStore,
and campaignDeployer that blocked the required type-check and build
gates (identical to PRs OrbitChainLabs#18 and OrbitChainLabs#19).
nasalehj added a commit to nasalehj/OrbitChain-Web that referenced this pull request Aug 23, 2026
The repository had no test infrastructure and no CI: package.json had no
test script, no test files existed, and .github/workflows did not exist,
so pure logic (Stellar formatting/validation/error mapping, the cache
manager, the auth and UI stores, image uploads) had zero regression
protection. Introduce Vitest as the runner (TS-native, zero-config unit
tests, built-in jsdom for browser-API tests, and a path to React Testing
Library for future component tests), add a test script, and write 76
unit tests across the seven modules the issue lists. Add a GitHub
Actions workflow that runs type-check, lint, test, and build on push and
pull request with no required secrets, and document npm test in the
README. Also fixes pre-existing type errors in adminStore, draftStore,
and campaignDeployer that blocked the required type-check and build
gates (identical to PRs OrbitChainLabs#18 and OrbitChainLabs#19).
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.

Wallet balance is hardcoded to Horizon testnet: mainnet wallets always display a zero balance

2 participants