From 7245123e498742ac3c0b75ccc9f453427a45e003 Mon Sep 17 00:00:00 2001 From: padreug Date: Sun, 7 Sep 2025 03:25:19 +0200 Subject: [PATCH] Improve merchant creation with automatic keypair generation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enhance the merchant creation process by automatically generating Nostr keypairs for users who don't have them, and streamline the API interface. Changes: - Add CreateMerchantRequest model to simplify merchant creation API - Auto-generate Nostr keypairs for users without existing keys - Update merchant creation endpoint to use user account keypairs - Improve error handling and validation in merchant creation flow - Clean up frontend JavaScript for merchant creation This ensures all merchants have proper Nostr keypairs for marketplace functionality without requiring manual key management from users. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- models.py | 4 ++++ static/js/index.js | 52 ++++++++++------------------------------------ views_api.py | 46 +++++++++++++++++++++++++++++++++++----- 3 files changed, 56 insertions(+), 46 deletions(-) diff --git a/models.py b/models.py index 58842d5..d56dbcf 100644 --- a/models.py +++ b/models.py @@ -49,6 +49,10 @@ class MerchantConfig(MerchantProfile): restore_in_progress: bool | None = False +class CreateMerchantRequest(BaseModel): + config: MerchantConfig = MerchantConfig() + + class PartialMerchant(BaseModel): private_key: str public_key: str diff --git a/static/js/index.js b/static/js/index.js index f14bf1d..b10220c 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -49,46 +49,19 @@ window.app = Vue.createApp({ } }, methods: { - generateKeys: function () { - const privateKey = nostr.generatePrivateKey() - const publicKey = nostr.getPublicKey(privateKey) - this.generateKeyDialog.privateKey = privateKey - this.generateKeyDialog.nsec = nostr.nip19.nsecEncode(privateKey) - this.generateKeyDialog.npub = nostr.nip19.npubEncode(publicKey) - this.generateKeyDialog.showNsec = false - this.generateKeyDialog.show = true - }, - confirmGenerateKey: async function () { - this.generateKeyDialog.show = false - await this.createMerchant(this.generateKeyDialog.privateKey) + 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 - let privateKey = this.importKeyDialog.data.privateKey - if (!privateKey) { - return - } - try { - if (privateKey.toLowerCase().startsWith('nsec')) { - privateKey = nostr.nip19.decode(privateKey).data - } - // Check if this key is already in use - const publicKey = nostr.getPublicKey(privateKey) - if (this.merchant?.public_key === publicKey) { - this.$q.notify({ - type: 'warning', - message: 'This key is already your current profile' - }) - return - } - } catch (error) { - this.$q.notify({ - type: 'negative', - message: `${error}` - }) - return - } - await this.createMerchant(privateKey) + // 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 @@ -143,12 +116,9 @@ window.app = Vue.createApp({ this.showKeys = false this.stallCount = 0 }, - createMerchant: async function (privateKey) { + createMerchant: async function () { try { - const pubkey = nostr.getPublicKey(privateKey) const payload = { - private_key: privateKey, - public_key: pubkey, config: {} } const {data} = await LNbits.api.request( diff --git a/views_api.py b/views_api.py index 1e9f5c5..8d41963 100644 --- a/views_api.py +++ b/views_api.py @@ -4,12 +4,14 @@ from http import HTTPStatus from fastapi import Depends from fastapi.exceptions import HTTPException from lnbits.core.models import WalletTypeInfo +from lnbits.core.crud import get_account, update_account from lnbits.core.services import websocket_updater from lnbits.decorators import ( require_admin_key, require_invoice_key, ) from lnbits.utils.exchange_rates import currencies +from lnbits.utils.nostr import generate_keypair from loguru import logger from . import nostr_client, nostrmarket_ext @@ -58,6 +60,7 @@ from .crud import ( ) from .helpers import normalize_public_key from .models import ( + CreateMerchantRequest, Customer, DirectMessage, DirectMessageType, @@ -90,15 +93,48 @@ from .services import ( @nostrmarket_ext.post("/api/v1/merchant") async def api_create_merchant( - data: PartialMerchant, + data: CreateMerchantRequest, wallet: WalletTypeInfo = Depends(require_admin_key), ) -> Merchant: try: - merchant = await get_merchant_by_pubkey(data.public_key) - assert merchant is None, "A merchant already uses this public key" + # Check if merchant already exists for this user + merchant = await get_merchant_for_user(wallet.wallet.user) + assert merchant is None, "A merchant already exists for this user" - merchant = await create_merchant(wallet.wallet.user, data) + # Get user's account to access their Nostr keypairs + account = await get_account(wallet.wallet.user) + if not account: + raise HTTPException( + status_code=HTTPStatus.NOT_FOUND, + detail="User account not found", + ) + + # Check if user has Nostr keypairs, generate them if not + if not account.pubkey or not account.prvkey: + # Generate new keypair for user + private_key, public_key = generate_keypair() + + # Update user account with new keypairs + account.pubkey = public_key + account.prvkey = private_key + await update_account(account) + else: + public_key = account.pubkey + private_key = account.prvkey + + # Check if another merchant is already using this public key + existing_merchant = await get_merchant_by_pubkey(public_key) + assert existing_merchant is None, "A merchant already uses this public key" + + # Create PartialMerchant with user's keypairs + partial_merchant = PartialMerchant( + private_key=private_key, + public_key=public_key, + config=data.config + ) + + merchant = await create_merchant(wallet.wallet.user, partial_merchant) await create_zone( merchant.id, @@ -113,7 +149,7 @@ async def api_create_merchant( await resubscribe_to_all_merchants() - await nostr_client.merchant_temp_subscription(data.public_key) + await nostr_client.merchant_temp_subscription(public_key) return merchant except AssertionError as ex: