diff --git a/api/lib/bolt/bolt11.js b/api/lib/bolt/bolt11.js index bfb4ccb4a..a8a7cc0da 100644 --- a/api/lib/bolt/bolt11.js +++ b/api/lib/bolt/bolt11.js @@ -1,6 +1,7 @@ /* eslint-disable camelcase */ import { payViaPaymentRequest, parsePaymentRequest } from 'ln-service' import { isBolt11 } from '@/lib/bolt/bolt11-tags' +import { estimateRouteFee } from '@/api/lnd' export { isBolt11 } export async function parseBolt11 ({ request }) { @@ -9,6 +10,7 @@ export async function parseBolt11 ({ request }) { } export async function payBolt11 ({ lnd, request, max_fee, max_fee_mtokens, ...args }) { + if (!lnd) throw new Error('lnd required') // check if forgot to pass lnd if (!isBolt11(request)) throw new Error('not a bolt11 invoice') return payViaPaymentRequest({ lnd, @@ -18,3 +20,9 @@ export async function payBolt11 ({ lnd, request, max_fee, max_fee_mtokens, ...ar ...args }) } + +export async function estimateBolt11RouteFee ({ lnd, destination, tokens, mtokens, request, timeout }) { + if (!lnd) throw new Error('lnd required') // check if forgot to pass lnd + if (request && !isBolt11(request)) throw new Error('not a bolt11 request') + return await estimateRouteFee({ lnd, destination, tokens, mtokens, request, timeout }) +} diff --git a/api/lib/bolt/bolt12.js b/api/lib/bolt/bolt12.js index cca860456..a8771f2bd 100644 --- a/api/lib/bolt/bolt12.js +++ b/api/lib/bolt/bolt12.js @@ -3,19 +3,39 @@ import { payViaBolt12PaymentRequest, decodeBolt12Invoice } from '@/api/lib/lndk' import { isBolt12Invoice, isBolt12Offer, isBolt12 } from '@/lib/bolt/bolt12-info' import { toPositiveNumber } from '@/lib/format' +import { estimateRouteFee } from '@/api/lnd' export { isBolt12Invoice, isBolt12Offer, isBolt12 } -export async function payBolt12 ({ lnd, request: invoice, max_fee, max_fee_mtokens }) { +export async function payBolt12 ({ lndk, request: invoice, max_fee, max_fee_mtokens }) { + if (!lndk) throw new Error('lndk required') // check if forgot to pass lndk if (!isBolt12Invoice(invoice)) throw new Error('not a bolt12 invoice') - return await payViaBolt12PaymentRequest({ lnd, request: invoice, max_fee, max_fee_mtokens }) + return await payViaBolt12PaymentRequest({ lndk, request: invoice, max_fee, max_fee_mtokens }) } -export async function parseBolt12 ({ lnd, request: invoice }) { +export async function parseBolt12 ({ lndk, request: invoice }) { + if (!lndk) throw new Error('lndk required') // check if forgot to pass lndk if (!isBolt12Invoice(invoice)) throw new Error('not a bolt12 request') - const decodedInvoice = await decodeBolt12Invoice({ lnd, request: invoice }) + const decodedInvoice = await decodeBolt12Invoice({ lndk, request: invoice }) return convertBolt12RequestToLNRequest(decodedInvoice) } +export async function estimateBolt12RouteFee ({ lnd, lndk, destination, tokens, mtokens, request, timeout }) { + if (!lndk) throw new Error('lndk required') // check if forgot to pass lndk + if (!lnd) throw new Error('lnd required') // check if forgot to pass lnd + if (request && !isBolt12Invoice(request)) throw new Error('not a bolt12 request') + + const { amount_msats, node_id } = request ? await decodeBolt12Invoice({ lndk, request }) : {} + + // extract mtokens and destination from invoice if they are not provided + if (!tokens && !mtokens) mtokens = toPositiveNumber(amount_msats) + destination ??= Buffer.from(node_id.key).toString('hex') + + if (!destination) throw new Error('no destination provided') + if (!tokens && !mtokens) throw new Error('no tokens amount provided') + + return await estimateRouteFee({ lnd, destination, tokens, mtokens, timeout }) +} + const featureBitTypes = { 0: 'DATALOSS_PROTECT_REQ', 1: 'DATALOSS_PROTECT_OPT', diff --git a/api/lib/bolt/index.js b/api/lib/bolt/index.js index 70d69618e..d0777273a 100644 --- a/api/lib/bolt/index.js +++ b/api/lib/bolt/index.js @@ -1,12 +1,11 @@ /* eslint-disable camelcase */ import { payBolt12, parseBolt12, isBolt12Invoice, isBolt12Offer } from '@/api/lib/bolt/bolt12' -import { payBolt11, parseBolt11, isBolt11 } from '@/api/lib/bolt/bolt11' +import { payBolt11, parseBolt11, isBolt11, estimateBolt11RouteFee } from '@/api/lib/bolt/bolt11' import { estimateBolt12RouteFee } from '@/api/lib/lndk' -import { estimateRouteFee } from '@/api/lnd' -export async function payInvoice ({ lnd, request: invoice, max_fee, max_fee_mtokens, ...args }) { +export async function payInvoice ({ lnd, lndk, request: invoice, max_fee, max_fee_mtokens, ...args }) { if (isBolt12Invoice(invoice)) { - return await payBolt12({ lnd, request: invoice, max_fee, max_fee_mtokens, ...args }) + return await payBolt12({ lndk, request: invoice, max_fee, max_fee_mtokens, ...args }) } else if (isBolt11(invoice)) { return await payBolt11({ lnd, request: invoice, max_fee, max_fee_mtokens, ...args }) } else if (isBolt12Offer(invoice)) { @@ -16,9 +15,9 @@ export async function payInvoice ({ lnd, request: invoice, max_fee, max_fee_mtok } } -export async function parseInvoice ({ lnd, request }) { +export async function parseInvoice ({ lndk, request }) { if (isBolt12Invoice(request)) { - return await parseBolt12({ lnd, request }) + return await parseBolt12({ lndk, request }) } else if (isBolt11(request)) { return await parseBolt11({ request }) } else if (isBolt12Offer(request)) { @@ -28,11 +27,11 @@ export async function parseInvoice ({ lnd, request }) { } } -export async function estimateFees ({ lnd, destination, tokens, mtokens, request, timeout }) { +export async function estimateFees ({ lnd, lndk, destination, tokens, mtokens, request, timeout }) { if (isBolt12Invoice(request)) { - return await estimateBolt12RouteFee({ lnd, destination, tokens, mtokens, request, timeout }) + return await estimateBolt12RouteFee({ lnd, lndk, destination, tokens, mtokens, request, timeout }) } else if (isBolt11(request)) { - return await estimateRouteFee({ lnd, destination, tokens, request, mtokens, timeout }) + return await estimateBolt11RouteFee({ lnd, destination, tokens, request, mtokens, timeout }) } else if (isBolt12Offer(request)) { throw new Error('bolt12 offer instead of invoice, please fetch a bolt12 invoice from the offer first') } else { diff --git a/api/lib/lndk.js b/api/lib/lndk.js index f589d4348..527e88403 100644 --- a/api/lib/lndk.js +++ b/api/lib/lndk.js @@ -5,19 +5,12 @@ import protobuf from 'protobufjs' import grpcCredentials from 'lightning/lnd_grpc/grpc_credentials' import { grpcSslCipherSuites } from 'lightning/grpc/index' import { fromJSON } from '@grpc/proto-loader' -import { estimateRouteFee } from '@/api/lnd' import * as bech32b12 from '@/lib/bech32b12' /* eslint-disable camelcase */ const { GRPC_SSL_CIPHER_SUITES } = process.env -const lndkInstances = new WeakMap() - -export function enableLNDK (lnd, { cert, macaroon, socket: lndkSocket }, withProxy) { - // already installed - if (lndkInstances.has(lnd)) return - console.log('enabling lndk', lndkSocket, 'withProxy', withProxy) - +export function authenticatedLndkGrpc ({ cert, macaroon, socket: lndkSocket }, withProxy) { // workaround to load from string const protoArgs = { keepCase: true, longs: Number, defaults: true, oneofs: true } const proto = protobuf.parse(LNDK_RPC_PROTO, protoArgs).root @@ -38,22 +31,13 @@ export function enableLNDK (lnd, { cert, macaroon, socket: lndkSocket }, withPro } const client = new OffersService(lndkSocket, credentials, params) - lndkInstances.set(lnd, client) -} - -export function getLNDK (lnd) { - if (!lndkInstances.has(lnd)) { - throw new Error('lndk not available, please use enableLNDK first') - } - return lndkInstances.get(lnd) + return client } export async function decodeBolt12Invoice ({ - lnd, + lndk, request }) { - const lndk = getLNDK(lnd) - // decode bech32 bolt12 invoice to hex string if (!request.startsWith('lni1')) throw new Error('not a valid bech32 encoded bolt12 invoice') const invoice_hex_str = bech32b12.decode(request.slice(4)).toString('hex') @@ -70,9 +54,7 @@ export async function decodeBolt12Invoice ({ return { ...decodedRequest, invoice_hex_str } } -export async function fetchBolt12InvoiceFromOffer ({ lnd, offer, msats, description, timeout = 10_000 }) { - const lndk = getLNDK(lnd) - +export async function fetchBolt12InvoiceFromOffer ({ lndk, offer, msats, description, timeout = 10_000 }) { return new Promise((resolve, reject) => { lndk.GetInvoice({ offer, @@ -87,7 +69,7 @@ export async function fetchBolt12InvoiceFromOffer ({ lnd, offer, msats, descript const bech32invoice = 'lni1' + bech32b12.encode(Buffer.from(response.invoice_hex_str, 'hex')) // sanity check - const { amount_msats } = await decodeBolt12Invoice({ lnd, request: bech32invoice }) + const { amount_msats } = await decodeBolt12Invoice({ lndk, request: bech32invoice }) if (toPositiveNumber(amount_msats) !== toPositiveNumber(msats)) { return reject(new Error('invalid invoice response')) } @@ -101,14 +83,12 @@ export async function fetchBolt12InvoiceFromOffer ({ lnd, offer, msats, descript } export async function payViaBolt12PaymentRequest ({ - lnd, + lndk, request: invoiceBech32, max_fee, max_fee_mtokens }) { - const lndk = getLNDK(lnd) - - const { amount_msats, invoice_hex_str } = await decodeBolt12Invoice({ lnd, request: invoiceBech32 }) + const { amount_msats, invoice_hex_str } = await decodeBolt12Invoice({ lndk, request: invoiceBech32 }) if (!max_fee_mtokens && max_fee) { max_fee_mtokens = toPositiveNumber(satsToMsats(max_fee)) @@ -130,16 +110,3 @@ export async function payViaBolt12PaymentRequest ({ }) }) } - -export async function estimateBolt12RouteFee ({ lnd, destination, tokens, mtokens, request, timeout }) { - const { amount_msats, node_id } = request ? await decodeBolt12Invoice({ lnd, request }) : {} - - // extract mtokens and destination from invoice if they are not provided - if (!tokens && !mtokens) mtokens = toPositiveNumber(amount_msats) - destination ??= Buffer.from(node_id.key).toString('hex') - - if (!destination) throw new Error('no destination provided') - if (!tokens && !mtokens) throw new Error('no tokens amount provided') - - return await estimateRouteFee({ lnd, destination, tokens, mtokens, timeout }) -} diff --git a/api/lnd/index.js b/api/lnd/index.js index 03af5eaa6..8ec7439c4 100644 --- a/api/lnd/index.js +++ b/api/lnd/index.js @@ -1,7 +1,7 @@ import { cachedFetcher } from '@/lib/fetch' import { toPositiveNumber } from '@/lib/format' import { authenticatedLndGrpc } from '@/lib/lnd' -import { enableLNDK } from '@/api/lib/lndk' +import { authenticatedLndkGrpc } from '@/api/lib/lndk' import { getIdentity, getHeight, getWalletInfo, getNode, getPayment } from 'ln-service' import { datePivot } from '@/lib/time' import { LND_PATHFINDING_TIMEOUT_MS } from '@/lib/constants' @@ -11,7 +11,7 @@ const lnd = global.lnd || authenticatedLndGrpc({ macaroon: process.env.LND_MACAROON, socket: process.env.LND_SOCKET }).lnd -enableLNDK(lnd, { +export const lndk = authenticatedLndkGrpc({ cert: process.env.LNDK_CERT, macaroon: process.env.LNDK_MACAROON, socket: process.env.LNDK_SOCKET diff --git a/api/paidAction/README.md b/api/paidAction/README.md index a32588076..d478b20bf 100644 --- a/api/paidAction/README.md +++ b/api/paidAction/README.md @@ -193,6 +193,7 @@ All functions have the following signature: `function(args: Object, context: Obj - `tx`: the current transaction (for anything that needs to be done atomically with the payment) - `models`: the current prisma client (for anything that doesn't need to be done atomically with the payment) - `lnd`: the current lnd client +- `lndk`: the current lndk client ## Recording Cowboy Credits diff --git a/api/paidAction/index.js b/api/paidAction/index.js index c1e7d1ac4..c1246cd74 100644 --- a/api/paidAction/index.js +++ b/api/paidAction/index.js @@ -213,7 +213,7 @@ async function beginPessimisticAction (actionType, args, context) { async function performP2PAction (actionType, args, incomingContext) { // if the action has an invoiceable peer, we'll create a peer invoice // wrap it, and return the wrapped invoice - const { cost, sybilFeePercent, models, lnd, me } = incomingContext + const { cost, sybilFeePercent, models, lnd, lndk, me } = incomingContext if (!sybilFeePercent) { throw new Error('sybil fee percent is not set for an invoiceable peer action') } @@ -233,7 +233,7 @@ async function performP2PAction (actionType, args, incomingContext) { feePercent: sybilFeePercent, description, expiry: INVOICE_EXPIRE_SECS - }, { models, me, lnd }) + }, { models, me, lnd, lndk }) context = { ...incomingContext, @@ -257,7 +257,7 @@ async function performP2PAction (actionType, args, incomingContext) { // we don't need to use the module for perform-ing outside actions // because we can't track the state of outside invoices we aren't paid/paying async function performDirectAction (actionType, args, incomingContext) { - const { models, lnd, cost } = incomingContext + const { models, lnd, cost, lndk } = incomingContext const { comment, lud18Data, noteStr, description: actionDescription } = args const userId = await paidActions[actionType]?.getInvoiceablePeer?.(args, incomingContext) @@ -276,7 +276,7 @@ async function performDirectAction (actionType, args, incomingContext) { description, expiry: INVOICE_EXPIRE_SECS, supportBolt12: false // direct payment is not supported to bolt12 for compatibility reasons - }, { models, lnd }) + }, { models, lnd, lndk }) } catch (e) { console.error('failed to create outside invoice', e) throw new NonInvoiceablePeerError() diff --git a/api/payingAction/index.js b/api/payingAction/index.js index ebe6a79d1..c75e5cd13 100644 --- a/api/payingAction/index.js +++ b/api/payingAction/index.js @@ -6,7 +6,7 @@ import { payInvoice, parseInvoice } from '@/api/lib/bolt' // paying actions are completely distinct from paid actions // and there's only one paying action: send // ... still we want the api to at least be similar -export default async function performPayingAction ({ bolt11, maxFee, walletId }, { me, models, lnd }) { +export default async function performPayingAction ({ bolt11, maxFee, walletId }, { me, models, lnd, lndk }) { try { console.group('performPayingAction', `${bolt11.slice(0, 10)}...`, maxFee, walletId) @@ -14,7 +14,7 @@ export default async function performPayingAction ({ bolt11, maxFee, walletId }, throw new Error('You must be logged in to perform this action') } - const decoded = await parseInvoice({ request: bolt11, lnd }) + const decoded = await parseInvoice({ request: bolt11, lnd, lndk }) const cost = toPositiveBigInt(toPositiveBigInt(decoded.mtokens) + satsToMsats(maxFee)) console.log('cost', cost) @@ -42,6 +42,7 @@ export default async function performPayingAction ({ bolt11, maxFee, walletId }, payInvoice({ lnd, + lndk, request: withdrawal.bolt11, max_fee: msatsToSats(withdrawal.msatsFeePaying), pathfinding_timeout: LND_PATHFINDING_TIMEOUT_MS, diff --git a/api/resolvers/item.js b/api/resolvers/item.js index cac80f53a..702fff028 100644 --- a/api/resolvers/item.js +++ b/api/resolvers/item.js @@ -945,7 +945,7 @@ export default { return await performPaidAction('POLL_VOTE', { id }, { me, models, lnd }) }, - act: async (parent, { id, sats, act = 'TIP', hasSendWallet }, { me, models, lnd, headers }) => { + act: async (parent, { id, sats, act = 'TIP', hasSendWallet }, { me, models, lnd, lndk, headers }) => { assertApiKeyNotPermitted({ me }) await validateSchema(actSchema, { sats, act }) await assertGofacYourself({ models, headers }) @@ -979,11 +979,11 @@ export default { } if (act === 'TIP') { - return await performPaidAction('ZAP', { id, sats, hasSendWallet }, { me, models, lnd }) + return await performPaidAction('ZAP', { id, sats, hasSendWallet }, { me, models, lnd, lndk }) } else if (act === 'DONT_LIKE_THIS') { - return await performPaidAction('DOWN_ZAP', { id, sats }, { me, models, lnd }) + return await performPaidAction('DOWN_ZAP', { id, sats }, { me, models, lnd, lndk }) } else if (act === 'BOOST') { - return await performPaidAction('BOOST', { id, sats }, { me, models, lnd }) + return await performPaidAction('BOOST', { id, sats }, { me, models, lnd, lndk }) } else { throw new GqlInputError('unknown act') } diff --git a/api/resolvers/paidAction.js b/api/resolvers/paidAction.js index 2b993c1f0..f6785bb15 100644 --- a/api/resolvers/paidAction.js +++ b/api/resolvers/paidAction.js @@ -50,7 +50,7 @@ export default { } }, Mutation: { - retryPaidAction: async (parent, { invoiceId }, { models, me, lnd }) => { + retryPaidAction: async (parent, { invoiceId }, { models, me, lnd, lndk }) => { if (!me) { throw new Error('You must be logged in') } @@ -67,7 +67,7 @@ export default { throw new Error(`Invoice is not in failed state: ${invoice.actionState}`) } - const result = await retryPaidAction(invoice.actionType, { invoice }, { models, me, lnd }) + const result = await retryPaidAction(invoice.actionType, { invoice }, { models, me, lnd, lndk }) return { ...result, diff --git a/api/resolvers/wallet.js b/api/resolvers/wallet.js index 3df417a14..b937a8d3e 100644 --- a/api/resolvers/wallet.js +++ b/api/resolvers/wallet.js @@ -25,7 +25,7 @@ import { canReceive, getWalletByType } from '@/wallets/common' import performPaidAction from '../paidAction' import performPayingAction from '../payingAction' import { parseInvoice } from '@/api/lib/bolt' -import lnd from '@/api/lnd' +import lnd, { lndk } from '@/api/lnd' import { isBolt12Offer } from '@/api/lib/bolt/bolt12' import { fetchBolt12InvoiceFromOffer } from '@/api/lib/lndk' import { timeoutSignal, withTimeout } from '@/lib/time' @@ -35,7 +35,7 @@ function injectResolvers (resolvers) { for (const walletDef of walletDefs) { const resolverName = generateResolverName(walletDef.walletField) console.log(resolverName) - resolvers.Mutation[resolverName] = async (parent, { settings, validateLightning, vaultEntries, ...data }, { me, models, lnd }) => { + resolvers.Mutation[resolverName] = async (parent, { settings, validateLightning, vaultEntries, ...data }, { me, models, lnd, lndk }) => { console.log('resolving', resolverName, { settings, validateLightning, vaultEntries, ...data }) let existingVaultEntries @@ -75,6 +75,7 @@ function injectResolvers (resolvers) { walletDef.testCreateInvoice(data, { logger, lnd, + lndk, signal: timeoutSignal(WALLET_CREATE_INVOICE_TIMEOUT_MS) }), WALLET_CREATE_INVOICE_TIMEOUT_MS) @@ -477,13 +478,13 @@ const resolvers = { __resolveType: invoiceOrDirect => invoiceOrDirect.__resolveType }, Mutation: { - createInvoice: async (parent, { amount }, { me, models, lnd, headers }) => { + createInvoice: async (parent, { amount }, { me, models, lnd, lndk, headers }) => { await validateSchema(amountSchema, { amount }) await assertGofacYourself({ models, headers }) const { invoice, paymentMethod } = await performPaidAction('RECEIVE', { msats: satsToMsats(amount) - }, { models, lnd, me }) + }, { models, lnd, lndk, me }) return { ...invoice, @@ -494,7 +495,7 @@ const resolvers = { createWithdrawl: createWithdrawal, sendToLnAddr, sendToBolt12Offer, - cancelInvoice: async (parent, { hash, hmac, userCancel }, { me, models, lnd, boss }) => { + cancelInvoice: async (parent, { hash, hmac, userCancel }, { me, models, lnd, lndk, boss }) => { // stackers can cancel their own invoices without hmac if (me && !hmac) { const inv = await models.invoice.findUnique({ where: { hash } }) @@ -503,7 +504,7 @@ const resolvers = { } else { verifyHmac(hash, hmac) } - await finalizeHodlInvoice({ data: { hash }, lnd, models, boss }) + await finalizeHodlInvoice({ data: { hash }, lnd, lndk, models, boss }) return await models.invoice.update({ where: { hash }, data: { userCancel: !!userCancel } }) }, dropBolt11: async (parent, { hash }, { me, models, lnd }) => { @@ -753,7 +754,7 @@ export const walletLogger = ({ wallet, models }) => { try { if (context?.bolt11) { // automatically populate context from invoice to avoid duplicating this code - const decoded = await parseInvoice({ request: context.bolt11, lnd }) + const decoded = await parseInvoice({ request: context.bolt11, lnd, lndk }) context = { ...context, amount: formatMsats(decoded.mtokens), @@ -921,7 +922,7 @@ async function upsertWallet ( return upsertedWallet } -export async function createWithdrawal (parent, { invoice, maxFee }, { me, models, lnd, headers, wallet, logger }) { +export async function createWithdrawal (parent, { invoice, maxFee }, { me, models, lnd, lndk, headers, wallet, logger }) { assertApiKeyNotPermitted({ me }) await validateSchema(withdrawlSchema, { invoice, maxFee }) await assertGofacYourself({ models, headers }) @@ -932,7 +933,7 @@ export async function createWithdrawal (parent, { invoice, maxFee }, { me, model // decode invoice to get amount let decoded, sockets try { - decoded = await parseInvoice({ request: invoice, lnd }) + decoded = await parseInvoice({ request: invoice, lnd, lndk }) } catch (error) { console.log(error) throw new GqlInputError('could not decode invoice') @@ -971,11 +972,11 @@ export async function createWithdrawal (parent, { invoice, maxFee }, { me, model throw new GqlInputError('SN cannot pay an invoice that SN is proxying') } - return await performPayingAction({ bolt11: invoice, maxFee, walletId: wallet?.id }, { me, models, lnd }) + return await performPayingAction({ bolt11: invoice, maxFee, walletId: wallet?.id }, { me, models, lnd, lndk }) } export async function sendToLnAddr (parent, { addr, amount, maxFee, comment, ...payer }, - { me, models, lnd, headers }) { + { me, models, lnd, lndk, headers }) { if (!me) { throw new GqlAuthenticationError() } @@ -985,14 +986,15 @@ export async function sendToLnAddr (parent, { addr, amount, maxFee, comment, ... { me, models, - lnd + lnd, + lndk }) // take pr and createWithdrawl - return await createWithdrawal(parent, { invoice: res.pr, maxFee }, { me, models, lnd, headers }) + return await createWithdrawal(parent, { invoice: res.pr, maxFee }, { me, models, lnd, lndk, headers }) } -export async function sendToBolt12Offer (parent, { offer, amountSats, maxFee, comment }, { me, models, lnd, headers }) { +export async function sendToBolt12Offer (parent, { offer, amountSats, maxFee, comment }, { me, models, lnd, lndk, headers }) { if (!me) { throw new GqlAuthenticationError() } @@ -1000,14 +1002,14 @@ export async function sendToBolt12Offer (parent, { offer, amountSats, maxFee, co if (!isBolt12Offer(offer)) { throw new GqlInputError('not a bolt12 offer') } - const invoice = await fetchBolt12InvoiceFromOffer({ lnd, offer, msats: satsToMsats(amountSats), description: comment }) - return await createWithdrawal(parent, { invoice, maxFee }, { me, models, lnd, headers }) + const invoice = await fetchBolt12InvoiceFromOffer({ lnd, lndk, offer, msats: satsToMsats(amountSats), description: comment }) + return await createWithdrawal(parent, { invoice, maxFee }, { me, models, lnd, lndk, headers }) } export async function fetchLnAddrInvoice ( { addr, amount, maxFee, comment, ...payer }, { - me, models, lnd, autoWithdraw = false + me, models, lnd, lndk, autoWithdraw = false }) { const options = await lnAddrOptions(addr) await validateSchema(lnAddrSchema, { addr, amount, maxFee, comment, ...payer }, options) @@ -1044,7 +1046,7 @@ export async function fetchLnAddrInvoice ( // decode invoice try { - const decoded = await parseInvoice({ request: res.pr, lnd }) + const decoded = await parseInvoice({ request: res.pr, lnd, lndk }) const ourPubkey = await getOurPubkey({ lnd }) if (autoWithdraw && decoded.destination === ourPubkey && process.env.NODE_ENV === 'production') { // unset lnaddr so we don't trigger another withdrawal with same destination diff --git a/api/ssrApollo.js b/api/ssrApollo.js index 7af73317e..e4cf21f9b 100644 --- a/api/ssrApollo.js +++ b/api/ssrApollo.js @@ -5,7 +5,7 @@ import resolvers from './resolvers' import typeDefs from './typeDefs' import models from './models' import { print } from 'graphql' -import lnd from './lnd' +import lnd, { lndk } from './lnd' import search from './search' import { ME } from '@/fragments/users' import { PRICE } from '@/fragments/price' @@ -31,6 +31,7 @@ export default async function getSSRApolloClient ({ req, res, me = null }) { ? session.user : me, lnd, + lndk, search } }), diff --git a/pages/api/graphql.js b/pages/api/graphql.js index 9d6626e93..9e8b4375a 100644 --- a/pages/api/graphql.js +++ b/pages/api/graphql.js @@ -2,7 +2,7 @@ import { ApolloServer } from '@apollo/server' import { startServerAndCreateNextHandler } from '@as-integrations/next' import resolvers from '@/api/resolvers' import models from '@/api/models' -import lnd from '@/api/lnd' +import lnd, { lndk } from '@/api/lnd' import typeDefs from '@/api/typeDefs' import { getServerSession } from 'next-auth/next' import { getAuthOptions } from './auth/[...nextauth]' @@ -74,6 +74,7 @@ export default startServerAndCreateNextHandler(apolloServer, { models, headers: req.headers, lnd, + lndk, me: session ? session.user : null, diff --git a/pages/api/lnurlp/[username]/pay.js b/pages/api/lnurlp/[username]/pay.js index fc7e21220..37e0cee3a 100644 --- a/pages/api/lnurlp/[username]/pay.js +++ b/pages/api/lnurlp/[username]/pay.js @@ -1,5 +1,5 @@ import models from '@/api/models' -import lnd from '@/api/lnd' +import lnd, { lndk } from '@/api/lnd' import { lnurlPayDescriptionHashForUser, lnurlPayMetadataString, lnurlPayDescriptionHash } from '@/lib/lnurl' import { schnorr } from '@noble/curves/secp256k1' import { createHash } from 'crypto' @@ -85,7 +85,7 @@ export default async ({ query: { username, amount, nostr, comment, payerdata: pa comment: comment || '', lud18Data: parsedPayerData, noteStr - }, { models, lnd, me: user }) + }, { models, lnd, lndk, me: user }) if (!invoice?.bolt11) throw new Error('could not generate invoice') diff --git a/pages/api/lnwith.js b/pages/api/lnwith.js index ed22d499f..57ed88582 100644 --- a/pages/api/lnwith.js +++ b/pages/api/lnwith.js @@ -2,7 +2,7 @@ // send back import models from '@/api/models' import { datePivot } from '@/lib/time' -import lnd from '@/api/lnd' +import lnd, { lndk } from '@/api/lnd' import { createWithdrawal } from '@/api/resolvers/wallet' export default async ({ query, headers }, res) => { @@ -68,7 +68,7 @@ async function doWithdrawal (query, res, headers) { try { const withdrawal = await createWithdrawal(null, { invoice: query.pr, maxFee: me.withdrawMaxFeeDefault }, - { me, models, lnd, headers }) + { me, models, lnd, lndk, headers }) // store withdrawal id lnWith so client can show it await models.lnWith.update({ where: { k1: query.k1 }, data: { withdrawalId: Number(withdrawal.id) } }) diff --git a/wallets/bolt12/server.js b/wallets/bolt12/server.js index a39fdc484..de012023a 100644 --- a/wallets/bolt12/server.js +++ b/wallets/bolt12/server.js @@ -3,14 +3,14 @@ import { parseInvoice } from '@/api/lib/bolt' import { toPositiveNumber } from '@/lib/format' export * from '@/wallets/bolt12' -export async function testCreateInvoice ({ offer }, { lnd }) { - const invoice = await fetchBolt12InvoiceFromOffer({ lnd, offer, msats: 1000, description: 'test' }) - const parsedInvoice = await parseInvoice({ lnd, request: invoice }) +export async function testCreateInvoice ({ offer }, { lnd, lndk }) { + const invoice = await fetchBolt12InvoiceFromOffer({ lnd, lndk, offer, msats: 1000, description: 'test' }) + const parsedInvoice = await parseInvoice({ lnd, lndk, request: invoice }) if (toPositiveNumber(parsedInvoice.mtokens) !== 1000) throw new Error('invalid invoice response') return offer } -export async function createInvoice ({ msats, description, expiry }, { offer }, { lnd }) { - const invoice = await fetchBolt12InvoiceFromOffer({ lnd, offer, msats, description }) +export async function createInvoice ({ msats, description, expiry }, { offer }, { lnd, lndk }) { + const invoice = await fetchBolt12InvoiceFromOffer({ lnd, lndk, offer, msats, description }) return invoice } diff --git a/wallets/server.js b/wallets/server.js index d8459b34d..b3c166cee 100644 --- a/wallets/server.js +++ b/wallets/server.js @@ -26,7 +26,7 @@ export default [lnd, cln, lnAddr, lnbits, nwc, phoenixd, blink, lnc, webln, bolt const MAX_PENDING_INVOICES_PER_WALLET = 25 -export async function createInvoice (userId, { msats, description, descriptionHash, expiry = 360, supportBolt12 = true }, { predecessorId, models, lnd }) { +export async function createInvoice (userId, { msats, description, descriptionHash, expiry = 360, supportBolt12 = true }, { predecessorId, models, lnd, lndk }) { // get the wallets in order of priority const wallets = await getInvoiceableWallets(userId, { predecessorId, models }) @@ -48,7 +48,7 @@ export async function createInvoice (userId, { msats, description, descriptionHa invoice = await walletCreateInvoice( { wallet, def }, { msats, description, descriptionHash, expiry }, - { logger, models, lnd, supportBolt12 }) + { logger, models, lnd, lndk, supportBolt12 }) } catch (err) { throw new Error('failed to create invoice: ' + err.message) } @@ -61,7 +61,7 @@ export async function createInvoice (userId, { msats, description, descriptionHa throw new Error('the wallet returned a bolt12 offer, but an invoice was expected') } - const parsedInvoice = await parseInvoice({ lnd, request: invoice }) + const parsedInvoice = await parseInvoice({ lnd, lndk, request: invoice }) logger.info(`created invoice for ${formatSats(msatsToSats(parsedInvoice.mtokens))}`, { bolt11: invoice }) @@ -91,7 +91,7 @@ export async function createInvoice (userId, { msats, description, descriptionHa export async function createWrappedInvoice (userId, { msats, feePercent, description, descriptionHash, expiry = 360 }, - { predecessorId, models, me, lnd }) { + { predecessorId, models, me, lnd, lndk }) { let logger, bolt11 try { const { invoice, wallet } = await createInvoice(userId, { @@ -100,12 +100,12 @@ export async function createWrappedInvoice (userId, description, descriptionHash, expiry - }, { predecessorId, models, lnd }) + }, { predecessorId, models, lnd, lndk }) logger = walletLogger({ wallet, models }) bolt11 = invoice const { invoice: wrappedInvoice, maxFee } = - await wrapInvoice({ bolt11, feePercent }, { msats, description, descriptionHash }, { me, lnd }) + await wrapInvoice({ bolt11, feePercent }, { msats, description, descriptionHash }, { me, lnd, lndk }) return { invoice, @@ -175,7 +175,7 @@ async function walletCreateInvoice ({ wallet, def }, { description, descriptionHash, expiry = 360 -}, { logger, models, lnd }) { +}, { logger, models, lnd, lndk }) { // check for pending withdrawals const pendingWithdrawals = await models.withdrawl.count({ where: { @@ -213,6 +213,7 @@ async function walletCreateInvoice ({ wallet, def }, { { logger, lnd, + lndk, signal: timeoutSignal(WALLET_CREATE_INVOICE_TIMEOUT_MS) } ), WALLET_CREATE_INVOICE_TIMEOUT_MS) diff --git a/wallets/wrap.js b/wallets/wrap.js index b7d230938..eb9d0dc91 100644 --- a/wallets/wrap.js +++ b/wallets/wrap.js @@ -29,7 +29,7 @@ const MAX_FEE_ESTIMATE_PERCENT = 3n // the maximum fee relative to outgoing we'l maxFee: number } */ -export default async function wrapInvoice ({ bolt11, feePercent }, { msats, description, descriptionHash }, { me, lnd }) { +export default async function wrapInvoice ({ bolt11, feePercent }, { msats, description, descriptionHash }, { me, lnd, lndk }) { try { console.group('wrapInvoice', description) @@ -38,7 +38,7 @@ export default async function wrapInvoice ({ bolt11, feePercent }, { msats, desc let outgoingMsat // decode the invoice - const inv = await parseInvoice({ request: bolt11, lnd }) + const inv = await parseInvoice({ request: bolt11, lnd, lndk }) if (!inv) { throw new Error('Unable to decode invoice') } @@ -150,6 +150,7 @@ export default async function wrapInvoice ({ bolt11, feePercent }, { msats, desc const { routingFeeMsat, timeLockDelay } = await estimateFees({ lnd, + lndk, destination: inv.destination, mtokens: inv.mtokens, request: bolt11, diff --git a/worker/autowithdraw.js b/worker/autowithdraw.js index deb0954d7..29bd246e9 100644 --- a/worker/autowithdraw.js +++ b/worker/autowithdraw.js @@ -2,7 +2,7 @@ import { msatsSatsFloor, msatsToSats, satsToMsats } from '@/lib/format' import { createWithdrawal } from '@/api/resolvers/wallet' import { createInvoice } from '@/wallets/server' -export async function autoWithdraw ({ data: { id }, models, lnd }) { +export async function autoWithdraw ({ data: { id }, models, lnd, lndk }) { const user = await models.user.findUnique({ where: { id } }) if ( user.autoWithdrawThreshold === null || @@ -42,12 +42,12 @@ export async function autoWithdraw ({ data: { id }, models, lnd }) { if (pendingOrFailed.exists) return - const { invoice, wallet, logger } = await createInvoice(id, { msats, description: 'SN: autowithdrawal', expiry: 360 }, { models, lnd }) + const { invoice, wallet, logger } = await createInvoice(id, { msats, description: 'SN: autowithdrawal', expiry: 360 }, { models, lnd, lndk }) try { return await createWithdrawal(null, { invoice, maxFee: msatsToSats(maxFeeMsats) }, - { me: { id }, models, lnd, wallet, logger }) + { me: { id }, models, lnd, lndk, wallet, logger }) } catch (err) { logger.error(`incoming payment failed: ${err}`, { bolt11: invoice }) throw err diff --git a/worker/index.js b/worker/index.js index f80643613..0704e01cc 100644 --- a/worker/index.js +++ b/worker/index.js @@ -17,7 +17,7 @@ import { computeStreaks, checkStreak } from './streak' import { nip57 } from './nostr' import fetch from 'cross-fetch' import { authenticatedLndGrpc } from '@/lib/lnd' -import { enableLNDK } from '@/api/lib/lndk' +import { authenticatedLndkGrpc } from '@/api/lib/lndk' import { views, rankViews } from './views' import { imgproxy } from './imgproxy' import { deleteItem } from './ephemeralItems' @@ -74,13 +74,14 @@ async function work () { macaroon: process.env.LND_MACAROON, socket: process.env.LND_SOCKET }) - enableLNDK(lnd, { + + const lndk = authenticatedLndkGrpc({ cert: process.env.LNDK_CERT, macaroon: process.env.LNDK_MACAROON, socket: process.env.LNDK_SOCKET }) - const args = { boss, models, apollo, lnd } + const args = { boss, models, apollo, lnd, lndk } boss.on('error', error => console.error(error)) diff --git a/worker/paidAction.js b/worker/paidAction.js index c26bf2f8f..47de8921c 100644 --- a/worker/paidAction.js +++ b/worker/paidAction.js @@ -194,7 +194,7 @@ export async function paidActionPaid ({ data: { invoiceId, ...args }, models, ln } // this performs forward creating the outgoing payment -export async function paidActionForwarding ({ data: { invoiceId, ...args }, models, lnd, boss }) { +export async function paidActionForwarding ({ data: { invoiceId, ...args }, models, lnd, lndk, boss }) { const transitionedInvoice = await transitionInvoice('paidActionForwarding', { invoiceId, fromState: 'PENDING_HELD', @@ -211,7 +211,7 @@ export async function paidActionForwarding ({ data: { invoiceId, ...args }, mode const { expiryHeight, acceptHeight } = hodlInvoiceCltvDetails(lndInvoice) const { bolt11, maxFeeMsats } = invoiceForward - const invoice = await parseInvoice({ request: bolt11, lnd }) + const invoice = await parseInvoice({ request: bolt11, lnd, lndk }) // maxTimeoutDelta is the number of blocks left for the outgoing payment to settle const maxTimeoutDelta = toPositiveNumber(expiryHeight) - toPositiveNumber(acceptHeight) - MIN_SETTLEMENT_CLTV_DELTA if (maxTimeoutDelta - toPositiveNumber(invoice.cltv_delta) < 0) { @@ -267,6 +267,7 @@ export async function paidActionForwarding ({ data: { invoiceId, ...args }, mode payInvoice({ lnd, + lndk, request: bolt11, max_fee_mtokens: String(maxFeeMsats), pathfinding_timeout: LND_PATHFINDING_TIMEOUT_MS, @@ -426,7 +427,7 @@ export async function paidActionHeld ({ data: { invoiceId, ...args }, models, ln }, { models, lnd, boss }) } -export async function paidActionCanceling ({ data: { invoiceId, ...args }, models, lnd, boss }) { +export async function paidActionCanceling ({ data: { invoiceId, ...args }, models, lnd, lndk, boss }) { const transitionedInvoice = await transitionInvoice('paidActionCanceling', { invoiceId, fromState: ['HELD', 'PENDING', 'PENDING_HELD', 'FAILED_FORWARD'], @@ -445,7 +446,7 @@ export async function paidActionCanceling ({ data: { invoiceId, ...args }, model if (transitionedInvoice.invoiceForward) { const { wallet, bolt11 } = transitionedInvoice.invoiceForward const logger = walletLogger({ wallet, models }) - const decoded = await parseInvoice({ request: bolt11, lnd }) + const decoded = await parseInvoice({ request: bolt11, lnd, lndk }) logger.info(`invoice for ${formatSats(msatsToSats(decoded.mtokens))} canceled by payer`, { bolt11 }) } } diff --git a/worker/wallet.js b/worker/wallet.js index ac09c7ac4..035bc54ef 100644 --- a/worker/wallet.js +++ b/worker/wallet.js @@ -115,7 +115,7 @@ function subscribeToHodlInvoice (args) { // if we already have the invoice from a subscription event or previous call, // we can skip a getInvoice call -export async function checkInvoice ({ data: { hash, invoice }, boss, models, lnd }) { +export async function checkInvoice ({ data: { hash, invoice }, boss, models, lnd, lndk }) { const inv = invoice ?? await getInvoice({ id: hash, lnd }) // invoice could be created by LND but wasn't inserted into the database yet @@ -148,7 +148,7 @@ export async function checkInvoice ({ data: { hash, invoice }, boss, models, lnd // transitions when held are dependent on the withdrawl status return await checkWithdrawal({ data: { hash: dbInv.invoiceForward.withdrawl.hash, invoice: inv }, models, lnd, boss }) } - return await paidActionForwarding({ data: { invoiceId: dbInv.id, invoice: inv }, models, lnd, boss }) + return await paidActionForwarding({ data: { invoiceId: dbInv.id, invoice: inv }, models, lnd, lndk, boss }) } return await paidActionHeld({ data: { invoiceId: dbInv.id, invoice: inv }, models, lnd, boss }) } @@ -241,7 +241,7 @@ export async function checkWithdrawal ({ data: { hash, withdrawal, invoice }, bo // The callback subscriptions above will NOT get called for JIT invoices that are already paid. // So we manually cancel the HODL invoice here if it wasn't settled by user action -export async function finalizeHodlInvoice ({ data: { hash }, models, lnd, boss, ...args }) { +export async function finalizeHodlInvoice ({ data: { hash }, models, lnd, lndk, boss, ...args }) { const inv = await getInvoice({ id: hash, lnd }) if (inv.is_confirmed) { return @@ -256,7 +256,7 @@ export async function finalizeHodlInvoice ({ data: { hash }, models, lnd, boss, await paidActionCanceling({ data: { invoiceId: dbInv.id, invoice: inv }, models, lnd, boss }) // sync LND invoice status with invoice status in database - await checkInvoice({ data: { hash }, models, lnd, boss }) + await checkInvoice({ data: { hash }, models, lnd, lndk, boss }) } export async function checkPendingDeposits (args) { diff --git a/worker/weeklyPosts.js b/worker/weeklyPosts.js index 275b23bab..02c55f677 100644 --- a/worker/weeklyPosts.js +++ b/worker/weeklyPosts.js @@ -22,7 +22,7 @@ export async function weeklyPost (args) { } } -export async function payWeeklyPostBounty ({ data: { id }, models, apollo, lnd }) { +export async function payWeeklyPostBounty ({ data: { id }, models, apollo, lnd, lndk }) { const itemQ = await apollo.query({ query: gql` query item($id: ID!) { @@ -56,6 +56,7 @@ export async function payWeeklyPostBounty ({ data: { id }, models, apollo, lnd } models, me: { id: USER_ID.sn }, lnd, + lndk, forcePaymentMethod: PAID_ACTION_PAYMENT_METHODS.FEE_CREDIT }) }