Skip to content

v2.0.0

Latest

Choose a tag to compare

@github-actions github-actions released this 24 Jul 00:50

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 NostrIdentity model plus a
    resolveAccountByPubkey seam. Any linked pubkey can sign in while the
    session still presents the account's primary identity. Ships an
    /admin/account page 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_at backoff (2 min → 1 h), with a webhooksPending
    gauge on /status.
  • /status never 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 querying RemoteWallet, 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).
    parseCardPaymentInvoice read decoded.expiry from light-bolt11-decoder
    as an absolute Unix timestamp when it is the BOLT-11 x tag's duration in
    seconds
    . Every invoice therefore resolved to expiry * 1000 ms —
    1970-01-01T01:00:00Z — and was rejected as expired. The timestamp + 3600
    fallback did not help: the decoder always populates expiry, 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 empty CardPaymentAttempt history,
    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-16 payRequest (#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) — fflate zip 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 deploy only on production with lock retry
    (#49), and keep buildCommand under Vercel's 256-character limit (#50).
  • Umbrel listing noted as under official review (#5801) (#105).

Release engineering & CI (#48, #53, #54, #74, #90, #99)

  • build added 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 deploy honours E2E_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 allowBuilds approval list moved into pnpm-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.log gitignored (#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 destructive prf_passkey_clean_break described above. Take a
    backup first if you have been tracking main.
  • 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_SECRET remains optional and is no longer on the passkey login
    path. Keep it set if you have ManagedNostrKey rows 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