16 KiB
Morphit — launch-day runbook
The morning-of and first-24-hour procedure for a Morphit node operator going live. This is distinct from
PRE-LAUNCH-CHECKLIST.md(which covers everything you must have done before this morning) and fromPOST-LAUNCH-WEEK-ONE.md(which covers ongoing monitoring after day-zero).
Audience: node operator (probably you, possibly a sysadmin handed this doc).
Assumption: every box in PRE-LAUNCH-CHECKLIST.md is
already ticked. If anything is unchecked, this runbook is
not yet for you — go finish the checklist first.
T-minus 24 hours
These checks are best done the day before launch so any problem has time to surface without crisis pressure.
-
Final rehearsal of
morphit-ops initend-to-end. On a fresh VM (or in a throwaway directory), run the wizard, look at the generated files in/etc/morphit-staging, and confirm: -morphit.config.envcontains your intended fee addresses, explorer URL lists, chat-link URLs, BLURT fallback price, and listing fee USD target. -morphit.env(mode 0600) contains your posting key wrapped (or in plaintext per your choice). -keystoredirectory and files are mode 0600. - The wizard's printReview summary matches your intent. -
Fund the relay account. Your relay needs BLURT on hand to pay the
account_creation_feefor each signup — currently ~100 BLURT each (witness-set, can change via witness consensus). As of beta.28 the relay creates each account with a directaccount_createop and pays that fee inline at signup time from its liquid BLURT; there are no Account Creation Tokens to pre-mint (Blurt disabledclaim_account/create_claimed_accountat hard fork 2). See the §"Funding the relay" section below for the sizing tables. -
Fund the fees account (if you set
MORPHIT_INDEXER_FEE_RECIPIENTto something other than@morphit-fees). No upfront BLURT is needed here — this account receives fees rather than paying them out — but it should exist on chain before launch so the first incoming fee transfer doesn't bounce. Verify with a chain explorer that the account name resolves. -
Cold-start the indexer and relay with production env, watch the first 60 seconds of logs. Confirm: - Indexer:
block_applyevents progressing - Indexer:treasury_resolve_ok(or equivalent) with the right chain-pinned addresses - Relay:boot_complete, nochain_props_*errors - Both: no zod validation errors at startup, no fatal clock-drift warnings -
Hit
/v1/healthfrom outside the box (cellphone tethered, not your home connection) to confirm reverse proxy + DNS + TLS are working end-to-end. Response should be HTTP 200 with JSON body. -
Hit the frontend from outside the box. Hard- refresh, check for: - Console errors (open browser devtools first) - Locale switcher works - Dark mode toggle works -
/postpage loads (you don't need to post, just confirm the flow renders cleanly) -
Run the smoke suite locally one last time:
bash bash scripts/run-smokes.shExpect 3,300+ scenarios passed, 0 runners failed (baseline ticks up as smokes are added each release; Part 122 cp27 baseline was 3,327; subsequent checkpoints from cp30 through cp52 added scenarios on top — current lower-bound floor is whatever yourrun-smokes.shprints against the repo state, with 0 runners failed being the load-bearing assertion). A failure here means don't launch tomorrow. -
Take a snapshot of your DB. PostgreSQL
pg_dump, store offsite. This is your "rollback point" if something goes catastrophically wrong on day-zero. -
Decide your launch-window monitoring posture. Are you watching live for 24h? Watching 1h then checking every 4h? Got a sysadmin on call? Be explicit about who's watching what and when.
Funding the relay
Why the relay needs upfront BLURT
The Morphit relay pays for these BLURT-cost activities:
-
Account creation (OPERATIONS §2) — at signup time the relay broadcasts a direct
account_createop and pays the chain'saccount_creation_fee(currently ~100 BLURT, witness-set) inline from its liquid balance. There is no pre-minting and no weekly ceremony — Blurt disabledclaim_account/create_claimed_accountat 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." This is the load- bearing cost. -
Welcome bonus + loyalty BP — every user who completes their first trade gets 20 BLURT (10 liquid + 10 vested) + a small BP delegation (~1 BLURT-equivalent). Per-user one-time cost paid from the relay's running balance.
-
Low-balance auto-refill (ADR-0010 §3) — when a user runs critically low on BLURT (default threshold 0.5 BLURT) and they've been active recently, your indexer signals the relay to top them up with a small refill (default 1 BLURT). This keeps active users from getting stuck mid-flow because they ran out of chain gas.
-
Routine relay ops — chat-identity registrations, feedback ops, etc. These are small (sub-BLURT) but add up.
How much to fund up front
The ~100 BLURT account-creation fee dominates. Sizing covers the inline fee charged per signup plus the bonuses/refills paid from the running balance:
| Use case | Approx cost breakdown | Suggested initial float |
|---|---|---|
| 1 signup | ~100 BLURT fee + ~21 BLURT bonus | ~121 BLURT |
| 5 signups (quiet soft-launch with testers) | ~500 BLURT fees + ~105 BLURT bonuses | ~700 BLURT |
| 50 signups (first-week small) | ~5,000 BLURT fees + ~1,050 BLURT bonuses | ~6,000 BLURT |
| 100 signups (first-week medium) | ~10,000 BLURT fees + ~2,100 BLURT bonuses | ~12,000 BLURT |
| 100 signups + 100 refills | ~10,000 BLURT fees + ~2,200 BLURT | ~12,500 BLURT |
Don't get caught short. An operator who funds
just 250 BLURT (the pre-Part-112 figure, since
corrected) cannot cover the creation fee for even 3
signups. The old sizing (50/250/500 BLURT) assumed
a ~1 BLURT/signup chain fee; the canonical default
in apps/indexer/src/config/index.ts is ~100 BLURT,
which the relay pays inline per account_create.
You can top up any time without restart — the relay
checks its own balance on every signup and emits an
operator_balance_low log line when it's running thin.
Where to send the BLURT
Send to whichever account name you set as
MORPHIT_RELAY_ACCOUNT in your relay env. The wizard
defaults this to @morphit-relay, but if you used a
different name (e.g. @your-org-relay), use that.
You can check your configured relay account at any time with:
grep MORPHIT_RELAY_ACCOUNT /etc/morphit/relay.env
What about @morphit-fees?
The fees account receives BLURT-paid listing fees —
it doesn't pay anything. No upfront funding is needed.
However the account itself must exist on chain. If you
configured MORPHIT_INDEXER_FEE_RECIPIENT=morphit-fees
(the default), the canonical @morphit-fees account
already exists. If you set a custom name, ensure the
account is created before your first listing-fee transfer
attempts to deliver to it.
Monitoring relay balance day-of
The /v1/health?verbose=1 endpoint exposes the relay's
last-known balance under diagnostics.relay. Watch for
that number to trend down, not for it to hit zero — once
the relay can't pay an account-creation fee, signups
silently start failing. Top up well before zero.
T-minus 1 hour
-
Re-run health checks.
/v1/healthreturns 200. Frontend loads. No new error events in the last hour's logs. -
Open the monitoring window. Whatever tool you'll use to watch logs for the next 24h —
journalctl -fu morphit-indexerin a tmux pane is perfectly fine if that's your style — start it now. -
Have the rollback plan one keypress away. See §"Rollback" below.
-
Tell your community / waitlist that you're about to go live. Set expectations: "first 24h is monitoring window, please report anything weird."
T-zero (launch)
Morphit doesn't have a "launch button" — going live is literally "stop telling people not to use it yet." The node has been running for at least 24h by now; you're just opening it to traffic.
-
Remove any geofence / waitlist gate if you had one.
-
Post the launch announcement.
-
Watch logs. See §"What to watch in the first hour" below.
What to watch in the first hour
These are the leading indicators that something is about to go wrong. Tail them aggressively for the first hour, then back off to spot-checks for the rest of day- zero.
Indexer logs (journalctl -fu morphit-indexer)
- ✅
block_applyevents progressing every ~3 seconds - ✅ Occasional
treasury_resolve_ok,price_refresh_ok - ⚠
chain_props_account_creation_fee_diverges_from_configappearing — means witnesses changed the chain fee and your env var is out of date. Not urgent (relay uses chain value for live ops) but update the env when you can. - 🚨
block_apply_error,fee_verifier_throwing,treasury_resolve_error— these are real problems. Stop and investigate.
Relay logs (journalctl -fu morphit-relay)
- ✅
signup_completeevents as users sign up - ✅
operator_balance_check_okperiodic events - ⚠
operator_balance_low— top up the relay account - 🚨
operator_balance_insufficientoraccount_creation_failed— signups are failing. Top up immediately.
Frontend (from a real user browser)
- Open
/post, try posting a test order with your own account (don't pay the fee — just confirm the form works). Cancel before signing. - Check that locale switching, dark-mode toggle, and navigation work.
/v1/health?verbose=1 polled every minute
Sally-operator finding So-3 (Part 119): you must enable verbose mode in your env first. The
diagnosticsblock (containingoperator_balances,price,explorers,sse_subscribers,last_error,started_at) only renders whenMORPHIT_INDEXER_VERBOSE_HEALTH=trueis set inops/env/indexer.env. Without it,?verbose=1returns the same minimal payload as the plain endpoint — by design (audit finding NEW-9-8: verbose mode is operator-opt-in to keep an attacker from timing a drain attempt against an unhardened instance). Set it before launch day or you'll waste 20 minutes wondering why the loop emits empty objects.
A minimal monitoring loop:
while true; do
curl -s http://localhost:PORT/v1/health?verbose=1 \
| jq '{
status: .status,
block_lag: .lag_blocks,
stale: .stale,
relay_balance: (.diagnostics.operator_balances[] | select(.role == "relay") | .last_observed_blurt),
price_source: .diagnostics.price.source
}'
sleep 60
done
(Field paths re-verified against apps/indexer/src/api/health.ts
in the Part 119 audit — the canonical fields are status
(values: ok / degraded), lag_blocks, stale, and the
verbose-only diagnostics.{operator_balances,price,explorers, sse_subscribers,last_error,started_at}. Earlier doc drafts
referenced diagnostics.indexer.blocks_behind,
diagnostics.relay.balance_blurt, and
diagnostics.treasury.address_source; those fields do not
exist in the actual response. For treasury verification,
poll /v1/release separately — the chain-pinned treasury
block lives there, not in /v1/health.)
Watch for:
status="degraded"ever → page yourself (the indexer is beyond the configuredstaleLagThreshold)block_lagclimbing → indexer falling behind chainrelay_balance< 50 → top upprice_source=static_floorfor sustained periods → upstreams (Klingex/Coingecko) are failing; not urgent but investigate/v1/releasereturning null treasury block after you broadcast the release-op → release didn't land properly or hasn't been seen by your indexer yet; investigate
Rollback
If something goes catastrophically wrong on day-zero, your rollback plan should already exist. This section documents the minimum viable rollback.
Symptoms requiring rollback
- Indexer crash-looping with no path forward
- Database corruption
- Fee verifier accepting payments it shouldn't (would require user money recovery)
- Privacy leak in API responses or logs (user IPs, full txids leaking inappropriately, etc.)
What rollback actually looks like
-
Stop services:
systemctl stop morphit-frontend morphit-indexer morphit-relay -
Take maintenance page live (or remove DNS record):
- Replace the frontend with a static "we're back shortly" page, OR
- Drop the A record so users get DNS-fail (less graceful but fast).
-
Announce on community channels that you're paused and you'll be back when fixed.
-
Restore from yesterday's DB snapshot if database state is the problem.
-
Investigate calmly. Don't ship a fix at 3am.
-
When fixed, bring services up in order: indexer first (let it catch up to head of chain), then relay, then frontend.
What rollback is NOT
-
Reverting code commits in production. By the time you need rollback, you don't need code surgery — you need the site down so people stop hitting the broken thing.
-
Refunding users. Morphit is non-custodial; there is no operator-side money to refund. Listing fees that paid your treasury are already on-chain and yours; failed fees that didn't pay your treasury are still in the user's wallet.
First 24 hours — pacing
| Hours after launch | Action |
|---|---|
| 0–1 | Aggressive log tailing. Spot-check frontend manually. |
| 1–4 | Spot-check every 15 minutes. Watch the monitoring loop. |
| 4–12 | Spot-check every hour. Reply to community questions. |
| 12–24 | Spot-check every 4 hours. Get some sleep if you have someone watching. |
| 24+ | Move to POST-LAUNCH-WEEK-ONE.md monitoring posture. |
End-of-day-zero retrospective
Before going to bed (or handing off to a sysadmin):
-
Snapshot the DB again. You now have two snapshots: pre-launch + end-of-day-zero. Both stored offsite.
-
Note any oddities you saw in the logs that weren't already in this runbook. Add them to a personal post-launch journal so you have a memory of what "normal day-zero traffic looks like" for the next time you do this.
-
Top up the relay account if it's drifted down meaningfully.
-
Check
MORPHIT-BRAG-LIST.mdclaims against reality — anything in there that turned out different in production? Flag for amendment. -
If everything went smoothly: good. Reward yourself in a way that doesn't involve looking at the screen.
-
If you're handing off to a sysadmin: point them at
POST-LAUNCH-WEEK-ONE.mdand this file.
Federation behavior
On day-zero you're not yet federated — yours is the canonical morphit.io launch, or you're a community operator standing up the first instance alongside it. Federation health monitoring properly belongs in post-launch week-one.
If you're a community operator launching alongside
canonical morphit.io, your indexer will start verifying
against canonical's chain-pinned treasury automatically
once you've configured the same Blurt RPC endpoint. No
explicit "join the federation" step exists; federation
is implicit in running the code with the same trust
anchor (MORPHIT_OFFICIAL_POSTING_PUBKEY, hardcoded).
Memory rule
Per Memory #5: this file must be updated in the same turn as any change that adds or removes a day-zero operator action. If a future Part adds a new operator-facing concern that affects launch-day behavior, this file is one of the docs that must be touched.