This document is the single source of truth for every network-specific constant in Disciplr-Frontend. It covers:
- The two supported networks (
TESTNET/PUBLIC) and how they map to Horizon and Stellar Expert URLs. - USDC issuer addresses per network.
- The
WalletNetworktype and normalization logic. - The expected wallet network used by the global mismatch warning.
- The Testnet fallback behaviour used by the explorer utilities.
- A step-by-step checklist for adding or changing a network.
The application recognises exactly two network identifiers, defined as the
union type WalletNetwork in
src/context/WalletContext.tsx:
export type WalletNetwork = 'TESTNET' | 'PUBLIC';| Identifier | Human label | Intended use |
|---|---|---|
'TESTNET' |
Testnet | Development and QA. Uses Stellar test infrastructure and Freighter test accounts. |
'PUBLIC' |
Mainnet | Production. Real assets, real transactions. |
Defined in src/utils/horizon.ts:
export const HORIZON_URLS: Record<WalletNetwork, string> = {
TESTNET: 'https://horizon-testnet.stellar.org',
PUBLIC: 'https://horizon.stellar.org',
};The helper horizonUrl(network) returns the URL for the given network and is
the canonical way to construct Horizon API endpoint strings:
import { horizonUrl } from '../utils/horizon';
const base = horizonUrl('TESTNET');
// → 'https://horizon-testnet.stellar.org'fetchUsdcBalance internally calls horizonUrl to build the accounts endpoint:
GET {horizonUrl(network)}/accounts/{encodeURIComponent(address)}
Never hard-code a Horizon URL. Always call
horizonUrl(network)or read fromHORIZON_URLSso that a future network addition only requires one change.
Defined in src/utils/horizon.ts:
export const USDC_ISSUERS: Record<WalletNetwork, string> = {
TESTNET: 'GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5',
PUBLIC: 'GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN',
};| Network | Issuer address |
|---|---|
TESTNET |
GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5 |
PUBLIC |
GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN |
The issuer address is used by fetchUsdcBalance to identify the USDC balance
line within a Horizon account response:
const usdcBalance = account.balances.find(
(balanceLine) =>
balanceLine.asset_type !== 'native' &&
balanceLine.asset_code === 'USDC' &&
balanceLine.asset_issuer === issuer, // ← USDC_ISSUERS[network]
);A trustline is considered present when usdcBalance is defined. When absent,
hasTrustline is false and balance defaults to '0.00'.
Verify issuer addresses against the official Centre / Circle documentation when updating for mainnet deployments. An incorrect issuer simply produces zero balances (no trustline) rather than throwing, which can be hard to diagnose.
export const MAX_HORIZON_BALANCES = 100;If the Horizon response contains more than 100 balance lines the fetch is
rejected with an INVALID_RESPONSE error. This guard prevents excessive memory
use when processing accounts with very large asset portfolios.
Defined in src/utils/explorer.ts:
const EXPLORER_BASE = 'https://stellar.expert/explorer';
const EXPLORER_BASES: Record<WalletNetwork, string> = {
TESTNET: 'https://stellar.expert/explorer/testnet',
PUBLIC: 'https://stellar.expert/explorer/public',
};| Function | Signature | Returns |
|---|---|---|
getExplorerTxUrl |
(txHash, network) |
Stellar Expert URL for a transaction |
getExplorerAccountUrl |
(address, network) |
Stellar Expert URL for an account (empty string when address is invalid) |
contractExplorerUrl |
(address, network) |
Stellar Expert URL for a contract (empty string when address is invalid) |
networkLabel |
(network) |
Human-readable label: 'Mainnet' or 'Testnet' |
Both getExplorerTxUrl and getExplorerAccountUrl accept network typed as
'TESTNET' | 'PUBLIC' | null. The segment is chosen with:
const segment = network === 'PUBLIC' ? 'public' : 'testnet';contractExplorerUrl uses the EXPLORER_BASES record with a nullish-coalescing
fallback:
const base =
EXPLORER_BASES[(network as WalletNetwork)] ??
EXPLORER_BASES.TESTNET;Consequence: any network value that is null, undefined, or an
unrecognised string silently defaults to the Testnet explorer. This is
intentional — it keeps the UI renderable during wallet initialisation before a
network is known, and during development against non-production networks.
networkLabel applies the same rule:
export function networkLabel(network: string | null | undefined): string {
if (network === 'PUBLIC') return 'Mainnet';
if (network === 'TESTNET') return 'Testnet';
return 'Testnet'; // fallback — UI is never blank
}When Freighter reports the active network it returns a raw string (e.g.
'TESTNET', 'PUBLIC', or potentially other values for custom networks).
WalletContext normalizes this before storing it:
// src/context/WalletContext.tsx
const normalizeNetwork = (networkName: string): WalletNetwork => {
return networkName === 'PUBLIC' ? 'PUBLIC' : 'TESTNET';
};Any value that is not the exact string 'PUBLIC' is treated as 'TESTNET'.
This mirrors the Testnet fallback pattern used by the explorer utilities and
ensures that future Stellar network identifiers (e.g. Futurenet) degrade
gracefully to Testnet behaviour in the UI.
The normalized value is set on the context as network: WalletNetwork | null
and is null until the wallet connects.
src/utils/networkMismatch.ts defines the deployment network expected by the
global network mismatch warning:
export const APP_EXPECTED_NETWORK = resolveExpectedNetwork(
import.meta.env.VITE_DISCIPLR_NETWORK,
);VITE_DISCIPLR_NETWORK accepts the same supported identifiers as
WalletNetwork: TESTNET and PUBLIC. Missing or unsupported values fall back
to TESTNET, matching the development-safe fallback used elsewhere in this
document.
isNetworkMismatch(walletNetwork, expectedNetwork) returns false while the
wallet is disconnected or still loading network state, and returns true when a
connected wallet reports a different or unsupported network. Layout mounts
NetworkMismatchBanner globally so users see the warning before creating or
validating vaults.
Follow these steps when adding a third network or updating an existing URL or issuer address.
- Add the new identifier to the
WalletNetworkunion type insrc/context/WalletContext.tsx:export type WalletNetwork = 'TESTNET' | 'PUBLIC' | 'FUTURENET';
- Add the Horizon URL to
HORIZON_URLSinsrc/utils/horizon.ts. - Add the USDC issuer address to
USDC_ISSUERSinsrc/utils/horizon.ts. Verify the issuer address from a trusted source before committing. - Add the explorer base URL to
EXPLORER_BASESinsrc/utils/explorer.ts. - Update
normalizeNetworkinsrc/context/WalletContext.tsxto handle the new identifier (or confirm the Testnet fallback is acceptable). - Update
networkLabelinsrc/utils/explorer.tsto return a human-readable label for the new identifier. - Update
resolveExpectedNetworkinsrc/utils/networkMismatch.tsif the new network should be a valid expected deployment network. - Update
getExplorerTxUrlandgetExplorerAccountUrlif their current binarynetwork === 'PUBLIC'ternary needs to distinguish more than two networks. - Add/update tests for every changed function.
- Update this document and the table in section 3.
- Update the relevant entry in
HORIZON_URLS. - Confirm the new endpoint returns the expected account response shape
(particularly the
balancesarray). - Run
fetchUsdcBalanceintegration tests against the new URL in a staging environment.
- Verify the replacement address against the official Circle / Centre documentation (or the equivalent authority for the target network).
- Update
USDC_ISSUERS[network]insrc/utils/horizon.ts. - Ensure existing accounts with a trustline to the old issuer are handled
in the migration plan —
hasTrustlinewill returnfalseuntil users establish a new trustline to the replacement issuer.
| File | Role |
|---|---|
src/utils/horizon.ts |
HORIZON_URLS, USDC_ISSUERS, fetchUsdcBalance, HorizonBalanceError |
src/utils/explorer.ts |
EXPLORER_BASES, getExplorerTxUrl, getExplorerAccountUrl, contractExplorerUrl, networkLabel |
src/utils/networkMismatch.ts |
APP_EXPECTED_NETWORK, resolveExpectedNetwork, isNetworkMismatch |
src/components/NetworkMismatchBanner.tsx |
Global warning for connected wallets on the wrong network |
src/context/WalletContext.tsx |
WalletNetwork type, normalizeNetwork, fetchNetworkAndBalance |
src/utils/stellarAddress.ts |
isValidStellarAddress — used by explorer helpers to guard empty/invalid addresses |