morphit/docs/PRE-LAUNCH-CHECKLIST.md

45 KiB
Raw Permalink Blame History

PRE-LAUNCH-CHECKLIST.md

Status: living document. Last refreshed: 2026-05-24 (Part 122 cp131 — 11 findings shipped end-to-end; backup-retention default + cp131 encryption/push features refreshed).

This is the consolidated, ordered list of operator actions required (or recommended) before flipping morphit.io from pre-launch into a live public instance. Items here are either:

  • [blocking] — launch cannot proceed until this is done
  • [recommended] — launch can proceed but operator experience or user trust suffers without it
  • [optional] — improvements that can ship post-launch

Every line traces back to a specific Part (audit campaign turn) that introduced or refined the action. If an item is closed in a later Part, this file is updated in the same turn as the closing change — never trailing.

Memory #5 makes this rule explicit: "All files kept current in the same work unit as code changes — docs, ADRs, brag list, FAQs, locale JSON, CI config, etc. No trailing stale files." This checklist is included in that rule: any Part that closes an item must update this file in the same turn. Any Part that ADDS an operator action must also update this file in the same turn.


A. Account setup (one-time, before first broadcast)

  • [blocking] Generate the @morphit Blurt account if it doesn't already exist. Posting key stays on the operator's personal laptop, OFF the morphit.io production server. This key signs the morphit_release_v1 ops that pin the canonical treasury addresses on chain. (Origin: Part 106.)

  • [blocking] Generate the @morphit-relay Blurt account. Active key is encrypted by the ops-cli init wizard (Section C below) into apps/relay/keystore.json (or .wif), read by the relay process for account creation, operator payouts, and all relay broadcasts. (Origin: ADR-0010 §3; keystore path corrected to match ops-cli init wizard output, Part 122 cp16.)

  • [blocking] Generate the canonical BTC treasury address. Native segwit (bc1q...) recommended. Seed backed up offline; spending key never reaches the morphit.io production server. (Origin: Part 106.)

  • [blocking] Generate the canonical XMR treasury wallet. Primary address (4..., 95 chars) is the only piece needed for production — no private view key required since Part 108++. Seed backed up offline. End-to-end sanity check: after the indexer is running with the address configured, have a trusted contact send a small test payment with a tx_proof and submit it through the real Morphit UI. (Origin: Part 106; view-key requirement removed in Part 108++; previous diagnostic script retired in Part 110.)

  • [recommended] Generate the @morphit-fees Blurt account if you want a distinct treasury account from @morphit. Used as the fee_recipient in operator-config; receives BLURT-paid listing fees. (Origin: Part 106; defaults to @morphit-fees.)

  • [blocking] Fund the @morphit-relay account with starter BLURT before launch. The relay pays for these BLURT-cost activities: - Account creation (OPERATIONS §2) — at signup time the relay broadcasts a direct account_create op and pays the chain's account_creation_fee (currently ~100 BLURT, witness-set) inline from its liquid balance. There is no pre-minting — Blurt disabled claim_account / create_claimed_account at hard fork 2, so Account Creation Tokens no longer exist. Operator-side, the cost is "fund the relay enough to cover the ~100 BLURT fee for each expected signup." - Welcome bonus — 10 BLURT liquid + 10 BLURT vested per user on first feedback. Paid at the moment the user earns it from the relay's running balance. - Loyalty BP delegation — small (1 BP → ~1 BLURT-equivalent) granted on first verified BLURT-paid fee. - Low-balance auto-refill — refills active users who run critically low on BLURT (default 1 BLURT per refill). - Routine ops — chat-identity registrations, feedback ops, signup-failure compensation; sub-BLURT each.

    Realistic sizing (~100 BLURT creation fee per
    expected signup, plus running bonuses/refills):
    
    | Expected signups | Suggested float |
    |---|---|
    | Quiet soft-launch (~5 testers) | **~700 BLURT** |
    | First-week ~50 users | **~6,000 BLURT** |
    | First-week ~100 users | **~12,000 BLURT** |
    
    The ~100 BLURT/signup creation fee dominates and
    is the load-bearing number.
    Top-ups any time without restart — the relay
    checks its own balance on every signup and
    emits `operator_balance_low` log lines when
    thin.  See OPERATIONS §0a + §1 + §2 for the
    full breakdown and the recurrent-transfer
    top-up mechanism.
    
    **Don't get caught short**: an operator who
    funds 250 BLURT (the old sizing-table
    conservative figure, since corrected) cannot
    cover the creation fee for even 3 signups, let
    alone a meaningful launch.
    
    *(Origin: Part 110; sizing figures corrected
    Part 112 after spotting the 1→100 BLURT
    error. The Part-112 mechanism note described the
    then-current ACT model (`create_claimed_account`);
    that was superseded at beta.28 / cp329 — the relay
    now uses a direct `account_create` op with the fee
    paid inline.)*
    
  • [blocking] Fund the @morphit account with ~10 BLURT before launch. Small fixed cost; not signup-rate-dependent. @morphit is the trust-anchor account that signs the chain- pinned morphit_release_v1 op (canonical BTC/XMR treasury addresses). Each op consumes BLURT mana; ~10 BLURT covers many years of releases with comfortable headroom.

    The **warrant canary** is a separate primitive
    — a PGP-signed static file at `/canary.txt`
    regenerated weekly by `scripts/canary/generate.sh`
    and signed by the operator's release PGP key
    (see OPERATIONS §36).  The canary does NOT
    consume `@morphit` BLURT; it lives off-chain
    and uses a PGP keypair.
    
    *(Origin: Part 112 — gap noticed when listing
    the three Morphit accounts and only the relay
    had explicit funding guidance.  Updated cp131
    HIGH-006 — removed misleading reference to a
    non-existent `morphit_warrant_canary_v1` chain
    op.)*
    
  • [recommended] Confirm the @morphit-fees account exists on chain (whether you use the default or a custom name). This account receives BLURT-paid listing fees and has no signing key on any production box — it is genuinely receive-only. No upfront BLURT funding required, but the account itself must exist before any fee transfer tries to deliver to it. (Origin: Part 110.)

  • [recommended] Review the listing fee USD target and fallback BLURT/USD price. The wizard asks for these during morphit-ops init; defaults are $0.25 USD listing fee and $0.002 fallback BLURT price. Re-confirm both still match your operator intent before launch — BTC/XMR prices drift, so the BTC sat and XMR piconero amounts computed at wizard-run time may be stale by launch day. Quick refresh: morphit-ops edit → Listing fee + fallback BLURT price The wizard re-fetches live Coingecko prices and recomputes amounts targeting your USD value. (Origin: Part 110.)

  • [blocking] Set MORPHIT_INSTANCE_OPERATOR_TAG to your instance's operator tag. Canonical morphit.io uses morphit. Community operators pick their own (e.g. example-community). The wizard captures this at init time (step 16) and writes it to morphit.config.env.

    **Without this set correctly, your relay queues
    nothing — every chain op looks like it belongs
    to a different operator, and the conservative
    default ("if I don't know who I am, I pay for
    nothing") engages.**
    
    This is the gate that makes Morphit's federation
    cost-attribution work: your relay pays only for
    ops carrying your operator tag.  Other operators
    see those ops and skip the payout queue insert,
    so no double-pay across the federation.
    *(Origin: Part 111.)*
    
  • [blocking — community operators only] Register your operator tag on chain via morphit_operator_register_v1. First-come- first-served on the tag value; once claimed, no other operator can use it. Without on-chain registration, your 90% operator-payout share cannot be attributed even if everything else is set up correctly.

    Canonical morphit.io is already registered with
    tag `morphit`; this step applies only to
    community operators standing up new instances.
    Use the wizard step 16 to pick + commit your
    tag; broadcast the registration op separately
    after the wizard completes.  Document for your
    operator-directory listing.  *(Origin: Part 111.)*
    

B. First-time chain broadcasts

  • [blocking] Broadcast the first morphit_release_v1 op with the treasury block containing: - btc.address and btc.satoshis - xmr.address and xmr.piconero

    The builder script
    `apps/indexer/scripts/release-build-payload.ts`
    enforces "no viewkey field" defense-in-depth.  The
    op is signed by `@morphit`'s posting key from the
    operator's personal off-server machine.  Build it,
    preview it, then sign + broadcast it with the
    laptop-only helper (masked key prompt, never stored;
    see OPERATIONS §40.6).  The BTC/XMR treasury addresses
    are pre-filled from the canonical treasury (cp315), so
    you only supply the version, the SRI hash manifest
    (built in §E), and your endpoints file:
    ```
    # 1. Build the frontend + the SRI release manifest (§E):
    cd apps/web && npm run build && \
      node scripts/build-manifest.mjs --release-json \
        --prefix index.html --prefix service-worker --prefix _app/immutable/entry/
    cd ../..
    
    # 2. Build the release payload (treasury pre-filled),
    #    pre-loading the manifest/version (cp436 — endpoints
    #    are no longer pinned on-chain; omit the endpoints file).
    #    (< /dev/null keeps it non-interactive so the
    #    redirected stdout is clean JSON, not prompts):
    MORPHIT_BUILD_VERSION=<semver> \
      MORPHIT_BUILD_HASH_MANIFEST_FILE=apps/web/build-manifest.release.json \
      npx tsx apps/indexer/scripts/release-build-payload.ts < /dev/null > release.json
    
    # 3. Preview, then sign + broadcast (masked key):
    npx tsx apps/indexer/scripts/release-broadcast.ts release.json --dry-run
    npx tsx apps/indexer/scripts/release-broadcast.ts release.json
    ```
    *(Origin: Part 106 + Part 107 + Part 108++; broadcast
    helper cp317; SRI manifest pipeline fix cp319.)*
    
  • [recommended] Verify federation propagation by polling each peer's /v1/release.treasury. Confirm every response shows the same canonical address+amount AND that none of them surface a viewkey field. (Origin: Part 107 + Part 108++.)

  • [blocking] Broadcast an operator registration op (morphit_operator_register_v1) for any operator_tag this instance will use. Tags are first-come-first-served; pick before community operators do. (Origin: ADR-0011 operator-earnings pipeline.)

C. Operator-config files (on the morphit.io production box)

  • [blocking] Run the setup wizard: npx morphit-ops init. As of Part 122 cp22+ the wizard covers ~23 prompts including the fee-verifier explorer URLs (BTC + XMR) and chat-link explorer URLs (BTC + XMR + BCH + LTC + DASH + DOGE + ZEC + ARRR + DCR + SOL + ETH + XRP; USDT, USDC, DAI have per-network explorers configured separately) with live health-checks, plus the trade-only asset policy step. (Exact count drifts as we add operator-config surface; see apps/ops-cli/src/init/steps.ts for the authoritative list — TOTAL_STEPS constant.) Writes: - morphit.config.env (allowlisted user-tunables) - morphit.env (critical infrastructure) - apps/relay/keystore.json or .wif (posting key) - Optional backup of morphit.env

    All four files set to mode 0600.  *(Origin: ops-cli
    init wizard, extended in Part 109; step-count
    audit Part 119.)*
    
  • [blocking] Verify both env files load cleanly by starting the indexer briefly and watching for Zod config-validation errors: cd apps/indexer && timeout 5 npm run start || true. Repeat for the relay: cd apps/relay && timeout 5 npm run start || true. Both apps' loadConfig() run synchronously via Zod schemas before any side effects — any misconfiguration shows up as a ZodError in the first ~100 ms of stderr. No --dry-run flag exists on either; a 5-second timeout is the simplest grandma-friendly way to exercise validation without staying connected to chain. (Origin: ops-cli init, post-Part-106 hardening; Part 119 audit corrected the nonexistent --dry-run flag reference; Part 122 cp16 walkthrough extended validation to the relay env after the cp16 VAPID walkthrough showed how easily a relay-env gap can hide.)

  • [blocking if running behind a reverse proxy] Verify MORPHIT_RELAY_TRUSTED_PROXY_IPS in /etc/morphit/relay.env matches your actual reverse-proxy posture. §32 CRITICAL: mis-setting this is the difference between functional per-IP rate limiting and trivially-spoofable signups. - If you run BunkerWeb colocated on the same host (the canonical ops/bunkerweb/ deploy), set to the Docker bridge subnet 172.20.0.0/16 (or whatever the compose pins as the BunkerWeb network). - If you run nginx/Caddy/another proxy on the same host, set to 127.0.0.1 (loopback only). - If your relay is internet-facing (no proxy), leave UNSET (default empty — uses socket peer IP directly). Verify by sending a request with a forged X-Forwarded-For: 1.2.3.4 header from a NON-trusted IP: the relay must ignore the header and use the actual peer IP. (Origin: Part 122 cp57 audit surfaced that this knob was in the Zod schema but missing from the canonical relay.env.example; the canonical example now documents it explicitly with the misconfiguration risks.)

  • [recommended for production deploys] Review the squatter- defense diamond preset settings if you expect any meaningful signup volume. In /etc/morphit/relay.env: - MORPHIT_RELAY_SIGNUP_DAILY_CEILING=50 (default; raise only if you've measured legitimate signup rate above this) - MORPHIT_RELAY_CREATE_RATE_PER_HOUR=5 (per-IP) - MORPHIT_RELAY_CREATE_RATE_PER_DAY=2 (per-IP) - MORPHIT_RELAY_CREATE_SPACING_MINUTES=60 (per-IP) - MORPHIT_RELAY_HIGHVALUE_NAME_POLICY=strict (or flag/off) - MORPHIT_RELAY_HIGHVALUE_SHORT_NAME_THRESHOLD=4 - MORPHIT_RELAY_SEQUENTIAL_DETECTOR_ENABLED=true See docs/OPERATIONS.md §38.7 for the diamond-hardened preset rationale and docs/RUN-A-MORPHIT-NODE.md §"Diamond- hardened squatter defense" for the tactical guide. Every successful squatter signup costs the relay ~100 BLURT — the tightened defaults are worth their friction. (Origin: Part 122 cp57 audit; these knobs were already in the Zod schema but operators needed them surfaced as a pre-launch decision.)

  • [blocking] Run the static smoke suite and confirm it returns clean. From the repo root: bash scripts/run-smokes.sh. Expected output: Total: 3,327+ scenarios passed, 0 runners failed (the "0 runners failed" is the load-bearing assertion — the scenario count is a moving lower bound that ticks up as smokes are added each release; baseline-source-of-truth is the cp27 floor of 3,327, with subsequent checkpoints adding scenarios on top: cp30 USDC, cp31 DAI, cp32 icon- coverage + payment-method-i18n-parity, cp33 DOGE, cp34 narrow-union-parity, cp39 ZEC, cp41 ARRR, cp43 DCR, cp44 LL #52 workspace-typecheck, cp45 SOL, cp46 asset-payload- precision-parity, cp47 ETH, cp48-O1 stand-in meta-assertion, cp49 XRP + cp49-O2 handler-test-stand-in, cp50-O3 per-asset- rss-feed-parity + 31 jitter unit tests, cp51-O4 category-b- descriptions-parity + cp51-O5 faq-per-tradable-asset-parity + 3 backfilled FAQs × 10 locales, cp52-O6 ansible-env- template-required-vars, cp53-O7 operator-doc-per-asset-coverage + 14 doc-drift inline fixes, cp54-O8 what-is-asset-faq-native- locale-floor + 60 native ES/FR/DE translations, cp55-O9 per- asset-key-family-native-locale-floor registry + 33 more native translations (93 total Memory #29 catch-up), cp56-O10 operator- doc-per-asset-config-example-coverage (shallow-mention floor), cp57-O11 env-example-schema-parity (bidirectional) + 9 indexer/ relay knob-doc additions (incl. TRUSTED_PROXY_IPS and squatter- defense diamond preset), cp80-O26 long-form-en-fallback-floor (1,758 translation pairs/CI run), cp81-O27 service-worker-single- registration (7 checks), cp81-O28 short-form-en-fallback-floor (4,770 translation pairs/CI run), cp111-O29 brag-list-claim- parity-smoke (81 scenarios: file paths, op IDs, env-vars, and asset/locale/ADR/brag-footer count anchors across the three marketing-class docs; mutation-tested across all 7 drift classes), cp112-O30 seo-url-consistency-smoke (366 scenarios verifying canonical/hreflang/sitemap URLs match byte-for-byte; catches the cp112 hreflang-form drift class), cp112-O31 og-image-freshness-smoke (6 scenarios catching PNG-older- than-SVG drift after the cp112 PNG fallback shipped). The exact current total is whatever run-smokes.sh prints against the repo state you're running; what you're verifying is that the count is ≥ 3,327 AND that zero runners failed).

    If you see several runners fail with
    `ERR_MODULE_NOT_FOUND` errors all referencing a
    `@morphit/*` package, you skipped the
    `npm install` step (or did it from inside one of
    the workspace sub-directories instead of the
    repo root).  Workspace symlinks live under
    `node_modules/@morphit/*` and are only created
    by an install run from the root.  Fix:
    `cd ~/morphit && npm install --no-audit --no-fund`,
    then re-run the smoke suite.  This is NOT a code
    regression — pure environment setup.  *(Origin:
    Part 121 audit found this drift while extending
    smoke coverage to the asset-registry expansion.)*
    
  • [recommended] If you want priority-#2 maximum independence, self-host a monero-block-explorer + monerod Docker stack and point MORPHIT_INDEXER_XMR_EXPLORER_URLS at localhost. ~50 GB disk, ~3-7 days initial monerod sync. See docs/OPERATIONS.md §40.4. Operator confirmed OFF the table for the initial launch (cost), but remains a documented option for later. (Origin: Part 108++.)

  • [blocking] Decide your trade-only-asset operator stance. The canonical morphit.io ships USDT, USDC, DAI, BCH, LTC, DASH, DOGE, ZEC, ARRR, DCR, SOL, ETH, XRP, and BARTER (goods/services — the one non-crypto tradable "asset"; disable-able the same way) enabled by default; alternative instances may want to disable one or more instance-wide on philosophical (centralization, fork preference), regulatory, or audience-specialization grounds.

    **The wizard handles this for you.**  `morphit-ops init`
    step 13 "Trade-only asset policy" (Part 122 cp22) walks
    through every shipped trade-only asset and asks
    per-ticker whether to enable it.  Default for each is YES.
    Pick "n" at the prompt to disable that asset; the wizard
    emits the correct `MORPHIT_INDEXER_DISABLED_ASSETS=` line
    into `morphit.config.env` for you.  Re-run the wizard
    later to change your mind without touching the env file
    by hand.
    
    Equivalent post-deploy env-edit per-asset options:
    1. Accept everything (default — no config change).
    2. Refuse USDT — set
       `MORPHIT_INDEXER_DISABLED_ASSETS="USDT"`.
    3. Refuse USDC — set
       `MORPHIT_INDEXER_DISABLED_ASSETS="USDC"`.
    4. Refuse DAI — set
       `MORPHIT_INDEXER_DISABLED_ASSETS="DAI"`.
    5. Refuse BCH — set
       `MORPHIT_INDEXER_DISABLED_ASSETS="BCH"`.
    6. Refuse LTC — set
       `MORPHIT_INDEXER_DISABLED_ASSETS="LTC"`.
    7. Refuse DASH — set
       `MORPHIT_INDEXER_DISABLED_ASSETS="DASH"`.
    8. Refuse DOGE — set
       `MORPHIT_INDEXER_DISABLED_ASSETS="DOGE"`.
    9. Refuse BARTER (goods/services — e.g. a crypto-only
       instance) — set
       `MORPHIT_INDEXER_DISABLED_ASSETS="BARTER"`.
    10. Refuse multiple —
       `MORPHIT_INDEXER_DISABLED_ASSETS="USDT,USDC,DAI,BCH,LTC,DASH,DOGE,ZEC,ARRR,DCR,SOL,ETH,XRP,BARTER"`.
    
    Federation note: disabling an asset means your own
    users cannot POST orders for it; you'll still see
    those orders from peer instances in read-only
    orderbook feeds (chain history is shared).
    
    Whichever stance you take, document it publicly so
    users know what your instance offers.  Memory #25
    (default-on + operator override for new assets),
    ADR-0023 (USDT), ADR-0024 (BCH), ADR-0025 (LTC),
    ADR-0027 (DASH), ADR-0028 (USDC), ADR-0029 (DAI),
    ADR-0030 (DOGE), ADR-0031 (ZEC), ADR-0032 (ARRR), ADR-0033 (DCR), ADR-0034 (SOL), ADR-0035 (ETH), ADR-0036 (XRP), and Part 122 cp22 (wizard step) explain
    the design.  *(Origin: Part 121 cp3 USDT integration,
    Part 122 cp21 BCH integration, Part 122 cp22 wizard
    step, Part 122 cp24 LTC integration, Part 122 cp27 DASH
    integration, Part 122 cp30 USDC integration, Part 122
    cp31 DAI integration, Part 122 cp33 DOGE integration, Part 122 cp39 ZEC integration, Part 122 cp41 ARRR integration, Part 122 cp43 DCR integration, Part 122 cp45 SOL integration, Part 122 cp47 ETH integration, Part 122 cp49 XRP integration.)*
    
  • [blocking] Decide BCH chat-link explorer URL. Default https://blockchair.com/bitcoin-cash/transaction/{txid} is fine for most operators. Override via MORPHIT_FRONTEND_BCH_CHAT_LINK_URL if you prefer a different explorer or run your own. See docs/OPERATIONS.md §"BCH chat-link explorer URL override" for the alternatives surveyed at cp21 addition time. (Origin: Part 122 cp21 BCH integration.)

  • [blocking] Decide LTC chat-link explorer URL. Default https://litecoinspace.org/tx/{txid} is fine for most operators. Override via MORPHIT_FRONTEND_LTC_CHAT_LINK_URL if you prefer a different explorer or run your own. See docs/OPERATIONS.md §"LTC chat-link explorer URL override" for the alternatives surveyed at cp24 addition time. (Origin: Part 122 cp24 LTC integration.)

  • [blocking] Decide DASH chat-link explorer URL. Default https://insight.dash.org/insight/tx/{txid} (official Dash project Insight, community-led, open-source) is fine for most operators. Override via MORPHIT_FRONTEND_DASH_CHAT_LINK_URL if you prefer a different explorer or run your own. See docs/OPERATIONS.md §"DASH chat-link explorer URL override" for the 9 alternatives surveyed at cp27 addition time. (Origin: Part 122 cp27 DASH integration.)

  • [blocking] Decide DOGE chat-link explorer URL. Default https://blockchair.com/dogecoin/transaction/{txid} is fine for most operators. Override via MORPHIT_FRONTEND_DOGE_CHAT_LINK_URL if you prefer a different explorer or run your own (e.g. https://dogechain.info/tx/{txid}, or a self-hosted Iquidus instance). (Origin: Part 122 cp33 DOGE integration; ADR-0030.)

  • [blocking] Decide ZEC chat-link explorer URL. Override MORPHIT_FRONTEND_ZEC_CHAT_LINK_URL if you prefer a different default from the bundled mainnet.zcashexplorer.app. Operator's 7-explorer survey at cp39 (mainnet.zcashexplorer.app, blockchair.com/zcash, zcashinfo.com, 3xpl.com/zcash, blockexplorer.one/zcash/mainnet, zcash.tokenview.io, cipherscan.app) is documented in ADR-0031. (Origin: Part 122 cp39 ZEC integration; ADR-0031.)

  • [blocking] Decide ARRR chat-link explorer URL. Override MORPHIT_FRONTEND_ARRR_CHAT_LINK_URL if you prefer a different default from the bundled explorer.piratechain.com. Operator's 3-explorer survey at cp41 (explorer.piratechain.com, pirate.explorer.dexstats.info, blockchain.com/explorer/assets/arrr) is documented in ADR-0032. (Origin: Part 122 cp41 ARRR integration; ADR-0032.)

  • [blocking] Decide DCR chat-link explorer URL. Override MORPHIT_FRONTEND_DCR_CHAT_LINK_URL if you prefer a different default from the bundled dcrdata.decred.org. Operator's 4-explorer survey at cp43 (dcrdata.decred.org, blockchain.com/explorer/assets/dcr, dcr.tokenview.io, bitinfocharts.com/decred/) is documented in ADR-0033. (Origin: Part 122 cp43 DCR integration; ADR-0033.)

  • [blocking] Decide SOL chat-link explorer URL. Override MORPHIT_FRONTEND_SOL_CHAT_LINK_URL if you prefer a different default from the bundled explorer.solana.com. Operator's 5-explorer survey at cp45 (explorer.solana.com, solscan.io, solanabeach.io, oklink.com/solana, solana.fm — unreachable at survey time) is documented in ADR-0034. (Origin: Part 122 cp45 SOL integration; ADR-0034.)

  • [blocking] Decide ETH chat-link explorer URL. Override MORPHIT_FRONTEND_ETH_CHAT_LINK_URL if you prefer a different default from the bundled eth.blockscout.com. Operator's 9-explorer survey at cp47 (eth.blockscout.com, etherscan.io, blockchair.com/ethereum, ethplorer.io, oklink.com/ethereum, blockchain.com/explorer/assets/eth, blockexplorer.one/ethereum/mainnet, routescan.io, beaconcha.in — consensus-layer only) is documented in ADR-0035. (Origin: Part 122 cp47 ETH integration; ADR-0035.)

  • [blocking] Decide XRP chat-link explorer URL. Override MORPHIT_FRONTEND_XRP_CHAT_LINK_URL if you prefer a different default from the bundled livenet.xrpl.org. Operator's 5-explorer survey at cp49 (livenet.xrpl.org, xrpscan.com, bithomp.com, blockchair.com/xrp-ledger, blockexplorer.one/xrp/mainnet) is documented in ADR-0036. (Origin: Part 122 cp49 XRP integration; ADR-0036.)

  • [recommended, non-blocking] VAPID keypair for Web Push notifications (Part 122 cp13). Without this, push notifications are disabled instance-wide and users on your instance see "Not supported on this device" in Settings → Notifications. In-tab ambient channels (title-bar badge, favicon dot, OS native notifications when the tab is open, audio cue, vibration) keep working without VAPID.

    One-time setup:
    ```
    bash scripts/generate-vapid-keys.sh
    ```
    Append the three printed lines to
    `/etc/morphit/relay.env` (replace the placeholder
    mailto: address with a real one — push services
    contact you there if something goes wrong).
    Restart the relay.
    
    The relay will boot-log either
    `push_enabled` (success) or
    `push_disabled_no_vapid_keys` (missing).  The
    worker's drain interval is tunable via
    `MORPHIT_RELAY_PUSH_POLL_INTERVAL_MS` (default
    2000).  Subscribe-endpoint authentication
    requires a valid posting-key signature by default
    (Part 122 cp14, `MORPHIT_RELAY_PUSH_REQUIRE_SIGNED=true`).
    Full operator reference at
    `docs/OPERATIONS.md` §42 and
    `docs/RUN-A-MORPHIT-NODE.md` Web Push subsection.
    *(Origin: Part 122 cp13cp14.)*
    

D. Infrastructure

  • [blocking] Postgres reachable from the morphit processes. Database URL configured in both indexer and relay envs. Initial schema applied via the indexer's auto-migrate on first boot (currently schema_migrations.version = 1, which under the May 2026 audit and the cp131 DEEP-002 framing extension is the pre-launch baseline that grows in place: it collapses historical versions 1-35 into the single canonical apps/indexer/src/db/schema.sql; the collapsed schema contains every v33+ feature originally added incrementally — push_subscriptions + push_pending tables, the locale column on push_subscriptions, the composite push_subscriptions(account, created_at DESC) index, the extension_count + last_extended_at columns on featured_slot_bids for the anti-snipe soft-close auction rule, ix_featured_bids_expires index for the snipe-window check, review_concentration (cp123 H2), and price_drift_baseline (cp127 defense B). Downstream code that checks "is vN applied?" via subsumesVersions still works for any v ∈ {2..35}. The v1 baseline grows in place until 1.0.0 launch; the first separate additive migration to be assigned an integer version will land at launch. No further collapse should happen until well after 1.0.0 ships.). (Origin: ADR-0001 schema management; collapsed-schema framing refreshed Part 122 cp82; subsumesVersions extended 2..27 → 2..35 + framing reconciled with schema.sql section markers Part 122 cp131 DEEP-002.)

  • [recommended] Enable the daily DB backup scheduled by the wizard (off by default; opt-in during setup). Retention configurable via RETAIN_DAYS in backup.env; default 30 days. The shipped script also supports optional age encryption (set AGE_RECIPIENT) and optional rsync push to an off-site host (set REMOTE_DESTINATION + SSH_KEY) — see RUN-A-MORPHIT-NODE.md §10 and OPERATIONS.md §37.12 for the recipe. (Origin: ops-cli init; encryption and push features Part 122 cp131 HIGH-001.)

  • [recommended] Reverse proxy in front of the indexer + frontend with HTTPS termination. Caddy or nginx; the wizard doesn't auto-configure this, it's deployment-flavor-specific. (Origin: ops runbook.)

  • [optional] Tor hidden service, Lokinet, I2P .b32 address for privacy-network access. All three are operator-configurable in the wizard. (Origin: Part D.5 federation directory + alt- network reachability.)

E. Frontend deployment

  • [blocking] Build the frontend bundle: cd apps/web && npm run build. Output goes to apps/web/build/.

  • [blocking] Hash the bundle and include the hash manifest in the next morphit_release_v1 op so federated frontends can verify integrity. build-manifest.mjs emits TWO distinct artifacts from the same apps/web/build/ walk — do not mix them up: ``` # (a) Reproducible-build fingerprint (sha256sum text, # for clone-vs-ship diffs — NOT the release op): cd apps/web && node scripts/build-manifest.mjs

    # (b) The ON-CHAIN release hash_manifest (SRI base64
    #     JSON, scoped to the tamper-critical BOOTSTRAP — shell +
    #     service worker + entry loader — to stay under the indexer's
    #     4 KB per-field JSONB cap):
    cd apps/web && node scripts/build-manifest.mjs \
      --release-json --prefix index.html --prefix service-worker \
      --prefix _app/immutable/entry/
    ```
    Artifact (b) writes `apps/web/build-manifest.release.json`
    — a JSON object of `/<served-path>: sha256-<base64>`,
    exactly the shape `@morphit/release-schema` requires and
    the frontend tamper-check (`releaseHashCheck.ts`) verifies
    the running bundle against.  Supply it to the release-op
    builder via the `MORPHIT_BUILD_HASH_MANIFEST_FILE` env var
    (see §B for the full broadcast flow).  *(Origin: ADR-0007
    chain-pinned hash manifest; build-manifest reference added
    Part 122 cp19; SRI `--release-json` mode + format fix
    cp319.)*
    
  • [recommended] Verify your frontend renders correctly for ALL 10 locales (en, es, fr, de, it, pl, ru, fa, zh-CN, zh-HK) before going live. Specifically check the post-order screen at /post which has the most complex locale-dependent UI (per-wallet proof generation instructions for XMR, etc.). (Origin: Memory #4 — 10-locale invariant.)

F. Marketing / outreach (pre-launch but soft-blocking)

  • [recommended] Launch blog post drafted using MORPHIT-BRAG-LIST.md as the source-of-truth set of claims. Every claim must be either verifiable in code or honestly disclosed as backlog. (Origin: Memory #15.)

  • [recommended] Matrix channel #agorise:matrix.org actively monitored, and the @agorise:matrix.org MXID actively monitored for DM security disclosures. (Origin: Memory facts.)

  • [optional] Beta-tester intake form actively used. Form is shipped (Memory: "beta-tester intake form" completed earlier) — track responses and reach out. Verify form is live + collecting submissions correctly. (Origin: pre-launch hardening campaign.)

G. Things explicitly NOT required to launch

These are tracked as REVISIT-LIST follow-ups but do not block initial launch:

  • Self-hosted BTC explorer (Esplora) on the morphit.io box. Public Esplora explorers (blockstream.info, mempool.space) are sufficient for the multi-explorer cross-check defaults. (Operator decision Part 109: off the table for now; revisit post-launch.)

  • Self-hosted XMR explorer on the morphit.io box. The 5 verified-compatible public explorers in the default config provide adequate cross-check. (Operator decision Part 109: off the table for now; revisit post-launch.)

  • Per-user explorer preference (Settings page + localStorage) for the chat-link feature. Part 109 shipped the per-OPERATOR override; per-user is a natural extension but not required to launch.

  • Auto-defaulted quorum threshold based on URL count. Part 109 ships default 1 (back-compat). A future part may bump to Math.max(1, ceil(N/2)).

H. Day-0 monitoring checklist (the first 24 hours after launch)

The full day-zero runbook lives in docs/LAUNCH-DAY.md. The checklist below is the condensed pre-flight; the full doc covers T-minus 24h rehearsals, T-zero procedures, what to watch hour-by-hour, rollback, and end-of-day-zero retrospective.

For days 17 after launch, see docs/POST-LAUNCH-WEEK-ONE.md. Covers monitoring cadence, paging thresholds, common week-one situations, and when to dial down to sustainable operations.

  • Check /v1/health returns 200 every minute.
  • Check /v1/release returns the canonical treasury block as broadcast.
  • Check journalctl -u morphit-indexer for any ERROR-level lines.
  • Check Postgres morphit_indexer_processed_block progresses (each Blurt block advances the indexer's pointer; if stuck, RPC issue).
  • Check the first user signup completes end-to-end (registration → relay broadcast → on-chain account created → orderbook visible).
  • Check the first BTC and XMR fee verifications complete. XMR fee verifications now require a user-supplied per-payment proof (Part 108++) — verify the post-order form rendered correctly with the proof textarea and per-wallet instructions for the first XMR-paying user. Inspect status via: psql -c "SELECT permlink, fee_method, fee_status FROM orders WHERE fee_status IS NOT NULL ORDER BY created_at DESC LIMIT 10;". Healthy: rows transition from pendingverified within a few minutes of the fee transfer being mined.
  • Watch the relay balance trend. If auto-refills are firing on day-zero traffic, the relay account drain rate is real; top up pre-emptively if you're approaching the low-balance threshold.
  • [if push enabled] Watch the push_pending queue size. Quick check: psql -c 'SELECT COUNT(*) FROM push_pending;'. Healthy: drains to ≤ batch_size (default 50) within poll_interval (default 30s). Growing unboundedly = worker is wedged (VAPID misconfig, web-push library auth failure, or DB lock). Cross-reference with the relay's push_sender_tick log lines. (Origin: Part 122 cp16 walkthrough — DD-10 single-relay invariant makes queue growth a clean failure signal.)

Update history

Part Date Change to this file
122 cp139 2026-05-25 No structural changes. cp139's 32 audit findings (across apps/{matrix-bot,ops-cli,relay,indexer,web} + packages/*) were all internal hardening — no operator-facing surface changes. The smoke battery floor moved from 5,983 → 6,076 with cp139-F-2's 9-scenario PPM-7-{1..9} SSRF regression sentinels. Memory drift caught + corrected: the previously-tracked "standing pre-launch operator action" to rotate CHANGE_ME_BEFORE_PRODUCTION was actually closed many checkpoints ago — that string is a DENYLIST entry in ops/postgres/init.sql:58-65 that REJECTS operator deployment with weak placeholders (it IS the safety feature). cp111 Lesson #1 already documented this for the other 2 items; cp139 closes the third. ALL 3 standing pre-launch operator items are now confirmed SHIPPED. Quintuple→sextuple-pulse stability invariant at 6076/6076. Operators just need to verify zero runners failed.
122 cp131 2026-05-24 D-section backup item updated: retention default corrected 14→30 days (real default in ops/backup/backup.env.example); cp131 HIGH-001 added AGE_RECIPIENT/REMOTE_DESTINATION/SSH_KEY env-var-honored encryption + rsync features to morphit-backup.sh, referenced from the item with pointers to RUN-A-MORPHIT-NODE §10 + OPERATIONS §37.12 for the recipe. No new operator action items for cp131 — all 11 findings were code/doc-side closures that landed without operator-facing surface change. Floor of 3,327 still load-bearing — operators just need to verify zero runners failed. Battery is now 5,713/0 across triple-pulse.
122 cp112 2026-05-22 Section C cumulative cp listing extended with cp112-O30 (seo-url-consistency-smoke, 366 scenarios — verifies canonical/hreflang URLs from the helper match the sitemap-builder's URLs byte-for-byte across every indexable route × locale; mutation-tested) + cp112-O31 (og-image-freshness-smoke, 6 scenarios — verifies the PNG OG image fallback exists, is 1200×630, is under 5 MB, and is no older than its SVG source). No operator-facing surface changes — both are internal hardening for SEO + share-preview surfaces. Floor of 3,327 still load-bearing — operators just need to verify zero runners failed.
122 cp111 2026-05-22 Section C scenario-count cumulative checkpoint listing extended to include cp111-O29 (brag-list-claim-parity-smoke, 81 scenarios across 7 drift classes — file paths, custom-JSON op IDs, MORPHIT_* env-vars, asset/locale/ADR/brag-footer count anchors; mutation-tested across all 7 classes). No operator-facing surface changes — the smoke is internal hardening for the three marketing-class docs (MORPHIT-BRAG-LIST, README, RELEASE-NOTES). Floor of 3,327 still load-bearing — operators just need to verify zero runners failed.
122 cp82 2026-05-21 Section C scenario-count cumulative checkpoint listing extended to include cp80-O26 (long-form-en-fallback-floor, 1,758 translation pairs/CI run), cp81-O27 (service-worker-single-registration, 7 checks against the cp81-D22 dual-registration regression), and cp81-O28 (short-form-en-fallback-floor, 4,770 translation pairs/CI run). No new operator action items — cp80/cp81 work is internal hardening + content backlog closure + bug-fix (push notifications were silently broken in production due to dual SW registration; clickPath was an operator-phishing primitive); all fixed without operator-facing surface change. Floor of 3,327 still load-bearing — operators just need to verify zero runners failed.
122 cp20 2026-05-17 Section C smoke baseline bumped from 3,173 → 3,187 to match the new version-consistency-smoke (14 scenarios) shipped this turn. The smoke catches drift between the root package.json version, the 9 sub-package versions, the runtime VERSION/INDEXER_VERSION constants in relay+indexer /v1/health, and the example responses in docs/API.md + apps/indexer/README.md — pre-cp20 those touchpoints had four different version strings, none of them the release tag. All 14 unified to 1.0.0-beta.1 and the smoke is wired into scripts/run-smokes.sh.
109 2026-05-10 Initial consolidated checklist. Items A-H gathered from Parts 106 + 107 + 108++ + 109 operator-action lists in REVISIT-LIST, OPERATIONS.md §40, RUN-A-MORPHIT-NODE.md, and TARBALL.md. Section G "explicitly NOT required" reflects operator decision in Part 109 to defer self-hosted explorers.
109 2026-05-10 Section G updated mid-Part-109 after the quorum gate and viewkey-env removal landed. Removed the two now-closed items (viewkey removal, quorum) and added two new deferred items (per-user explorer preference, auto-defaulted quorum threshold) per Memory #5 same-turn update rule.
110 2026-05-10 Section A gained two new items: relay-account funding ([blocking]) and fees-account-exists-on-chain ([recommended]). Operator-reported gap: previous revisions assumed operators knew the relay needed BLURT upfront; explicit checkbox reduces failed first-day launches. Section A also updated to retire the verify-xmr-viewkey.ts reference (script retired Part 110). Section H expanded with reference to LAUNCH-DAY.md (new) and POST-LAUNCH-WEEK-ONE.md (new) for day-zero and week-one runbooks.
110 2026-05-10 Section A gained a [recommended] "Review listing fee + fallback BLURT price" item. The wizard prompts for these during init, but BTC/XMR prices drift between wizard-run and launch day so a pre-launch re-confirmation is worth doing — morphit-ops edit → Listing fee + fallback BLURT price re-fetches live Coingecko prices and recomputes amounts.
111 2026-05-10 Section A gained two new items for the federation-cost-attribution model: [blocking] set MORPHIT_INSTANCE_OPERATOR_TAG (canonical morphit.io uses morphit; community operators pick their own) and [blocking — community operators only] register the tag on chain via morphit_operator_register_v1. Without these, the relay queues NO payouts because it can't prove which ops are "ours."
122 cp19 2026-05-17 Pre-launch dry-run walkthrough surfaced 4 doc gaps: (1) Section A mint-acts invocation used bare tsx scripts/mint-acts.ts 25 which fails on operator boxes without global tsx; corrected to npm run mint-acts -- 25 (matching npm script added to apps/relay/package.json); (2) Section C smoke baseline bumped from 3,154 → 3,173 to match cp18; (3) Section E added build-manifest script invocation (node scripts/build-manifest.mjs) so operators know HOW to produce the hash manifest, not just that they need to; (4) Section H fee-verification check now includes the psql SELECT permlink, fee_method, fee_status query so operators can inspect status without spelunking.
122 cp16 2026-05-16 Sally-operator walkthrough surfaced 4 doc gaps: (1) Section A keystore path was stale (/etc/morphit/keys/relay-active.key → corrected to apps/relay/keystore.{wif,json} matching ops-cli init); (2) Section C env-load verification now covers relay too, not just indexer; (3) Section C smoke-count baseline bumped from 2,900+ → 3,100+ (cp16 baseline 3,154); (4) Section H Day-0 monitoring gains push_pending queue-health check when push is enabled. Section C VAPID setup step + Section D schema-v33 reference + audit-cp15 refinements landed earlier in cp16.
119 2026-05-11 Section C wizard step-count corrected (14 → ~17 with disclaimer pointing at apps/ops-cli/src/init/steps.ts as the source-of-truth list). Section C also corrected the nonexistent --dry-run flag — the indexer has no such flag; use a 5-second timeout npm run start to exercise Zod env validation instead. Section D schema version updated v29 → v31 to reflect Part 113's Signal C addition.