-
Morphit v1.21.8
StableAll checks were successfulmorphit-ci / Supply-chain audit gate (every advisory triaged, none fixable in range) (push) Successful in 42smorphit-ci / TypeScript typecheck (sweep all workspaces) (push) Successful in 1m9smorphit-ci / apps/web svelte-check (svelte-kit sync + svelte-aware tsc) (push) Successful in 53smorphit-ci / Integration tests (real Postgres 16) (push) Successful in 5m43smorphit-ci / ansible-lint (playbook quality gate) (push) Successful in 48smorphit-ci / Smoke suite (run-smokes.sh, triple-pulse) (push) Successful in 1h26m49smorphit-release / Build + publish release tarball (push) Successful in 39m35sreleased this
2026-10-15 04:09:33 +00:00 | 13 commits to main since this releaseMorphit 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 upgradeon 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_passin 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
forbrand.json. On an unbranded site the file sets nothing. The logo, icons,theme.cssand
brand.jsonare 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.csskeeps its stored copy over an error, since without it the page would show Morphit's
look anyway. Wheretheme.cssnames the site,brand.jsonis 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.jsonarrived; it now takes the site's colour fromtheme.css, before the page renders. - Also branded: the minimal page an operator can serve under heavy load (
degraded.html) now
linkstheme.cssand 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, onchampagne-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 onchampagne-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), runsudo morphit-ops branding applyon 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.
- Orders — orders posted through its site (carrying its operator tag) whose fee this site holds
- The indexer now records every fee payment with the transfers that paid it (two new tables,
fee_paymentsandfee_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/operatorsaddsfee_account, andstatsfor 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-syncdownloads 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.txtorpgp_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.shthere — 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/operatorsroute): 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/operatorsentries withfee_accountandstats,
and the shapes that must be refused. Seen failing.apps/web/src/lib/brand/brandCss.test.ts: the site name survives the trip throughtheme.cssfor
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, withoutbrand.json; a stylesheet that names no site falls back tobrand.json;
a prerendered page keeps its stamped name. Seen failing before the change. Also: the address
bar's colour comes fromtheme.csswhilebrand.jsonnever 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=1read 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 writestheme.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-smokeTT-10: the shippedtheme.csssets nothing, andapp.htmllinks it
render-blocking, before any script.- The service worker is now type-checked (
tsconfig.sw.json, innpm run check, the workspace
typecheck smoke and CI):svelte-checkskipped it and the build only warned. - Checked in Chromium, frame by frame: on a
champagne-goldbuild, 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-smokeTT-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/bright1.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_statsnever 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.gzbut 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 storedbrand.jsonis not handed out over an error; a server
that never answers gets the worker's 504 for a logo, whiletheme.csskeeps its stored copy;
colours fromtheme.cssare never painted over by an olderbrand.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.jsonon the test machine). The fake
dockerinbunkerwebWafIdentify.test.tswrites its state atomically: twodocker inspect
calls at once could read it half-written and fail the test now and then (also on v1.21.7). mediakit-freshness-smokechecks the kit's README colours againsttheme.cssitself, and no
longer calls the kit stale whentheme.csschanged 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 --recutprints 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.
- earnings: a featured-slot bid is credited by its order's own op, not an earlier malformed one
rtl-handle-and-footer-tasks-smoke: an @-name in Persian text is kept left to right, and an
e-mail address, a link or acodespan 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-goldbuild draws#fff9e8at 90% on the
#d6b26aface (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
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
- What happened. Opening some pages of a branded site for the first time — an order, a chat, a