Month 5 starts here
Remote Wallets,
cards & polish.
Remote Wallets land first as the named Lightning-source abstraction every card downstream draws from. Then the Card pipeline — QR JWT login, card-installer, the simple-card-manager rewrite, and the end-user "Activate Card" flow. Cards are SIMPLE or MASTER; QRs are ONE_TIME or FOREVER — orthogonal. Topped with the Platform Polish shelf: NIP-05, customizable domain landing, admin home redesign, infra-aware onboarding.
An 8-month OpenSats journey,
five months in.
Four months shipped — backend, CI, the admin dashboard, the user wallet + monorepo. M5 lays the abstraction cards depend on, ships the card-side apps end-to-end, and sets up the M6 settlement layer.
Wallets first.
Cards bind to them.
Remote Wallets
Lightning-source provider with a type discriminator. NWC active in M5; LND / CLN / BTCPay reserved.
- RemoteWallet model + driver interface
- Wallet UI · create / rename / default / disable
- Connection Map UI · LA + Card ↔ Wallet
- NWCConnection rows migrate forward
Card System Apps & Flows
From blank chip to tap-to-pay — sequenced. QR JWT login is the shared contract both card-side apps build against.
- B.0 · QR-based JWT login in apps/web
- B.1 · card-installer Android
- B.2 · simple-card-manager rewrite + activation QR + rescue
- B.3 · Activate Card · ONE_TIME / FOREVER QR
Platform Polish & Landing
NIP-05, infra-aware onboarding, the customizable domain landing, the admin home redesign, PWA, bug fixes.
- Full NIP-05 + relay picker + user data cache
- Customizable domain landing (white-label)
- Admin home redesign · animated @ hero
- Onboarding v2 · infra-aware .well-known rewrites
One model.
Many Lightning sources.
Every Lightning address and every card binds to a RemoteWallet by id. The type field is the door for future drivers — NWC is the only one wired in M5; LND, Core Lightning and BTCPayServer slots are reserved and call sites don't change when they land.
lookupInvoice() · subscribeToPayments()
Adding a new type is a new driver module — no call-site changes in apps/web or apps/listener.
Existing NWCConnection rows migrate forward into RemoteWallet rows with type = NWC. LightningAddress.remoteWalletId and Card.remoteWalletId replace direct references.
Lightning addresses + Cards
to Remote Wallets, visualised.
The primary surface for editing bindings. Desktop: two-column canvas with sources on the left, wallets on the right, drag-to-rebind. Mobile: three tabs (Addresses · Cards · Wallets), chip-pickers do the rebind.
Tap chip → bottom-sheet picker rebinds. Wallets tab is read-only summary; rebinds happen from Addresses / Cards.
From blank chip to tap-to-pay
in five sequenced steps.
B.0 ships first — the shared QR JWT login both card-side apps consume. Then the field tool, the admin workbench, the end-user claim, and the full Playwright + simulated-NFC happy path.
QR JWT Login
Shared login surface in apps/web. Backend displays QR; both card apps authenticate against it.
P0 · prerequisitecard-installer
Android app provisions blank NTAG424s in the field; pair-call carries remoteWalletId.
P0simple-card-manager
Rewrite. Drives the E2E. Re-issues activation QR for any card (ONE_TIME / FOREVER) + rescue path.
P1Activate Card
End-user wallet scans QR (as new or existing user); ONE_TIME burns + transfers card; FOREVER shares full account.
P0Connect E2E
issue → install → activate-QR → claim → pair → pay. Playwright + simulated NFC.
P0Admin mints the token.
QR carries the JWT.
The admin picks a user, ticks permissions, sets an expiration, and the backend signs a JWT. It renders as a QR on the admin screen; the device scans it and runs with it. Stateless — no session record, no polling, no revocation surface.
Pick user
Admin opens Settings → Device Tokens; chooses target user.
Tick permissions
RBAC subset checklist — JWT scope ⊆ admin's own RBAC.
Pick expiration
Preset list · 1h · 8h · 24h · 7d · custom
Generate JWT
POST /api/auth/qr-jwt/generate → signs { sub, scopes, exp } → returns { jwt }
QR + scan
Admin UI renders the JWT as a QR. Card app scans → JWT lands in the device.
Card kind · QR kind.
Separate axes.
A card is declared as SIMPLE or MASTER at creation. A QR issued for a card is either ONE_TIME or FOREVER. Max one active QR of each kind per card — a new QR of the same kind invalidates the previous.
The polish shelf
that compounds.
Runs in parallel with the card pipeline. NIP-05 + relay picker + user data cache lay the Nostr-identity surface the listener (M6) will lean on; the customizable landing and admin home redesign reshape first impressions.
NIP-05 — full
.well-known/nostr.json + relays + avatar.
Relay picker
Per-user preference. WSS only.
User Data Cache
kind:0 + kind:10002 with TTL.
Onboarding v2
Infra detect + .well-known rewrites.
Dashboard cache
Dedupe getSettings across admin pages.
PWA Wallet
Manifest + service worker + install.
Domain Landing
White-label entry · cover + isotype + you@domain live.
Admin home redesign
Animated @ hero · LA-first onboarding.
Screenshots + social
In-app strip + social_* footer icons.
Bug fixes
Card-design dropdown · settings dedupe.
Monthly roadmap
Per-month pages on lawallet-landing.
Landing CRM swap
Tally → operator's CRM.
First impressions,
redrawn.
Customizable Domain Landing
White-label entry screen. Cover banner, isotype, color + radius tokens, live you@domain preview, optional benefits step, login, continue.
Admin Home Redesign
Replaces the four-stat-card layout. Animated username @ domain hero, Lightning-address-first onboarding sequence, Remote Wallet picker inline when none is set.
Copy-pasteable rewrite recipes for /.well-known/*
| Detected stack | Mechanism | Snippet hint |
|---|---|---|
| Cloudflare | Transform Rule / Worker | /.well-known/* → origin |
| CF Tunnel | cloudflared ingress | path: /.well-known/.* |
| Vercel | vercel.json rewrites | /.well-known/:path* |
| Netlify | _redirects | /.well-known/* ... 200 |
| Nginx | location + proxy_pass | location ~ ^/\.well-known/ |
| Caddy | handle_path + reverse_proxy | handle_path /.well-known/* |
| Apache | mod_rewrite [P,L] | RewriteRule ^/\.well-known/(.*)$ |
| Direct origin | DNS A/AAAA points at host | — no rewrite needed — |
HEAD-probes each path · /lnurlp · /nostr.json · /verify · before the wizard completes. Red/amber/green; cannot finish until all three are green.
Let's ship M5.
Remote Wallets first, then the card pipeline, then the polish shelf. The RemoteWallet migration is the lock-in — every card downstream binds to one from day one.