diff --git a/.env.example b/.env.example index 490f1ea..5417389 100644 --- a/.env.example +++ b/.env.example @@ -47,7 +47,9 @@ WATCHED_PAIRS_TESTNET=XLM:native/USDC:GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4N # --- Stellar Network — Mainnet --- HORIZON_URL_MAINNET=https://horizon.stellar.org -RPC_URL_MAINNET=https://mainnet.sorobanrpc.com +# Mainnet has no free Soroban RPC — set an external provider endpoint here. +# Placeholder only; never commit a real provider URL or API key. +RPC_URL_MAINNET=https://your-provider.example.com/soroban-rpc NETWORK_PASSPHRASE_MAINNET=Public Global Stellar Network ; September 2015 # Mainnet Soroswap factory contract address — see https://github.com/soroswap/core SOROSWAP_FACTORY_ADDRESS_MAINNET=CA4HEQTL2WPEUYKYKCDOHCDNIV4QHNJ7EL4J4NQ6VADP7SYHVRYZ7AW2 diff --git a/docs/DEPLOY_MAINNET.md b/docs/DEPLOY_MAINNET.md new file mode 100644 index 0000000..a233019 --- /dev/null +++ b/docs/DEPLOY_MAINNET.md @@ -0,0 +1,167 @@ +# Mainnet Deployment Guide (Render + UptimeRobot + Neon + external RPC) + +This runbook walks through running a **mainnet** Lens node on Render, kept +awake by UptimeRobot, backed by a managed Postgres (Neon) and an external +Soroban RPC provider. + +> **No secrets or keys are committed anywhere in this guide.** Every credential +> below is a placeholder — set the real values in your Render dashboard / host +> environment only. + +## Why mainnet is different from testnet + +Lens is already closer to mainnet than testnet out of the box: its Soroswap +factory, Reflector oracle and Aquarius defaults are **mainnet** addresses. To +point a node at mainnet you mostly need to flip the network passphrase, +Horizon/RPC URLs and set mainnet `WATCHED_PAIRS`. + +Two things have no free mainnet equivalent: + +- **Soroban RPC** — there is no free public mainnet Soroban RPC. You must bring + your own provider (see [RPC provider](#rpc-provider)). +- **Render free Postgres** — it is **deleted after 90 days**. Use Neon or + another managed Postgres for any durable deployment. + +## Architecture + +``` +UptimeRobot ──GET /status──▶ Render web service (Lens) + │ + ├── external Soroban RPC provider (RPC_URL) + ├── Horizon (https://horizon.stellar.org) + └── Neon Postgres (DATABASE_URL) + └── Redis (REDIS_URL) +``` + +- **Compute:** Render web service (free tier OK for a single node). +- **Keep-alive:** UptimeRobot pings `/status` every 5 minutes so the free web + service never sleeps. +- **Database:** Neon (or any managed Postgres) — **never** Render free Postgres. +- **Cache/queues:** Redis (managed, e.g. Render Redis or Upstash). + +## Env matrix + +| Var | testnet | mainnet | +|-----|---------|---------| +| `STELLAR_NETWORK` | `testnet` | `mainnet` | +| `HORIZON_URL` | `https://horizon-testnet.stellar.org` | `https://horizon.stellar.org` | +| `RPC_URL` | `https://soroban-testnet.stellar.org` | external provider — **placeholder only** (see below) | +| `NETWORK_PASSPHRASE` | `Test SDF Network ; September 2015` | `Public Global Stellar Network ; September 2015` | +| `SOROSWAP_FACTORY_ADDRESS` | `CDKP5WSEZMDL53VZFPBGCL47WBPKFCN5OPYQVXB3CJWUXHPZRPHSSZ3` | `CA4HEQTL2WPEUYKYKCDOHCDNIV4QHNJ7EL4J4NQ6VADP7SYHVRYZ7AW2` | +| `REFLECTOR_CONTRACT_ID` | *(empty)* | `CCYXZMNHFXHKF3YEX4VJJ5TH3YHCVZIBPNBGM7C4PJIMCIMNNWDOQYA` | +| `WATCHED_PAIRS` | testnet USDC issuer | mainnet USDC issuer | +| `DATABASE_URL` | testnet DB | **separate** mainnet DB | +| `REDIS_URL` | testnet Redis | **separate** mainnet Redis | + +> Lens resolves each field in this priority order: paired var +> (`HORIZON_URL_MAINNET`) → generic var (`HORIZON_URL`) → built-in default. +> Prefer the paired `_MAINNET` / `_TESTNET` vars so both networks can coexist +> in one env file. + +### Mainnet USDC issuer + +The mainnet `WATCHED_PAIRS` uses the mainnet USDC issuer: + +``` +WATCHED_PAIRS_MAINNET=XLM:native/USDC:GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN +``` + +## RPC provider + +Mainnet has **no free Soroban RPC**. You must provision one from a provider +(e.g. a hosted Soroban RPC service, a self-hosted `soroban-rpc` node, or a +third-party RPC gateway). Set the endpoint in the host environment only: + +``` +RPC_URL_MAINNET=https://your-provider.example.com/soroban-rpc # placeholder +``` + +Never commit a real provider URL or API key to the repo. + +## Render keep-alive (UptimeRobot) + +Render **free web services sleep** after ~15 minutes of inactivity. Because +Lens is an indexer that must keep ingesting, you need a keep-alive: + +1. Deploy the Lens image as a Render **web service**. +2. Create an UptimeRobot monitor that hits `GET /status` every **5 minutes**. + `/status` is public (no API key required) and returns indexer health. +3. Each ping wakes the service and keeps it running. + +> **750 instance-hours/month free cap.** A single always-on free instance uses +> 24 × 31 ≈ 744 hours/month — right at the cap. If you also run a testnet node, +> either run it on-demand, on a second account, or upgrade to a paid instance. +> See [Running testnet + mainnet side-by-side](#running-testnet--mainnet-side-by-side). + +## Database durability (Neon / managed Postgres) + +**Do not use Render's free Postgres for mainnet** — it is **deleted after +90 days**. Use Neon or another managed Postgres with: + +- Automatic backups / point-in-time recovery. +- A dedicated database per network. + +Lens is an aggregator, so the DB is also re-derivable by re-ingesting from +chain — but a nightly `pg_dump` makes restores fast. See the +[backup & restore runbook](../../issues/119) (L053) for the nightly `pg_dump` +workflow and restore procedure. + +## Running testnet + mainnet side-by-side + +Run **two separate deployments** (same image, one env set each). Storage must +be network-segregated — the schema has no `network` column, so the same +`pairKey` on two networks collides. Give each network its **own** database and +Redis: + +| | testnet node | mainnet node | +|---|---|---| +| `STELLAR_NETWORK` | `testnet` | `mainnet` | +| `DATABASE_URL` | `postgresql://…testnet…` | `postgresql://…mainnet…` | +| `REDIS_URL` | `redis://…testnet…` | `redis://…mainnet…` | + +See [`docs/DUAL_NETWORK.md`](./DUAL_NETWORK.md) for the full dual-network +architecture. + +## Step-by-step + +1. **Provision the database.** Create a Neon project (or your managed Postgres) + and a dedicated database for mainnet. Copy the connection string. +2. **Provision Redis.** Create a managed Redis instance for mainnet. +3. **Provision an RPC provider.** Sign up / self-host a mainnet Soroban RPC + endpoint. +4. **Deploy on Render.** Create a web service from the Lens repo/image and set + the mainnet env vars (below) in the Render dashboard — never in a committed + file. +5. **Set up UptimeRobot.** Add a monitor for `https://.onrender.com/status` + every 5 minutes. +6. **Verify.** `curl https://.onrender.com/status` should return + `"ok": true` with your mainnet watched pairs and a recent `lastIndexedLedger`. + +### Mainnet env set (placeholders) + +```bash +# --- Active network --- +STELLAR_NETWORK=mainnet + +# --- Stellar (mainnet) --- +HORIZON_URL_MAINNET=https://horizon.stellar.org +RPC_URL_MAINNET=https://your-provider.example.com/soroban-rpc # placeholder +NETWORK_PASSPHRASE_MAINNET=Public Global Stellar Network ; September 2015 +SOROSWAP_FACTORY_ADDRESS_MAINNET=CA4HEQTL2WPEUYKYKCDOHCDNIV4QHNJ7EL4J4NQ6VADP7SYHVRYZ7AW2 +REFLECTOR_CONTRACT_ID_MAINNET=CCYXZMNHFXHKF3YEX4VJJ5TH3YHCVZIBPNBGM7C4PJIMCIMNNWDOQYA +WATCHED_PAIRS_MAINNET=XLM:native/USDC:GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN + +# --- Storage (separate from testnet) --- +DATABASE_URL=postgresql://user:password@your-neon-host/mainnet # placeholder +REDIS_URL=redis://user:password@your-redis-host:6379 # placeholder + +# --- Auth (set real secrets in the host env only) --- +ADMIN_API_KEY=change-me +ADMIN_TOKEN=change-me +``` + +## Related docs + +- [`docs/DUAL_NETWORK.md`](./DUAL_NETWORK.md) — dual-network architecture +- [`docs/architecture.md`](./architecture.md) — system overview +- [Backup & restore runbook](../../issues/119) — nightly `pg_dump` workflow (L053)