# smirk-backend-core — example environment. # # Copy to `.env` and fill in for your deployment. NEVER commit a real `.env`. # Generate secrets with: openssl rand -hex 32 # ── Server ──────────────────────────────────────────────────────────────── SERVER_HOST=0.0.0.0 SERVER_PORT=8080 # Public absolute API base URL. REQUIRED when admin/erasure features are enabled: # it is the canonical value the NIP-98 `u` tag is verified against (never the # request Host header). Boot fails if unset while those features are on. PUBLIC_API_URL=https://backend.example.org/api/v1 ENVIRONMENT=development # Stable per-node identifier. Bound into signed admin/erasure actions so a proof # captured against one node cannot be replayed across a load-balanced fleet. INSTANCE_ID= # ── Database ────────────────────────────────────────────────────────────── DATABASE_URL=postgres://user:password@127.0.0.1:5432/smirk # ── Auth (JWT) ──────────────────────────────────────────────────────────── JWT_SECRET= JWT_EXPIRY_HOURS=24 # ── At-rest identity secrets (REQUIRED; generate each: openssl rand -hex 32) ─ # HMAC peppers keep stored identity values (pubkey_hash, seed_fingerprint) # non-reproducible from a candidate seed; the salt unlinks client IPs in abuse # tables. Boot fails closed if any is unset or too short. SEED_FINGERPRINT_PEPPER= REFRESH_TOKEN_PEPPER= IP_SALT= # ── Proof-of-work signup gate (ALTCHA v2) ───────────────────────────────── ALTCHA_HMAC_KEY= POW_REQUIRED=false ALTCHA_COST=100000 # ── Registration gates (composable; each advertised via /capabilities) ──── # Require an operator-minted invite code to create a NEW wallet. Mint codes with # `smirk-admin mint-invite --count `. Returning wallets bypass it, and # self-hosting bypasses all gates (run your own backend). PoW is the ALTCHA gate # above. Compose freely (e.g. invite + PoW + pay-to-register). REGISTRATION_REQUIRE_INVITE=false # Pay-to-register: require a SETTLED payment invoice to create a new wallet. The # invoice is minted at POST /auth/payment-invoice and paid — directly to YOUR own # wallet — via an external, non-custodial processor. The `btcpay` adapter speaks # the BTCPay-compatible invoice API, which also covers this project's xmrcheckout # /wowcheckout apps and the BTCPay Monero plugin, so you pick the coin by pointing # at the processor that accepts it. The backend only READS invoice status (pull # model) — it never holds funds or keys. Self-hosting bypasses this (leave off). REGISTRATION_REQUIRE_PAYMENT=false PAYMENT_PROVIDER=btcpay # Processor base URL, store/merchant id, and an API key that can create + read # invoices (held redacted; never logged). Required when the gate is on. PAYMENT_PROVIDER_URL=https://pay.example.org PAYMENT_STORE_ID= PAYMENT_API_KEY= # Registration price + currency ('XMR', or a fiat code the processor converts). PAYMENT_AMOUNT=0.01 PAYMENT_CURRENCY=XMR # Confirmations-to-finalize (mapped to the processor's speed policy), and how long # an unpaid invoice stays payable before it expires. PAYMENT_CONFIRMATIONS=1 PAYMENT_EXPIRES_MINUTES=60 # ── Bitcoin ─────────────────────────────────────────────────────────────── BTC_NETWORK=mainnet BTC_USE_LOCAL_NODE=false BTC_RPC_URL=http://127.0.0.1:8332 BTC_RPC_USER=bitcoinrpc BTC_RPC_PASS= BTC_ELECTRUM_URL= BTC_ELECTRUM_FALLBACKS= # ── Litecoin ────────────────────────────────────────────────────────────── LTC_NETWORK=mainnet LTC_USE_LOCAL_NODE=false LTC_RPC_URL=http://127.0.0.1:9332 LTC_RPC_USER=litecoinrpc LTC_RPC_PASS= LTC_ELECTRUM_URL= LTC_ELECTRUM_FALLBACKS= # ── Monero (daemon + light wallet server) ───────────────────────────────── XMR_LWS_URL=http://127.0.0.1:8443 XMR_LWS_ADMIN_URL=http://127.0.0.1:9443 XMR_LWS_ADMIN_KEY= XMR_DAEMON_URL=http://127.0.0.1:18081 # ── Wownero (daemon + light wallet server) ──────────────────────────────── WOW_LWS_URL=http://127.0.0.1:18443 WOW_LWS_ADMIN_URL=http://127.0.0.1:19443 WOW_LWS_ADMIN_KEY= WOW_DAEMON_URL=http://127.0.0.1:34568 # ── Grin (wallet owner/foreign API + node API) ──────────────────────────── GRIN_OWNER_API_URL=http://127.0.0.1:3420/v3/owner GRIN_OWNER_API_SECRET= GRIN_WALLET_PASSWORD= GRIN_FOREIGN_API_URL=http://127.0.0.1:3415/v2/foreign GRIN_NODE_API_URL=http://127.0.0.1:3413/v2/owner GRIN_NODE_API_USER=grin GRIN_NODE_API_PASS= GRIN_NODE_FOREIGN_API_URL=http://127.0.0.1:3413/v2/foreign GRIN_NODE_FOREIGN_API_SECRET= # ── Wallet restore (import) policy ──────────────────────────────────────── # How far back a wallet may ask THIS instance to scan on import. The backfill # cost lands on your LWS/node, so it is your call; it is advertised via # /capabilities so the wallet adapts its import UX. Self-sovereignty is # preserved — a user needing a deeper restore runs their own instance or picks # an operator with a looser policy. # create-only — new wallets only (scan starts at the tip). DEFAULT — and it # betrays nothing: self-sovereignty is run-your-own-backend. # bounded — restore up to WALLET_MAX_RESTORE_DEPTH_DAYS behind the tip # unlimited — any restore height (the natural choice for self-hosting) WALLET_RESTORE_POLICY=create-only WALLET_MAX_RESTORE_DEPTH_DAYS=365 # PoW-priced restore: charge the importer compute proportional to the scan depth # they impose on you. Free below WALLET_RESTORE_POW_FREE_DAYS, then +1 hashcash # difficulty bit per WALLET_RESTORE_POW_DAYS_PER_BIT days of depth (0 = pricing # OFF), capped at WALLET_RESTORE_POW_MAX_BITS. Applies within bounded/unlimited. WALLET_RESTORE_POW_FREE_DAYS=90 WALLET_RESTORE_POW_DAYS_PER_BIT=0 WALLET_RESTORE_POW_MAX_BITS=24 # ── Feature flags (each feature ships in the open core, toggled per deployment) ── # The wallet client reads GET /api/v1/capabilities and greys out disabled features. FEATURE_PRICES=true PRICES_PROVIDER=coingecko PRICES_FETCH_INTERVAL_SECS=300 # Tips are parked for the conditional-transfer redesign; off until that lands. FEATURE_TIPS=false # ── Admin surface (see docs; default posture is loopback/Tor) ────────────── # Admin auth is "Sign in with Smirk": only admin PUBLIC keys are stored here, # never a seed or private key. The allowlist is mutated CLI/loopback-only in v1. ADMIN_ENABLED=false ADMIN_BIND=127.0.0.1:8081 ADMIN_JWT_SECRET= # Integrity MAC secret protecting admin/setup trust anchors against DB tampering. # Losing this locks out admin — back it up (see docs/operations). ADMIN_KEY_INTEGRITY_SECRET= ADMIN_MAX_KEYS=8 ADMIN_PENDING_KEY_TTL_DAYS=7 # ── Public landing page (OFF by default; full + per-field tunable when on) ── PUBLIC_LANDING_ENABLED=false PUBLIC_EXPOSE_FEATURES=false PUBLIC_EXPOSE_UPTIME=false PUBLIC_STATS_ENABLED=false PUBLIC_STATS_CACHE_HOURS=24 # ── Data retention / erasure ────────────────────────────────────────────── ERASURE_ENABLED=false ERASURE_PURGE_LOGIN_EVENTS=true ERASURE_EXPORT_PER_DAY=3 # Log retention (auto-deletion); 0 = keep indefinitely. RETENTION_LOGIN_EVENTS_DAYS=90 RETENTION_AUDIT_DAYS=365