789d7626f7
* Add NWC receives * Refactor sendPayment+createInvoice with nwcCall function * Update badge * Add method support checks * Add timeout to NWC test invoice * Fix NWC isConfigured state All NWC fields are marked as optional but NWC should only be considered configured if one of them is set. * Fix relay.fetch() throws 'crypto is not defined' in node nip04.encrypt() was failing in worker because 'crypto is not defined'. Updating to nostr-tools v2.7.2 fixed that. However, now crypto.randomUUID() in relay.fetch() was throwing 'crypto is not defined'. Importing crypto from 'crypto' fixed that. However, with the import, randomUUID() does not work so I switched to randomBytes(). Running relay.fetch() now works in browser and node. * recv must not support pay_invoice * Fix Relay connection check * this.url was undefined * error was an object * Fix additional isConfigured check runs always It was meant to only catch false positives, not turn negatives into false positives. * Rename testConnectServer to testCreateInvoice * Rename testConnectClient to testSendPayment * Only run testSendPayment if send is configured The return value of testSendPayment was used before but it only returned something for LNC. And for LNC, we only wanted to save the transformation during validation, so it was not needed. * Always use withTimeout in NWC test functions * Fix fragment name * Use get_info command exclusively * Check permissions more efficiently * Log NWC request-response flow * Fix variable name * Call ws.send after listener is added * Fix websocket not closed after timeout * Also check that pay_keysend etc. are not supported * fix lnc session key save --------- Co-authored-by: Keyan <34140557+huumn@users.noreply.github.com> Co-authored-by: k00b <k00b@stacker.news>
162 lines
3.9 KiB
JavaScript
162 lines
3.9 KiB
JavaScript
import { InvoiceCanceledError, InvoiceExpiredError } from '@/components/payment'
|
|
import { bolt11Tags } from '@/lib/bolt11'
|
|
import { Mutex } from 'async-mutex'
|
|
export * from 'wallets/lnc'
|
|
|
|
async function disconnect (lnc, logger) {
|
|
if (lnc) {
|
|
try {
|
|
lnc.disconnect()
|
|
logger.info('disconnecting...')
|
|
// wait for lnc to disconnect before releasing the mutex
|
|
await new Promise((resolve, reject) => {
|
|
let counter = 0
|
|
const interval = setInterval(() => {
|
|
if (lnc?.isConnected) {
|
|
if (counter++ > 100) {
|
|
logger.error('failed to disconnect from lnc')
|
|
clearInterval(interval)
|
|
reject(new Error('failed to disconnect from lnc'))
|
|
}
|
|
return
|
|
}
|
|
clearInterval(interval)
|
|
resolve()
|
|
})
|
|
}, 50)
|
|
} catch (err) {
|
|
logger.error('failed to disconnect from lnc', err)
|
|
}
|
|
}
|
|
}
|
|
|
|
export async function testSendPayment (credentials, { logger }) {
|
|
let lnc
|
|
try {
|
|
lnc = await getLNC(credentials)
|
|
|
|
logger.info('connecting ...')
|
|
await lnc.connect()
|
|
logger.ok('connected')
|
|
|
|
logger.info('validating permissions ...')
|
|
await validateNarrowPerms(lnc)
|
|
logger.ok('permissions ok')
|
|
|
|
return lnc.credentials.credentials
|
|
} finally {
|
|
await disconnect(lnc, logger)
|
|
}
|
|
}
|
|
|
|
const mutex = new Mutex()
|
|
|
|
export async function sendPayment (bolt11, credentials, { logger }) {
|
|
const hash = bolt11Tags(bolt11).payment_hash
|
|
|
|
return await mutex.runExclusive(async () => {
|
|
let lnc
|
|
try {
|
|
lnc = await getLNC(credentials)
|
|
|
|
await lnc.connect()
|
|
const { paymentError, paymentPreimage: preimage } =
|
|
await lnc.lnd.lightning.sendPaymentSync({ payment_request: bolt11 })
|
|
|
|
if (paymentError) throw new Error(paymentError)
|
|
if (!preimage) throw new Error('No preimage in response')
|
|
|
|
return { preimage }
|
|
} catch (err) {
|
|
const msg = err.message || err.toString?.()
|
|
if (msg.includes('invoice expired')) {
|
|
throw new InvoiceExpiredError(hash)
|
|
}
|
|
if (msg.includes('canceled')) {
|
|
throw new InvoiceCanceledError(hash)
|
|
}
|
|
throw err
|
|
} finally {
|
|
await disconnect(lnc, logger)
|
|
}
|
|
})
|
|
}
|
|
|
|
async function getLNC (credentials = {}) {
|
|
const { default: { default: LNC } } = await import('@lightninglabs/lnc-web')
|
|
return new LNC({
|
|
credentialStore: new LncCredentialStore({ ...credentials, serverHost: 'mailbox.terminal.lightning.today:443' })
|
|
})
|
|
}
|
|
|
|
function validateNarrowPerms (lnc) {
|
|
if (!lnc.hasPerms('lnrpc.Lightning.SendPaymentSync')) {
|
|
throw new Error('missing permission: lnrpc.Lightning.SendPaymentSync')
|
|
}
|
|
if (lnc.hasPerms('lnrpc.Lightning.SendCoins')) {
|
|
throw new Error('too broad permission: lnrpc.Wallet.SendCoins')
|
|
}
|
|
// TODO: need to check for more narrow permissions
|
|
// blocked by https://github.com/lightninglabs/lnc-web/issues/112
|
|
}
|
|
|
|
// default credential store can go fuck itself
|
|
class LncCredentialStore {
|
|
credentials = {
|
|
localKey: '',
|
|
remoteKey: '',
|
|
pairingPhrase: '',
|
|
serverHost: ''
|
|
}
|
|
|
|
constructor (credentials = {}) {
|
|
this.credentials = { ...this.credentials, ...credentials }
|
|
}
|
|
|
|
get password () {
|
|
return ''
|
|
}
|
|
|
|
set password (password) { }
|
|
|
|
get serverHost () {
|
|
return this.credentials.serverHost
|
|
}
|
|
|
|
set serverHost (host) {
|
|
this.credentials.serverHost = host
|
|
}
|
|
|
|
get pairingPhrase () {
|
|
return this.credentials.pairingPhrase
|
|
}
|
|
|
|
set pairingPhrase (phrase) {
|
|
this.credentials.pairingPhrase = phrase
|
|
}
|
|
|
|
get localKey () {
|
|
return this.credentials.localKey
|
|
}
|
|
|
|
set localKey (key) {
|
|
this.credentials.localKey = key
|
|
}
|
|
|
|
get remoteKey () {
|
|
return this.credentials.remoteKey
|
|
}
|
|
|
|
set remoteKey (key) {
|
|
this.credentials.remoteKey = key
|
|
}
|
|
|
|
get isPaired () {
|
|
return !!this.credentials.remoteKey || !!this.credentials.pairingPhrase
|
|
}
|
|
|
|
clear () {
|
|
this.credentials = {}
|
|
}
|
|
}
|