• v1.21.16 0eb31fe46d

    Morphit v1.21.16
    All checks were successful
    morphit-ci / Supply-chain audit gate (every advisory triaged, none fixable in range) (push) Successful in 40s
    morphit-ci / TypeScript typecheck (sweep all workspaces) (push) Successful in 1m16s
    morphit-ci / apps/web svelte-check (svelte-kit sync + svelte-aware tsc) (push) Successful in 55s
    morphit-ci / Integration tests (real Postgres 16) (push) Successful in 6m23s
    morphit-ci / ansible-lint (playbook quality gate) (push) Successful in 49s
    morphit-ci / Smoke suite (run-smokes.sh, pulses 1–2 of 3) (push) Successful in 1h4m28s
    morphit-ci / Smoke suite (run-smokes.sh, pulse 3 of 3) (push) Successful in 32m0s
    morphit-release / Build + publish release tarball (push) Successful in 44m33s
    Stable

    Ghost released this 2026-10-26 14:48:38 +00:00 | 0 commits to main since this release

    Signed by agorise
    GPG key ID: 53524E1F1017EB9C

    Morphit v1.21.16

    The chat's buttons say what they do, and name the coin. The button at the bottom of a trade chat used to
    read "Pay now" or "Share crypto address" whatever the trade was. Now it names the coin the order is about:

    • If you are the one who sends the coin, it reads Send BLURT, Send XMR, Send BTC and so on.
    • If you are the one who receives it, it reads Share your XMR address (or BTC, LTC…), and for BLURT, which
      arrives at an account rather than an address, Share your Blurt account.
    • A goods-and-services order that accepts one coin names that coin too, and when that coin is BLURT, "Send BLURT"
      sends it from the app like on a BLURT order (before, it asked you to paste a transaction id). One that accepts
      several coins keeps "Pay now" and "Share crypto address", since the coin is chosen in the next step.

    The "Share your … address" window now offers only the order's coin, so you cannot share an address for a
    different coin by mistake. The BLURT button on a shared Blurt account in the conversation reads "Send BLURT" as
    well.

    The chat's subject line reads from your side. Both people used to see the same "RE: I'm buying …" at the top of
    the chat, which on the other side read as if they were the buyer. Now the person who posted the order sees
    "Regarding my order: I'm buying at least 25 BRL of BLURT with XMR or BTC", and the other person sees "Regarding
    @alice's order: Buying at least 25 BRL of BLURT with XMR or BTC". The inbox shows each order the same way:
    yours as "I'm buying …", the other person's as "Buying …". A chat saved as a PDF heads the order's messages with
    the same line.

    The buttons fit a phone. On a phone the buttons at the bottom of a chat are full width, one under the other,
    and tall enough to tap comfortably; the order's title gets its own line under "Regarding …". On a wider screen
    they sit side by side as before.

    The help pages and their translations use the new button names.

    For node operators: stopping a snapshot download always says so. When a snapshot download (fast-sync) was
    stopped with Ctrl+C, a closed terminal or kill on the whole process, it could, depending on timing, report "every
    source failed" and try the next source instead of stopping with "Nothing was changed". It now always stops.

    Downloads
  • v1.21.15 7b784d367c

    Morphit v1.21.15
    All checks were successful
    morphit-ci / Supply-chain audit gate (every advisory triaged, none fixable in range) (push) Successful in 44s
    morphit-ci / TypeScript typecheck (sweep all workspaces) (push) Successful in 1m20s
    morphit-ci / apps/web svelte-check (svelte-kit sync + svelte-aware tsc) (push) Successful in 50s
    morphit-ci / Integration tests (real Postgres 16) (push) Successful in 6m41s
    morphit-ci / ansible-lint (playbook quality gate) (push) Successful in 47s
    morphit-ci / Smoke suite (run-smokes.sh, pulses 1–2 of 3) (push) Successful in 1h7m54s
    morphit-ci / Smoke suite (run-smokes.sh, pulse 3 of 3) (push) Successful in 39m32s
    morphit-release / Build + publish release tarball (push) Successful in 52m48s
    Stable

    Ghost released this 2026-10-24 18:04:50 +00:00 | 1 commits to main since this release

    Signed by agorise
    GPG key ID: 53524E1F1017EB9C

    Morphit v1.21.15

    On a Tor or I2P site, every explorer link stays on Tor or I2P. A payment's "view on explorer" link used to
    open a clearnet block explorer (mempool.space, Blockchair, etherscan…) even when you were using the site's
    .onion or .i2p address — one click and you left the hidden network. Now a page opened on a .onion, .i2p
    or .loki address links only to explorers that are Tor or I2P services themselves:

    • Bitcoin: mempool.space, Blockstream, mempool.emzy.de, Foundation and Cake Wallet onions, then Blockchair
      and 3xpl; on an I2P site, RunBTC's I2P explorer first.
    • Monero: xmr.mx and P2Pool.io's explorer, then Blockchair and 3xpl; on an I2P site, xmr.mx's and
      moneroexamples' I2P explorers first.
    • Bitcoin Cash, Litecoin, Dash, Dogecoin, Zcash, Solana, Ethereum, XRP and every token network (USDT, USDC,
      DAI on Ethereum, Tron, Solana, BNB Chain, Base, Polygon and Arbitrum): Blockchair's and 3xpl's onions.
    • Decred and Pirate Chain have no working Tor or I2P explorer, so a hidden site shows no link for them; the
      transaction id stays on the page to copy. The "check on blocks.blurtwallet.com" link is not shown there either.

    Every address was opened in Tor Browser or an I2P browser before it went in. The clearnet site is unchanged.

    Two fixes ride along on the links: a Solana transaction's link keeps its signature exactly as written
    (letter case matters there), and an Ethereum one always gets its 0x.

    The warrant canary keeps working when clearnet explorers and nodes do not. Its Blurt and Bitcoin block
    heights and its news headline now come from onion (and I2P) sources when every clearnet one fails — asked all
    at once — and a Tor-only server asks them first. They are reached only through the server's Tor proxy or i2pd,
    never directly. Two hidden explorers must agree on the Bitcoin head before it is signed, so one lying explorer
    cannot choose it.

    What the canary signs is checked first. A Blurt head must be a real block id that carries its own height, a
    Bitcoin hash must show its proof of work, every answer is read up to 64 KB, and the news headline becomes one
    line of plain ASCII (at most 200 characters) that is filled in literally. Before, a headline with an & (say
    "Q&A") came out as a broken placeholder that the canary check then rejected, and a headline could carry text
    that looked like part of the signature block.

    More hidden data sources. Bitcoin fee checks also ask mempool.foundation.xyz's onion and RunBTC's I2P
    explorer; Monero fee checks also ask xmr.mx's I2P explorer. (Cake Wallet's onion timed out and xmr.mx's onion
    answers only behind a captcha, so neither is a data source; both stay as links.)

    "No such payment" only when an explorer says so. A source that answers with a captcha or an error page —
    even with HTTP 404 — counts as a malformed answer, not as "no such payment": only an explorer's own "not found"
    does (Bitcoin: Esplora's Transaction not found; Monero: the explorer's JSON error). Before, any 404 counted,
    so explorers whose API was off or blocked could, together, reject a fee that was paid.

    When clearnet does not answer, the hidden network is tried (on servers that still use clearnet):

    • Upgrades: if the code host and every mirror are unreachable, and @morphit's signed record names a newer
      release, the upgrade fetches it from the other Morphit instances over Tor/I2P, checked against that record, as
      a zero-clearnet server always does. A newer release you dropped in the offline folder still comes first;
      --check-only and --json never fetch.
    • Clock: a new timer (morphit-clock-fallback.timer) checks every 6 hours whether NTP keeps the clock;
      if it cannot (UDP blocked, pools unreachable), the clock is set from onion time sources over Tor, only when at
      least three agree it is more than 30 seconds off. While NTP works it does nothing. Only one time check runs at
      a time, and a server switched to Tor-only has this timer removed (its own replaces it).
    • Prices (BLURT, BTC, XMR): if every clearnet price source is down, the median of other instances' prices
      comes before this server's own trades alone — only in USD, and only where the chain-pinned fee bounds it; where
      that bound cannot be read, the federation's price is not used at all, so sybil instances cannot set it. A peer
      whose web address does not answer is asked on its Tor or I2P address; a peer registered under a hidden name is
      reached only through Tor or I2P, never through this server's DNS. Every server now samples the other instances'
      prices (every 30 minutes); MORPHIT_INDEXER_PEER_PRICE_MONITOR_ENABLED still decides whether a clearnet server
      alerts on disagreement.

    For operators

    • Upgrade every instance (sudo morphit-ops, option 2). No database change. The upgrade adds the new
      hidden fee sources to a fee-source list the wizard wrote, once (a source you remove later stays removed), and
      installs the clock fallback on a server that keeps clearnet NTP.
    • Your explorer overrides (MORPHIT_FRONTEND_*_CHAT_LINK_URL) apply on the pages that can open them: a
      clearnet explorer on your clearnet site, a Tor onion on your .onion / .loki addresses, an I2P or Tor one on
      your .i2p address; otherwise the built-in list is used.
      An override may now use plain http:// for a Tor v3 .onion or an .i2p host (the wizard, the indexer and
      the browser all accept it); everything else must still be https://. The wizard never checks such an address
      from the server, and none of its checks follows a redirect to one. See OPERATIONS, "Explorer links on a hidden
      site".
    • The canary reads MORPHIT_CANARY_TOR_SOCKS (default 127.0.0.1:9050) and the new
      MORPHIT_CANARY_I2P_HTTP_PROXY (default 127.0.0.1:4444) for those hidden sources, and the new
      MORPHIT_CANARY_ONION_NEWS_RSS (space-separated .onion feeds; default the BBC's) and
      MORPHIT_CANARY_ONION_NEWS_WAIT (seconds they get, default 60) for the news line. Pinning
      MORPHIT_CANARY_BTC_EXPLORER asks that explorer only. On a clearnet box without Tor the hidden sources fail at
      once and change nothing.
    • MORPHIT_HIDDEN_RPC_TIMEOUT_MS that is not a positive number now falls back to 60 seconds; before, it made
      every Tor/I2P call time out at once.
    • Dependencies: body-parser, qs, iconv-lite, fast-uri and undici updated within their versions for new advisories. A new zod
      advisory (memory use on an unbounded list) does not reach Morphit: every list it checks from outside input has
      a size limit, and the one that had none (a broadcast's extensions) now has one.
    Downloads
  • v1.21.14 7bd5a74243

    Morphit v1.21.14
    All checks were successful
    morphit-ci / Supply-chain audit gate (every advisory triaged, none fixable in range) (push) Successful in 42s
    morphit-ci / TypeScript typecheck (sweep all workspaces) (push) Successful in 1m29s
    morphit-ci / apps/web svelte-check (svelte-kit sync + svelte-aware tsc) (push) Successful in 1m7s
    morphit-ci / Integration tests (real Postgres 16) (push) Successful in 6m38s
    morphit-ci / ansible-lint (playbook quality gate) (push) Successful in 45s
    morphit-ci / Smoke suite (run-smokes.sh, pulses 1–2 of 3) (push) Successful in 1h7m53s
    morphit-ci / Smoke suite (run-smokes.sh, pulse 3 of 3) (push) Successful in 38m5s
    morphit-release / Build + publish release tarball (push) Successful in 47m58s
    Stable

    Ghost released this 2026-10-23 03:53:17 +00:00 | 4 commits to main since this release

    Signed by agorise
    GPG key ID: 53524E1F1017EB9C

    Morphit v1.21.14

    Chat is fast again. Two people on morphit.io saw every message about a minute late. The server had each
    message within a second; BunkerWeb, which buffers what it relays, held the live chat stream until later traffic
    pushed it out. The indexer marks every live stream "do not buffer", but the frontend container in front of it
    dropped that mark (nginx never passes it on by itself), so BunkerWeb never saw it. The frontend now passes it
    on, and a message reaches the other person's browser in under a second. Tor and I2P visitors, who do not go
    through BunkerWeb, were not affected by this part.

    A full audit of chat and the inbox followed, measured end to end on a private Tor network with three instances
    (clearnet, clearnet with an onion, and Tor-only): every combination of sender and recipient arrived in under four
    seconds, a paid first message in about one, and a message still arrived in under four seconds with a peer's
    onion down. Messages from accounts this server had never stored a key for (most accounts older than Morphit)
    used to wait for the chain too; they are now checked at once.

    No more "Sender not verified" or "Not sent: these are not keys you have checked" on a first chat. Chat now
    uses one key per account, its posting key, so a conversation never starts with a key warning. Messages from
    older versions stay readable, with a calm grey note.

    Your browser now talks to this site only. The once-a-day release check used to ask public Blurt RPC nodes
    directly from your browser, so those nodes saw your IP address, even on a Tor or I2P site. It now asks this site,
    which answers only with what two Blurt RPC operators agree on; your browser still checks the release's signature
    itself, so a site cannot fake a release. Every page's Content-Security-Policy now lets the browser connect to
    this site and nothing else (connect-src 'self'). See the FAQ "Does anything see or leak my IP address?".

    Chat and inbox

    • Every message is pushed to every instance at once, one push per sender. People writing to users of the
      same instance at the same moment no longer wait in line behind each other. Each push still travels on its
      own fresh Tor circuit; a peer whose onion does not answer within two seconds is also tried at its web
      address through Tor.
    • A paid first message reaches the inbox in about a second, also when the message arrives before its fee.
    • Blocking someone takes effect at once, not when the block reaches a final block.
    • A message from someone you blocked, or a first message that never reaches the chain, sends no notice.
    • Trade buttons only for the trade they belong to. "Pay now" and "Mark sent" act only on this
      conversation's own order, and only on what its two parties said.
    • Fewer connections per tab (three at most), so sending and reading never wait behind live streams on Tor
      and I2P; live streams come back by themselves after an error.
    • One live inbox stream per browser, not one per tab. One tab keeps it and passes its news to your other tabs;
      when that tab closes, another takes over and catches up. On Tor and I2P, where a browser allows only a few
      connections per site, ten open tabs no longer use them all up.
    • Two orders with the same name are two trades. Two people can each post an order whose link name is the
      same; a payment or an address on one of them no longer shows on the other, in chat, the inbox or My orders.
    • "Leave feedback" in the inbox only on the card of the person you traded with, not on every card about the
      same order.
    • Ready for messages bound to their order and time. This version reads a new message format that cannot be
      shown under another order than the one it was sent about, or more than a few minutes away from when it was
      sent. It still sends the current format; the next version sends the new one, once every instance can read it.
    • The composer is free again at once after you press Send, a failed send says so, and drafts are kept per
      conversation.
    • Live chat needs a ticket signed by your posting key. Before, anyone could watch an account's chat streams
      on every instance and tell from which one showed a message first which instance that account uses. The browser
      signs the ticket for you, for seven days at a time, so a device whose clock is hours or days off still gets live
      chat; a paired desktop's ticket lasts as long as the pairing. (The person you are writing with can still tell,
      by timing; see the leak catalogue.)
    • Chat records on the chain (messages, read receipts, folders, blocks, fees) are applied once two RPC operators
      serve the same block. If your instance can reach only one operator for ten minutes, chat blocks are applied on
      that operator's word until two answer again — so a hostile node can write chat history only while it is the
      only one your instance reaches.
    • The safety number now checks your own half. Verify peer compares the keys this site lists for your account
      with the key you signed in with: if yours is missing it shows no number, and any other key listed for you is
      named above the number (messages to you are encrypted to it too). Before, a site serving both of you could add
      a key of its own to your account, and both of you still saw the same number.
    • Signing out keeps what you have checked. The keys you have pinned for your partners are sealed under your
      own key at Sign Out and come back when you sign in again, as the docs said they did. Before, every partner was a
      first contact again after a sign-out.
    • A message you read stays read. Reading a message the moment it arrived, then leaving, no longer lights the
      thread up again a few seconds later when its copy reaches the chain.
    • After you accept a partner's new key, their earlier messages show the calm grey note, not a request to compare
      the number you have just compared.
    • One busy sender cannot crowd out the others on the way to another instance; a slow onion is no longer cut
      off by a refusal from the same instance's web address; a notification whose first try hit a database hiccup is
      sent on the next pass, not five minutes later.

    For operators

    • Upgrade every instance (sudo morphit-ops, option 2). The upgrade rebuilds the frontend container, which
      carries the stream fix above, and replaces the page Content-Security-Policy (BunkerWeb, the frontend
      container, or the bare-metal nginx site) with this release's, in which visitors' browsers connect to this site
      only. A policy you wrote yourself is left alone and reported.
    • New endpoint: POST /v1/chain/agreed. It answers only the release check's two reads (@morphit's newest
      release record, and that release's transaction from the block a checked history named), and only with what two
      RPC operators of your pool agree on; it says 502 when they do not, and logs chain_agreed_no_quorum at most
      every ten minutes. One read serves every visitor and is kept a minute (a block ten, a failure 30 s), so a
      flood costs your pool nothing more. Two reachable RPC operators are needed for the release banner.
    • A browser from before this version gets no live chat from an upgraded instance until it reloads (the
      streams need a ticket).
    • Instances older than v1.21.13 cannot read chat from this version (every message is now sent the
      posting-key way, which v1.21.13 introduced), and cannot write to accounts created from now on. Upgrade them.
    • Fan-out to other instances is Tor-only: every installer configuration runs Tor and publishes an onion.
    • The head tailer polls every second by default (MORPHIT_INDEXER_FASTPATH_INTERVAL_MS, now 1000).
    • New limits, all in OPERATIONS §24 and ADR-0052: stream tickets (new tickets checked at 100 a second for members whose key the instance holds, 40 for
      others; their chain reads have their own ceiling), hidden-service streams (1,500, of which 1,000 without a ticket; 12 per account),
      pushes in flight (8 per peer, 96 in all), relay push passes (4 at once).
    • When only one RPC operator answers, a block with chat or inbox ops waits up to 10 minutes once per outage,
      then such blocks are applied on that operator's word (chat_block_single_source_after_wait in the log) until
      two operators answer again; the pool is asked again once a minute. If you see that line, check that your RPC
      operators are reachable. Blocks with a release, RPC-directory or BTC-order op still wait for two operators.
      The second operator's copy of each chat block is fetched ahead, eight at a time, so catching up stays fast.
    • A refused live-chat stream answers 403 (not 401, which BunkerWeb's bad-behaviour ban counts: many visitors
      behind one address would otherwise get it banned for an hour). A stream whose visitor leaves while its ticket
      is checked no longer keeps its slot.
    • The sign-in pairing wait (/v1/login-pairing/<id>/wait) is relayed unbuffered too.
    • A desktop paired before this version gets live chat back after pairing again; until then it still updates
      every few seconds.

    Also in this version: password fields

    The settings page no longer stays blurred. Opening Settings from the avatar menu in Firefox left the whole
    page grey and blurred until the next click. The browser's password manager had written the saved user name
    (for example "morphit") into the "Also show orders in" language field as the page loaded; that opened the
    field's list, and the open list blurs the page behind it.

    The same mistake happened elsewhere, found while fixing it and seen in Firefox: the saved user name went into
    the memo of a BLURT transfer (a memo is public on chain) and into the amount to power up, and the saved
    Morphit password went into the Active key box of "unlock with your Active key".

    Upgrading: sudo morphit-ops upgrade on every server. No database change.

    What changed

    • Every Morphit password field is a form of its own, with your account name as its user name (hidden,
      read-only). Firefox's password manager now keeps to that form: it never writes into another field of the page,
      and it offers the password saved for the account you are signed in with — not the password of another account
      saved in the same browser. This covers Settings (change password, hardware key), sending BLURT, power up/down,
      paying in chat, the chat fee, featuring an order, posting an order, backing up keys, sign-in, two-factor
      setup, creating an account and importing one.
    • A box for a key (an Active key, a secret) starts read-only and becomes writable the moment you touch it or
      move to it with the keyboard. Firefox does not fill a saved password into a read-only field. Pasting and typing
      work as before.
    • A search-and-pick field (language, currency, payment method) opens its list only for your own typing. If a
      browser or an extension writes into it, the text is cleared and nothing opens.
    • A new password set in Settings, or while importing an account with its posting key, is now offered for saving
      under your account name.

    Good to know

    • If Firefox saved your Morphit password without your account name, it no longer fills it into these boxes.
      Type it once; Firefox offers to save it under your account name.
    • Firefox still offers to save anything typed into a box that shows dots — the key icon in the address bar —
      including an Active key typed into "unlock with your Active key". Websites cannot switch that off. For a key,
      choose "Not now" ("Never save" would also stop it saving your Morphit password): Morphit uses the key once and
      forgets it, and it should not sit in a password list.

    For operators and contributors

    • A new check runs with the other smoke tests (password-field-scope-smoke): every password field in the web
      app must be a Morphit-password field inside <PasswordScope> (or a <form> with a <LoginNameField> right
      before it), or a key field that starts read-only and unlocks on touch or focus.
    • Another new smoke (ops-harness-anchors-smoke) runs every mutation harness in ops/test with its slow test
      runs skipped, and fails when a planted bug no longer lands on the code it names. 37 of them had stopped
      landing (the code had moved), so the bugs they stood for were guarded by nothing; all are re-anchored (or
      retired with the reason, where the code they named no longer exists). Nine more landed but were no longer
      caught: seven are caught again, and two are retired because they change nothing on any path that still exists.
    • The smoke runners keep a failing smoke's whole output (MORPHIT_SMOKE_FAIL_DIR, default the temp folder) and
      print where. A test in one workspace no longer imports another workspace's source
      (noWebSourceImports.test.ts): the web app's needs a generated folder that a fresh checkout lacks.
    • Dependencies: sanitize-html 2.18.0 → 2.18.1 (used by the Matrix bot; GHSA-c9rx-x947-fp3h, unbounded recursion);
      dompurify 3.4.16 → 3.4.17 (used by the PDF export library; GHSA-cg2g-xh64-v4q7, GHSA-mrv2-3pqr-j5jr,
      GHSA-c6gr-qvv4-5v3f); ip-address 10.7.2 → 10.7.4 (used by the MCP server's rate limiter; GHSA-rxp7-6hxx-x8q5).
      A new Vite advisory (GHSA-rq7h-c2jc-7f22, server.fs.allow) concerns only the Vite dev server, which no node
      runs; triaged in scripts/audit-allowlist.json (the fix is in Vite 6, a major upgrade).
    Downloads
  • v1.21.13 a42076c979

    Morphit v1.21.13
    All checks were successful
    morphit-ci / Supply-chain audit gate (every advisory triaged, none fixable in range) (push) Successful in 39s
    morphit-ci / TypeScript typecheck (sweep all workspaces) (push) Successful in 1m13s
    morphit-ci / apps/web svelte-check (svelte-kit sync + svelte-aware tsc) (push) Successful in 54s
    morphit-ci / Integration tests (real Postgres 16) (push) Successful in 5m56s
    morphit-ci / ansible-lint (playbook quality gate) (push) Successful in 47s
    morphit-ci / Smoke suite (run-smokes.sh, pulses 1–2 of 3) (push) Successful in 59m40s
    morphit-ci / Smoke suite (run-smokes.sh, pulse 3 of 3) (push) Successful in 30m26s
    morphit-release / Build + publish release tarball (push) Successful in 46m6s
    Stable

    Ghost released this 2026-10-21 01:01:41 +00:00 | 5 commits to main since this release

    Signed by agorise
    GPG key ID: 53524E1F1017EB9C

    Morphit v1.21.13

    Chat reaches anyone, including someone who has never opened chat. A buyer pressed "Message" on a new
    member's order. They got a red "This recipient hasn't opened Morphit chat yet", nothing was stored for the
    recipient, and the message was gone after leaving the chat and coming back.

    The cause was not that member: every message was encrypted to a key the recipient had to publish on chain
    first. The app published it silently, from the recipient's own BLURT, the first time they opened chat, and a
    member with no BLURT left could never be reached at all. Messages to an account without that key now go to
    its posting keys, which every Blurt account has on chain from the moment it exists. The message goes on
    chain at once, the recipient is notified as usual, and they read it whenever they sign in. The sender pays
    the network fee; the recipient needs no BLURT to receive or read it. Replying is sending: the replier pays the
    fee for their reply.

    New accounts now start with 4 BLURT instead of 2, enough to fill in the whole profile, post a first order
    and send a few messages (details below).

    Upgrading: sudo morphit-ops upgrade on every server. No database change. Upgrade every instance,
    including federated ones you chat with.
    A version older than this one:

    • cannot open a message sent in the new format. It shows "This message couldn't be decrypted on this
      device. It may be damaged…" — the message is fine; that version cannot read it;
    • cannot send to a member who never opened chat (it still says "This recipient hasn't opened Morphit chat
      yet").

    New members never publish a chat key, so everything they send is in the new format, to everyone. Every
    instance must run v1.21.1 or later before 2026-11-01 00:00 UTC.

    Chat to someone who never opened it

    • What happened. Each message was encrypted to the recipient's published chat key, a key made from their
      posting key and put on chain by the app the first time they opened chat, paid from their own BLURT. Until
      then nobody could write to them, and their replies could not be proved on the other side.
    • Now. When the chat-key format can't be used, the message is encrypted to the recipient's posting keys,
      read from their account on chain. One random key encrypts the message; it is sealed separately for each of
      the recipient's posting keys that can sign on its own (up to three — whichever one their session holds
      opens it), and once more for your own copy. Each seal uses the same construction as before (a fresh
      one-time key for every message, mixed with both parties' keys, then ChaCha20-Poly1305), and each is tied to
      this exact message, so nobody holding one of those keys can reuse it to write something else in your name.
      Only the recipient can open it, and it proves who sent it.
    • Pairs that already chatted keep their format. When your published chat key is current and the other
      person has one, nothing changes.
    • No fee for opening chat. Opening chat no longer broadcasts anything. A key published earlier is still
      re-published after its owner changes their posting key (a small network fee), so people writing to them
      keep reaching the right key.
    • Never moved off keys you checked.
      • The posting keys first seen for someone are pinned on your device, as a set. A key removed from their
        account narrows the pin. A key ADDED stops sending with "this person's account has a key it didn't have
        when you last wrote to them" until you compare the new safety number and accept it. Accepting a changed
        posting key never accepts a changed chat key, and the reverse.
      • If your server suddenly says that someone whose chat key you pinned has none, sending stops ("a key
        recorded on the blockchain can't disappear"); it is not moved to their posting keys.
      • Someone whose chat key you already trust gets no posting keys on first use: sending to them in the new
        format stops with "these are not keys you have checked" until you compare the safety number in Verify
        peer and accept, and their messages in the new format read "Sender not verified" until then.
      • If your server can't say whether your own chat key is published, sending stops with "try again"; nothing
        is guessed. Once your key has been seen published on this device, a later "none" from a server changes
        nothing.
      • A message made with a key that isn't pinned reads "Sender not verified" and never moves a trade.
      • The pins and that record are encrypted when you lock and removed when you sign out, as before.
    • Safety number. Verify peer uses the same rule as sending, so both sides compare numbers made from the
      same keys — every posting key of each account, in any order. Opening it pins no posting keys: "The numbers
      match — use these keys" (or "accept" for a changed set) pins exactly the keys whose number you compared.
      (A chat key is still remembered the first time the panel or a message looks it up, as before.) It now says plainly when an account doesn't exist, when it has no
      posting key of its own, when the chain couldn't be reached, and when your own key on chain is an old one
      (the other person may see a different number until it is replaced).
    • Accounts with no posting key of their own (managed by other accounts) are named as such, instead of "no
      account by this name". A message made for another posting key of your account (an account can list
      several, and only one is in your session) says so, instead of "may be damaged".
    • Answering in the same format. When someone writes to you in the new format although you hold their
      chat key — their own published key is out of date, for example — your replies go in the new format too, so
      they can read them. Messages already sent in the older format to a key that has since gone out of date
      stay unreadable until its owner opens chat with enough BLURT to re-publish it (a small fee).
    • Settled with someone else. The automatic "this order was settled with someone else" note follows the
      same rules and pins, and reaches every posting key of people who never published a chat key.

    Chat stays fast

    • The keys a first message needs are looked up side by side as the conversation opens, while you type, so
      the first Send waits on nothing (on Tor/I2P each lookup is a slow round trip).
    • A person without a chat key is looked up once per conversation, not once per message; a failed lookup is
      asked again after 30 seconds (or at once on Retry), so a long history costs one lookup, not one per message.
      Opening a conversation no longer asks the server for the same key twice.
    • A long history is opened a few messages at a time, so the page stays responsive while it fills.
    • A message from someone whose keys are pinned opens with no lookup at all. On a server CPU the first message
      from a person takes about 6 ms of crypto to open, later ones about 3 ms (they reuse part of the work);
      sending takes about 11 ms.
    • The account-keys lookup now has a deadline (15 s, longer through Tor/I2P), so a server that stalls can't
      hold a send open forever.
    • A long message costs less in the new format than before: your own copy is a 48-byte key, not a second copy
      of the text.

    A message that could not be sent stays in the chat

    • A message that failed before the network took it lived only on the open chat page: leaving and coming back
      showed nothing where it had been. It now stays in the conversation, marked failed, with the reason and
      Retry, until it is sent.
    • Like your other unsent words, it is kept in memory only, never on disk. It is forgotten when you lock or
      sign out, and not kept at all in "destroy after you leave this chat" mode — including when you switch to
      that mode after the send failed.

    Out of BLURT, in chat too

    • v1.21.12 said the out-of-funds message showed on every screen that writes to the chain. In chat it did not:
      a send refused for lack of BLURT showed the chain's own text ("Assert Exception: acnt.balance >= fee …").
      A failed chat send now reads like every other screen: "Broadcast rejected due to insufficient funds. Buy
      BLURT to save more data to the chain."
      , and the other refusals (clock off, wrong key, too large, the
      server unreachable) say what to do.
    • A message to an account that does not exist on the chain says so instead of failing without a reason.

    New accounts start with 4 BLURT

    Blurt takes a fee for every operation from the sender's liquid BLURT: 0.050 per operation plus 0.200 per
    1,024 bytes of the signed transaction. Every Save on the settings page is its own operation. Measured with the
    real builders:

    Operation Fee (BLURT)
    Display name 0.084
    Short bio (128 characters) 0.111
    Website, streaming or Nostr link about 0.09–0.10 each (0.187 at the 512-character limit)
    Preferred languages 0.090
    Avatar about 0.89 for 4 KB, 1.29 at the 6 KB limit
    Settings sync (one per batch of changes) 0.223
    First order about 0.18
    Chat message about 0.20 (short), 0.31 (500 characters)

    2 BLURT ran out after an avatar, one order and a few saves; the next field was refused. 4 BLURT pays for every
    profile field once (avatar at the limit), two settings saves, the first order and about seven short
    messages. It adds 2 BLURT to the ~100 BLURT the chain already charges for each new account (~104 per signup;
    about 2,100 BLURT still covers the first 20). The relay now needs the account fee plus 5 BLURT on hand before
    it starts a signup.

    OpenMonero: what it still owes

    The FAQ ("How is Morphit different from OpenMonero?"), the brag list and the media kit now give its full
    record: about 77.85 XMR drained in June 2025; an exploit alert in May 2026; about 200 XMR taken in June 2026
    and about 112 XMR in July 2026 (122 in his later count; 399 XMR across the three drains, by his own count); and on 20
    October 2026 its operator told his community that 150–200 XMR of refunds are still pending, owed to four
    vendors, and that he needs more fees to pay them back. An unverified "~40 XMR" figure for May 2026 was
    removed.

    Downloads
  • v1.21.12 30f9bd77e1

    Morphit v1.21.12
    All checks were successful
    morphit-ci / Supply-chain audit gate (every advisory triaged, none fixable in range) (push) Successful in 40s
    morphit-ci / TypeScript typecheck (sweep all workspaces) (push) Successful in 1m13s
    morphit-ci / apps/web svelte-check (svelte-kit sync + svelte-aware tsc) (push) Successful in 50s
    morphit-ci / Integration tests (real Postgres 16) (push) Successful in 6m4s
    morphit-ci / ansible-lint (playbook quality gate) (push) Successful in 45s
    morphit-ci / Smoke suite (run-smokes.sh, pulses 1–2 of 3) (push) Successful in 1h0m23s
    morphit-ci / Smoke suite (run-smokes.sh, pulse 3 of 3) (push) Successful in 30m15s
    morphit-release / Build + publish release tarball (push) Successful in 42m51s
    Stable

    Ghost released this 2026-10-20 03:49:00 +00:00 | 7 commits to main since this release

    Signed by agorise
    GPG key ID: 53524E1F1017EB9C

    Morphit v1.21.12

    Chat, profile saves and every other error the app reads work again on BunkerWeb sites. BunkerWeb,
    on its default settings, replaced every error answer from the site with its own web page. The app
    reads those answers for their reason. On morphit.io, a message to a new member failed with
    "Malformed response from indexer (status 404)", and a short-bio save showed "the chain rejected the
    transaction" without saying why. Worse, the app never published a new member's chat key, so nobody
    could write to anyone who joined after BunkerWeb went in. The upgrade turns that replacing off and proves
    it on the running site. And when an account is short of BLURT, the app now says so on every screen that
    writes to the chain: "Broadcast rejected due to insufficient funds. Buy BLURT to save more data to
    the chain."

    Also fixed: members whose name ends like a file (bob.old, ann.git, max.svg) got the web server's
    404 page for their chat key and their pages. And on BunkerWeb sites the upgrade did not set up, normal
    browsing could ban a visitor for a day.

    This release includes everything in v1.21.11 (see its notes): the download page about Morphit,
    the barter chat buttons, and www.<domain> on BunkerWeb sites.

    Upgrading: sudo morphit-ops upgrade on every server. No database change. On a BunkerWeb site the
    upgrade changes a few lines of bunkerweb.env, recreates BunkerWeb's services so they read them, and
    checks the result on the running site. The frontend container is rebuilt from this release, and a
    bare-metal nginx site's own file is updated. Members who joined while answers were replaced get their
    chat key published the next time they open a chat. Publishing it costs the usual small fee, so an
    account with no BLURT left needs some first. The www note in v1.21.11 applies when you upgrade from
    v1.21.10 or earlier. Every instance must run v1.21.1 or later before 2026-11-01 00:00 UTC.

    BunkerWeb replaced the site's error answers

    • What happened. BunkerWeb's REVERSE_PROXY_INTERCEPT_ERRORS is yes by default. It swaps every
      400, 401, 403, 404, 405, 413, 429 and 500–504 answer from the site for BunkerWeb's own HTML error
      page. Morphit's indexer and relay answer refusals as JSON with a reason, and the app acts on that
      reason.
      • Seen on morphit.io (v1.21.10): asked directly, the indexer answered a missing chat key correctly
        (404, "no chat identity for @…"); through BunkerWeb, the same request got its HTML page.
      • None of Morphit's code had changed: every BunkerWeb site has had this since BunkerWeb went in.
      • The bio save was a real refusal by the chain. The account had 0.076 BLURT left, and the save
        costs about 0.11 (see below).
    • What it broke, everywhere the app reads an error answer:
      • Chat.
        • Opening a chat for the first time looks up your own chat key, and "not found" is the signal to
          publish it. With BunkerWeb's page instead, the app could not tell, so it never published the key.
          Nobody could write to a member who joined after BunkerWeb went in.
        • A message to such a member showed "Malformed response from indexer (status 404)" instead of
          "they have not set up chat yet".
      • Profile. A member with no profile yet could not save a display name, bio, website or links
        from Settings: the page read "no profile yet" as a failed load and kept saving off. (An avatar
        could still be saved, and that created the profile.)
      • Every chain write (chat, profile, listings, feedback, sending, paying):
        • The chain's reason for a refusal was lost, so the user never saw "not enough BLURT", "your
          device's clock is off", "wrong key" or "too large".
        • The broadcast service's "this is already on the network, do not send it again" warning was lost
          too, inviting a second payment.
      • Listings.
        • A listing just posted showed "failed to load" instead of "still posting", and the edit page
          skipped its wait for the indexer.
        • A gone listing read "load failed" instead of "not found", and the feature-bid form's "listing
          not found" never showed.
      • Sign-up.
        • Every relay refusal became "the chain rejected…": signups paused, the daily limit reached (and
          its voucher steps), "try again in N minutes", a name already taken.
        • After a sign-up whose result was unknown, the safe retry of your own name was lost.
      • Elsewhere. Compare-orderbook reasons, the login QR "not in the directory" screen, push and
        email notification setup errors, and the explorer's "history is busy, retrying" wait.
    • Now. REVERSE_PROXY_INTERCEPT_ERRORS=no: the site's own answers reach the browser unchanged, with
      their status and reason. BunkerWeb's own answers (a firewall block, a request limit) keep its pages.
      • New installs set it (the Ansible template and bunkerweb.env.example).
      • On installed boxes, morphit-ops upgrade:
        • sets it, and turns off every copy of the line (an operator's own yes too: the app cannot work
          with it);
        • recreates BunkerWeb's services (a plain restart keeps a container's old settings);
        • checks a chat key that cannot exist: it must come back as the indexer's own JSON answer, not
          BunkerWeb's page;
        • if the page still comes back, recreates them once more (not while BunkerWeb is still loading)
          and checks again;
        • failing that, gives the exact command to run. When BunkerWeb's Compose service sets the
          setting itself, it says where.
    • When a page comes back anyway (a firewall in front of the site, or a box not upgraded yet), the app
      says who answered instead of "Malformed response":
      • "Your Morphit server's web firewall answered with its own page instead of the indexer's reply
        (status 404), so the reason was lost…"
      • "…could not reach its indexer (status 502)…"
      • "…is limiting requests right now (status 429)…"
      • A chain refusal whose reason was replaced says that the reason was replaced.

    Account names that end like a file

    • What happened. A Blurt name can end in .old, .git, .bak, .svg and the like (each part of
      a name is 3 or more characters). The web server's rule that hides leftover files (.env, .sql,
      .bak …) and its rule for SVG images matched every address. That included the API and the app's
      pages:
      • /v1/chat-identity/bob.old got the web server's own 404 page, so that member's chat key was
        never published.
      • /en/@bob.old (the profile) and /en/chat/max.svg (the chat) answered 404 instead of the app.
    • Now. The API (/v1/, /relay/) is never matched by those rules. Under a language path, every
      such address is a page of the app, never a file. A leftover file elsewhere is still never served:
      missing or not, the answer is the same 404.
    • Where.
      • BunkerWeb sites: the frontend container's nginx. The upgrade reads its running config and
        rebuilds it from this release when it has the old rules.
      • Bare-metal nginx sites: the upgrade rewrites the two rules in the site's own web.conf copy,
        tests it with nginx -t, reloads, and checks what nginx loaded.
      • Checked on a real nginx 1.24 with both shipped configs, and with v1.21.11's web.conf after the
        upgrade's rewrite.

    Bans on BunkerWeb sites

    • Morphit counts only 401, 405 and 444 towards BunkerWeb's bad-behavior bans, at 50 a minute, banning
      for an hour (the install template).
      • A 404 is a member with no chat key or profile yet, or the app checking for a file; a 403 is
        BunkerWeb's own answer to a banned visitor; a 429 is a short burst.
      • Earlier upgrades only took 400 out of BunkerWeb's default list. A box without the template's lines
        therefore kept counting 403, 404 and 429, at BunkerWeb's 10 a minute with a ban of a day.
      • Ordinary browsing could ban a real visitor for a day.
    • Now. The upgrade sets Morphit's list where BunkerWeb's default or the earlier upgrade's list is in
      force (an operator's own list only loses 400). It sets the threshold, window and ban time where they
      are missing.

    Out of BLURT

    • Blurt charges every transaction a fee from the account's liquid BLURT: 0.050 BLURT per operation plus
      0.200 BLURT per KB of the whole signed transaction. These are the witnesses' current settings, as the
      chain reported them on 2026-10-19.
      • A short-bio save costs about 0.11 BLURT.
      • The avatar in this report (5.8 KB on chain) cost about 1.2 BLURT.
    • When the balance cannot cover the fee (or a payment), the chain refuses with "Account does not have
      sufficient funds…".
    • That refusal now reads "Broadcast rejected due to insufficient funds. Buy BLURT to save more data
      to the chain."
      in all 10 languages. It shows on every screen that writes to the chain: settings,
      chat, feedback, posting and editing a listing, featuring a listing, sending, powering up and down,
      and paying in a chat.
    Downloads
  • v1.21.11 94574b6303

    Morphit v1.21.11
    All checks were successful
    morphit-ci / Supply-chain audit gate (every advisory triaged, none fixable in range) (push) Successful in 41s
    morphit-ci / TypeScript typecheck (sweep all workspaces) (push) Successful in 1m10s
    morphit-ci / apps/web svelte-check (svelte-kit sync + svelte-aware tsc) (push) Successful in 46s
    morphit-ci / Integration tests (real Postgres 16) (push) Successful in 5m48s
    morphit-ci / ansible-lint (playbook quality gate) (push) Successful in 48s
    morphit-ci / Smoke suite (run-smokes.sh, pulses 1–2 of 3) (push) Successful in 59m35s
    morphit-ci / Smoke suite (run-smokes.sh, pulse 3 of 3) (push) Successful in 32m4s
    morphit-release / Build + publish release tarball (push) Successful in 42m50s
    Stable

    Ghost released this 2026-10-19 22:59:13 +00:00 | 8 commits to main since this release

    Signed by agorise
    GPG key ID: 53524E1F1017EB9C

    Morphit v1.21.11

    The download page is about Morphit, on every site. On a branded site it said "Get Vigilante
    Trading" and invited visitors to "install a Vigilante Trading instance … or run your own node", as if
    the site were the software. Its heading, browser-tab title and the title of a shared link now read
    "Download Morphit", with no site name, and the introduction says the site runs on open source software called
    Morphit, which anyone can use to run their own site and be listed in the Federation directory. Also:
    in a chat about a goods or services (barter) listing, "Pay now" now shows to the person who pays and
    "Share crypto address" to the one who is paid — it was the other way round — and the mailing-address
    and shipment buttons appear. And on a BunkerWeb site with a domain of its own, www.<domain> now
    works and is secure: it goes to https://<domain> instead of BunkerWeb's "403 Forbidden" page.

    Upgrading: sudo morphit-ops upgrade on every server. No database change. On a BunkerWeb site
    whose address is a domain of its own (vigilante.trading, morphit.io — not a subdomain such as
    morphit.timeapp.foundation) and whose www record points at the server, the upgrade adds www to
    BunkerWeb's settings and to the Let's Encrypt certificate (see below; BunkerWeb's services restart for
    the settings, then again for the certificate). Check
    the record first, on any computer: getent ahosts www.<domain> should list the same addresses as
    getent ahosts <domain>; if not, add a CNAME record www → <domain> at your DNS provider.
    Every instance must run v1.21.1 or later before 2026-11-01 00:00 UTC.

    Download page

    • Title: "Download Morphit" (it was "Get your site"), the same on every site — the heading, the
      browser tab and the title of a shared link, in all 10 languages. No site name follows it: a branded
      site added none already, and an unbranded site no longer adds its name ("— your site") on this
      page; every other page keeps its title as before.
    • Description (search results and shared links): it said "Install your site as an app", which
      the page no longer covers. It now says Morphit is the free, open source software the site runs on,
      which anyone can use to run their own site, and that its code is mirrored and verifiable.
    • Introduction: "This website runs on open source software called Morphit. It enables anyone to
      run their own Morphit-powered website like this one and get auto-listed in the Federation directory
      so you can start earning from it." — "run their own" opens the run-a-node page, "the Federation
      directory" the instances page.
    • Source code: the canonical repository "lives on the Agorise server" (it said "our own Forgejo
      server").

    Chat about a barter listing

    • The right button for the right person. A barter listing says what happens to the goods ("I'm
      selling bananas for XMR"), so its crypto runs the other way: the goods buyer pays, the goods seller
      is paid. The chat read the listing as a crypto order and offered the goods seller "Pay now" and the
      goods buyer "Share crypto address". It now turns the direction round for barter listings only — the
      rule the orderbook already used — on whichever side of the chat you are.
    • Mailing address and shipment. "Share mailing address" (for the one who pays) and "Record
      shipment" (for the one who is paid and sends the goods) appear in a barter chat. They showed only
      when a listing named a payment method that is posted (metals, cash by mail, barter goods), and a
      barter listing names only the coins it accepts, so they never appeared there.
    • Contributed by another developer, with tests; crypto orders behave exactly as before.

    www.(your domain) on BunkerWeb sites

    • What happened. http://www.vigilante.trading showed BunkerWeb's "403 Forbidden" page. The
      www record pointed at the server, but BunkerWeb serves only the names in its SERVER_NAME
      setting, which held the domain alone, and turns every other name away. The certificate named the
      domain alone too, so https://www… could not be secure either.
    • Now. BunkerWeb answers for www.<domain> and sends every request for it, on http and https, to
      https://<domain> with the same page (one redirect), and the Let's Encrypt certificate names both.
      Renewals check www through that redirect, the way they check the domain.
    • Which sites. A domain of its own: two parts (example.com) or a country's three
      (example.com.mx, example.co.uk). Never a subdomain (morphit.timeapp.foundation,
      trade.example.org), an address that already starts with www., or an onion or I2P address.
    • New installs set it up. They ask Let's Encrypt for the domain's certificate, then add www to
      it; while www does not point at the server yet, only that addition is refused (the domain's own
      requests stay under Let's Encrypt's limit of five failed checks an hour).
    • The upgrade looks up where www points, asking DNS servers directly (systemd-resolved's own
      upstream servers when it runs, so not this server's hosts file). When it points at the server, it
      adds www to SERVER_NAME and the redirect rule to bunkerweb.env (a copy of the old file is
      kept next to it), recreates BunkerWeb's services (a plain restart keeps a container's old settings),
      and checks on the server that www answers with the redirect. It then proves Let's Encrypt can
      check www (a test file in the web build must come back through http://www…), asks for the
      certificate with www added, reads it back, and checks that https://www… passes a certificate
      check. The certificate keeps what the operator set for it (its own deploy hook, preferred chain,
      key reuse; not OCSP Must-Staple, which Let's Encrypt no longer issues — the upgrade says so). When BunkerWeb refuses the settings or the site stops answering, the old
      file goes back. A settings file that already names www while the running BunkerWeb does not (a
      fresh install, or an upgrade that could not recreate it) is loaded by recreating BunkerWeb's
      services — once per version of the file. When BunkerWeb's Compose service sets SERVER_NAME
      itself, or does not read bunkerweb.env, the file is left alone and the upgrade names what to
      change instead. When www has no record, the upgrade says which record to add; when it points
      elsewhere, it says where. A certificate that names www after its record is gone is issued again
      without it, so renewals keep working. All of it runs in the background part of the upgrade and
      stops in time: a step that would not finish before that run's time limit is left for the next
      upgrade, so BunkerWeb is never stopped half-way (when that background part cannot be started on a
      server, the upgrade says that www waits for it).
    • Installer. morphit-ops bunkerweb reads the domain from a SERVER_NAME that also lists
      www, keeps www when the domain stays the same, and moves the certificate paths with a new
      domain.
    • Onion-Location header (corrected comment). The install template claimed the header kept the
      page a visitor is on. It never did: BunkerWeb sends the header exactly as written, and Docker
      Compose dropped the $request_uri it ended with. The header names the onion (as it always did in
      practice), and the page's own onion-location tag keeps carrying the page for Tor Browser.

    Build tooling

    • postcss-selector-parser 6.1.5, published today as the fix for the 6.x line of a CPU-exhaustion
      advisory (GHSA-rj75-hqrm-r3gf). It is used only while the frontend's CSS is built and linted, never
      on a node. The advisory database still lists it until it names 6.1.5 as patched, so it stays in
      the project's reviewed list until then.
    Downloads
  • v1.21.10 224c7ab702

    Morphit v1.21.10
    All checks were successful
    morphit-ci / Supply-chain audit gate (every advisory triaged, none fixable in range) (push) Successful in 40s
    morphit-ci / TypeScript typecheck (sweep all workspaces) (push) Successful in 1m15s
    morphit-ci / apps/web svelte-check (svelte-kit sync + svelte-aware tsc) (push) Successful in 53s
    morphit-ci / Integration tests (real Postgres 16) (push) Successful in 6m11s
    morphit-ci / ansible-lint (playbook quality gate) (push) Successful in 45s
    morphit-ci / Smoke suite (run-smokes.sh, pulses 1–2 of 3) (push) Successful in 59m34s
    morphit-ci / Smoke suite (run-smokes.sh, pulse 3 of 3) (push) Successful in 30m25s
    morphit-release / Build + publish release tarball (push) Successful in 42m26s
    Stable

    Ghost released this 2026-10-18 05:18:42 +00:00 | 10 commits to main since this release

    Signed by agorise
    GPG key ID: 53524E1F1017EB9C

    Morphit v1.21.10

    Setting up offline notifications, made to work for a real operator. sudo morphit-ops offline-notify can now make your site's XMPP account on a free public server (og.im, openim.nl or
    jabber.de, asked live), with every answer checked before anything is sent. The Matrix step explains
    that the account must already exist and says exactly why a sign-in was refused. A Matrix test
    invitation can now be accepted, and the email step is written in plain words. Also: a contact link,
    name or address changed with morphit-ops edit now reaches your card on the Instances page,
    including on servers installed before v1.12.20.

    Upgrading: sudo morphit-ops upgrade on every server. No database change. The upgrade removes
    the install-time copies of your site's name, tagline, contact link and addresses that an older
    install still has in /etc/morphit/indexer.env, where morphit.config.env sets them too, and
    restarts the indexer if that changes anything (see below). Every instance must run v1.21.1 or
    later before 2026-11-01 00:00 UTC.

    Your card on the Instances page shows what you set

    • What happened. An operator changed the site's contact from a Matrix room to an email address.
      The Operators page (the on-chain registration) showed the email at once; the site's card on other
      instances' Instances pages kept "Contact via Matrix". The card is copied from the site's own
      indexer, which still had the old value.
    • Old copies in a file read last. The indexer reads /opt/morphit/morphit.env, then
      morphit.config.env, then /etc/morphit/indexer.env — the last one wins. A server installed
      before v1.12.20 still has its install-time name, tagline and contact link in indexer.env, and the
      Ansible files carry the operator tag and the Blurt node lists, so an edit was written, the service
      restarted, and nothing changed. Now morphit-ops edit and morphit-ops alt-address remove an old
      copy of every setting they just saved from indexer.env and relay.env (a copy of the file is kept
      next to it, ….bak-<number>), restart, and then read the running services' settings and say
      whether they have the new values. A Blurt node list there that keeps the server off public nodes
      (empty, or hidden .onion/.i2p nodes only) is kept, and named. Each upgrade does the same for the
      name, tagline, contact link and addresses wherever morphit.config.env also sets them.
    • The relay is restarted too when the site's name, origin, Tor/I2P/Lokinet address or its own
      Blurt node list changes: its offline notes carry the name and the addresses, and it used to keep the
      old ones until its next restart (even an onion just replaced).
    • At once after a restart. The indexer refreshes your own card at the first check after it starts,
      instead of when it next falls due.
    • Other servers' cards. A card for another instance changes when that instance's own indexer
      answers with the new value (each instance asks every 10 minutes), so that instance needs this fix
      (or a restart of its indexer) too.

    Offline notifications: setting up the accounts

    • XMPP: a new account in a few answers. Choose Make a new one: the wizard asks og.im, openim.nl
      and jabber.de (or a server you name) whether they take new accounts right now and offers only those
      that do. It checks the name (2–32 of a–z, 0–9, . _ -) and the password (12–128 characters, not
      containing the name, three kinds of characters or 20 and more, plain characters, typed twice) before
      anything is sent, or makes a strong 24-character one if you just press Enter. A server that wants a
      picture check shows a web page and you type what it shows (within about half a minute; a late answer
      gets a new picture). A refusal shows the server's own reason. Then it turns the account's message
      archive off (and reads the setting back), sends you a test note and saves. With no test address it
      offers to save the new account untested; a new account you choose not to save stays on its server,
      and the wizard says so. Use an account that already exists finds the server from the domain's DNS
      records. On a zero-clearnet site only .onion servers, and no DNS lookups. It never logs in or
      registers without encryption.
    • Matrix. The step now says the account must already exist (the wizard signs in; it does not make
      one) and how to make one on matrix.org. A refused sign-in says why: wrong username or password, no
      such account yet, too many attempts, no password sign-in, not a homeserver, unreachable. An address
      whose /.well-known names another (matrix.org → matrix-client.matrix.org) is signed in at, and saved
      as, that one. A homeserver that answers the sign-in with a redirect is refused, so the password is
      never sent on to another address.
    • The Matrix test invitation can be accepted. The bot used to leave the test chat at once, so the
      invitation could not be accepted ("the person who invited you has already left"). It now waits until
      you have joined, up to about a minute and a half, and the wizard says so; it always finishes within
      the wizard's time limit.
    • Email. The step explains in plain words that no mail server runs on your server: notes go out
      through a mailbox at a provider, and it names the four answers to have ready.

    Relay hardening (found in the review)

    • XMPP replies are read in one pass. The relay read a server's replies with patterns that could
      take minutes on crafted input, and a hostile XMPP server could have stalled the whole relay. Replies
      are now split into elements by a scanner that looks at each character once.
    • Passwords as servers keep them. The relay now logs in with the password prepared the standard way
      (SASLprep): an account whose password has a no-break space, a ligature or an accent typed as a
      separate mark could not be logged in to before.
    • Zero-clearnet read the way the indexer reads it. Every check that decides from
      /etc/morphit/indexer.env whether a server is zero-clearnet (morphit-ops, first-online, the IPFS
      setup, the snapshot pinning) now reads MORPHIT_INDEXER_RPC_ENDPOINTS as bash does: an empty list
      written with a comment after it (…= # tor-only), quoted, or with export is empty, and the last
      line wins. Before, such a line read as clearnet: first-online could ask public RPC nodes and IPFS
      could join the public network from the server's own address.
    • CI: the third run of the smoke suite is now its own job, started after the first two: a run of
      the suite had grown to about 32 minutes, and three in one job no longer fit its 90-minute limit.
    • Smaller: a malformed name refused by the server asks for another name; a registration form that also
      needs an email address is reported as such; a character split across two network packets is no
      longer garbled; a server repeating the password in a refusal no longer puts it on the screen; Ctrl+C
      at the picture question stops the wizard like any other prompt; the relay tool ending during a
      question no longer leaves a second prompt or a spinner behind.
    Downloads
  • v1.21.9 24949433cc

    Morphit v1.21.9
    All checks were successful
    morphit-ci / Supply-chain audit gate (every advisory triaged, none fixable in range) (push) Successful in 48s
    morphit-ci / TypeScript typecheck (sweep all workspaces) (push) Successful in 1m9s
    morphit-ci / apps/web svelte-check (svelte-kit sync + svelte-aware tsc) (push) Successful in 48s
    morphit-ci / Integration tests (real Postgres 16) (push) Successful in 5m47s
    morphit-ci / ansible-lint (playbook quality gate) (push) Successful in 45s
    morphit-ci / Smoke suite (run-smokes.sh, triple-pulse) (push) Successful in 1h24m30s
    morphit-release / Build + publish release tarball (push) Successful in 40m14s
    Stable

    Ghost released this 2026-10-17 01:46:07 +00:00 | 12 commits to main since this release

    Signed by agorise
    GPG key ID: 53524E1F1017EB9C

    Morphit v1.21.9

    Offline notifications. A user can now ask for a short note on an app she already uses — Matrix,
    Session, XMPP, Telegram or email — when someone sends her a chat message while she is away. The note
    says only "Someone just sent you a chat message on your site", with a link that opens that
    conversation for her alone; never who wrote or what. It is off until she turns it on, on the apps you
    choose to offer, and her address is kept encrypted under a key outside the database, never shown to
    anyone and left out of backups. Also: script files no longer fail to load with "429 Too Many
    Requests" behind BunkerWeb, and a small fix that lets the W3C validator accept every page.

    Upgrading: sudo morphit-ops upgrade on every server. One database change (migration v69, seven
    new tables, all local: never published in snapshots, never in backups), applied by the upgrade.
    Offline notifications stay off until you set up at least one app with
    sudo morphit-ops offline-notify (also in the sudo morphit-ops menu); see
    OPERATIONS.md §54. The 429 fix reaches BunkerWeb boxes on this upgrade (see
    below). Every instance must run v1.21.1 or later before 2026-11-01 00:00 UTC.

    Offline notifications

    • For your users. Settings → Offline Notifications: she picks an app you offer, types her
      address, and confirms it with a code sent there (Telegram the other way round: she sends your bot a
      code, and a button does it for her). From then on, a chat message that reaches her while she is away
      earns her a note there, in her language: "Someone just sent you a chat message on Vigilante Trading.
      Open it here to read it and reply: …". She can change an address (the old one keeps working until
      the new one is confirmed — cancel, a code that never arrives, expires or is typed wrong five times
      gives her the old one back, and it keeps getting her notes meanwhile), turn one app off, or delete
      every address at once.
    • When a note goes. A random 10 to 60 minutes after the message, and only on a quarter hour
      (:00, :15, :30, :45 UTC) — the same moments for everyone — so a note's arrival does not point at one
      message on the public chain. None while she has the site open (her browser tells the relay once a
      minute, kept in memory only), none once she has read the conversation or replied, one per
      conversation until she looks, at most one a day from someone she has never written to, none from
      someone she has blocked since, at most 20 a day. A note that reached none of her apps is tried again
      twice, and does not count as one she was given.
    • How her address is kept. Sealed with AES-256-GCM under a key in the relay's own state folder
      (/var/lib/morphit-relay/offline-notify.key), never in the database, and padded to one size, so a
      copy of the database opens none of them. Nothing sends an address back to a browser, not even to
      her: once saved, the card shows only "On". It is never in a log, a backup or a snapshot. A relay
      that finds its key missing or replaced turns notices off rather than make a new key over the saved
      addresses; Start over in the wizard (offered only then) deletes them and makes a new one.
    • Signed, once, for your site. Every request is signed with her posting key — the key as two of
      your RPC operators agree it is, and one that signs for her on its own — for your relay only, and
      each signature works once, within five minutes. A link in a note opens the conversation only for
      her account.
    • Not a spam cannon. At most 6 codes a day per user, 3 of them to one address, and 5 a day to one
      address whoever asks. Past those 5 the card still says the code was sent (nothing is), and every
      "send me a code" is answered eight seconds after it arrived whatever happened, so neither the answer
      nor its timing tells anyone whether an address was used today. On a hidden site every visitor
      reaches your relay from the same address, so only failed requests that cost the relay something
      count against an address's limit, and a user whose key was seen within a day is still let through
      (within a small shared budget per second) while that address is being flooded.
    • Honest about the apps in between. Apart from Session (end-to-end encrypted), the servers that
      carry a note can read it: they learn that she uses your site and when a note came — never who wrote
      or what. And to deliver anything, her address goes to the provider of the account your notes come
      from. The card and a new FAQ answer ("How do offline notifications work, and how is my address
      kept private?") say so plainly, and "What data does it collect about me?" now lists the address she
      gives for them.
    • Matrix rooms are left. The bot leaves and forgets every chat no saved address holds any more —
      a removed or changed address, a code nobody confirmed, a first message that failed — retrying for
      about two days when your homeserver does not answer.
    • Setting it up. sudo morphit-ops offline-notify asks for the account your site sends from for
      each app, tests it (a real note to an address you type, or for Telegram the token checked with
      Telegram), saves it to /etc/morphit/relay-notify.env (root:morphit-relay 0640; the wizard
      changes only its own lines), restarts the relay and checks the app is offered. Turn an app off
      deletes every address saved for it first. On a Tor-only site only .onion servers can be used, and
      Telegram and Session are not offered.
    • What your WAF records. BunkerWeb's ModSecurity rule for the relay's paths now turns ModSecurity's
      rules off there and, belt and braces, its audit log and request-body reading too, so the WAF never
      records what she typed (the upgrade brings the older rule up to date).

    Script files no longer fail with 429 behind BunkerWeb

    • What happened. Some visitors' browsers logged "Failed to fetch dynamically imported module" on
      first visit: BunkerWeb's own catch-all request limit (2 per second, counted per visitor and per
      file) answered 429 when the browser fetched the same script file three times within a second (the
      page, the offline cache, the tamper check).
    • Now. The site's own files are not limited; the 2-per-second limit stays only on IPFS, RSS and
      MCP. BunkerWeb also allows 256 parallel streams per connection (it allowed 100, fewer than nginx's
      128). The upgrade fixes boxes that still have BunkerWeb's defaults (your own values are kept),
      recreates BunkerWeb and checks the home page three times in a second; if it still gets a 429 it
      tries once more, then tells you the command to run. Bare-metal nginx's connection limit per visitor
      is raised from 40 to 256 likewise.

    Smaller changes

    • The no-JavaScript style moved from the page body into its head, where the W3C validator accepts it;
      pages look and behave the same with and without JavaScript.
    • The FAQ search finds "notify me by email" and similar questions (new words: notify, notified,
      writes; filler words by, me, am, at).
    Downloads
  • v1.21.8 563389124d

    Morphit v1.21.8
    All checks were successful
    morphit-ci / Supply-chain audit gate (every advisory triaged, none fixable in range) (push) Successful in 42s
    morphit-ci / TypeScript typecheck (sweep all workspaces) (push) Successful in 1m9s
    morphit-ci / apps/web svelte-check (svelte-kit sync + svelte-aware tsc) (push) Successful in 53s
    morphit-ci / Integration tests (real Postgres 16) (push) Successful in 5m43s
    morphit-ci / ansible-lint (playbook quality gate) (push) Successful in 48s
    morphit-ci / Smoke suite (run-smokes.sh, triple-pulse) (push) Successful in 1h26m49s
    morphit-release / Build + publish release tarball (push) Successful in 39m35s
    Stable

    Ghost released this 2026-10-15 04:09:33 +00:00 | 13 commits to main since this release

    Signed by agorise
    GPG key ID: 53524E1F1017EB9C

    Morphit v1.21.8

    A branded site never shows Morphit's logo or colours, not even for an instant on a first visit. The
    Operators page shows, for every operator, how many orders came through its site and what it has
    earned since it started. The thin bright edge a main button lights on hover shows again on sites with
    bright (gold) buttons, an upgrade puts back a warrant canary signed on another computer, and Morphit
    works with hidden-rpc v1.3.0's built-in snapshot mirror. Plus a Morphit entry in the glossary and the
    requested wording changes on the homepage, the privacy pages and the FAQ.

    Upgrading: sudo morphit-ops upgrade on every server. One database change (migration v68, three new
    tables), applied by the upgrade. After it, the indexer fills in the operator earnings from fee
    payments it indexed before by reading them from the chain again, in the background; the Operators
    page's earnings can be short until it finishes (it reads 25 blocks with fee payments every 10 seconds;
    fee_legs_backfill_pass in the indexer log).
    Like every change to the upgrade, the canary fix starts with the upgrade AFTER the one that installs
    it: this upgrade still clears a canary signed on another computer, so re-sign it once more afterwards,
    on that computer (bash ~/.morphit/update-canary.sh). Every instance must run v1.21.1 or later
    before 2026-11-01 00:00 UTC.

    A branded site never shows Morphit's look

    • What happened. Opening some pages of a branded site for the first time — an order, a chat, a
      profile: the pages the app draws in the browser — showed Morphit's logo and colours for a moment
      before the site's own. Those pages start from one shared page that branding does not rewrite, and
      learned the site's name and colours from /brand/brand.json, which arrives after the first paint.
      And a browser that had visited the site before it was branded kept showing the old Morphit logo
      until a refresh, because the stored copy was shown first and the new one fetched in the background.
    • Now. Every page links a small stylesheet, /brand/theme.css, in its head; the browser paints
      nothing until it has it. Branding writes the site's colours into it, and its name, so every page —
      prerendered or drawn in the browser — has the site's look from its very first frame, with no wait
      for brand.json. On an unbranded site the file sets nothing. The logo, icons, theme.css and
      brand.json are now always asked of the server first, so a visitor who saw the site before it was
      branded, or before a change, gets the new look on the very next page, without a refresh. The
      browser's stored copy is used only when the server cannot be reached at all (offline, or the
      connection drops) — since it may be from before the site was branded, it is never handed out
      because the server is slow, nor in place of an error (a 404 or 503, or a server that has not
      answered within 30 seconds (45 on a .onion, .i2p or .loki address): the logo is then missing rather than
      possibly Morphit's). Only
      theme.css keeps its stored copy over an error, since without it the page would show Morphit's
      look anyway. Where theme.css names the site, brand.json is no longer read at all, so a copy
      stored before a re-brand cannot paint the old colours back. (A page now waits for that small
      file, as it already waits for the page itself.)
    • Until the new service worker takes over. A returning visitor's browser keeps running the old
      service worker until the visitor accepts the update or closes every tab of the site, and that
      worker handed out its stored logo first. The pages now ask for the logo as
      /brand/site-logo.svg?fresh=1 (and the footer's likewise), which the old worker, too, always
      fetches from the server — so those pages show the site's logo as well.
    • The browser's own colour. A phone's address bar (theme-color) took Morphit's green until
      brand.json arrived; it now takes the site's colour from theme.css, before the page renders.
    • Also branded: the minimal page an operator can serve under heavy load (degraded.html) now
      links theme.css and takes the site's colours (background, panel, text and accent), and the
      banner that offers to install the site as an app is tinted in the site's colour instead of
      Morphit's green (on an unbranded site, a shade different from before).
    • Nothing to do: the upgrade re-applies your branding, which writes the new file. A site branded
      before this release keeps working the old way until then.

    Branding

    • The hover edge on bright buttons. The 1px edge a main button (Start, top right, and the other
      solid-colour buttons) lights on hover was drawn in the accent colour. On a theme with bright buttons
      the accent colour is the button's own colour, so the edge never showed — on Vigilante Trading's gold
      buttons (champagne-gold) it was gold on gold. It now has a colour of its own: on bright buttons the
      theme's lightest colour, lifted until it stands out (a pale gold, #fff9e8, on champagne-gold); on
      deep buttons the accent colour, as before. With some colours of your own a deep button sat right on
      the accent colour too (1.01:1); the edge is lifted lighter there as well. In every theme the edge, as
      drawn (at 90% opacity over the button), is at least 1.8:1 from the button — lighter than it, or, on a
      button so light that not even white stands out from it (Morphit's emerald, yellows, cyans, white), a
      darker shade of it. (Many buttons
      inside dialogs, chat and settings also brighten by 10% on hover, edge included; there it stands out
      a little less — about 1.6:1 on champagne-gold, where it used to be invisible.) An unthemed site
      paints exactly as before (Morphit's emerald edge).
      Nothing to do: the upgrade re-applies your branding — on a branded site its output shows
      "✓ Applied your branding (…) to the live frontend." near the end. If, after the upgrade, the edge
      still shows the old colour on your buttons (whatever the upgrade printed — an upgrade whose last
      step ran out of time can leave the old look), run sudo morphit-ops branding apply on that server.
    • Branding stopped half-way. The server sends the pre-compressed copies of a page (.gz, .br)
      to most browsers. If applying the branding stopped half-way (a full disk, a power cut) after the
      page was rewritten but before its compressed copies were, the next run saw the page already
      branded and skipped it — and browsers kept getting the old compressed copy. Branding now writes
      the compressed copies before the page and checks them too before skipping a file, and a reset puts
      back every original, compressed copies included, even after a run that stopped half-way.

    Operators page: orders and earnings for every operator

    • What was wrong. The page promised each operator's orders and earnings, but showed them only on
      the operator's own site, only for BLURT listing fees, and only from the version that started
      counting them — so nearly every operator showed nothing, everywhere.
    • Now every operator has both figures, since it started, and the list is ordered by orders,
      then earnings:
      • Orders — orders posted through its site (carrying its operator tag) whose fee this site holds
        as paid (verified, or verified by attestation): paid in BLURT, BTC or XMR, or a free first buy,
        whatever the order's status now. This is each site's own count: a site that does not list an
        asset has none of its orders, and BTC and XMR payments are confirmed by each site's own
        explorers, so sites can differ a little.
      • Earned — the BLURT that reached its fees account in Morphit fee payments: listing fees,
        first-contact fees and featured-slot bids. Computed from the chain alone, so it is the same on
        every Morphit site, whatever assets or settings each has. It follows the money: each payment is credited to the
        operator whose registered fees account received it, as registered when it was paid (a payment
        from before an operator first registered its fees account counts for the first account it
        registers, if the payment names that operator). The 10% that goes to the Morphit treasury is
        nobody's earnings, and neither is a fee paid from an operator's fees account itself (it cannot
        pay itself, so that fee goes wholly to the treasury; BLURT a fees account sends itself never
        counts). A payment counts only when the treasury
        got its tenth — the check every fee already passes — so BLURT sent round in a circle cannot
        inflate the figure for free, and registering someone else's account claims nothing paid into it
        before. A featured-slot bid is credited by the tag of the order it features, as that order's own
        op on the chain carries it. BTC and XMR fees go entirely to the treasury, so they add orders, not earnings. For an operator whose fees account is the Morphit treasury (morphit.io, or a site that
        left its fees account blank) the page says "Paid to the Morphit treasury" under the figure.
    • The indexer now records every fee payment with the transfers that paid it (two new tables,
      fee_payments and fee_payment_legs, carried in snapshots). Payments indexed before this version
      are filled in after the upgrade from the chain: each original transaction is fetched again through
      the node's own RPC pool and checked against its id before anything is recorded; a block that
      cannot be fetched is set aside and tried again every 10 minutes. At most 16 transfers are recorded
      per fee payment (a fee has at most two), so a crafted transaction cannot make the count expensive.
    • The figures are computed in the background into a third small table, operator_stats (local, not
      in snapshots): within a minute of a new fee payment and at least every 10 minutes, so the page
      never waits on the count. /v1/operators adds fee_account, and stats for every operator
      counted (an operator registered since the last count has none until the next one, and every
      operator has none until the first count after the upgrade, which waits until the node has caught
      up with the chain).

    Pages and wording

    • Glossary: a "Morphit" entry — the open source software that runs this site and many others,
      each run by its own operator, developed by the Dev Team at Agorise; every site reads the same
      Blurt blockchain, so your account works on all of them, and the software never holds your coins
      or your keys.
    • Homepage: the first sentence ("Anonymously trade cryptocurrencies, fiat, goods, services and
      more") is 10% smaller at every screen size, and the logo above it sits a little higher.
    • Privacy & terms: "We go further than encouraging privacy" links to the privacy page.
    • Privacy page: "the next asset we add will get a guide automatically", and the large heading
      at the top (and on each asset's guide) is drawn in the site's own colours, like the other pages'
      headings, instead of plain white.
    • FAQ: every edit requested, in all ten languages — "Who runs Morphit?", the comparisons with
      LocalBitcoins/LocalMonero, BasicSwap and Bisq/Haveno, the fees, Blurt and BP answers, the chat and
      privacy answers (with the @-name examples), the operator answers — and two more "Related" links:
      "How do I sell crypto?" → "What are the fees?", and "How does the chat inbox work?" → "If I block
      someone, will they know?". "encrypted chat chat" in the request was written "encrypted chat".
    • The bookmark tip on the Instances page names ENS addresses too, in every language.
    • Translations: a round of fixes found by a native-language review (Spanish, French, Italian,
      German, Polish, Russian, Persian and both Chinese) — grammar, a wrong word or two, terms that must
      stay in English; the FAQ question about the Blurt-blog checkbox now quotes the checkbox as it
      reads (French, German, Polish, Russian, Persian, Chinese). In Persian, an @-name, a Matrix
      address (@agorise:matrix.org) and a /@name path in the middle of a sentence now read left to
      right instead of being reordered by the right-to-left text around them.
    • Narrow phones. On a 320 px screen the menu row under the header, in Russian, German, Polish,
      Spanish and Italian, pushed its last link (Help) off the screen, and in French the header's Start
      button (also at 768 px, a tablet, in French and Russian); all now fit, and no label is cut in the
      middle of a word. A long FAQ question and the
      Instances page's filter menu no longer push past the screen's edge either. The Operators page
      keeps long operator tags inside the screen and writes its numbers the page language's way
      (1,234 in English, 1.234 in German), not the browser's.
    • Without JavaScript the notice says "This site works without JavaScript — partly." and "This
      site never sees your keys." On a branded site, the pages the app draws in the browser (an order, a
      chat, a profile) said "Morphit" there.

    Hidden RPC nodes (hidden-rpc v1.3.0)

    • hidden-rpc v1.3.0 puts a small gateway in front of blurtd on every hidden RPC node, which serves
      the indexer snapshot (checked against @morphit's signed SHA-256) and passes JSON-RPC through.
      Checked against Morphit's own code before this release: fast-sync downloads and accepts the
      snapshot from it, the snapshot mirror imports and pins it, and single and batch JSON-RPC calls
      reach blurtd byte for byte. Nothing changes in Morphit's code; the docs now say that nodes on
      hidden-rpc v1.3.0 or later serve the snapshot (OPERATIONS.md, HIDDEN-RPC-SNAPSHOT-MIRROR.md).

    Warrant canary

    • An upgrade puts your canary back. An upgrade installs a fresh served folder, which has no
      canary.txt or pgp_keys.asc. A canary signed on the same server was signed anew by the upgrade,
      but one signed on another computer (the recommended set-up for a VPS) stayed missing until you re-ran
      bash ~/.morphit/update-canary.sh there — a canary that disappears reads as one that tripped. The
      upgrade now puts the canary served before it and its public key back, the same bytes (so the
      signature still checks), and says until when the canary is valid; its "Valid through" date still runs
      out on its own, so a canary you stop signing still goes stale as before. The upgrade's last lines ask
      you to re-sign when it could not put the canary back (and says why), when the canary it put back runs
      out within 5 days (or has, or has no readable date), or to publish the public key again when the key
      could not be put back — each with the command for where the canary is signed; otherwise there is
      nothing to do. Where the canary is signed on the server itself and the upgrade can run its refresh
      (its timer, or the uploader's refresh script), it is signed anew there, replacing the one put back.
    • It copies only what a canary is. The upgrade runs as root and reads a folder your canary upload can
      write, so a file is copied only when it is a regular file with one name (checked again after it
      is read, so a second name made in the meantime is caught too), reached with no link anywhere
      inside the old install (so nothing planted there — a link, or a second name for another file — can make the
      upgrade copy out a file only root can read), of a canary's or a key's size, and, as a whole, a signed
      Morphit canary or PGP public key blocks. Anything else is left behind, and the upgrade says so.

    Tests (maintainer)

    • apps/indexer/test/integration/operator-earnings-v1218.test.ts (real dispatcher, handlers,
      Postgres and /v1/operators route): every operator's orders and earnings from listing,
      first-contact and featured-slot fees; the treasury leg of a split fee and a fee paid from a fees
      account itself credit nobody; BLURT sent to a fees account without the treasury's tenth (1,000,000
      BLURT with a refused op, or 9% to the treasury) credits nobody; an operator that registers, later,
      an account others were paid in claims none of it (also at the boundary block); an operator that
      changes its fees account, twice in one block, is credited by the account in force at each
      payment's block; a treasury-account operator is credited only for whole fees that name it; an
      operator that registers its fees account after a payment is credited for it; a second operator
      registering the same account later is not; an underpaid order counts no order but its BLURT; a
      decoy transfer is ignored; every fee op is recorded, even one refused before any handler; the
      back-fill reproduces the same figures from the chain, runs in the background (the indexer goes on
      while its block fetches are out), fetches nothing once done, and records nothing for a transaction
      served altered (the rest of its block is recorded). Seen failing on v1.21.7's code, and with each
      rule removed or loosened in turn (the treasury's tenth, the whole-fee treasury rule, the
      named-operator preference, the first-registration preference, the legacy grace and its naming
      condition, each end of the as-of-block interval); the back-fill test failed while a pass still
      held the indexer.
    • The same test file, after the deep review: two nodes with different settings (another fees
      account, BTC not listed) compute the same earnings; orders whose fee was verified by attestation
      count; a fee op with 20 transfers records 16; a NUL character in a memo or payload is recorded
      the same way live and by the back-fill; a featured-slot bid takes the tag from the featured order's own op, not from this
      node's orders table; a back-fill whose RPC fails for a while sets those blocks aside and fills
      them in once the RPC answers; and the real poller loop back-fills and computes the figures on its
      own. Each seen failing with its rule removed (mutation runs: the feature tag, the payload tag, the
      leg cap, the signer check, the payload cleaning, attested orders, the retry, the set-aside
      handling, the named-operator preference, each of the poller's two calls).
    • api-response-shape-smoke (matrix-bot): /v1/operators entries with fee_account and stats,
      and the shapes that must be refused. Seen failing.
    • apps/web/src/lib/brand/brandCss.test.ts: the site name survives the trip through theme.css for
      every usable name (accents, Hebrew, Chinese, punctuation), as one CSS identifier that nothing can
      break out of; anything else names no site; a page the app draws in the browser has the name before
      anything renders, without brand.json; a stylesheet that names no site falls back to brand.json;
      a prerendered page keeps its stamped name. Seen failing before the change. Also: the address
      bar's colour comes from theme.css while brand.json never answers, and from a stylesheet that
      arrives late; a name hidden inside a longer value is not read as one.
    • apps/web/src/lib/net/serviceWorkerBrand.test.ts: the service worker's whole answer to a brand
      request, with real requests and a cache that matches as the browser's does — a logo stored before
      the branding is never handed out while the server answers, nor in place of an error; offline the
      stored copy is used; a ?fresh=1 read never gets it; the logo URLs the pages use carry
      ?fresh=1. Each check seen failing (six mutations).
    • apps/web/src/lib/net/brandResponse.test.ts: the service worker answers brand files from the
      network first, keeps its copy for offline use only, and waits for a slow server rather than hand
      out a copy that may predate the branding. Seen failing with the old stale-while-revalidate order.
    • apps/ops-cli/test/branding.test.ts: branding writes theme.css (and its gzip copy) with the name
      and the colours, and a colour theme alone writes it too. Seen failing before the change. A full
      disk (injected) while writing a page's .gz, while saving its original .gz, and a page an older
      version left with its old .gz: the next apply finishes the job (and, after the failure while
      saving the originals, a reset restores every byte). Seen failing with the old skip check.
    • theme-tokens-smoke TT-10: the shipped theme.css sets nothing, and app.html links it
      render-blocking, before any script.
    • The service worker is now type-checked (tsconfig.sw.json, in npm run check, the workspace
      typecheck smoke and CI): svelte-check skipped it and the build only warned.
    • Checked in Chromium, frame by frame: on a champagne-gold build, a brand-new browser opening a
      profile, the orders page and the glossary, and a browser that had the unbranded site (and its
      service worker) before it was branded, paint the branded background and logo from the first frame
      and never Morphit's — also with every /brand/* answer held back 1.5 s and 3 s. (The same check on
      an unbranded build finds Morphit's logo in the header, so it would see it.) A first try that
      served the stored copy after a 2 s deadline showed the old Morphit logo for a whole page with 3 s
      answers; that deadline was dropped.
    • theme-tokens-smoke TT-9: reads the hover rule and the button's face from the CSS (the sources, and
      the built frontend when present), resolves both to theme tokens, takes the rule's own opacity, and
      checks that the edge as painted stands out from the face (1.8:1) in every preset with deep and bright
      buttons, on white and near-white bright buttons, and in 200 random themes the engine accepts. Seen failing with the hover
      painted in brand-primary (the shipped bug: champagne-gold/bright 1.00:1), with the old deep-button
      rule (1.01:1), with a white bright button (1.00:1), with the contrast measured on the opaque colour
      (1.72:1 as painted) and with the rule's opacity lowered to 0.5.
    • apps/ops-cli/test/theme.test.ts: the edge token, deep and bright, on 240 random themes and on white
      and near-white bright buttons.
    • apps/ops-cli/test/canaryCarry.test.ts: the canary put back byte for byte (also with bytes that are
      not UTF-8, and with CRLF line ends and armor headers as gpg writes them); a link in the file's name or
      in a folder above it, a hard link, a FIFO, an oversized file, contents that only start like a canary
      or a key, a crafted key file of 4 million lines and a file already in the new folder are not copied
      (and never make it throw); a failed write leaves nothing behind; the last lines' words for a canary
      that runs out soon, has run out, has no date, or lost its key; a second name made for the file
      while it is read; a canary uploaded with a strict umask is still readable by the web server; a
      canary without its marker or with an odd armor line is refused. Each check seen failing with its
      protection removed.
    • upgrade-frontend-deploy-smoke: the upgrade puts the canary back before it hands the served folder to
      the canary uploader, and does not then call the canary cleared.
    • Reviewed three times by a separate reviewer attacking the change and then each round of fixes, with
      real gpg output, a real bind mount and Chromium's own pixels; every finding fixed and guarded, except
      the dimmer edge on buttons that brighten on hover, where the wording now says what holds.
    • Then a deep review of the whole release by eight separate reviewers (indexer, web and service
      worker in Chromium, ops-cli, security, the release checklist, and three translation reviewers),
      and a second round of two reviewers attacking the fixes, whose findings were fixed in turn:
      • earnings: a featured-slot bid is credited by its order's own op, not an earlier malformed one
        or a later repeat with the same name; BLURT a fees account sends itself counts for nobody; a
        block set aside gets its three tries again after its time aside; the figures are counted
        without holding the indexing loop; operator_stats never leaves the node in a snapshot
        (snapshot-trust). Each seen failing.
      • branding: a full disk while saving a page's original, or its .br, then the next apply and a
        reset; a page left with a new .gz but its old .br; a full disk as the page itself is
        written (its compressed copies are already branded). Each seen failing.
      • canary: whether the last lines call the canary cleared, over every case; a canary with
        something before its signed message is not copied. Each seen failing.
      • web: the logo URLs the components actually render carry ?fresh=1
        (MorphitLogoBling.test.ts); a stored brand.json is not handed out over an error; a server
        that never answers gets the worker's 504 for a logo, while theme.css keeps its stored copy;
        colours from theme.css are never painted over by an older brand.json, and a late
        stylesheet sets the toolbar colour; the Operators page's numbers follow the page language
        (i18n-formatters-smoke); Persian Matrix addresses and /@paths (rtl-handle-and-footer-tasks-smoke).
        Each seen failing.
      • Two tests no longer depend on the machine they run on: the canary tests point every upgrade
        state file into their own folder, and the relay heal test's self-heal records its web heal
        there too (it wrote /var/lib/morphit-ops/web-heal.json on the test machine). The fake
        docker in bunkerwebWafIdentify.test.ts writes its state atomically: two docker inspect
        calls at once could read it half-written and fail the test now and then (also on v1.21.7).
      • mediakit-freshness-smoke checks the kit's README colours against theme.css itself, and no
        longer calls the kit stale when theme.css changed without changing those colours (git
        keeps a byte-identical rebuilt zip at its older commit time; this failed the first v1.21.8
        release run). eli5-release.sh --recut prints the ceremony for a tag whose release run
        failed: BLOCK 2 moves the tag.
      • Checked in Chromium on a fresh build, branded and unbranded: no horizontal overflow at 320,
        360, 375 and 768 px in all ten languages on the home, FAQ, Instances, Operators, orderbook,
        glossary and privacy pages (none at 800, 900 and 1024 px on the home page either), and no menu
        label cut mid-word from 320 to 600 px.
    • rtl-handle-and-footer-tasks-smoke: an @-name in Persian text is kept left to right, and an
      e-mail address, a link or a code span is not touched; other languages get no invisible marks.
      Seen failing.
    • TT-9 reads the edge's opacity only from rgb(var(--…) / <number>); any other way of writing it
      fails the check instead of counting as fully opaque.
    • Checked in Chromium: hovering Start on a champagne-gold build draws #fff9e8 at 90% on the
      #d6b26a face (was #d6b26a); the unthemed build still draws #00da69; a build branded by v1.21.7's
      code and then by this release's (the upgrade's order) ends with the pale-gold edge. Compiled-CSS
      comparison with v1.21.7: 1511 rules each, every one paints the same values.
    Downloads
  • v1.21.7 e098915eda

    Morphit v1.21.7
    All checks were successful
    morphit-ci / Supply-chain audit gate (every advisory triaged, none fixable in range) (push) Successful in 44s
    morphit-ci / TypeScript typecheck (sweep all workspaces) (push) Successful in 1m16s
    morphit-ci / apps/web svelte-check (svelte-kit sync + svelte-aware tsc) (push) Successful in 47s
    morphit-ci / Integration tests (real Postgres 16) (push) Successful in 5m33s
    morphit-ci / ansible-lint (playbook quality gate) (push) Successful in 45s
    morphit-ci / Smoke suite (run-smokes.sh, triple-pulse) (push) Successful in 1h20m54s
    morphit-release / Build + publish release tarball (push) Successful in 39m48s
    Stable

    Ghost released this 2026-10-13 19:43:53 +00:00 | 15 commits to main since this release

    Signed by agorise
    GPG key ID: 53524E1F1017EB9C

    Morphit v1.21.7

    A fresh server goes from the guided install to a live, listed node without hand work: it fast-syncs,
    sets up its warrant canary where you choose, lists itself when you say yes, and offers branding — and Ctrl+C at any point
    says what stopped and what is left. The SSH safety net after an install no longer empties the firewall,
    the hardening step no longer opens SSH wider than you had it, fast-sync checks every snapshot before
    unpacking it and never serves a database that did not pass its checks, and the stats page's RPC card
    measures the Blurt nodes only while someone is reading it.

    Upgrading: sudo morphit-ops upgrade on every server. No database change. Like every change to the
    upgrade, the new behaviour starts with the upgrade AFTER the one that installs it. Every instance must
    run v1.21.1 or later before 2026-11-01 00:00 UTC.
    On a server installed with v1.21.6 or earlier whose
    SSH question went unanswered, run sudo morphit-ops health after upgrading: it says whether the
    firewall still lets SSH in (docs/UPGRADING.md, Troubleshooting).

    Installing a node

    • Run a node, step 6, now has the exact commands: connect over SSH, update and restart the machine
      (with a three-minute wait and the reconnect), start tmux, download the offline release into
      ~/morphit (or the normal one on an ARM machine), check it, unpack it, and run the installer. The old
      text pointed at a download-page anchor that did not exist. The README's install step links the same
      commands.
    • Ctrl+C during the guided install now says that you stopped it, and the exact command to start
      again (cd <your folder> && sudo bash morphit-setup.sh), instead of "The install hit an unexpected
      problem" and a support address — also at a check under the spinner while the questions are asked (it
      used to end without a word there), and while Ansible is being installed; the file holding the database
      password is always removed. Every other install failure names that command too. Once the
      install itself has finished, Ctrl+C in one of the steps after it (fast-sync, the canary, the listing,
      branding) stops only that step; Ctrl+C at a question or a wait ends the wizard with a list of what was
      left and how to do each part later — the installed node is not called a failed install.
    • The "save your database password" screen says how to copy without stopping the installer
      (Ctrl+Shift+C; Cmd+C on a Mac).
    • The guided install remembers your answers. Stopped part-way (Ctrl+C, a dropped connection, a failed
      install), the next run shows what you entered and offers to reuse it, so you don't answer everything
      again. Asked again every time, because they are never written to disk: your relay account's active key,
      the alerts step (it holds a bot token) and, at home, the dynamic-DNS update URL. The database password is
      new on every run, and after any earlier run or install on the box the save screen says so. The saved answers are deleted once the install succeeds, or
      when you choose to start fresh. ("Configure only" already did this; the guide said the guided install did
      too, and it did not.)
    • docs/VERIFY-YOUR-DOWNLOAD.md names the offline bundle by its real file name,
      morphit-vX.Y.Z-offline.tar.gz.
    • Inside tmux, the guided install ends by saying so and how to leave: type exit, or Ctrl+B then D to
      keep it running (tmux attach to come back). It doesn't close tmux itself: that would wipe the summary
      you still need to read.
    • Encrypted backups: the key file's place is spelled out. The wizard used to say
      age-keygen -o morphit-backup-key.txt, which saves the key in whatever folder the terminal is in, and
      never said where. It now gives a fixed path in the home folder, named for the instance
      (age-keygen -o ~/morphit-backup-key-<your domain>.txt), says where that is (/home/<you>/…,
      /Users/<you>/… on a Mac), and to keep it in your password manager. Keys for several nodes no longer
      overwrite each other.

    The SSH safety net after the install

    • An unanswered "a NEW SSH login still works?" no longer empties the firewall. When that question went
      unanswered (at the end of a long install, nobody is watching), the safety revert put back the firewall
      rules from before the install. On a fresh VPS that is no rules at all with the firewall still on: SSH
      itself was no longer allowed (only the open session survived), and the rules the install added for the
      web containers were gone, so every /v1 call from the site hung and other instances listed the node as
      "Clearnet blocked". The revert now only puts SSH's own settings back, and keeps the SSH port open to
      everyone only when it was open to everyone before; a port that only some addresses or a VPN
      could reach keeps those rules and is not widened, and one that was closed (SSH over Tor only, say) stays
      closed. It re-asserts Docker and never touches the firewall's other rules. The install's hardened SSH
      settings are kept, and morphit-ops health gives the one command that puts them back once you have
      checked your key login (sudo bash /var/lib/morphit-root/reharden-ssh.sh: it puts them back only if
      sshd accepts them, and otherwise leaves SSH exactly as it is; if SSH then fails to start with them,
      it puts the old ones back, starts SSH again and says which settings it runs now).
    • The hardening step no longer opens SSH to the whole internet on a box with its own firewall. It
      always ran ufw allow 22 and ufw limit 22: on a server whose firewall was already on with your own SSH
      rules (an office address, a VPN, SSH over Tor only) that opened SSH to everyone, and on a server whose SSH
      listens on another port (2222) the firewall it switched on locked you out. It now sets SSH rules only when
      the firewall was off (or let everything in) before it, on the port(s) SSH really listens on — read from
      where it listens (ListenAddress 0.0.0.0:2222 is port 2222, though sshd -T still says "port 22"), and
      none at all when SSH listens on this box only (SSH over Tor).
    • morphit-ops health warns when the firewall lets no one in on SSH, with the command that does
      (sudo ufw limit 22/tcp). On a fresh VPS installed with v1.21.6 or earlier, an unanswered SSH question
      left exactly that: the next login failed. docs/UPGRADING.md explains it.
    • The question now waits 10 minutes (was 2), rings the terminal bell, and asks again on a bare Enter
      (Enter alone is not "no"). Answering "no" puts the SSH settings back at once. Ctrl+C at the question
      leaves the safety net armed and shows the command that cancels it. After "no" it waits (spinner) for the
      revert to finish before saying it did — it used to say "did not report back" while it was still
      running. A box with no SSH server gets no safety net (nothing to put back). If the revert does run,
      morphit-ops health says so ("SSH safety revert ran …") with how to check and re-apply the SSH
      hardening; the notice goes once the hardened settings are back, or after a later confirmed install.
    • The upgrade restores the web containers' firewall rules (relay 8080, indexer 8081, IPFS gateway
      8082, MCP 8124 from the bunkerweb_net subnet) where they are missing, on the install's own layout only,
      and checks they took. It reads the subnet's IPv4 range even on a dual-stack network, and only ever a
      private range.

    Listing your instance

    • "List this instance?" → yes now lists it. Two faults together kept a fresh VPS off /instances:
      • The wizard registered on the spot only if every row of the review was ✓. On a fresh VPS some rows
        never are at that point (the PGP contact key and the canary are signed later on your own computer;
        i2pd takes minutes), so it always deferred. It now registers right away when the node is ready to be
        listed: the chain is reachable and the database, relay, indexer and website are up, with the relay
        funded.
      • Deferring flipped a switch for the background finisher (morphit-first-online), which had already done
        its other jobs during the install and switched its own timer off, so nothing ever read the switch.
        Deferring now also starts the finisher again; it retries every 5 minutes. A registration that goes
        through on the spot no longer leaves a second one queued.
    • Declining at register's own "Publish this registration?" question is no longer reported as listed.
      register exits with its own code (5) then, so the wizard says "Not listed" and edit says "Not
      published", where both used to report success. Ctrl+C at that question counts as declining too, and is
      not queued for the background finisher. Having just said yes to "List this instance?", the wizard's own
      register question defaults to yes.
    • A Tor-only node whose .onion is not in its settings yet is never registered as a bare https://;
      it is queued, and listed once the settings carry the onion. register (by hand or queued) now uses
      http://<onion> when the origin setting is empty and the onion is set, so the menu's "Set up a Tor /
      Lokinet / I2P address" is enough to list such a node.
    • The canary setup is never told the instance URL is a bare https:// (a Tor-only node before its
      onion is saved); it then finds the URL itself, onion included.
    • Why a peer probe failed is now in the indexer's log (peer_probe_failed, with the peer and the
      reason), once per change. It was only in the database, so a new instance shown as "Clearnet blocked"
      had nothing to read. Text a peer chose (its error, its relay account, its treasury address) is cut short
      in the log, so a hostile peer cannot write huge log lines, and control characters are removed (a NUL in
      a peer's answer made the reason unstorable, so it was logged again on every probe).
    • A registration marker from an earlier install on the same box no longer makes the queued listing
      think it is already done.
    • When the finisher cannot list the node, its log says why: what register said (its ✗ lines and its
      last lines) is in journalctl -t morphit-first-online, instead of a generic list of possible causes,
      with terminal control characters and bytes that are not text removed, each line cut to 300 bytes. A
      ✗ line is kept even when the output holds such bytes.

    Fast-sync

    • The guided install offers fast-sync (default yes), right after installing: the newest signed
      federation snapshot instead of a full replay from genesis. Only "Configure only" offered it, so a fresh
      VPS started millions of blocks behind. The indexer is started again (unless the restore left the
      database unusable, see below), and the step waits until it is connected before the review.
    • Fast-sync fetches the snapshot through the node's own IPFS (on a node in the public IPFS network —
      never a Tor/I2P-only one), before the public gateways, which rate-limit (429). The node's own gateway
      serves only what it already holds.
    • The "127.0.0.1" source was the relay. The default source list included http://127.0.0.1:8080 —
      kubo's stock gateway port, which on a Morphit node is the relay — so it always answered 404. The node's
      own IPFS gateway is now read from its IPFS configuration (it is on 8082).
    • The hidden Blurt RPC nodes are asked for the snapshot too, at /ipfs/<cid> on their own
      .onion/.b32.i2p and port: they are the one set of hidden addresses a brand-new Tor/I2P-only node always
      knows. One that does not mirror answers 404. What they serve, and a ready script for their operators:
      docs/HIDDEN-RPC-SNAPSHOT-MIRROR.md. The snapshot mirror on every Morphit node asks them too.
    • Re-installing over a box that already ran a node, the wizard's fast-sync defaults to no and says
      that a restore replaces that node's own data (pending payouts, push subscriptions, moderation). Offline,
      it is not offered (it could only fail).
    • The chain check after a restore shows a turning spinner with the time taken and how far it is
      ("block 12 of 41"), and the RPC library's "Didn't failover…" notes are gone from fast-sync's output.
      It used to sit for minutes with nothing moving.
    • No more double "Start the indexer" after fast-sync when fast-sync (or the wizard) starts it itself.
    • The first-online job never starts a stopped indexer or relay (try-restart): one may be stopped for
      a restore.
    • A failed fast-sync starts the indexer again. It stopped the indexer, and only a success started it
      again, so a failed download left the node's indexer stopped. Ctrl+C during the restore now stops only
      the restore: morphit-ops (and its launcher) stay up long enough to start the indexer again. Before, on
      an installed node the whole command died on Ctrl+C and left the indexer stopped. A closed terminal
      (a dropped SSH session) no longer ends it before it has started the indexer again either. Ctrl+C, a
      closed terminal or a stop before the restore begins stops at once and leaves nothing in /tmp.
    • A hidden snapshot source can no longer redirect the download (it could have pointed a Tor/I2P-only
      node at a clearnet address through its proxy). A source that stalls is dropped: after two minutes for
      a clearnet source or a hidden RPC node, ten for a hidden snapshot mirror (an I2P tunnel can take minutes
      before the first byte), five for the node's own IPFS (with a turning spinner while it waits).
    • A snapshot is checked before it is unpacked. A download larger than the signed record says is cut
      off at that size, one of the wrong size or hash is refused and the next source is tried, and the archive
      is read by fast-sync itself: only the two files a snapshot holds, never a link or a path outside its
      folder, and never much more than the signed size (a full disk is a refused source like any other).
    • A restore that replaced the database but could not finish is reported as such (exit code 4: the
      check after the restore failed, the tidy-up failed, or Ctrl+C after the database was replaced). The
      indexer is left stopped, with what to do: run fast-sync again, or wait for the next snapshot (published
      daily). The guided install does not then list the node, and does not call it "running". A Ctrl+C during
      the check on a busy server used to end the restore so abruptly that fast-sync read it as "nothing
      changed" and started the indexer on the unchecked database; fast-sync now knows the restore had begun
      even when it is killed outright.
    • While the wizard restores the snapshot, the first-online job is paused, so it cannot restart Tor or
      i2pd under a download going over them.
    • The wizard waits up to a minute for the chain connection before deciding whether to offer fast-sync,
      instead of one early look.

    Backups and health

    • Encrypted backups count as backups. With an age key, dumps are ….sql.gz.age; morphit-ops health
      looked only for ….sql.gz and showed "Backups ✗ missing — no dump has ever been written" on every node
      with encrypted backups. The install's own wait for the first backup had the same fault, so it retried
      for five minutes on every encrypted install. Half-written dumps (.partial) still never count.
    • printf: I/O error is gone from every backup's log. The "is the schema migrated yet?" check stopped
      reading early, and under systemd that made printf fail on the rest of a large schema. Harmless, but it
      read like a failure.
    • morphit-ops health has an Addresses section (a Tor-only node's onion origin is not shown as
      "Clearnet"; a daemon still starting shows ?, not ✗; run without sudo, it says the settings need root
      instead of calling every address "none yet"): your clearnet origin as set, and your .onion and
      .b32.i2p, each with ✓ when the service behind it runs (✗ when it does not, ○ never created). The Tor
      check follows the Tor service that really runs (tor@default on Debian and Ubuntu), in health and in
      the install review. The release-sharing line is
      in plain words ("This node keeps a copy of the Morphit release for other nodes to download…"), and the
      canary line says how to set one up, here or on your own computer.
    • The install review shows the .b32.i2p too, and marks the .onion / .b32.i2p ✓ only when they are
      also in your settings (what the node advertises), not just created.
    • morphit-ops health shows your addresses. It never loaded the instance's settings, so it
      printed no "Reachable at" rows and "No hidden address yet" on nodes whose .onion and .b32.i2p were
      configured and serving.

    Warrant canary

    • The guided install asks where a VPS's warrant canary is signed: here on the server, automatically
      every week (the same turnkey setup upgrade offers), on your own computer (the key never sits on the
      server), or later. It used to be offered on home installs only, so a VPS ended with no canary and no
      /pgp_keys.asc. Its first publish waits for the weekly timer only when the chain cannot be reached at
      install time (it used to wait whenever the offline bundle was used, which the guide now uses on every
      x86 VPS).
    • morphit-ops health's canary hint gives the right command for where the canary is signed: the
      server's own refresh script when it is signed here.

    Branding

    • The setup wizard offers branding (optional) just before its final review: the same guided setup as
      sudo morphit-ops branding setup.
    • Branding setup first says exactly what it needs: three SVG files — logo.svg (header, 32 px tall;
      homepage, 44–96 px tall; any width), logo-footer.svg (footer, 40 px tall; optional) and icon.svg
      (a square symbol that reads at 16 × 16 px) — each at most 2 MB with a viewBox, nothing loaded from
      elsewhere, how to copy them to the server, and that they are saved under those names.
    • Colours from one colour, or from your logo. Answer the colour question with just your main colour
      (#d4a017) or logo, and the rest is worked out: a gradient from a lighter tint to a deeper shade (or
      the logo's two main colours), a near-black page background with a trace of that colour, bright or deep
      buttons to suit, everything checked for readability. Setup shows the result and asks before using it.
      Two or three colours and the presets work as before, except that two or three colours now replace the
      whole theme (an earlier preset's middle stop or button style is no longer left over).
    • Colours are read from any logo, quickly. A large logo kept the colour suggestion busy for minutes
      (9 for a 2 MB file); it now takes a moment whatever the size. Colours written as names (navy), hsl()
      or 4/8-digit hex are now read too, where such a logo was called "no colour".
    • edit's "publish the change?" hints say sudo, Ctrl+C there is reported as not published, and
      having said yes, register's own question defaults to yes (Enter, Enter used to mean "not published").

    The stats page's RPC card

    • The card stops when nobody is there. It re-read the figures every 10 s for as long as the tab was
      visible, even a tab left open on a second screen for days. It now pauses after 3 minutes without mouse,
      touch, keyboard or scroll input (and while the tab is hidden, as before), says "Paused while you're
      away", and reads again the moment there is input.
    • The indexer measures the nodes only while someone reads the card. It measured every published Blurt
      node every 20 s from boot, forever, page open or not: about one request a second from every instance,
      Tor and I2P circuits included. It now measures nothing until the card is read and stops a minute after
      the last read; the first read after a quiet spell measures at once, and when the figures it gets are
      more than a minute old (first load, back from a pause, back to the tab) the card reads again 3 s later
      so old figures don't stay on screen.
    • No number of open pages, clicks or scripted requests makes an instance contact a node more often.
      The refresh (?probe=1, open to anyone) re-measured every node every 5 s for as long as someone kept
      asking — a single script could make an instance contact all ~20 nodes four times as often as the
      schedule. A refresh now re-measures only nodes not measured in the last 10 s, and waits for a
      measurement already running instead of starting another: each node is measured at most once per 10 s
      (a measurement that misses retries once), whatever the load. A refresh answers within 8 s even when a
      hidden node never does: that node keeps its previous figure, with how long ago it was measured (or shows
      as not measured yet).
    • The card has its own request limit, so a flood of it can no longer use up an address's limit for
      the other public reads (sending a transaction, say). The directory reads behind it are shared between
      requests arriving together.
    • morphit-ops health measures fresh (it asks for a refresh), and a node whose figure is over two
      minutes old shows as "being measured" with no latency, instead of an old number.
    Downloads