Auto-provision merchant from account keypair on first access
Some checks failed
ci.yml / Auto-provision merchant from account keypair on first access (pull_request) Failing after 0s

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>
This commit is contained in:
Padreug 2026-04-27 12:14:40 +02:00
commit 8606dce908
3 changed files with 62 additions and 136 deletions

View file

@ -13,19 +13,6 @@ window.app = Vue.createApp({
orderPubkey: null, orderPubkey: null,
showKeys: false, showKeys: false,
stallCount: 0, stallCount: 0,
importKeyDialog: {
show: false,
data: {
privateKey: null
}
},
generateKeyDialog: {
show: false,
privateKey: null,
nsec: null,
npub: null,
showNsec: false
},
wsConnection: null, wsConnection: null,
nostrStatus: { nostrStatus: {
connected: false, connected: false,
@ -49,23 +36,6 @@ window.app = Vue.createApp({
} }
}, },
methods: { methods: {
generateKeys: async function () {
// No longer need to generate keys here - the backend will use user's existing keypairs
await this.createMerchant()
},
importKeys: async function () {
this.importKeyDialog.show = false
// Import keys functionality removed since we use user's native keypairs
// Show a message that this is no longer needed
this.$q.notify({
type: 'info',
message: 'Merchants now use your account Nostr keys automatically. Key import is no longer needed.',
timeout: 3000
})
},
showImportKeysDialog: async function () {
this.importKeyDialog.show = true
},
toggleShowKeys: function () { toggleShowKeys: function () {
this.showKeys = !this.showKeys this.showKeys = !this.showKeys
}, },
@ -379,7 +349,11 @@ window.app = Vue.createApp({
} }
}, },
created: async function () { created: async function () {
await this.getMerchant() const merchant = await this.getMerchant()
if (!merchant) {
// Auto-create merchant using the account's existing Nostr keypair
await this.createMerchant()
}
await this.checkNostrStatus() await this.checkNostrStatus()
setInterval(async () => { setInterval(async () => {
if ( if (

View file

@ -124,58 +124,9 @@
</q-card> </q-card>
</div> </div>
<q-card v-else> <q-card v-else>
<q-card-section> <q-card-section class="text-center q-pa-xl">
<span class="text-h4">Welcome to Nostr Market!</span><br /> <q-spinner color="primary" size="3em" class="q-mb-md"></q-spinner>
In Nostr Market, merchant and customer communicate via NOSTR relays, so <div class="text-h6">Setting up Nostr Market...</div>
loss of money, product information, and reputation become far less
likely if attacked.
</q-card-section>
<q-card-section>
<span class="text-h4">Terms</span><br />
<ul>
<li>
<span class="text-bold">merchant</span> - seller of products with
NOSTR key-pair
</li>
<li>
<span class="text-bold">customer</span> - buyer of products with
NOSTR key-pair
</li>
<li>
<span class="text-bold">product</span> - item for sale by the
merchant
</li>
<li>
<span class="text-bold">stall</span> - list of products controlled
by merchant (a merchant can have multiple stalls)
</li>
<li>
<span class="text-bold">marketplace</span> - clientside software for
searching stalls and purchasing products
</li>
</ul>
</q-card-section>
<q-card-section>
<div class="row">
<div class="col-12">
<q-btn
@click="showImportKeysDialog"
label="Import Key"
color="primary"
class="float-left"
>
<q-tooltip> Use an existing private key (hex or npub) </q-tooltip>
</q-btn>
<q-btn
label="Generate New Key"
color="green"
@click="generateKeys"
class="float-right"
>
<q-tooltip> A new key pair will be generated for you </q-tooltip>
</q-btn>
</div>
</div>
</q-card-section> </q-card-section>
</q-card> </q-card>
</div> </div>

View file

@ -93,31 +93,21 @@ from .services import (
######################################## MERCHANT ###################################### ######################################## MERCHANT ######################################
@nostrmarket_ext.post("/api/v1/merchant") async def _auto_create_merchant(
async def api_create_merchant( wallet: WalletTypeInfo,
data: CreateMerchantRequest, config: MerchantConfig | None = None,
wallet: WalletTypeInfo = Depends(require_admin_key),
) -> Merchant: ) -> Merchant:
"""
try: Provision a merchant record from the user's account keypair.
# Check if merchant already exists for this user Called automatically on first GET or explicitly via POST.
merchant = await get_merchant_for_user(wallet.wallet.user) """
assert merchant is None, "A merchant already exists for this user"
# Get user's account to access their Nostr keypairs
account = await get_account(wallet.wallet.user) account = await get_account(wallet.wallet.user)
if not account: assert account, "User account not found"
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND,
detail="User account not found",
)
# Check if user has Nostr keypairs, generate them if not # In our fork, accounts always have keypairs.
# Generate as fallback only if somehow missing.
if not account.pubkey or not account.prvkey: if not account.pubkey or not account.prvkey:
# Generate new keypair for user
private_key, public_key = generate_keypair() private_key, public_key = generate_keypair()
# Update user account with new keypairs
account.pubkey = public_key account.pubkey = public_key
account.prvkey = private_key account.prvkey = private_key
await update_account(account) await update_account(account)
@ -125,15 +115,13 @@ async def api_create_merchant(
public_key = account.pubkey public_key = account.pubkey
private_key = account.prvkey private_key = account.prvkey
# Check if another merchant is already using this public key
existing_merchant = await get_merchant_by_pubkey(public_key) existing_merchant = await get_merchant_by_pubkey(public_key)
assert existing_merchant is None, "A merchant already uses this public key" assert existing_merchant is None, "A merchant already uses this public key"
# Create PartialMerchant with user's keypairs
partial_merchant = PartialMerchant( partial_merchant = PartialMerchant(
private_key=private_key, private_key=private_key,
public_key=public_key, public_key=public_key,
config=data.config config=config or MerchantConfig(),
) )
merchant = await create_merchant(wallet.wallet.user, partial_merchant) merchant = await create_merchant(wallet.wallet.user, partial_merchant)
@ -150,10 +138,22 @@ async def api_create_merchant(
) )
await resubscribe_to_all_merchants() await resubscribe_to_all_merchants()
await nostr_client.merchant_temp_subscription(public_key) await nostr_client.merchant_temp_subscription(public_key)
return merchant return merchant
@nostrmarket_ext.post("/api/v1/merchant")
async def api_create_merchant(
data: CreateMerchantRequest,
wallet: WalletTypeInfo = Depends(require_admin_key),
) -> Merchant:
try:
merchant = await get_merchant_for_user(wallet.wallet.user)
assert merchant is None, "A merchant already exists for this user"
return await _auto_create_merchant(wallet, data.config)
except AssertionError as ex: except AssertionError as ex:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST, status_code=HTTPStatus.BAD_REQUEST,
@ -170,12 +170,13 @@ async def api_create_merchant(
@nostrmarket_ext.get("/api/v1/merchant") @nostrmarket_ext.get("/api/v1/merchant")
async def api_get_merchant( async def api_get_merchant(
wallet: WalletTypeInfo = Depends(require_invoice_key), wallet: WalletTypeInfo = Depends(require_invoice_key),
) -> Optional[Merchant]: ) -> Merchant:
try: try:
merchant = await get_merchant_for_user(wallet.wallet.user) merchant = await get_merchant_for_user(wallet.wallet.user)
if not merchant: if not merchant:
return None # Auto-provision merchant from the user's account keypair
merchant = await _auto_create_merchant(wallet)
merchant = await touch_merchant(wallet.wallet.user, merchant.id) merchant = await touch_merchant(wallet.wallet.user, merchant.id)
assert merchant assert merchant