v2.0.0
Release date
2026-07-23
Summary
The first major release since the 1.0 line. Passkey login lands as a
first-class authentication method — and, unlike the interim design, derives
its Nostr key client-side from the WebAuthn PRF extension, so the server
never holds a user's secret. Accounts gained a real identity model: a single
account can now carry multiple Nostr pubkeys, with linking and merge flows.
Alongside that, this release consolidates everything shipped across v1.0.3 →
v1.4.0: the NWC Listener service (a standalone relay bridge with its own
admin dashboard, webhook delivery guarantees, and an optional fast path for
card payments), the card system (design editor, 3D preview, emulator,
spend history), backup & restore, an installable PWA wallet, and
first-class packaging for Umbrel, Start9, Netlify, Coolify and Vercel.
It also carries a fix for a defect that made card tap payments fail every
time — see Cards.
Breaking changes
Passkey credentials from the server-custody scheme are deleted (#106)
The interim passkey design (#101) generated a random Nostr key per account
and held it server-side, encrypted under KEY_VAULT_SECRET. #106 replaced this
with deterministic client-side derivation: the WebAuthn PRF extension is
evaluated against a frozen app salt and run through HKDF-SHA256 to derive the
nsec in the browser. The same passkey therefore yields the same nsec on every
device it syncs to, and a passkey session becomes an ordinary nsec session
(NIP-98 → /api/jwt, silent reload restore, normal refresh).
Because the two schemes derive keys incompatibly, credentials registered under
the old one cannot authenticate in the new model. Migration
20260723151642_prf_passkey_clean_break removes them:
DELETE FROM "PasskeyCredential";Who is actually affected: passkey support (#101) was merged after the
v1.4.0 tag and never appeared in a tagged release. If you are upgrading from
v1.4.0 or earlier, this table is empty and the migration is a no-op — for
you, passkey login is simply a new feature. Only deployments tracking main
between #101 and #106 will lose credentials; those accounts remain reachable
only via an already-exported nsec.
Requirements: PRF support is required for passkey login — iOS 18+/macOS
15+, recent Chrome/Android. All other login methods (NIP-07 extension, nsec,
NsecBunker) are unaffected.
Removed passkey API surface (#106)
These routes and helpers no longer exist:
auth/passkey/authentication/{options,verify}, .../signer-key,
.../session/refresh, .../nsec/export/{options,},
.../link/{options,verify}, plus mintPasskeySessionJwt,
verifyStoredCredentialAssertion, and the amr / cred / custody /
auth_time JWT claims. Registration is the only remaining server ceremony and
serves both signup and add-a-passkey. Account link/merge proofs are now
Nostr-only and produced client-side.
ManagedNostrKey and the key vault remain in the tree, dormant, for future bot
accounts. KEY_VAULT_SECRET stays optional.
Highlights
Authentication & accounts (#101, #102, #106, #79)
- Passkey login for admin and wallet, with the nsec derived client-side via
WebAuthn PRF (#101, #106). Includes a "Create a passkey account" path in the
admin login modal for first-time visitors. - Multi-pubkey accounts (#102) — a new
NostrIdentitymodel plus a
resolveAccountByPubkeyseam. Any linked pubkey can sign in while the
session still presents the account's primary identity. Ships an
/admin/accountpage with link and merge flows; merging is blocked until an
absorbed custodied key has been exported. - Admins can grant the ADMIN role to other users (#79).
NWC Listener service (#71–#84, #86, #97)
A standalone relay-bridge service (apps/listener) introduced in v1.1.0 and
hardened steadily since.
- Relay bridge with a live NWC pool, webhook fan-out, and an admin
dashboard (#71); settings-driven configuration under a new NWC Services
tab (#72). - Missed-event recovery — hybrid catch-up with dashboard tabs (#73).
- Webhooks are never dropped (#84). The old hard 25-attempt sweep cap could
permanently discard payment webhooks when the web origin was briefly
unreachable; delivery now retries indefinitely, paced by a per-event
webhook_next_attempt_atbackoff (2 min → 1 h), with awebhooksPending
gauge on/status. /statusnever 500s, keep-alive guards, and automatic archival of dead
LNCurl connections (#80).- Dashboard UX: live-count badge, activity sort, pagination (#81); realtime
status without feed flicker (#82); clickable rows opening a detail modal
(#86). - Correct relay status for bare-domain relays (#77); waits for web migrations
before queryingRemoteWallet, fixing a fresh-install crash loop (#76). - Published as its own image with hub compose wiring (#75), and documented with
a full HTTP API + webhook contract reference (#78). - Optional listener-accelerated card payments (#97).
Cards (#56, #59, #97, #107)
- Card design page, emulator payment flow, and a 3D card preview (#56).
- Card detail tabs with spend history, an own-cards default toggle, and
search + pagination for designs (#59). - Fixed: card tap payments failed 100% of the time (#107).
parseCardPaymentInvoicereaddecoded.expiryfromlight-bolt11-decoder
as an absolute Unix timestamp when it is the BOLT-11xtag's duration in
seconds. Every invoice therefore resolved toexpiry * 1000ms —
1970-01-01T01:00:00Z — and was rejected as expired. Thetimestamp + 3600
fallback did not help: the decoder always populatesexpiry, so that branch
was unreachable and no invoice could pass. Validation runs before the SUN
counter is consumed, so no tap was ever burned and no data was corrupted —
affected cards show failing taps with an emptyCardPaymentAttempthistory,
which is the diagnostic signature.
Wallet (#52, #55, #57, #62, #67, #93–#96)
- Installable PWA with an offline service worker (#62).
- Wallet-view redesign, dev impersonation, and self-profile editing (#55);
wallet-holder mobile tabs for the USER role (#52). - Expanded settings and payment flows (#96); the primary wallet now follows
the primary address (#95). - More robust invoice payment during address registration (#57), and a clearer
error when invoice minting cannot reach the relay (#67). - New sat symbol component (#94); send-summary "Done" returns to
/wallet
(#93).
Nostr & LUD-16 (#58, #60, #63, #66, #68)
- Serve relays and CORS headers on
.well-known/nostr.json(NIP-05) (#63). - Per-user Nostr relay picker — badge and editor on the profile (#68).
- Server-side Nostr profile cache (#60); the cached avatar is embedded as a
base64 image in the LUD-16payRequest(#66). - LUD-16 callback and verify URLs are built from the configured API
endpoint rather than the domain (#58).
Admin (#51, #61, #83, #85)
- Backup & restore with an export/import wizard (#85) —
fflatezip export
across 14 models (NDJSON + manifest, optional AES-256-GCM password
encryption), read-only analysis with conflict classification, and both merge
(skip/overwrite/rename with relation remapping) and replace import modes,
behind ADMIN-only routes. - Admins can view any lightning address read-only (#83).
- Settings tabs reordered — Infrastructure first (#51); settings request
dedupe (#61).
Deployment & packaging (#49, #50, #69, #104, #103, #105)
- Start9/StartOS package target (#69) and clarified deployment docs (#103).
- Netlify one-click deploy button (#104); Coolify deployment guide.
- Vercel fixes: run
prisma migrate deployonly on production with lock retry
(#49), and keepbuildCommandunder Vercel's 256-character limit (#50). - Umbrel listing noted as under official review (#5801) (#105).
Release engineering & CI (#48, #53, #54, #74, #90, #99)
buildadded to the release gates so unbuildable code cannot be tagged
(#48) — the client/server import split that broke v1.0.1 would now be caught.- Release images are built from the bump commit rather than the dispatch
SHA (#53), fixing images that baked in the previous version. - Hardened the end-to-end release → docker → Umbrel flow (#54): native
per-arch runners with a manifest merge, cutting publish time from ~21 min
(QEMU) to ~6 min. prisma migrate deployhonoursE2E_DATABASE_URL, so e2e runs stop
migrating the dev database (#74).- The changelog generator detects squash-merged PRs (#99), with a hard
failure when commits reference PRs but none parse — the regression that
silently emptied changelog bodies. - pnpm
allowBuildsapproval list moved intopnpm-workspace.yaml(#90).
Docs & housekeeping (#88, #89, #91, #92, #98)
- Roadmap synced — Month 5 complete and the card flow moved to Month 6 (#88),
then M5–M6 marked complete with M7 re-baselined and stale docs tidied (#98). - WooCommerce plugin, ecosystem apps, and Vision cleanup (#89); "Getting
started" kept as the first sidebar item (#91);dev.loggitignored (#92). - Compose runtime port corrected in the deployment docs.
Upgrade notes
- Database migrations run on deploy. Four migrations are new since v1.4.0,
including the destructiveprf_passkey_clean_breakdescribed above. Take a
backup first if you have been trackingmain. - Upgrading from v1.4.0 or earlier: no passkey credentials exist yet, so
nothing is deleted. Passkey login is simply available for the first time. KEY_VAULT_SECRETremains optional and is no longer on the passkey login
path. Keep it set if you haveManagedNostrKeyrows you intend to reuse.- The listener is optional. If you do not run it, card payments fall back
to the direct transport automatically.
Per-version changelogs
Full detail for each release folded into this one:
v1.0.3 · v1.0.4 · v1.0.5 ·
v1.0.6 · v1.0.7 · v1.0.8 ·
v1.0.9 · v1.0.10 · v1.1.0 ·
v1.1.1 · v1.2.0 · v1.2.1 ·
v1.3.0 · v1.4.0