v2.3.0
v2.3.0
Release date
2026-08-06
Summary
LaWallet NWC 2.3.0 turns a RemoteWallet into a personal receive proxy: an
eligible NWC wallet can forward every payment it receives to one or more
Lightning Addresses, split by weight, with durable accounting for fees, routing
reserve and partial delivery. Around that it adds per-wallet notifications
over webhooks and Nostr, NIP-57 zap receipts and LUD-21 verification for
wallet-backed invoices, and a protocol capability view that tells an address
owner exactly which of LUD-16, NIP-05, LUD-21, NIP-57 and LUD-12 their address
actually speaks.
Forwarding to an address hosted on the same instance is supported, guarded by
config-time cycle detection and a runtime hop counter rather than by refusing
the case outright. The DEFAULT_NWC address mode is removed — an address now
always names its wallet explicitly instead of inheriting whichever wallet the
primary address happened to point at.
The NWC listener also gains a documented one-command Fly.io deployment.
Highlights
RemoteWallet receive forwarding (#129, #135)
- One
FORWARDreceive action per activeSEND_RECEIVENWC wallet, with one
or more Lightning Address destinations weighted in basis points summing to
100%. - Immutable configuration revisions, receipts, per-destination legs and payment
attempts. Receipt capture is deduplicated by(walletId, paymentHash)and
keeps the listener event key. - Integer millisatoshi maths throughout, deterministic largest-remainder
allocation, entry fees, and a 1% + 1 sat routing reserve. Reserve, actual
routing cost, unused reserve, fee overage and tolerated shortfall are all
auditable per leg. - Amounts too small to forward economically accumulate as pending balance
instead of being lost, shown as recoverableBLOCKEDreceipts and retried
once the grouped amount can be sent. - Force Forward safely advances open receipts and wakes the worker, but
never replaces aPENDING/UNKNOWNpayment. - Work is claimed through per-wallet leases using
FOR UPDATE SKIP LOCKED.
Every outgoing attempt is persisted before payment with a deterministic
listener request id; ambiguous payments stayUNKNOWNuntil a lookup or a
payment_sentnotification proves the result.
Forwarding within the same instance (#135)
- A destination may be a Lightning Address on this instance. It is resolved
internally against the instance's own API origin rather than fetched over the
public internet, so it works even where the public host is not reachable from
the server. - Config-time cycle detection walks the forwarding graph — deferred proxy
redirects, the address-to-wallet binding, and wallet forward destinations —
and refuses any destination that leads back to the source. It runs on every
route that writes a destination, so no single edit can complete a ring. - A runtime hop counter stamps each local hop with
parent + 1, keyed by
payment hash so it cannot be spoofed, and stops at three hops before minting
anything. This covers what the graph walk cannot see: concurrent edits racing
a cycle into existence, or a restored backup.
Wallet notifications (#135)
- Multiple per-wallet channels for
RECEIVEDandFORWARDEDevents. - Webhook delivery accepts only public HTTPS endpoints, pins DNS after a
private-network check, carries an idempotency key and event key, and stores
every response. A possibly-delivered webhook becomesUNKNOWNrather than
being duplicated. - Nostr delivery supports a configurable kind, recipient
ptag, relays,
templated content and optional NIP-44 encryption. Retries republish the same
signed event id. - Owner-scoped notification, delivery, pause/resume and retry APIs with live
SSE refreshes.
Lightning and Nostr protocol support (#135)
- NIP-57 zaps: zap requests are accepted, persisted, and a signed kind:9735
receipt is published once the payment settles — via the listener webhook, the
LUD-21 verify endpoint, or a reconciler catch-up, with lease-based guards so a
retry cannot publish twice. - Zap request validation follows NIP-57 rather than being stricter than it: the
lnurltag is optional and accepted in any spelling clients use, and thep
tag identifies the profile being zapped rather than being required to equal
the address owner. - LUD-21 verification is exposed for RemoteWallet-backed invoices, and
NIP-57 is advertised only while the listener can observe settlement and the
receipt signer exists. - LUD-12 comments are surfaced on receive data — the payer's own words are
shown with the payment rather than only being folded into the invoice memo. - The instance receipt signer is created and published as root NIP-05 identity
_..well-knownNIP-05/LNURL-pay, LUD-16 callbacks and the LUD-21 verifier
are publicly readable with CORS and preflight support.
Protocol capability view (#135)
- The address list gains a Protocols column and the address page reports all
five protocols, each an interactive badge: hover for a summary, click for a
description, use cases and the spec link. - Capabilities follow the payment. An
ALIASaddress reports what its alias
target supports, a deferred proxy reports the proxy's, and a bound wallet
reports this instance's. NIP-05 is always this domain's, since it publishes
the name rather than routing the payment. - Alias capabilities are probed when the redirect is saved and stored on the
address, so listing many addresses never reaches out to their targets.
Unsupported and not-yet-checked are kept visually distinct.
DEFAULT_NWC address mode removed (#135)
- An address in this mode routed through "whatever wallet the primary address
happens to point at", so its destination could change behind the owner's back
whenever the primary moved. Addresses now always name their wallet explicitly. - New addresses inherit the account's active wallet as their own binding, so
onboarding still produces an address that works immediately.
Fly.io listener deployment (#130, #131, #132, #133, #134)
pnpm deploy:flyprovisions the four required secrets and deploys
apps/listenerwith the flags the service needs, from a documented
apps/listener/fly.toml.- The listener adopts shared vault secrets from Vercel so web and listener
cannot drift apart, and the install is documented at/docs/deploy/fly.
Send-screen recipient suggestions (#138)
- The
/wallet/sendrecipient field now shows the last 10 saved recipients
while it is empty, and the first keystroke replaces them with suggestions
derived from what was typed, so recents and typed matches no longer compete
for the same space. - Native browser autofill and password-manager dropdowns are suppressed on that
field, which previously rendered on top of the custom suggestion list.
Upgrade notes
- Run the migrations. This release adds eleven, applied automatically by the
web image or with
pnpm --filter @lawallet-nwc/web exec prisma migrate deploy. - Take a database snapshot first. Unlike the additive ones,
20260806060000_remove_default_nwc_address_moderewrites address rows: every
DEFAULT_NWCaddress is bound to the wallet it resolves to today, and any with
no active wallet to bind becomesIDLE. Payments keep landing where they did,
but the change is not trivially reversible. - The listener is required for automatic receive forwarding and NIP-57
receipt publication. KeepNWC_VAULT_SECRETidentical in web and listener,
then start and health-check the listener before relying on either. - Configure or verify the instance NIP-57 receipt signer in Admin ▸ Settings ▸
NWC Services. Its public key is published at/.well-known/nostr.json?name=_. - Forwarding is disabled by default on existing wallets. Enable it
deliberately per eligible RemoteWallet and configure destinations totalling
100%. - Lightning Addresses aliased before this release report their protocols as
"not checked" until their redirect is saved again, which runs the probe.
Documentation
- Owner and operator runbook:
docs/services/REMOTE-WALLET-FORWARDING.md - Architecture reference:
apps/docs/content/docs/architecture/remote-wallet-forwarding.mdx - Fly.io install guide:
apps/docs/content/docs/deploy/fly.mdx