From e481c9179d117991d694e497530fc2789773d91c Mon Sep 17 00:00:00 2001 From: Padreug Date: Sun, 3 May 2026 16:11:15 +0200 Subject: [PATCH] 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 "'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) --- views_api.py | 58 ++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 49 insertions(+), 9 deletions(-) diff --git a/views_api.py b/views_api.py index 3489e86..8241162 100644 --- a/views_api.py +++ b/views_api.py @@ -101,6 +101,10 @@ async def _auto_create_merchant( """ Provision a merchant record from the user's account keypair. Called automatically on first GET or explicitly via POST. + + Also creates a default "Online" shipping zone and a default stall named + after the user, then publishes the stall to relays so that any product + the user creates references a stall the customer-facing client can find. """ account = await get_account(wallet.wallet.user) assert account, "User account not found" @@ -127,16 +131,37 @@ async def _auto_create_merchant( merchant = await create_merchant(wallet.wallet.user, partial_merchant) - await create_zone( - merchant.id, - Zone( - id=f"online-{merchant.public_key}", - name="Online", - currency="sat", - cost=0, - countries=["Free (digital)"], - ), + online_zone = Zone( + id=f"online-{merchant.public_key}", + name="Online", + currency="sat", + cost=0, + countries=["Free (digital)"], ) + await create_zone(merchant.id, online_zone) + + # Create + publish a default stall so products created through the UI + # always have a published parent. Without this, a product publish lands + # on relays referencing a stall_id that no relay has seen, and the + # customer client renders "Unknown Stall". + display_name = account.username or "My" + default_stall = Stall( + wallet=wallet.wallet.id, + name=f"{display_name}'s Store", + currency="sat", + shipping_zones=[online_zone], + ) + default_stall = await create_stall(merchant.id, default_stall) + try: + stall_event = await sign_and_send_to_nostr(merchant, default_stall) + default_stall.event_id = stall_event.id + await update_stall(merchant.id, default_stall) + except Exception as ex: + # Non-fatal: merchant is usable; a product publish (or self-heal) + # will republish the stall later. + logger.warning( + f"[NOSTRMARKET] Failed to publish default stall for {merchant.id}: {ex}" + ) await resubscribe_to_all_merchants() await nostr_client.merchant_temp_subscription(public_key) @@ -767,6 +792,21 @@ async def api_create_product( assert stall, "Stall missing for product" data.config.currency = stall.currency + # Re-publish the parent stall before publishing the product. NIP-33 + # parameterized replaceable events make this idempotent on relays. + # This guarantees the customer client never sees a product whose + # parent stall isn't on the relay (e.g., when the original stall + # publish failed transiently or never ran). + try: + stall_event = await sign_and_send_to_nostr(merchant, stall) + stall.event_id = stall_event.id + await update_stall(merchant.id, stall) + except Exception as ex: + logger.warning( + f"[NOSTRMARKET] Failed to refresh stall {stall.id} " + f"before product publish: {ex}" + ) + product = await create_product(merchant.id, data=data) event = await sign_and_send_to_nostr(merchant, product)