ekzyis 72e2d19433
supercharged wallet logs (#1516)
* Inject wallet logger interface

* Include method in NWC logs

* Fix wrong page total

* Poll for new logs every second

* Fix overlapping pagination

* Remove unused total

* Better logs for incoming payments

* Use _setLogs instead of wrapper

* Remove inconsistent receive log

* Remove console.log from wallet logger on server

* Fix missing 'wallet detached' log

* Fix confirm_withdrawl code

* Remove duplicate autowithdrawal log

* Add context to log

* Add more context

* Better table styling

* Move CSS for wallet logs into one file

* remove unused logNav class
* rename classes

* Align key with second column

* Fix TypeError if context empty

* Check content-type header before calling res.json()

* Fix duplicate 'failed to create invoice'

* Parse details from LND error

* Fix invalid DOM property 'colspan'

* P2P zap logs with context

* Remove unnecessary withdrawal error log

* the code assignment was broken anyway
* we already log withdrawal errors using .catch on payViaPaymentRequest

* Don't show outgoing fee to receiver to avoid confusion

* Fix typo in comment

* Log if invoice was canceled by payer

* Automatically populate context from bolt11

* Fix missing context

* Fix wrap errors not logged

* Only log cancel if client canceled

* Remove unused imports

* Log withdrawal/forward success/error in payment flow

* Fix boss not passed to checkInvoice

* Fix TypeError

* Fix database timeouts caused by logger

The logger shares the same connection pool with any currently running transaction.

This means that we enter a classic deadlock when we await logger calls: the logger call is waiting for a connection but the currently running transaction is waiting for the logger call to finish before it can release a connection.

* Fix cache returning undefined

* Fix typo in comment

* Add padding-right to key in log context

* Always use 'incoming payment failed:'

---------

Co-authored-by: Keyan <34140557+huumn@users.noreply.github.com>
2024-11-08 13:26:40 -06:00

95 lines
2.6 KiB
JavaScript

import { Relay } from '@/lib/nostr'
import { parseNwcUrl } from '@/lib/url'
import { string } from '@/lib/yup'
import { finalizeEvent, nip04, verifyEvent } from 'nostr-tools'
export const name = 'nwc'
export const walletType = 'NWC'
export const walletField = 'walletNWC'
export const fields = [
{
name: 'nwcUrl',
label: 'connection',
type: 'password',
optional: 'for sending',
clientOnly: true,
requiredWithout: 'nwcUrlRecv',
validate: string().nwcUrl()
},
{
name: 'nwcUrlRecv',
label: 'connection',
type: 'password',
optional: 'for receiving',
serverOnly: true,
requiredWithout: 'nwcUrl',
validate: string().nwcUrl()
}
]
export const card = {
title: 'NWC',
subtitle: 'use Nostr Wallet Connect for payments',
badges: ['send', 'receive', 'budgetable']
}
export async function nwcCall ({ nwcUrl, method, params }, { logger, timeout } = {}) {
const { relayUrl, walletPubkey, secret } = parseNwcUrl(nwcUrl)
const relay = await Relay.connect(relayUrl, { timeout })
logger?.ok(`connected to ${relayUrl}`)
try {
const payload = { method, params }
const encrypted = await nip04.encrypt(secret, walletPubkey, JSON.stringify(payload))
const request = finalizeEvent({
kind: 23194,
created_at: Math.floor(Date.now() / 1000),
tags: [['p', walletPubkey]],
content: encrypted
}, secret)
// we need to subscribe to the response before publishing the request
// since NWC events are ephemeral (20000 <= kind < 30000)
const subscription = relay.fetch([{
kinds: [23195],
authors: [walletPubkey],
'#e': [request.id]
}], { timeout })
await relay.publish(request, { timeout })
logger?.info(`published ${method} request`)
logger?.info(`waiting for ${method} response ...`)
const [response] = await subscription
if (!response) {
throw new Error(`no ${method} response`)
}
logger?.ok(`${method} response received`)
if (!verifyEvent(response)) throw new Error(`invalid ${method} response: failed to verify`)
const decrypted = await nip04.decrypt(secret, walletPubkey, response.content)
const content = JSON.parse(decrypted)
if (content.error) throw new Error(content.error.message)
if (content.result) return content.result
throw new Error(`invalid ${method} response: missing error or result`)
} finally {
relay?.close()
logger?.info(`closed connection to ${relayUrl}`)
}
}
export async function supportedMethods (nwcUrl, { logger, timeout } = {}) {
const result = await nwcCall({ nwcUrl, method: 'get_info' }, { logger, timeout })
return result.methods
}