669f262dcdd1f6674e17461b7fde61512758e7f0feea7c5841041ed9036999d7.json raw
1 {"ast":null,"code":"import _asyncToGenerator from \"/home/mleku/src/orly.dev/next/signer/node_modules/@babel/runtime/helpers/esm/asyncToGenerator.js\";\n/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { NostrHelper } from '@common';\nimport { finalizeEvent, nip04, nip44, getPublicKey } from 'nostr-tools';\nimport { NWC_METHODS } from './types';\n/**\n * NWC Client for communicating with NIP-47 wallet services\n */\nexport class NwcClient {\n connectionData;\n ws = null;\n connected = false;\n pendingRequests = new Map();\n subscriptionId = null;\n conversationKey;\n clientPubkey;\n encryptionMode = 'nip44';\n logCallback = null;\n constructor(connectionData, logCallback) {\n this.connectionData = connectionData;\n this.logCallback = logCallback ?? null;\n // Derive the conversation key for NIP-44 encryption\n this.conversationKey = nip44.v2.utils.getConversationKey(NostrHelper.hex2bytes(connectionData.secret), connectionData.walletPubkey);\n // Derive our public key from the secret\n this.clientPubkey = getPublicKey(NostrHelper.hex2bytes(connectionData.secret));\n }\n log(level, message) {\n if (this.logCallback) {\n this.logCallback(level, message);\n }\n }\n /**\n * Connect to the NWC relay\n */\n connect() {\n var _this = this;\n return _asyncToGenerator(function* () {\n if (_this.connected) {\n return;\n }\n return new Promise((resolve, reject) => {\n try {\n _this.log('info', `Connecting to ${_this.connectionData.relayUrl}...`);\n _this.ws = new WebSocket(_this.connectionData.relayUrl);\n const timeout = setTimeout(() => {\n _this.log('error', 'Connection timeout');\n reject(new Error('Connection timeout'));\n _this.disconnect();\n }, 10000);\n _this.ws.onopen = () => {\n clearTimeout(timeout);\n _this.connected = true;\n _this.log('info', 'Connected to relay');\n _this.subscribe();\n resolve();\n };\n _this.ws.onerror = () => {\n clearTimeout(timeout);\n _this.log('error', 'WebSocket error');\n reject(new Error('WebSocket error'));\n };\n _this.ws.onclose = () => {\n _this.connected = false;\n _this.subscriptionId = null;\n // Reject all pending requests\n for (const [, pending] of _this.pendingRequests) {\n clearTimeout(pending.timeout);\n pending.reject(new Error('Connection closed'));\n }\n _this.pendingRequests.clear();\n };\n _this.ws.onmessage = event => {\n _this.handleMessage(event.data);\n };\n } catch (error) {\n reject(error);\n }\n });\n })();\n }\n /**\n * Disconnect from the relay\n */\n disconnect() {\n if (this.ws) {\n if (this.subscriptionId) {\n this.ws.send(JSON.stringify(['CLOSE', this.subscriptionId]));\n }\n this.ws.close();\n this.ws = null;\n }\n this.connected = false;\n this.subscriptionId = null;\n }\n /**\n * Check if connected\n */\n isConnected() {\n return this.connected && this.ws?.readyState === WebSocket.OPEN;\n }\n /**\n * Get wallet info\n */\n getInfo() {\n var _this2 = this;\n return _asyncToGenerator(function* () {\n const response = yield _this2.sendRequest({\n method: NWC_METHODS.GET_INFO\n });\n if (response.error) {\n throw new Error(response.error.message);\n }\n return response.result;\n })();\n }\n /**\n * Get wallet balance\n */\n getBalance() {\n var _this3 = this;\n return _asyncToGenerator(function* () {\n const response = yield _this3.sendRequest({\n method: NWC_METHODS.GET_BALANCE\n });\n if (response.error) {\n throw new Error(response.error.message);\n }\n return response.result;\n })();\n }\n /**\n * Pay a Lightning invoice\n */\n payInvoice(params) {\n var _this4 = this;\n return _asyncToGenerator(function* () {\n const response = yield _this4.sendRequest({\n method: NWC_METHODS.PAY_INVOICE,\n params: params\n });\n if (response.error) {\n throw new Error(response.error.message);\n }\n return response.result;\n })();\n }\n /**\n * Create a Lightning invoice\n */\n makeInvoice(params) {\n var _this5 = this;\n return _asyncToGenerator(function* () {\n const response = yield _this5.sendRequest({\n method: NWC_METHODS.MAKE_INVOICE,\n params: params\n });\n if (response.error) {\n throw new Error(response.error.message);\n }\n return response.result;\n })();\n }\n /**\n * List transaction history\n */\n listTransactions(params) {\n var _this6 = this;\n return _asyncToGenerator(function* () {\n const response = yield _this6.sendRequest({\n method: NWC_METHODS.LIST_TRANSACTIONS,\n params: params\n });\n if (response.error) {\n throw new Error(response.error.message);\n }\n return response.result;\n })();\n }\n /**\n * Encrypt content using current encryption mode\n */\n encryptContent(plaintext) {\n var _this7 = this;\n return _asyncToGenerator(function* () {\n if (_this7.encryptionMode === 'nip04') {\n return nip04.encrypt(_this7.connectionData.secret, _this7.connectionData.walletPubkey, plaintext);\n } else {\n return nip44.v2.encrypt(plaintext, _this7.conversationKey);\n }\n })();\n }\n /**\n * Send a request to the wallet\n */\n sendRequest(_x) {\n var _this8 = this;\n return _asyncToGenerator(function* (request, timeoutMs = 30000, isRetry = false) {\n if (!_this8.isConnected()) {\n yield _this8.connect();\n }\n // Encrypt the request content\n const plaintext = JSON.stringify(request);\n _this8.log('info', `Sending ${request.method} request (using ${_this8.encryptionMode.toUpperCase()})`);\n const ciphertext = yield _this8.encryptContent(plaintext);\n // Create the NIP-47 request event (kind 23194)\n const eventTemplate = {\n kind: 23194,\n created_at: Math.floor(Date.now() / 1000),\n tags: [['p', _this8.connectionData.walletPubkey]],\n content: ciphertext\n };\n // Sign with the client secret\n const signedEvent = finalizeEvent(eventTemplate, NostrHelper.hex2bytes(_this8.connectionData.secret));\n return new Promise((resolve, reject) => {\n const timeout = setTimeout(() => {\n _this8.pendingRequests.delete(signedEvent.id);\n _this8.log('error', `Request timeout for ${request.method}`);\n reject(new Error('Request timeout'));\n }, timeoutMs);\n _this8.pendingRequests.set(signedEvent.id, {\n resolve,\n reject,\n timeout,\n request,\n isRetry\n });\n // Send the event\n _this8.ws.send(JSON.stringify(['EVENT', signedEvent]));\n });\n }).apply(this, arguments);\n }\n /**\n * Retry a request with NIP-04 encryption\n */\n retryWithNip04(request) {\n var _this9 = this;\n return _asyncToGenerator(function* () {\n _this9.log('warn', 'Retrying with NIP-04 encryption...');\n _this9.encryptionMode = 'nip04';\n return _this9.sendRequest(request, 30000, true);\n })();\n }\n /**\n * Subscribe to response events from the wallet\n */\n subscribe() {\n if (!this.ws || !this.connected) {\n return;\n }\n // Generate a subscription ID\n this.subscriptionId = Math.random().toString(36).substring(2, 15);\n // Subscribe to kind 23195 (response) events addressed to us\n const filter = {\n kinds: [23195],\n '#p': [this.clientPubkey],\n since: Math.floor(Date.now() / 1000) - 10 // Last 10 seconds\n };\n this.ws.send(JSON.stringify(['REQ', this.subscriptionId, filter]));\n }\n /**\n * Handle incoming WebSocket messages\n */\n handleMessage(data) {\n try {\n const message = JSON.parse(data);\n if (!Array.isArray(message)) {\n return;\n }\n const [type, ...rest] = message;\n switch (type) {\n case 'EVENT':\n this.handleEvent(rest[1]);\n break;\n case 'OK':\n // Event was received by relay\n break;\n case 'EOSE':\n // End of stored events\n break;\n case 'NOTICE':\n this.log('warn', `Relay notice: ${rest[0]}`);\n break;\n }\n } catch (error) {\n this.log('error', `Error parsing message: ${error.message}`);\n }\n }\n /**\n * Check if an error indicates a decryption/encryption problem\n */\n isEncryptionError(errorMsg) {\n const lowerMsg = errorMsg.toLowerCase();\n return lowerMsg.includes('decrypt') || lowerMsg.includes('initialization vector') || lowerMsg.includes('iv') || lowerMsg.includes('encrypt') || lowerMsg.includes('cipher') || lowerMsg.includes('parse');\n }\n /**\n * Handle an incoming event (response from wallet)\n */\n handleEvent(event) {\n var _this0 = this;\n return _asyncToGenerator(function* () {\n if (!event || event.kind !== 23195) {\n return;\n }\n // Check if this event is from the wallet\n if (event.pubkey !== _this0.connectionData.walletPubkey) {\n return;\n }\n // Find the request ID from the 'e' tag\n const eTag = event.tags?.find(t => t[0] === 'e');\n if (!eTag) {\n return;\n }\n const requestId = eTag[1];\n const pending = _this0.pendingRequests.get(requestId);\n if (!pending) {\n // Response for unknown request (might be old or from another session)\n return;\n }\n // Clear the timeout and remove from pending\n clearTimeout(pending.timeout);\n _this0.pendingRequests.delete(requestId);\n try {\n // Try to decrypt the response\n let decrypted;\n // First, check if content looks like plain JSON (unencrypted error)\n if (event.content.startsWith('{') || event.content.startsWith('\"')) {\n // Might be unencrypted error response\n try {\n const parsed = JSON.parse(event.content);\n // If it has an error field, this is an unencrypted error response\n if (parsed.error) {\n _this0.log('error', `Wallet error: ${parsed.error.message || JSON.stringify(parsed.error)}`);\n // Check if it's an encryption error and we haven't retried yet\n const errorMsg = parsed.error.message || JSON.stringify(parsed.error);\n if (!pending.isRetry && _this0.encryptionMode === 'nip44' && _this0.isEncryptionError(errorMsg)) {\n _this0.log('warn', 'Wallet returned encryption error, switching to NIP-04');\n try {\n const retryResponse = yield _this0.retryWithNip04(pending.request);\n pending.resolve(retryResponse);\n return;\n } catch (retryError) {\n pending.reject(retryError);\n return;\n }\n }\n pending.resolve(parsed);\n return;\n }\n } catch {\n // Not valid JSON, continue with decryption\n }\n }\n // Detect encryption format and decrypt\n // NIP-04 format contains \"?iv=\" in the ciphertext\n if (event.content.includes('?iv=')) {\n _this0.log('info', 'Decrypting response (NIP-04 format)');\n decrypted = yield nip04.decrypt(_this0.connectionData.secret, _this0.connectionData.walletPubkey, event.content);\n } else {\n _this0.log('info', 'Decrypting response (NIP-44 format)');\n try {\n decrypted = nip44.v2.decrypt(event.content, _this0.conversationKey);\n } catch (nip44Error) {\n // NIP-44 decryption failed, maybe it's NIP-04 without standard format?\n // Try NIP-04 as fallback\n _this0.log('warn', `NIP-44 decryption failed: ${nip44Error.message}, trying NIP-04...`);\n try {\n decrypted = yield nip04.decrypt(_this0.connectionData.secret, _this0.connectionData.walletPubkey, event.content);\n } catch {\n // Both failed, throw original error\n throw nip44Error;\n }\n }\n }\n const response = JSON.parse(decrypted);\n // Check if the decrypted response contains an encryption error\n if (response.error) {\n const errorMsg = response.error.message || '';\n if (!pending.isRetry && _this0.encryptionMode === 'nip44' && _this0.isEncryptionError(errorMsg)) {\n _this0.log('warn', `Wallet returned encryption error: ${errorMsg}, retrying with NIP-04`);\n try {\n const retryResponse = yield _this0.retryWithNip04(pending.request);\n pending.resolve(retryResponse);\n return;\n } catch (retryError) {\n pending.reject(retryError);\n return;\n }\n }\n _this0.log('error', `Wallet error: ${errorMsg}`);\n } else {\n _this0.log('info', 'Request successful');\n }\n pending.resolve(response);\n } catch (error) {\n const errorMsg = error.message;\n _this0.log('error', `Failed to decrypt response: ${errorMsg}`);\n // If this is an encryption error and we haven't retried, try NIP-04\n if (!pending.isRetry && _this0.encryptionMode === 'nip44' && _this0.isEncryptionError(errorMsg)) {\n _this0.log('warn', 'Decryption failed, retrying with NIP-04 encryption');\n try {\n const retryResponse = yield _this0.retryWithNip04(pending.request);\n pending.resolve(retryResponse);\n return;\n } catch (retryError) {\n pending.reject(retryError);\n return;\n }\n }\n pending.reject(new Error(`Failed to decrypt response: ${errorMsg}`));\n }\n })();\n }\n}","map":null,"metadata":{},"sourceType":"module","externalDependencies":[]}