From 9c75bc6bdd8f6420cf105e108f044064e5b99925 Mon Sep 17 00:00:00 2001 From: jwinterm Date: Thu, 2 Jul 2026 17:57:13 -0400 Subject: [PATCH] docs(examples): operator cookbook + premium-relay spec MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit examples/: an operator "cookbook" turning the backend's composable knobs (registration gates, relay write policy, restore policy, feature flags) into ready-to-run recipes per persona — personal, public-free (what the reference instance runs), friends, paid-access, community, and paid-relay (the flagship: free wallet + paid premium Nostr posting). Plus LEGAL_AND_OPS.md, a not-legal-advice considerations checklist that points operators at their own counsel and at the project's own published docs as a worked example. Recipes use placeholders only. docs/design/premium-relay.md: the premium-relay feature spec (reconciled to the shipped design — dedicated premium_invoices table, transactional activate). --- docs/design/premium-relay.md | 204 ++++++++++++++++++++++++++++++ examples/LEGAL_AND_OPS.md | 53 ++++++++ examples/README.md | 68 ++++++++++ examples/paid-relay/.env.example | 79 ++++++++++++ examples/personal/.env.example | 56 ++++++++ examples/public-free/.env.example | 57 +++++++++ 6 files changed, 517 insertions(+) create mode 100644 docs/design/premium-relay.md create mode 100644 examples/LEGAL_AND_OPS.md create mode 100644 examples/README.md create mode 100644 examples/paid-relay/.env.example create mode 100644 examples/personal/.env.example create mode 100644 examples/public-free/.env.example diff --git a/docs/design/premium-relay.md b/docs/design/premium-relay.md new file mode 100644 index 0000000..80332d4 --- /dev/null +++ b/docs/design/premium-relay.md @@ -0,0 +1,204 @@ +# Spec — Premium relay access (recurring) + +Status: **DRAFT / spec-first** · Owner: Such Software · Target: v0.4 + +## 1. Summary + +A **premium tier**: registration and wallet use stay **free** (PoW-gated); an +optional **recurring fee** (e.g. $5 / quarter) grants **posting access to the +Smirk-operated Nostr relay**. This reframes payment from *pay-to-register* (a +one-time gate on wallet creation) to a *subscription* (a recurring gate on relay +writes). Everything else — register, unlock, balances, tip stats, receiving DMs, +posting to public relays — remains free. + +This is the flagship **cookbook recipe** (`examples/paid-relay/`): the live, +dogfooded demonstration of operator monetization, built entirely on seams that +already exist (PaymentProvider + the relay admission engine). + +## 2. Goals / non-goals + +**Goals** +- Recurring revenue with real utility (relay posting), not a cosmetic badge. +- Compose from existing seams — no new architecture. +- Operator-configurable + advertised via `/capabilities`; self-hosting bypasses it. +- Rigorous: migration, OpenAPI, capabilities golden, unit + L2 + E2E tests. + +**Non-goals (v1)** +- **Read-gating.** Reads stay open (or DM-reads via NIP-42 later). Paid relays + conventionally gate *writes*; gating reads needs NIP-42 AUTH + a premium check + and is a possible v2. +- **Auto-charge subscriptions.** Crypto has no card-on-file; premium is a + **prepaid period** — the user re-pays each term or posting lapses. Honest + simple. +- **Fiat.** Uses the existing crypto PaymentProvider; fiat on-ramp is separate. + +## 3. Model + +The free tier includes everything that makes the **wallet** work — including +publishing **wallet-functional events** to our relay (encrypted DMs that ride with +tips, and future P2P atomic-swap coordination). Premium adds using our relay as a +**general-purpose Nostr relay** (notes, reactions, anything). + +| Capability | Free | Premium | +|---|---|---| +| Register / unlock / use wallet (all chains) | ✅ | ✅ | +| Login, view tip/balance stats | ✅ | ✅ | +| Receive DMs (gift-wrap delivery to you) | ✅ | ✅ | +| Publish **wallet events** to the Smirk relay (encrypted DMs, tips, swap coordination) | ✅ | ✅ | +| Publish to **public** relays (damus, nos.lol…) | ✅ | ✅ | +| Publish **general Nostr** to the Smirk relay (kind-1 notes, reactions, …) | ❌ | ✅ while `premium_until > now` | + +The value line: **the relay is free for what makes your wallet work; you pay to use +it as your everyday Nostr relay.** Free users are never cut off from wallet features. + +## 4. Data model + +Migration `..._premium.sql`: +```sql +ALTER TABLE users ADD COLUMN premium_until TIMESTAMPTZ; -- NULL = never premium +CREATE INDEX users_premium_until_idx ON users (premium_until); +``` +A **dedicated `premium_invoices` table** (not a `purpose` column on `payment_invoices`), +because premium binds on the authenticated `user_id` (a logged-in purchase) while +registration invoices bind on plaintext `pubkey_hash` — cleaner than a nullable +either-or. Columns: `invoice_id` PK, `user_id` FK (ON DELETE CASCADE), `provider`, +`plan_id`, `period_days`, `amount`, `currency`, `created_at`, `consumed_at`; a partial +`WHERE consumed_at IS NULL` index for the single-use lookup + a per-user pending cap. + +DB fns (`infra/db/premium_invoices.rs` + `users.rs`): +- `is_premium_npub(nostr_pubkey) -> bool` — `SELECT EXISTS(... WHERE nostr_pubkey = $1 AND premium_until > NOW())` (npub is public → not peppered). +- `activate_premium(invoice_id, user_id, days) -> Option` — consume the + single-use invoice AND extend `premium_until` in **one transaction** (so a mid-flight + failure rolls the consume back and the paid invoice stays redeemable). Extends from + `GREATEST(COALESCE(premium_until, NOW()), NOW())`, so early/longer renewals stack. +- `count_unconsumed_premium_invoices(user_id)` — bounds pending invoices per user. + +## 5. Admission policy change + +The pure engine (`infra/relay/policy.rs::decide`) classifies each event and gates on +**kind × membership**. A new `WritePolicy::PremiumPost` and an `author_premium` input: + +- **Wallet-functional kinds are free for registered users.** Define `WALLET_KINDS` — + the events that make the wallet + its peer features work: `1059` (NIP-59 gift-wrap / + encrypted DMs, incl. tip + swap-coordination payloads), `10050` (NIP-17 DM relay + list), and the tip / atomic-swap coordination kinds as those features define their + on-Nostr shape. Extensible constant. +- **`PremiumPost` decision order** in the pure engine (first match wins). The + `max_event_bytes` size cap is enforced in the `nauthz` adapter *before* the pure + engine runs (fail-closed), so it is not a branch here: + 1. `author_premium` → **Permit** (any kind — the general-purpose relay). + 2. `author_registered` && `kind ∈ WALLET_KINDS` → **Permit** (free wallet use). + 3. `author_registered` (non-wallet kind, not premium) → **Deny** (premium required). + 4. external author → gift-wrap inbox delivery only (`kind == 1059` to a registered + recipient, PoW-gated), else **Deny**. +- Existing `Open` / `AuthorAllowlist` / `InboxOutbox` unchanged. + +`nauthz.rs::event_admit` passes the event `kind`, `is_registered_npub(author)`, and +the new `is_premium_npub(author)` into `decide`. Fail-closed on error (deny). + +Config (`config.rs`): `RELAY_WRITE_POLICY=premium-post`; validate() keeps the +loopback-admission requirement (non-open policies need the admission service). + +## 6. Payment flow (recurring + tiered, reuses the PaymentProvider seam) + +- `POST /premium/invoice` (JWT auth), body `{ plan }` → mint an invoice for the + selected plan's amount via the PaymentProvider, atomically bound to the caller with + `purpose='premium'` and the plan's `days`. Returns the invoice (pay URL / address). +- Settle (pull model, same poller as pay-to-register): on a `premium` invoice + settling, call `extend_premium(user_id, plan_days)` **once** (idempotent on invoice + id). Never holds funds — pull-only. +- `GET /premium/status` (JWT auth) → `{ active, premium_until }`. + +**Tiered plans (a discount feature we build + exercise).** `PREMIUM_PLANS` is a list +of `{ id, days, amount }`: + +| id | days | price | vs. quarterly | +|---|---|---|---| +| `quarter` | 90 | $5 | — | +| `halfyear` | 182 | $9 | ~10% off | +| `year` | 365 | $15 | ~25% off | + +The invoice endpoint validates `plan` against the configured list; `/capabilities` +advertises it so the client shows the tiers with the discount visible. +`extend_premium` stacks from `max(now, current_expiry)`, so buying early (or a longer +plan) just extends. Single shared `PREMIUM_CURRENCY`. + +Config: `PREMIUM_ENABLED` (default false), `PREMIUM_CURRENCY`, `PREMIUM_PLANS` +(`id:days:amount` entries), reuses `PAYMENT_PROVIDER_*`. `validate()`: +`PREMIUM_ENABLED` ⇒ provider configured **and** `RELAY_ENABLED` **and** +`RELAY_WRITE_POLICY=premium-post` **and** ≥1 valid plan (fail closed otherwise). + +## 6.5 Resource governance — the box is shared + +The reference instance runs the relay **on the same box as the wallet backend and the +LWS scanners**, so the relay must stay within a budget and never starve them. A paid +base is naturally bounded (only paying users post general content), but we set hard +limits regardless: +- **Size + retention** — `RELAY_MAX_EVENT_BYTES` (64 KB) and `RELAY_RETENTION_DAYS` + (30) cap storage; general (non-DM) content can carry a shorter retention than + gift-wraps if needed. +- **Connection / subscription / rate limits** — cap max connections, subscriptions + per connection, and ingest rate in the nostr-rs-relay config so a burst can't + monopolize CPU / DB / bandwidth. +- **DB isolation** — the relay uses its own tables + retention job; the wallet + backend's hot auth / LWS paths are untouched. +- **Watch + escape hatch** — the `paid-relay` recipe ships a suggested cap set and + says: if relay load approaches the wallet backend's headroom, move the relay to its + own box — the seam already supports `RELAY_MODE=external`. + +## 7. Capabilities + OpenAPI + +- `/capabilities`: `features.premium_relay: bool` + a `premium` object + `{ currency, plans: [{ id, days, amount }], relay_url }` (present only when enabled), + so the client renders the plan tiers with the discount visible. The client greys the + **general-Nostr-posting** affordance unless `premium.active`; wallet events + DMs + stay available free. +- OpenAPI: document `POST /premium/invoice` + `GET /premium/status`; the existing + **OpenAPI no-drift check** must pass. Capabilities **golden test** updated to + include `premium_relay` + `premium`. + +## 8. Free ↔ premium enforcement points (defense in depth) + +1. **Relay admission (authoritative):** the gRPC hook denies non-premium *general* + writes to our relay regardless of client behavior (wallet events stay free). This + is the real gate. +2. **Client UX:** reads `premium.active` from `/premium/status` and disables the + affordance — a courtesy, never the enforcement. + +The client is never trusted; a user who bypasses the UI still hits admission-deny. + +## 9. Migration / compatibility + +- Additive column; existing users get `premium_until = NULL` (free). No break. +- v0.2 backend unaffected. `PREMIUM_ENABLED=false` (default) → zero behavior change; + `/premium/*` return 404/feature-off and `/capabilities` omits `premium`. +- The relay stays **off** until an operator opts in (`RELAY_ENABLED=true` + + `premium-post`); `api.smirk.cash` enables it as the reference paid relay. + +## 10. Testing (Ps and Qs) + +- **Unit** — `policy.rs`: PremiumPost permit (premium author), deny (lapsed / + non-premium), gift-wrap delivery to premium recipient, PoW on external inbound. +- **Unit** — `extend_premium` stacks from `max(now, current)`; `is_premium_npub` + respects expiry (peppered lookup). +- **L2** — admission service: premium author PERMIT, lapsed author DENY. +- **L2** — settle handler: a `premium` invoice extends `premium_until` once (idempotent). +- **Golden** — capabilities includes `premium_relay` + `premium` when enabled, omits when off. +- **No-drift** — OpenAPI covers the two new endpoints. +- **E2E** — pay invoice → post to Smirk relay PERMITted → fast-forward past expiry + → post DENIED → renew → PERMITted again. + +## 11. Open questions + +1. **Grace period** on expiry (e.g. 3-day soft window) vs hard cutoff? (Lean hard; + `extend_premium` early-renewal stacking softens it.) +2. **Read-gating** (NIP-42) — defer to v2; note it in the cookbook recipe. +3. **Default price/period** for the reference instance — product call. +4. **Refunds** — none (prepaid access to a service); state in ToS/recipe README. + +## 12. Cookbook tie-in + +`examples/paid-relay/` = `PREMIUM_ENABLED=true` + `RELAY_ENABLED=true` + +`RELAY_WRITE_POLICY=premium-post` + open+PoW registration. Its README is the +operator playbook: pricing, the NCMEC/DMCA content posture (Terms §6/§9.1), +resource expectations, and the "reads open, writes paid" decision. See +[`examples/README.md`](../../examples/README.md). diff --git a/examples/LEGAL_AND_OPS.md b/examples/LEGAL_AND_OPS.md new file mode 100644 index 0000000..ce09ff0 --- /dev/null +++ b/examples/LEGAL_AND_OPS.md @@ -0,0 +1,53 @@ +# Legal & operational considerations (per recipe) + +> **This is not legal advice.** It's a plain-English checklist of what changes, +> legally and operationally, as you pick a heavier recipe — so you know what to take +> to your own lawyer. Smirk's own published [Terms](https://smirk.cash/terms) + +> [Privacy Policy](https://smirk.cash/privacy) (source in the `smirk-website` repo) +> are a useful worked example for a non-custodial, multi-chain, Nostr-enabled wallet +> backend — but they are **ours**. Adapt with your own counsel; don't copy blind. + +## The through-line: stay non-custodial + +Every recipe keeps the backend **non-custodial** — you never hold seeds, spend keys, +or user funds. That single property is what keeps you out of money-transmitter / +custodial-exchange territory. Preserve it in anything you add. + +## By recipe + +**`personal` / `friends`** — lowest surface; you're running infrastructure for +yourself or a known group. If you enable the relay, you're carrying (encrypted) +messages — see *Running a relay*. + +**`public-free`** — open to the public, so have a Terms + Privacy that disclose what +your backend actually receives: Monero/Wownero **view keys** (for LWS scanning), +**IP handling** (hash + purpose), the wallet **fingerprint**, and **Nostr identity**. +No money changes hands → no payment/consumer-sale angle. + +**`paid-access` / `paid-relay` (you charge a fee)** +- You're selling access to **your** service (a fee), not transmitting user funds → you + act as a **merchant**, not a money transmitter. Keep it non-custodial and + **pull-model** (read invoice status; never hold or route user funds). +- Terms should state: what the fee buys, **no refunds** (prepaid access), the service + is best-effort and may change, and **fair-use limits** (bandwidth / storage / content) + for relay posting. +- Treat fees as **business income** (taxes). Confirm your jurisdiction + entity, and + have counsel bless the non-custodial → not-an-MSB analysis for *you*. + +**Running a relay (`friends` / `paid-relay` / `community`)** — you're hosting user +content, which is intermediary-liability territory: +- **Copyright (DMCA):** consider registering a **Designated Agent** with the U.S. + Copyright Office and running a notice / counter-notice process with a named contact. +- **CSAM:** U.S. providers hosting content have mandatory reporting duties (NCMEC / + 18 U.S.C. §2258A) — register and have a process. Note that **end-to-end-encrypted** + DMs you cannot read are handled differently from plaintext you can. +- **Acceptable use + moderation rights:** prohibit unlawful content; reserve the right + to rate-limit, refuse, or remove; state that you are **not the author** of user + content and don't guarantee delivery/retention. +- Smirk's Terms **§6** (reporting, DMCA, acceptable use) and **§9.1** (relay) are a + worked example of this posture. + +## Again: a reference, not a template + +Use Smirk's live docs to see *how one operator did it* — then write your own with a +lawyer. Requirements vary by country, entity, and exactly what you enable. diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..f6064a2 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,68 @@ +# Operator cookbook + +`smirk-backend-core` is one binary with **composable knobs** — registration gates +(PoW / invite / pay-to-register), an optional Nostr relay with a configurable write +policy, restore policy, and per-feature flags. This directory turns those knobs into +**ready-to-run recipes**, one per operator persona. + +Each recipe is a directory with: +- **`.env.example`** — the tuned config (only the lines that differ from defaults are + called out; copy the full `../../.env.example` and apply the deltas). +- **`README.md`** — what it's for, what it costs to run, the legal/operational notes. + +> **Status.** `personal/`, `public-free/`, and `paid-relay/` (the flagship — it +> uses the shipped [premium-relay feature](../docs/design/premium-relay.md)) have +> full `.env.example`s. `friends/`, `paid-access/`, and `community/` are summarized +> below and get their own directories on the next pass. + +## Every recipe, the same three steps +1. `cp ../../.env.example .env`, then apply the recipe's deltas. +2. Generate each secret: `openssl rand -hex 32` (`JWT_SECRET`, `SEED_FINGERPRINT_PEPPER`, + `REFRESH_TOKEN_PEPPER`, `IP_SALT`, `ALTCHA_HMAC_KEY`, `ADMIN_*_SECRET`). Fill chain + endpoints. `chmod 600 .env`. +3. Run the binary (migrations apply on boot). Reverse-proxy TLS; bind the API to + `127.0.0.1`. + +**Non-custodial in every recipe** — the backend never holds seeds, spend keys, or +funds. **Self-hosting bypasses every gate.** Secrets are never committed. + +## The menu + +| Recipe | For | Registration | Relay | Pitch | Status | +|---|---|---|---|---|---| +| [`personal/`](personal/) | Just you | open, no gate | off (or open, just you) | Your own sovereign backend | ✅ boots today | +| [`public-free/`](public-free/) | A free public instance | **open + PoW** | off (v1) | What `api.smirk.cash` runs | ✅ boots today | +| `friends/` | Your circle | **invite-only** | on, `inbox-outbox` | A backend for people you trust | 📝 2nd pass | +| `paid-access/` | A paid wallet service | **pay-to-register** | optional | Charge once for a hosted wallet backend | 📝 2nd pass | +| `paid-relay/` | Monetize a relay | **open + PoW** (free wallet) | on, **`premium-post`** | Free wallet, paid ($/quarter) Nostr posting | ✅ premium-relay shipped | +| `community/` | A project/DAO space | invite | on, `author-allowlist` | A gated relay for a named group | 📝 2nd pass | + +## Recipe summaries (config deltas) + +- **`personal`** — `POW_REQUIRED=false`, all registration gates off, + `WALLET_RESTORE_POLICY=unlimited`, `RELAY_ENABLED=false` (or on, just for you), + `ADMIN_ENABLED=false`. Minimal footprint; you are the only user. +- **`public-free`** — `POW_REQUIRED=true`, gates off, `WALLET_RESTORE_POLICY=bounded` + + PoW-priced, `FEATURE_PRICES=true`, `FEATURE_TIPS=false`, `RELAY_ENABLED=false` at + launch. Open to all, spam-gated by compute. +- **`friends`** — `REGISTRATION_REQUIRE_INVITE=true` (mint with + `smirk-admin mint-invite`), `RELAY_ENABLED=true` + `RELAY_WRITE_POLICY=inbox-outbox`, + `WALLET_RESTORE_POLICY=unlimited`. Hand out codes; run a locked-down relay for the group. +- **`paid-access`** — `REGISTRATION_REQUIRE_PAYMENT=true` + `PAYMENT_*` (a BTCPay-compatible + processor paying *your* wallet). A one-time access fee gates wallet creation. Non-custodial; + you never touch funds (pull-model invoice reads only). +- **`paid-relay`** — free wallet (open + PoW) **plus** `PREMIUM_ENABLED=true`, + `RELAY_ENABLED=true`, `RELAY_WRITE_POLICY=premium-post`, `PREMIUM_AMOUNT` / + `PREMIUM_CURRENCY` / `PREMIUM_PERIOD_DAYS`. Recurring revenue for relay posting; reads + open (v1). **Content posture:** running a relay carries user content — see the NCMEC/DMCA + clauses in the published Terms (§6, §9.1). Spec: [premium-relay](../docs/design/premium-relay.md). +- **`community`** — `REGISTRATION_REQUIRE_INVITE=true`, `RELAY_ENABLED=true` + + `RELAY_WRITE_POLICY=author-allowlist` (only your registered members publish). + +## Choosing + +- Want **freedom**? `personal` (or `friends` for a group). +- Want a **free public good**? `public-free`. +- Want it to **pay for itself**? `paid-relay` (recurring, real utility) or `paid-access` + (one-time). `paid-relay` is the flagship — recurring revenue tied to a service people + actually value, and the live demo runs at `api.smirk.cash`. diff --git a/examples/paid-relay/.env.example b/examples/paid-relay/.env.example new file mode 100644 index 0000000..77ac9cc --- /dev/null +++ b/examples/paid-relay/.env.example @@ -0,0 +1,79 @@ +# ── Recipe: paid-relay ─────────────────────────────────────────────────────── +# Free wallet (open + PoW) PLUS a paid premium tier for GENERAL Nostr posting to +# your relay. Wallet-functional events (encrypted DMs, tips, swap coordination) +# stay FREE for registered users; premium unlocks general posting. This is the +# flagship monetization recipe — recurring revenue tied to a real service. +# +# ⚠️ Running a relay means hosting user content — read ../LEGAL_AND_OPS.md +# (DMCA designated agent + NCMEC reporting + acceptable-use + moderation). +# +# Start from ../../.env.example and apply the lines below. openssl rand -hex 32 +# for each secret. +# ───────────────────────────────────────────────────────────────────────────── + +SERVER_HOST=127.0.0.1 +SERVER_PORT=8081 +PUBLIC_API_URL=https://api.your-domain.example/api/v1 +ENVIRONMENT=production +INSTANCE_ID=paid-relay-1 + +DATABASE_URL=postgresql:///smirk_v3_db?host=/var/run/postgresql + +# Secrets (REQUIRED; openssl rand -hex 32 each). +JWT_SECRET= +SEED_FINGERPRINT_PEPPER= +REFRESH_TOKEN_PEPPER= +IP_SALT= +ALTCHA_HMAC_KEY= +ADMIN_KEY_INTEGRITY_SECRET= + +# ── Free registration, PoW-gated (wallet use is always free) ── +POW_REQUIRED=true +REGISTRATION_REQUIRE_INVITE=false +REGISTRATION_REQUIRE_PAYMENT=false + +# ── Relay ON, premium-post policy ── +RELAY_ENABLED=true +RELAY_MODE=bundled +RELAY_URL=wss://relay.your-domain.example +RELAY_WRITE_POLICY=premium-post +# Blunt cross-ecosystem DM spam (inbound gift-wraps from non-registered authors). +RELAY_INBOUND_POW_BITS=8 +RELAY_ADMISSION_BIND=127.0.0.1:8090 +# Shared-box resource governance — the relay runs beside the wallet backend + LWS. +# Also cap connections / subscriptions / ingest rate in the nostr-rs-relay config +# (see deploy/relay); move to RELAY_MODE=external on its own box if load climbs. +RELAY_MAX_EVENT_BYTES=65536 +RELAY_RETENTION_DAYS=30 + +# ── Premium tier: tiered discount plans (id:days:amount), reusing the processor ── +PREMIUM_ENABLED=true +# Price currency: a fiat code the processor converts (so "5" = $5, paid in any coin +# your processor offers), or a specific coin (then amounts are in that coin). +PREMIUM_CURRENCY=USD +PREMIUM_PLANS=quarter:90:5,halfyear:182:9,year:365:15 + +# Payment processor (BTCPay-compatible; also serves xmrcheckout/wowcheckout). The +# backend only READS invoice status — it never holds funds. Invoices are paid +# directly to YOUR wallet. +PAYMENT_PROVIDER=btcpay +PAYMENT_PROVIDER_URL=https://pay.your-domain.example +PAYMENT_STORE_ID=your-store-id +PAYMENT_API_KEY=your-processor-api-key +PAYMENT_CONFIRMATIONS=1 +PAYMENT_EXPIRES_MINUTES=60 + +# Prices on; tips parked; admin plane optional. +FEATURE_PRICES=true +FEATURE_TIPS=false +ADMIN_ENABLED=false + +# Chain endpoints — see ../../.env.example (XMR/WOW LWS + daemon, GRIN_*, electrum). +XMR_LWS_URL= +XMR_LWS_ADMIN_URL= +XMR_LWS_ADMIN_KEY= +XMR_DAEMON_URL= +WOW_LWS_URL= +WOW_LWS_ADMIN_URL= +WOW_LWS_ADMIN_KEY= +WOW_DAEMON_URL= diff --git a/examples/personal/.env.example b/examples/personal/.env.example new file mode 100644 index 0000000..fd1e5d5 --- /dev/null +++ b/examples/personal/.env.example @@ -0,0 +1,56 @@ +# ── Recipe: personal ──────────────────────────────────────────────────────── +# Your own sovereign backend — one user (you), no gates, your rules. +# +# Start from ../../.env.example, then apply the lines below (they are what make +# this recipe "personal"). Generate each secret with: openssl rand -hex 32 +# ───────────────────────────────────────────────────────────────────────────── + +SERVER_HOST=127.0.0.1 +SERVER_PORT=8080 +PUBLIC_API_URL=https://your-domain.example/api/v1 +ENVIRONMENT=production +INSTANCE_ID=my-personal-node + +# Unix socket + peer auth (run the service as the DB-owning OS user) — no password. +DATABASE_URL=postgresql:///smirk?host=/var/run/postgresql + +# Secrets (REQUIRED; openssl rand -hex 32 each). +JWT_SECRET= +SEED_FINGERPRINT_PEPPER= +REFRESH_TOKEN_PEPPER= +IP_SALT= +ALTCHA_HMAC_KEY= + +# No gates — it's just you. +POW_REQUIRED=false +REGISTRATION_REQUIRE_INVITE=false +REGISTRATION_REQUIRE_PAYMENT=false + +# Your node, your rules — scan as far back as you like on import. +WALLET_RESTORE_POLICY=unlimited + +# Prices on; tips parked (v0.3 redesign). +FEATURE_PRICES=true +FEATURE_TIPS=false + +# Relay off — one person doesn't need a public inbox. Flip RELAY_ENABLED=true +# (with RELAY_URL + RELAY_WRITE_POLICY=author-allowlist) if you want your own +# DM relay that only you can publish to. +RELAY_ENABLED=false + +# Admin HTTP plane off — the smirk-admin CLI (break-glass) is enough solo. Keep +# ADMIN_KEY_INTEGRITY_SECRET set so the CLI works. +ADMIN_ENABLED=false +ADMIN_KEY_INTEGRITY_SECRET= + +# Chain endpoints — point at YOUR nodes / light-wallet servers. See the full list +# and formats in ../../.env.example (XMR/WOW LWS + daemon, GRIN_*, BTC/LTC electrum). +XMR_LWS_URL= +XMR_LWS_ADMIN_URL= +XMR_LWS_ADMIN_KEY= +XMR_DAEMON_URL= +WOW_LWS_URL= +WOW_LWS_ADMIN_URL= +WOW_LWS_ADMIN_KEY= +WOW_DAEMON_URL= +# BTC_ELECTRUM_FALLBACKS=..., LTC_ELECTRUM_FALLBACKS=..., GRIN_* — see root example. diff --git a/examples/public-free/.env.example b/examples/public-free/.env.example new file mode 100644 index 0000000..aa904de --- /dev/null +++ b/examples/public-free/.env.example @@ -0,0 +1,57 @@ +# ── Recipe: public-free ────────────────────────────────────────────────────── +# A free, open public instance — anyone can register and use the wallet; spam is +# gated by proof-of-work, not payment. This is (minus secrets) what the reference +# instance api.smirk.cash runs. +# +# Start from ../../.env.example and apply the lines below. openssl rand -hex 32 +# for each secret. +# ───────────────────────────────────────────────────────────────────────────── + +SERVER_HOST=127.0.0.1 +SERVER_PORT=8081 +PUBLIC_API_URL=https://api.your-domain.example/api/v1 +ENVIRONMENT=production +INSTANCE_ID=public-1 + +DATABASE_URL=postgresql:///smirk_v3_db?host=/var/run/postgresql + +# Secrets (REQUIRED; openssl rand -hex 32 each). +JWT_SECRET= +SEED_FINGERPRINT_PEPPER= +REFRESH_TOKEN_PEPPER= +IP_SALT= +ALTCHA_HMAC_KEY= +ADMIN_KEY_INTEGRITY_SECRET= + +# Free registration, spam-gated by compute (ALTCHA proof-of-work). +POW_REQUIRED=true +REGISTRATION_REQUIRE_INVITE=false +REGISTRATION_REQUIRE_PAYMENT=false + +# Bounded restore, PoW-priced by depth (protects your LWS/node from deep backfills; +# a user needing more runs their own instance). +WALLET_RESTORE_POLICY=bounded +WALLET_MAX_RESTORE_DEPTH_DAYS=365 +WALLET_RESTORE_POW_FREE_DAYS=90 +WALLET_RESTORE_POW_DAYS_PER_BIT=30 +WALLET_RESTORE_POW_MAX_BITS=24 + +FEATURE_PRICES=true +FEATURE_TIPS=false + +# Relay off at launch. To run it, see the `friends` (locked-down) or `paid-relay` +# (monetized) recipes. +RELAY_ENABLED=false + +# Admin HTTP plane off at launch; enable + bootstrap a key when you need it. +ADMIN_ENABLED=false + +# Chain endpoints — reuse your existing daemons / LWS. See ../../.env.example. +XMR_LWS_URL= +XMR_LWS_ADMIN_URL= +XMR_LWS_ADMIN_KEY= +XMR_DAEMON_URL= +WOW_LWS_URL= +WOW_LWS_ADMIN_URL= +WOW_LWS_ADMIN_KEY= +WOW_DAEMON_URL=