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>
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>
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>
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>
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>
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>