Choosing a Network
Stellar offers three networks for development and production. This guide helps you choose the right one for your needs.
Network Comparison Table
| Aspect | Futurenet | Testnet | Mainnet |
|---|---|---|---|
| Purpose | Latest features | Development/Testing | Production |
| Reset Policy | Monthly | Monthly | Never |
| Uptime | ~95% | ~99% | 99.95%+ |
| Real XLM | No (free) | No (free) | Yes (real money) |
| Use Case | Experimental features | App development & QA | Production dApps |
| Friendbot | Yes | Yes | No (must buy XLM) |
| RPC Endpoint | soroban-futurenet.stellar.org | soroban-testnet.stellar.org | No public host — paid provider (see Mainnet RPC) |
| Horizon API | horizon-futurenet.stellar.org | horizon-testnet.stellar.org | horizon.stellar.org |
| Network ID | StellarFuturenetNetwork | Test SDF Network | Public Global Stellar |
| Fee Rate | ~100 stroops/tx | ~100 stroops/tx | ~100 stroops/tx |
Futurenet
For: Testing new Soroban features, experimenting with upcoming protocol changes
When to Use Futurenet
- ✅ Testing cutting-edge Soroban features not yet on Testnet
- ✅ Experimenting with protocol changes before they reach Testnet
- ✅ Rapid iteration on contracts
- ❌ NOT for production
- ❌ NOT for user-facing features (data resets monthly)
Futurenet Setup
# Environment variables for Futurenet
export STELLAR_NETWORK="futurenet"
export SOROBAN_RPC_HOST="https://soroban-futurenet.stellar.org"
export SOROBAN_RPC_PORT="443"
export HORIZON_URL="https://horizon-futurenet.stellar.org"
export FRIENDBOT_URL="https://friendbot-futurenet.stellar.org"
export NETWORK_PASSPHRASE="StellarFuturenetNetwork"Get Free Futurenet XLM
# Get XLM via Friendbot
curl "https://friendbot-futurenet.stellar.org/?addr=GBBD47XCZVNQWMFE2IWHHT4AFYK6QKM3ZDMK5GVLL5IXacelași5QNTUVA"Veil Futurenet Deployment
# Deploy Veil contract to Futurenet
soroban contract deploy \
--wasm target/wasm32-unknown-unknown/release/veil_wallet.wasm \
--rpc-url https://soroban-futurenet.stellar.org \
--network-passphrase "StellarFuturenetNetwork"Futurenet data resets monthly. Never store production data or user funds here. All accounts and contracts are wiped.
Testnet
For: Active development, integration testing, QA before production
When to Use Testnet
- ✅ Normal app development
- ✅ Testing wallet features
- ✅ Integration testing
- ✅ User acceptance testing (before mainnet)
- ❌ NOT for production
- ❌ NOT for long-term data storage (data resets monthly)
Testnet Setup
# Environment variables for Testnet
export STELLAR_NETWORK="testnet"
export SOROBAN_RPC_HOST="https://soroban-testnet.stellar.org"
export SOROBAN_RPC_PORT="443"
export HORIZON_URL="https://horizon-testnet.stellar.org"
export FRIENDBOT_URL="https://friendbot-testnet.stellar.org"
export NETWORK_PASSPHRASE="Test SDF Network ; September 2015"Get Free Testnet XLM
# Get XLM via Friendbot
curl "https://friendbot-testnet.stellar.org/?addr=GBBD47XCZVNQWMFE2IWHHT4AFYK6QKM3ZDMK5GVLL5IXSAMQ5QNTUVA"
# Or use the Stellar Laboratory
# https://laboratory.stellar.org/#account-creatorTestnet Explorer
- StellarChain: https://testnet.steexp.com
- Stellar Laboratory: https://laboratory.stellar.org
- View Contracts: https://testnet.steexp.com/contracts
Veil Testnet Deployment
# Deploy Veil contract to Testnet
soroban contract deploy \
--wasm target/wasm32-unknown-unknown/release/veil_wallet.wasm \
--rpc-url https://soroban-testnet.stellar.org \
--network-passphrase "Test SDF Network ; September 2015"
# Initialize wallet
soroban contract invoke \
--contract-id CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABUFC \
--function init \
--arg-xdr-base64 "AAAAQQAAAAACc4fQYlzfXGqoHUZRXlzfXGqoHUZRXlzfXGqoHUZRXlzfXGqoHUZRXl=" \
--rpc-url https://soroban-testnet.stellar.org \
--network-passphrase "Test SDF Network ; September 2015"Most Veil development happens on Testnet. It has stable features and monthly resets are acceptable.
Mainnet
For: Production deployments, real user wallets, actual XLM transactions
When to Use Mainnet
- ✅ Production dApps
- ✅ Real user wallets
- ✅ Live payments
- ✅ Real XLM settlement
- ❌ NOT for testing (uses real money)
- ❌ NOT for unaudited code
Mainnet Setup
# Environment variables for Mainnet
export STELLAR_NETWORK="mainnet"
# There is NO public mainnet Soroban RPC. Point this at your own paid/keyed
# provider endpoint (see "Mainnet RPC is paid" below). Horizon, by contrast,
# does have a free public host.
export SOROBAN_RPC_HOST="$MAINNET_RPC_URL"
export SOROBAN_RPC_PORT="443"
export HORIZON_URL="https://horizon.stellar.org"
export NETWORK_PASSPHRASE="Public Global Stellar Network ; September 2015"Mainnet RPC is paid (and why the wallet proxies it)
Unlike Futurenet and Testnet, Stellar runs no free public Soroban RPC for
mainnet. There is no soroban-mainnet.stellar.org host — following any table
that lists one lands on a dead DNS name. Mainnet Soroban RPC is a paid, keyed
service. Practical providers:
- QuickNode
- Blockdaemon
- Validation Cloud
- Self-hosted Stellar RPC
Because that endpoint carries an account key, the Veil wallet never ships it to
the browser. It lives in a server-only variable and is reached through a
same-origin proxy, app/api/rpc/mainnet/route.ts:
- Keep the URL server-side. Set
MAINNET_RPC_URL(note: noNEXT_PUBLIC_prefix). ANEXT_PUBLIC_variable is inlined into the client bundle, so it would publish the key to every visitor of the live site — an immediate, metered cost.SOROBAN_MAINNET_RPC_URLis also accepted. - The proxy is not an open relay. It allow-lists only the eleven JSON-RPC
methods the wallet actually calls (
getHealth,getNetwork,getVersionInfo,getLatestLedger,getFeeStats,getLedgerEntries,getEvents,getTransaction,getTransactions,simulateTransaction,sendTransaction) and rejects anything else with403, so a public endpoint can’t be used to drain the quota. The upstream URL is never echoed in an error. - The UI checks before offering mainnet.
GET /api/rpc/mainnetreturns{"configured": <bool>}—truewhen a URL is set,falseotherwise. The network switcher uses this to decide whether to offer the mainnet toggle at all; when unset,POSTreplies503and the wallet stays on testnet rather than failing halfway through a transaction.
For local development against your own unkeyed or self-hosted RPC, an
explicit NEXT_PUBLIC_MAINNET_RPC_URL overrides the proxy so the browser talks
to it directly.
Get Mainnet XLM
Mainnet has no Friendbot. You must buy XLM from an exchange:
- Kraken — https://www.kraken.com
- Binance — https://www.binance.com
- Coinbase — https://www.coinbase.com
- CoinMarketCap — https://coinmarketcap.com/currencies/stellar/
Mainnet Explorer
- StellarChain: https://steexp.com
- Stellar Laboratory: https://laboratory.stellar.org
- View Contracts: https://steexp.com/contracts
Veil Mainnet Deployment
# Deploy Veil contract to Mainnet (REAL XLM WILL BE CHARGED)
# $MAINNET_RPC_URL is your own paid provider endpoint — there is no public one.
soroban contract deploy \
--wasm target/wasm32-unknown-unknown/release/veil_wallet.wasm \
--rpc-url "$MAINNET_RPC_URL" \
--network-passphrase "Public Global Stellar Network ; September 2015"
# Initialize wallet on Mainnet
soroban contract invoke \
--contract-id CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABUFC \
--function init \
--arg-xdr-base64 "AAAAQQAAAAACc4fQYlzfXGqoHUZRXlzfXGqoHUZRXlzfXGqoHUZRXlzfXGqoHUZRXlzfXGqoHUZRXl=" \
--rpc-url "$MAINNET_RPC_URL" \
--network-passphrase "Public Global Stellar Network ; September 2015"BE CAREFUL ON MAINNET. Every transaction costs real XLM. Test thoroughly on Testnet first. Never deploy unaudited code to Mainnet.
Runtime Network Switching & Per-Network Storage
The deployed wallet is not pinned to one network at build time — a visitor can
switch between Testnet and Mainnet from the NetworkSwitcher control without a
redeploy. When mainnet traffic goes through the same-origin proxy (the
default), the switcher first probes GET /api/rpc/mainnet and only enables
the Mainnet option when it reports {"configured": true} — meaning the server
has MAINNET_RPC_URL set. A deployment that instead points
NEXT_PUBLIC_MAINNET_RPC_URL straight at an RPC skips the probe and enables
Mainnet directly. Either way, an unconfigured Mainnet renders disabled with a
tooltip explaining why, rather than as an enabled control that would fail on
first use.
The same passkey resolves to a DIFFERENT wallet address on each network.
The wallet contract is deployed per network — Testnet and Mainnet each have
their own factory contract (CAUK4… vs CCZ3J…) — so the C… address your
passkey derives on Testnet is not the same account as the one it derives on
Mainnet. Any integration, support flow, or mental model that assumes “one
passkey → one address” is wrong for this wallet.
Why storage is namespaced per network
Early on, wallet identity (invisible_wallet_address, veil_signer_secret,
and related keys — see the full list in lib/network.ts’s WALLET_KEYS) was
stored under plain, un-namespaced localStorage/sessionStorage keys. Because
one passkey maps to two different addresses, this let one network’s data
clobber the other’s:
- Switching to Testnet stranded the Mainnet address.
- Registering a new wallet on one network silently overwrote the other’s keys.
- Worst case: using “Reset wallet” while on Testnet could delete the bare keys and destroy a real-funds Mainnet wallet, since Testnet reads/writes those same un-suffixed keys.
The fix (mirrored from the mobile app’s lib/walletStore.ts) gives each
network its own storage slot:
- Testnet keeps the historical, un-suffixed keys, so existing installs keep working with no migration needed.
- Mainnet keys carry a
_mainnetsuffix. namespaceKey()inlib/network.tsis the single place that maps a logical key to its per-network physical slot;lib/walletStorage.tswrapslocalStorage/sessionStoragewith it so app code never has to namespace keys by hand.clearActiveNetworkWallet()(the “Reset wallet” primitive) only ever touches the active network’s namespaced keys — it cannot reach the other network’s slot.- A one-time legacy migration (
migrateLegacyStorageOnce, gated by aveil_storage_schemaflag) copies a pre-namespacing Mainnet install’s bare keys into the new_mainnetslots the first time this code runs, so existing Mainnet users don’t lose access to an already-funded wallet.
What happens on switch
setActiveNetwork() persists the choice to localStorage and then reloads
the page — deliberately, rather than swapping state in place. Every module
that captured a value derived from the old network (the SDK client, cached
balances, in-flight requests) is rebuilt from scratch against the new one,
which avoids a stale RPC client quietly reading Testnet data while the UI
claims to be on Mainnet.
The switch also drops the current network’s session address
(invisible_wallet_address in sessionStorage) before reloading. This is
intentional: after the reload, /lock re-derives the address against the new
network’s factory. If the old address were left in place, /lock would see a
stored address that doesn’t match the freshly-derived one and treat it as
tampering (“Account mismatch”) — the wrong verdict for someone who simply
changed networks. The fee-payer key is not dropped on switch, since a
Stellar G… keypair is the same address on every network.
If you’re integrating against this wallet: never assume a user’s address is stable across a network switch, and always re-read the active address after a switch completes rather than caching it.
Recommended Development Workflow
Stage 1: Local Development
# Start local Soroban simulator
soroban network start local
# Test contracts locally
soroban contract test --network localStage 2: Futurenet (Optional)
# Test experimental features
export STELLAR_NETWORK="futurenet"
export SOROBAN_RPC_HOST="https://soroban-futurenet.stellar.org"
# Deploy and test
soroban contract deploy --network-passphrase "StellarFuturenetNetwork"Stage 3: Testnet (Required)
# Full integration testing
export STELLAR_NETWORK="testnet"
export SOROBAN_RPC_HOST="https://soroban-testnet.stellar.org"
# Test with realistic conditions
soroban contract deploy --network-passphrase "Test SDF Network ; September 2015"
# Load test with multiple transactions
npm run test:integrationStage 4: Mainnet (Production)
# Only after:
# - Security audit ✓
# - Testnet validation ✓
# - Mainnet simulation ✓
export STELLAR_NETWORK="mainnet"
export SOROBAN_RPC_HOST="$MAINNET_RPC_URL" # your paid provider — no public host
# Deploy with caution
soroban contract deploy --network-passphrase "Public Global Stellar Network ; September 2015"Network Passphrase Reference
Keep these handy:
# Futurenet
StellarFuturenetNetwork
# Testnet
Test SDF Network ; September 2015
# Mainnet
Public Global Stellar Network ; September 2015Quick Network Switcher
Create a shell script to switch networks quickly:
#!/bin/bash
# ~/.stellar-network
case "$1" in
futurenet)
export STELLAR_NETWORK="futurenet"
export SOROBAN_RPC_HOST="https://soroban-futurenet.stellar.org"
export NETWORK_PASSPHRASE="StellarFuturenetNetwork"
;;
testnet)
export STELLAR_NETWORK="testnet"
export SOROBAN_RPC_HOST="https://soroban-testnet.stellar.org"
export NETWORK_PASSPHRASE="Test SDF Network ; September 2015"
;;
mainnet)
export STELLAR_NETWORK="mainnet"
# No public mainnet RPC — set MAINNET_RPC_URL to your paid provider endpoint.
export SOROBAN_RPC_HOST="$MAINNET_RPC_URL"
export NETWORK_PASSPHRASE="Public Global Stellar Network ; September 2015"
;;
*)
echo "Usage: source ~/.stellar-network {futurenet|testnet|mainnet}"
exit 1
;;
esac
echo "✓ Network set to: $STELLAR_NETWORK"
echo "✓ RPC: $SOROBAN_RPC_HOST"Usage:
source ~/.stellar-network testnet
# ✓ Network set to: testnet
# ✓ RPC: https://soroban-testnet.stellar.org
source ~/.stellar-network mainnet
# ✓ Network set to: mainnet
# ✓ RPC: $MAINNET_RPC_URL (your paid provider — no public host)Network Status & Monitoring
- Stellar Status: https://status.stellar.org
- Futurenet Uptime: Check #soroban-status on Stellar Discord
- Testnet Issues: Refer to #soroban-testnet Discord channel
- Mainnet Health: https://status.stellar.org/pages/production
Troubleshooting Network Issues
”Cannot connect to RPC"
# Check network connectivity
curl https://soroban-testnet.stellar.org
# Verify firewall isn't blocking port 443
telnet soroban-testnet.stellar.org 443"Invalid network passphrase"
# Double-check your passphrase (exact spelling matters)
# Testnet: "Test SDF Network ; September 2015" ← note the semicolon
# Mainnet: "Public Global Stellar Network ; September 2015""Insufficient balance"
# Check your account balance
soroban contract invoke \
--contract-id native \
--function balance \
--account YOUR_ACCOUNT"Contract not found”
# Verify contract is deployed on the right network
# Use the network's block explorer
# Testnet: https://testnet.steexp.com
# Mainnet: https://steexp.comStill stuck? Ask in the Stellar Discord #soroban-support channel. The community is very helpful!