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>
189 lines
5.1 KiB
JavaScript
189 lines
5.1 KiB
JavaScript
import { galoyBlinkUrl } from 'wallets/blink'
|
|
export * from 'wallets/blink'
|
|
|
|
export async function testSendPayment ({ apiKey, currency }, { logger }) {
|
|
currency = currency ? currency.toUpperCase() : 'BTC'
|
|
logger.info('trying to fetch ' + currency + ' wallet')
|
|
await getWallet(apiKey, currency)
|
|
logger.ok(currency + ' wallet found')
|
|
}
|
|
|
|
export async function sendPayment (bolt11, { apiKey, currency }) {
|
|
const wallet = await getWallet(apiKey, currency)
|
|
const preImage = await payInvoice(apiKey, wallet, bolt11)
|
|
return { preImage }
|
|
}
|
|
|
|
async function payInvoice (authToken, wallet, invoice) {
|
|
const walletId = wallet.id
|
|
const out = await request(authToken, `
|
|
mutation LnInvoicePaymentSend($input: LnInvoicePaymentInput!) {
|
|
lnInvoicePaymentSend(input: $input) {
|
|
status
|
|
errors {
|
|
message
|
|
path
|
|
code
|
|
}
|
|
transaction {
|
|
settlementVia {
|
|
... on SettlementViaIntraLedger {
|
|
preImage
|
|
}
|
|
... on SettlementViaLn {
|
|
preImage
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
{
|
|
input: {
|
|
paymentRequest: invoice,
|
|
walletId
|
|
}
|
|
})
|
|
const status = out.data.lnInvoicePaymentSend.status
|
|
const errors = out.data.lnInvoicePaymentSend.errors
|
|
if (errors && errors.length > 0) {
|
|
throw new Error('failed to pay invoice ' + errors.map(e => e.code + ' ' + e.message).join(', '))
|
|
}
|
|
|
|
// payment was settled immediately
|
|
if (status === 'SUCCESS') {
|
|
const preimage = out.data.lnInvoicePaymentSend.transaction.settlementVia.preImage
|
|
if (!preimage) throw new Error('no preimage')
|
|
return preimage
|
|
}
|
|
|
|
// payment failed immediately
|
|
if (status === 'FAILED') {
|
|
throw new Error('failed to pay invoice')
|
|
}
|
|
|
|
// payment couldn't be settled (or fail) immediately, so we wait for a result
|
|
if (status === 'PENDING') {
|
|
while (true) {
|
|
// at some point it should either be settled or fail on the backend, so the loop will exit
|
|
await new Promise(resolve => setTimeout(resolve, 100))
|
|
|
|
const txInfo = await getTxInfo(authToken, wallet, invoice)
|
|
// settled
|
|
if (txInfo.status === 'SUCCESS') {
|
|
if (!txInfo.preImage) throw new Error('no preimage')
|
|
return txInfo.preImage
|
|
}
|
|
// failed
|
|
if (txInfo.status === 'FAILED') {
|
|
throw new Error(txInfo.error || 'failed to pay invoice')
|
|
}
|
|
// still pending
|
|
// retry later
|
|
}
|
|
}
|
|
|
|
// this should never happen
|
|
throw new Error('unexpected error')
|
|
}
|
|
|
|
async function getTxInfo (authToken, wallet, invoice) {
|
|
const walletId = wallet.id
|
|
let out
|
|
try {
|
|
out = await request(authToken, `
|
|
query GetTxInfo($walletId: WalletId!, $paymentRequest: LnPaymentRequest!) {
|
|
me {
|
|
defaultAccount {
|
|
walletById(walletId: $walletId) {
|
|
transactionsByPaymentRequest(paymentRequest: $paymentRequest) {
|
|
status
|
|
direction
|
|
settlementVia {
|
|
... on SettlementViaIntraLedger {
|
|
preImage
|
|
}
|
|
... on SettlementViaLn {
|
|
preImage
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
{
|
|
paymentRequest: invoice,
|
|
walletId
|
|
})
|
|
} catch (e) {
|
|
// something went wrong during the query,
|
|
// maybe the connection was lost, so we just return
|
|
// a pending status, the caller can retry later
|
|
return {
|
|
status: 'PENDING',
|
|
preImage: null,
|
|
error: ''
|
|
}
|
|
}
|
|
const tx = out.data.me.defaultAccount.walletById.transactionsByPaymentRequest.find(t => t.direction === 'SEND')
|
|
if (!tx) {
|
|
// the transaction was not found, something went wrong
|
|
return {
|
|
status: 'FAILED',
|
|
preImage: null,
|
|
error: 'transaction not found'
|
|
}
|
|
}
|
|
const status = tx.status
|
|
const preImage = tx.settlementVia.preImage
|
|
return {
|
|
status,
|
|
preImage,
|
|
error: ''
|
|
}
|
|
}
|
|
|
|
async function getWallet (authToken, currency) {
|
|
const out = await request(authToken, `
|
|
query me {
|
|
me {
|
|
defaultAccount {
|
|
wallets {
|
|
id
|
|
walletCurrency
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`, {})
|
|
const wallets = out.data.me.defaultAccount.wallets
|
|
for (const wallet of wallets) {
|
|
if (wallet.walletCurrency === currency) {
|
|
return wallet
|
|
}
|
|
}
|
|
throw new Error(`wallet ${currency} not found`)
|
|
}
|
|
|
|
async function request (authToken, query, variables = {}) {
|
|
const options = {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-API-KEY': authToken
|
|
},
|
|
body: JSON.stringify({ query, variables })
|
|
}
|
|
const res = await fetch(galoyBlinkUrl, options)
|
|
if (res.status >= 400 && res.status <= 599) {
|
|
if (res.status === 401) {
|
|
throw new Error('unauthorized')
|
|
} else {
|
|
throw new Error('API responded with HTTP ' + res.status)
|
|
}
|
|
}
|
|
return res.json()
|
|
}
|