DeenBridge runs on Stellar testnet by default (STELLAR_NETWORK=testnet).
This document is the complete checklist for moving the payment stack to
mainnet (the Stellar public network, STELLAR_NETWORK=public or
mainnet). Following it end-to-end means the switch requires no code
reading — only environment changes, wallet/trustline setup, and a smoke
test.
⚠️ Config is validated at boot. The backend validates the Stellar configuration at startup (seesrc/config/stellar.js). A wrong or incomplete configuration fails fast with an error naming the exact problem instead of failing later on the first Horizon call. If you see❌ Stellar configuration errorin the logs at boot, fix the named variable and restart — do not ship it.
Everything network-dependent resolves from a single source of truth
(src/config/stellar.js):
| Setting | testnet | mainnet (mainnet / public) |
|---|---|---|
| Network passphrase | Test SDF Network ; September 2015 |
Public Global Stellar Network ; September 2015 |
| Default Horizon URL | https://horizon-testnet.stellar.org |
https://horizon.stellar.org |
| USDC issuer (Circle) | GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5 |
GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN |
| EURC issuer | GB3Q6QDZYTHWT7E5PVS3W7FUT5GVAFC5KSZFFLPU25GO7VTC3NM2ZTVO |
GDHU6WRG4IEQXM5NZ4BMPKOXHW76MZM4Y2IEMFDVXBSDP6SJY4ITNPP2 |
STELLAR_NETWORK accepts testnet, mainnet, or public (public is the
SDF name for the production network and is treated as mainnet). The USDC
issuer and default Horizon URL are derived from the network — you cannot
accidentally combine a mainnet flag with a testnet issuer or Horizon URL; the
boot-time validation rejects it.
Change these in the backend deployment (.env / Render / Vercel env):
# The one switch that matters
STELLAR_NETWORK=mainnet # or "public" — both mean mainnet
# Optional: explicit Horizon endpoints (comma-separated, for redundancy).
# Leave UNSET to use the network default (https://horizon.stellar.org).
# Never point a mainnet deployment at the testnet Horizon URL — boot will fail.
# HORIZON_URLS=https://horizon.stellar.org,https://horizon-fr.stellar.org
# Horizon client tuning (optional, same defaults as testnet)
# HORIZON_TIMEOUT_MS=10000
# HORIZON_MAX_RETRIES=3
# HORIZON_CB_THRESHOLD=5
# HORIZON_CB_COOLDOWN_MS=30000Also confirm the platform-level keys are set for mainnet (they are network-agnostic, but they move real money now):
# Platform fee wallet (receives the platform share of a fee-split purchase)
PLATFORM_WALLET_PUBLIC_KEY=G...
# Donation fund destination
DONATION_WALLET_PUBLIC_KEY=G...
# SEP-10 auth keypair public key (published in stellar.toml)
SIGNING_KEY=G...PLATFORM_FEE_PERCENT,PLATFORM_COLLECT_ENABLED— unchanged.- The secret keys of user wallets are never stored on the backend (non-custodial). Users hold their own funds.
The frontend must run on the same network or signatures will be rejected
(wrong network passphrase). In the dnb-frontend deployment set:
NEXT_PUBLIC_STELLAR_NETWORK=mainnetThis must match the backend's STELLAR_NETWORK exactly. A mismatch
(backend on mainnet, frontend on testnet) produces signatures that fail with
op_bad_auth / bad network passphrase on submit.
Creators receive USDC directly to their own wallets (direct settlement) or the platform wallet receives it (platform-collect mode). For a creator to be able to receive USDC on mainnet, their wallet must have a USDC trustline to the mainnet Circle issuer:
USDC issuer (mainnet): GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN
Notes:
- A trustline added on testnet does not carry over to mainnet — trustlines are per-account and per-network. Every creator must add the mainnet USDC trustline even if they already had one on testnet.
- Freighter / xBull / Albedo have a "manage assets" / "add asset" flow; pasting the issuer above adds the trustline. This costs a small one-time XLM reserve (the account must also hold a bit of XLM for fees and the reserve).
- A purchase to a creator without the USDC trustline fails on-chain
(
op_no_trust). The preflight check (POST /api/stellar/payment/preflight) surfaces this before the wallet is asked to sign, and the initialize endpoint can return{ fallback: "claimable_balance" }so the buyer can still complete via a claimable balance instead of dead-ending. - If the platform is in platform-collect mode, the platform wallet itself must have the mainnet USDC trustline.
# Replace G... with the creator's public key
curl "https://horizon.stellar.org/accounts/G..." | jq '.balances[] | select(.asset_code=="USDC")'An entry with asset_code: "USDC" and asset_issuer equal to the mainnet
Circle issuer confirms the trustline exists. If the array is empty of USDC,
the creator needs to add it.
Run these in order, on the mainnet deployment, after the env changes are live. Do not proceed past a failed step.
- Boot check — start the backend. Confirm it logs
✅ Environment variables validated successfullyand no❌ Stellar configuration error. A badSTELLAR_NETWORKvalue or a mainnet/testnet Horizon or issuer mismatch aborts startup with the exact variable named.curl -s http://localhost:5000/health # -> {"success":true,"message":"pong"} - Network sanity — confirm the app resolves mainnet:
and, from the code,
curl -s http://localhost:5000/.well-known/stellar.toml | grep -i networkNETWORK/networkPassphraseresolve toPublic Global Stellar Network ; September 2015. - Creator trustline — pick a test creator, confirm their mainnet USDC trustline via the Horizon query above. If missing, have them add it.
- Buyer setup — a test buyer connects a mainnet wallet with a small amount of USDC (≥ item price) and enough XLM for fees/reserve.
- Preflight — call
POST /api/stellar/payment/preflightfor a paid course. Expectsuccess: truewith nodestination_no_trustlinereason. - Initialize —
POST /api/stellar/payment/initializereturns unsigned XDR +expectedHash. Confirm the returnednetworkPassphraseis the public passphrase. - Sign & submit — the wallet signs the XDR on mainnet;
POST /api/stellar/payment/submitreturns"Payment successful!"with a mainnetstellar.expertexplorer URL. - On-chain verify — open the explorer link. Confirm a USDC payment to the
creator (or platform) and that the buyer now owns the item
(
GET /api/stellar/payment/transactionsshows itconfirmed). - Creator received USDC — confirm the creator's mainnet USDC balance increased by the expected amount (minus platform fee if enabled).
To go back to testnet, revert STELLAR_NETWORK=testnet (backend) and
NEXT_PUBLIC_STELLAR_NETWORK=testnet (frontend) and redeploy. Both sides must
change together. Testnet and mainnet data (transactions, balances) are
completely separate — records created on mainnet are not visible on testnet
and vice versa.
- Stellar docs: Networks, Claimable balances
- Circle USDC: USDC on Stellar
- Config source of truth:
src/config/stellar.js, asset registry:src/config/assets.js