Replace NIP-04 messaging with NIP-17 (NIP-44 + NIP-59) #2

Merged
padreug merged 6 commits from refactor/nip17-messaging into main 2026-05-03 15:00:17 +00:00

6 commits

Author SHA1 Message Date
05ebf042ac Extract provision_merchant() service for shared use
Some checks failed
ci.yml / Extract provision_merchant() service for shared use (pull_request) Failing after 0s
ci.yml / Extract provision_merchant() service for shared use (push) Failing after 0s
Both _auto_create_merchant (lazy GET fallback in views_api) and
LNbits' _create_default_merchant (eager signup hook) used to
reimplement merchant + zone + stall creation independently. Moves the
canonical implementation to services.provision_merchant() so both
call sites stay in lockstep — future changes (NIP-17 kind 10050 relay
list, additional default zones, etc.) only happen in one place.

- services.provision_merchant(user_id, wallet_id, public_key,
  private_key, display_name, config): creates merchant if absent,
  default 'Online' zone, default '<username>'s Store' stall, and
  publishes the kind 30017 stall event. Idempotent on the merchant
  pubkey: returns the existing merchant unchanged if one exists.
- views_api._auto_create_merchant: now a 10-line wrapper that loads
  the account, generates fallback keys if missing, then delegates.

The LNbits-side hook (lnbits/core/services/users.py:_create_default_merchant)
will be updated in a companion commit to also call this service.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-03 16:59:03 +02:00
e481c9179d Auto-create + publish default stall, republish stall on product publish
Two complementary fixes for the "Unknown Stall" bug, where a customer
sees a product on the relay but the parent stall is missing.

1. _auto_create_merchant() now creates a default "<username>'s Store"
   stall and publishes its kind 30017 event before returning. New users
   land with a fully-published merchant identity, so the very first
   product they create has a known parent stall on relays.

2. POST /api/v1/product (api_create_product) now republishes the parent
   stall before publishing the product. NIP-33 parameterized replaceable
   events make this idempotent, but it self-heals every existing case
   where the stall publish failed or never happened (transient relay
   issues, accounts that pre-date the auto-publish flow, manual stall
   creation that didn't reach all relays).

This complements the LNbits-side fix in core/services/users.py
(_create_default_merchant publishes the stall on signup) and the
webapp self-heal in useMarketStallSelfHeal.ts. With all three layers,
"Unknown Stall" should disappear from the customer view.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-03 16:59:03 +02:00
3cc798aab2 Remove orphaned key import/generate UI
Phase 3 (auto-provision merchant from account keypair) removed the
generateKeys / importKeys methods and the dialog data fields, but
left the dialog templates and dropdown menu items behind. They
referenced importKeyDialog.show and generateKeyDialog.show, which
were now undefined — breaking the merchant dashboard with
"Cannot read properties of undefined (reading 'show')".

Removes:
- The Import Key and Generate New Key dialogs from index.html
- The corresponding dropdown items from merchant-tab.html
- The 'import-key' and 'generate-key' emits from merchant-tab.js
- The dangling @import-key / @generate-key listeners in index.html

Merchants are auto-provisioned from the account keypair on first
GET; key rotation is handled by the migrate-keys feature instead.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-03 16:59:03 +02:00
25023df8bd Add keypair rotation detection and migration feature
When a user rotates their Nostr keypair in account settings, the
merchant still holds the old key. This adds:

- key_mismatch flag on MerchantConfig (runtime, not persisted) -
  detected on each GET /api/v1/merchant by comparing account vs
  merchant pubkey
- POST /api/v1/merchant/{id}/migrate-keys endpoint that updates
  the merchant keys, republishes all stalls/products under the new
  identity, and resubscribes
- Warning banner in the UI with a "Migrate Keys" button and
  confirmation dialog
- update_merchant_keys() crud function

Orders and DM history are preserved since they reference customer
pubkeys. Old stall/product events on relays become orphaned.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-03 16:59:03 +02:00
5c38947fc6 Auto-provision merchant from account keypair on first access
The LNbits user account IS the merchant identity. GET /api/v1/merchant
now auto-creates the merchant record using the account's existing Nostr
keypair if one doesn't exist yet, so the extension is immediately
usable without any setup screen.

- Extract _auto_create_merchant() helper used by both GET and POST
- Remove welcome/key-generation screen (replaced with loading spinner)
- Remove dead frontend code (generateKeys, importKeys dialogs)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-03 16:59:03 +02:00
725944ae9c Replace NIP-04 messaging with NIP-17 (NIP-44 + NIP-59 gift wrapping)
Modernize the entire customer-merchant communication layer from deprecated
NIP-04 (kind 4, AES-256-CBC) to NIP-17 private direct messages using
NIP-44 v2 encryption (ChaCha20 + HMAC-SHA256) and NIP-59 gift wrapping
(rumor/seal/gift-wrap protocol). No backwards compatibility retained.

New modules:
- nostr/nip44.py: NIP-44 v2 encryption verified against official spec vectors
- nostr/nip59.py: NIP-59 gift wrap with wrap/unwrap convenience functions
- tests/: 44 unit tests for NIP-44 and NIP-59

Key changes:
- Subscription filters: kind 4 → kind 1059 gift wraps
- Message handler: _handle_nip04_message → _handle_gift_wrap (unwrap + route)
- send_dm/reply_to_structured_dm: NIP-59 gift wrap to recipient + self-archive
- Merchant model: removed NIP-04 crypto methods (decrypt/encrypt/build_dm_event)
- helpers.py: removed NIP-04 functions, kept Schnorr signing + key normalization
- views_api.py: consolidated DM sending through send_dm() service function

Reliability improvements:
- Event deduplication via bounded LRU set in NostrClient
- Subscription health monitor (resubscribes after 120s of silence)
- Preserved 5-minute lenient time window from prior work

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-03 16:59:03 +02:00