From 5b2e835722866aa98dc22fb59fa7ff8a26343d56 Mon Sep 17 00:00:00 2001 From: ekzyis Date: Tue, 16 Jul 2024 15:45:32 +0200 Subject: [PATCH] Separate client and server imports by files * wallets now consist of an index.js, a client.js and a server.js file * client.js is imported on the client and contains the client portion * server.js is imported on the server and contains the server porition * both reexport index.js so everything in index.js can be shared by client and server * every wallet contains a client.js file since they are all imported on the client to show the cards * client.js of every wallet is reexported as an array in wallets/client.js * server.js of every wallet is reexported as an array in wallets/server.js FIXME: for some reason, worker does not properly import the default export of wallets/server.js --- api/resolvers/wallet.js | 88 +++++++++++++---- api/typeDefs/wallet.js | 7 +- components/wallet-logger.js | 6 +- lib/wallet.js | 63 ------------ next.config.js | 3 + wallets/_example.js | 147 ---------------------------- wallets/client.js | 8 ++ wallets/cln/client.js | 1 + wallets/cln/index.js | 3 - wallets/cln/server.js | 75 +++++++------- wallets/index.js | 20 ++-- wallets/lightning-address/client.js | 1 + wallets/lightning-address/index.js | 3 - wallets/lightning-address/server.js | 55 ++++++----- wallets/lnbits/client.js | 2 + wallets/lnbits/index.js | 3 - wallets/lnc/client.js | 2 + wallets/lnc/index.js | 3 - wallets/lnd/client.js | 1 + wallets/lnd/index.js | 3 - wallets/lnd/server.js | 85 ++++++++-------- wallets/nwc/client.js | 2 + wallets/nwc/index.js | 3 - wallets/server.js | 8 ++ worker/autowithdraw.js | 23 ++--- 25 files changed, 234 insertions(+), 381 deletions(-) delete mode 100644 wallets/_example.js create mode 100644 wallets/client.js create mode 100644 wallets/cln/client.js create mode 100644 wallets/lightning-address/client.js create mode 100644 wallets/lnd/client.js create mode 100644 wallets/server.js diff --git a/api/resolvers/wallet.js b/api/resolvers/wallet.js index dd1d8847..2a468acc 100644 --- a/api/resolvers/wallet.js +++ b/api/resolvers/wallet.js @@ -1,32 +1,30 @@ -import { getIdentity, createHodlInvoice, createInvoice, decodePaymentRequest, payViaPaymentRequest, cancelHodlInvoice, getInvoice as getInvoiceFromLnd, getNode, authenticatedLndGrpc, deletePayment, getPayment } from 'ln-service' +import { createHodlInvoice, createInvoice, decodePaymentRequest, payViaPaymentRequest, cancelHodlInvoice, getInvoice as getInvoiceFromLnd, getNode, deletePayment, getPayment, getIdentity } from 'ln-service' import { GraphQLError } from 'graphql' import crypto, { timingSafeEqual } from 'crypto' import serialize from './serial' import { decodeCursor, LIMIT, nextCursorEncoded } from '@/lib/cursor' import { SELECT, itemQueryWithMeta } from './item' import { msatsToSats, msatsToSatsDecimal } from '@/lib/format' -import { amountSchema, ssValidate, withdrawlSchema } from '@/lib/validate' +import { amountSchema, ssValidate, withdrawlSchema, lnAddrSchema } from '@/lib/validate' import { ANON_BALANCE_LIMIT_MSATS, ANON_INV_PENDING_LIMIT, USER_ID, BALANCE_LIMIT_MSATS, INVOICE_RETENTION_DAYS, INV_PENDING_LIMIT, USER_IDS_BALANCE_NO_LIMIT } from '@/lib/constants' import { datePivot } from '@/lib/time' import assertGofacYourself from './ofac' import assertApiKeyNotPermitted from './apiKey' -import { createInvoice as clnCreateInvoice } from '@/lib/cln' import { bolt11Tags } from '@/lib/bolt11' import { checkInvoice } from 'worker/wallet' -import * as lnd from 'wallets/lnd' -import * as lnAddr from 'wallets/lightning-address' -import * as cln from 'wallets/cln' -import { fetchLnAddrInvoice, generateResolverName } from '@/lib/wallet' - -export const SERVER_WALLET_DEFS = [lnd, lnAddr, cln] +import walletDefs from 'wallets/server' +import { generateResolverName } from '@/lib/wallet' +import { lnAddrOptions } from '@/lib/lnurl' function injectResolvers (resolvers) { console.group('injected GraphQL resolvers:') for ( - const w of SERVER_WALLET_DEFS) { + // FIXME: this throws + // TypeError: import_server.default is not iterable + const w of walletDefs) { const { schema, - server: { walletType, walletField, testConnect } + walletType, walletField, testConnect // app and worker import file differently } = w.default || w const resolverName = generateResolverName(walletField) @@ -40,10 +38,7 @@ function injectResolvers (resolvers) { data, { me, - models, - addWalletLog, - lnService: { authenticatedLndGrpc, createInvoice }, - cln: { createInvoice: clnCreateInvoice } + models } ) }, { settings, data }, { me, models }) @@ -726,10 +721,69 @@ export async function sendToLnAddr (parent, { addr, amount, maxFee, comment, ... { me, models, - lnd, - lnService: { decodePaymentRequest, getIdentity } + lnd }) // take pr and createWithdrawl return await createWithdrawal(parent, { invoice: res.pr, maxFee }, { me, models, lnd, headers }) } + +export async function fetchLnAddrInvoice ( + { addr, amount, maxFee, comment, ...payer }, + { + me, models, lnd, autoWithdraw = false + }) { + const options = await lnAddrOptions(addr) + await ssValidate(lnAddrSchema, { addr, amount, maxFee, comment, ...payer }, options) + + if (payer) { + payer = { + ...payer, + identifier: payer.identifier ? `${me.name}@stacker.news` : undefined + } + payer = Object.fromEntries( + Object.entries(payer).filter(([, value]) => !!value) + ) + } + + const milliamount = 1000 * amount + const callback = new URL(options.callback) + callback.searchParams.append('amount', milliamount) + + if (comment?.length) { + callback.searchParams.append('comment', comment) + } + + let stringifiedPayerData = '' + if (payer && Object.entries(payer).length) { + stringifiedPayerData = JSON.stringify(payer) + callback.searchParams.append('payerdata', stringifiedPayerData) + } + + // call callback with amount and conditionally comment + const res = await (await fetch(callback.toString())).json() + if (res.status === 'ERROR') { + throw new Error(res.reason) + } + + // decode invoice + try { + const decoded = await decodePaymentRequest({ lnd, request: res.pr }) + const ourPubkey = (await getIdentity({ lnd })).public_key + if (autoWithdraw && decoded.destination === ourPubkey && process.env.NODE_ENV === 'production') { + // unset lnaddr so we don't trigger another withdrawal with same destination + await models.wallet.deleteMany({ + where: { userId: me.id, type: 'LIGHTNING_ADDRESS' } + }) + throw new Error('automated withdrawals to other stackers are not allowed') + } + if (!decoded.mtokens || BigInt(decoded.mtokens) !== BigInt(milliamount)) { + throw new Error('invoice has incorrect amount') + } + } catch (e) { + console.log(e) + throw e + } + + return res +} diff --git a/api/typeDefs/wallet.js b/api/typeDefs/wallet.js index 50769b06..b8e9935a 100644 --- a/api/typeDefs/wallet.js +++ b/api/typeDefs/wallet.js @@ -1,10 +1,11 @@ import { gql } from 'graphql-tag' -import { SERVER_WALLET_DEFS } from '@/api/resolvers/wallet' import { generateResolverName } from '@/lib/wallet' +import walletDefs from 'wallets/server' + function injectTypeDefs (typeDefs) { console.group('injected GraphQL type defs:') - const injected = SERVER_WALLET_DEFS.map( + const injected = walletDefs.map( (w) => { let args = 'id: ID, ' args += w.fields.map(f => { @@ -15,7 +16,7 @@ function injectTypeDefs (typeDefs) { return arg }).join(', ') args += ', settings: AutowithdrawSettings!' - const resolverName = generateResolverName(w.server.walletField) + const resolverName = generateResolverName(w.walletField) const typeDef = `${resolverName}(${args}): Boolean` console.log(typeDef) return typeDef diff --git a/components/wallet-logger.js b/components/wallet-logger.js index 02e8a280..361bb319 100644 --- a/components/wallet-logger.js +++ b/components/wallet-logger.js @@ -5,7 +5,7 @@ import { Button } from 'react-bootstrap' import { useToast } from './toast' import { useShowModal } from './modal' import { WALLET_LOGS } from '@/fragments/wallet' -import { getServerWallet } from 'wallets' +import { getWalletByType } from 'wallets' import { gql, useMutation, useQuery } from '@apollo/client' import { useMe } from './me' @@ -128,7 +128,7 @@ export const WalletLoggerProvider = ({ children }) => { .map(({ createdAt, wallet: walletType, ...log }) => { return { ts: +new Date(createdAt), - wallet: tag(getServerWallet(walletType)), + wallet: tag(getWalletByType(walletType)), ...log } }) @@ -146,7 +146,7 @@ export const WalletLoggerProvider = ({ children }) => { { onCompleted: (_, { variables: { wallet: walletType } }) => { setLogs((logs) => { - return logs.filter(l => walletType ? l.wallet !== getServerWallet(walletType).name : false) + return logs.filter(l => walletType ? l.wallet !== getWalletByType(walletType).name : false) }) } } diff --git a/lib/wallet.js b/lib/wallet.js index fba35541..bff1d915 100644 --- a/lib/wallet.js +++ b/lib/wallet.js @@ -1,66 +1,3 @@ -import { lnAddrOptions } from './lnurl' -import { lnAddrSchema, ssValidate } from './validate' - -export async function fetchLnAddrInvoice ({ addr, amount, maxFee, comment, ...payer }, - { - me, models, lnd, autoWithdraw = false, - lnService: { decodePaymentRequest, getIdentity } - }) { - const options = await lnAddrOptions(addr) - await ssValidate(lnAddrSchema, { addr, amount, maxFee, comment, ...payer }, options) - - if (payer) { - payer = { - ...payer, - identifier: payer.identifier ? `${me.name}@stacker.news` : undefined - } - payer = Object.fromEntries( - Object.entries(payer).filter(([, value]) => !!value) - ) - } - - const milliamount = 1000 * amount - const callback = new URL(options.callback) - callback.searchParams.append('amount', milliamount) - - if (comment?.length) { - callback.searchParams.append('comment', comment) - } - - let stringifiedPayerData = '' - if (payer && Object.entries(payer).length) { - stringifiedPayerData = JSON.stringify(payer) - callback.searchParams.append('payerdata', stringifiedPayerData) - } - - // call callback with amount and conditionally comment - const res = await (await fetch(callback.toString())).json() - if (res.status === 'ERROR') { - throw new Error(res.reason) - } - - // decode invoice - try { - const decoded = await decodePaymentRequest({ lnd, request: res.pr }) - const ourPubkey = (await getIdentity({ lnd })).public_key - if (autoWithdraw && decoded.destination === ourPubkey && process.env.NODE_ENV === 'production') { - // unset lnaddr so we don't trigger another withdrawal with same destination - await models.wallet.deleteMany({ - where: { userId: me.id, type: 'LIGHTNING_ADDRESS' } - }) - throw new Error('automated withdrawals to other stackers are not allowed') - } - if (!decoded.mtokens || BigInt(decoded.mtokens) !== BigInt(milliamount)) { - throw new Error('invoice has incorrect amount') - } - } catch (e) { - console.log(e) - throw e - } - - return res -} - export function generateResolverName (walletField) { const capitalized = walletField[0].toUpperCase() + walletField.slice(1) return `upsertWallet${capitalized}` diff --git a/next.config.js b/next.config.js index 00b51f49..6a6c02ed 100644 --- a/next.config.js +++ b/next.config.js @@ -232,7 +232,10 @@ module.exports = withPlausibleProxy()({ }) } + // const ignorePlugin = new webpack.IgnorePlugin({ resourceRegExp: /server\.js$/ }) + config.plugins.push(workboxPlugin) + // config.plugins.push(ignorePlugin) } config.module.rules.push( diff --git a/wallets/_example.js b/wallets/_example.js deleted file mode 100644 index c4a2369d..00000000 --- a/wallets/_example.js +++ /dev/null @@ -1,147 +0,0 @@ -import { lnbitsSchema } from '@/lib/validate' - -// ~~~ -// AFTER YOU HAVE FILLED OUT THIS TEMPLATE, IMPORT THIS FILE IN components/wallet/index.js -// AND ADD IT TO THE `WALLET_DEFS` array. -// DO THE SAME IN api/resolvers/wallet.js WITH THE `SERVER_WALLET_DEFS` ARRAY. -// (these arrays are separate to avoid backend imports in frontend) -// ~~~ - -// This name is used to identify this wallet and thus must be unique. -// It is used with the useWallet hook to select this wallet, see components/wallet/index.js. -// [required] -export const name = 'lnbits-as-an-example' - -// The form to configure this wallet is generated from these fields, -// see the component in pages/settings/wallets/[wallet].js. -// -// -// If not handled otherwise in , field properties are simply -// passed into or as props (component depends on 'type'). -// -// For example, the following fields will generate a config in this shape (depending on user inputs): -// { -// url: 'https://demo.lnbits.com/', -// adminKey: 'a47acd6feba4489e9e99b256b4ae9049' -// } -// [required] -export const fields = [ - { - name: 'url', - label: 'lnbits url', - // 'type' can be 'text' or 'password' - type: 'text' - }, - { - name: 'adminKey', - label: 'admin key', - type: 'password' - // see other wallets for more complex fields - } -] - -// Used to display information about this wallet to the user in the wallet list or during configuration, -// see components/wallet-card.js and pages/settings/wallets/[wallet].js. -// [required] -export const card = { - title: 'LNbits', - // subtitle supports markdown - subtitle: 'use [LNbits](https://lnbits.com/) for payments', - badges: ['send only', 'non-custodialish'] -} - -// The validation schema that will be used on the client and server during save -// [required] -export const schema = lnbitsSchema - -// This optional function will be called during save to abort the save if the configuration is invalid. -// It receives the config and context as arguments. -// It must throw an error if validation fails. -// [optional] -export async function validate (config, context) { - // what the config object will contain is determined by the fields array above - // const { url, adminKey } = config - - // the context includes the logger and other useful stuff, see save method in components/wallet/index.js - const { logger } = context - - // validate should log useful, wallet-specific information for the user - logger.info('running some wallet-specific validation') - - // ... - // throw error if validation failed -} - -// If this wallet supports payments, you need to implement this function: -// -// sendPayment: (bolt11, config, context) => Promise<{ preimage: string }> -// -// [required for payments] -export async function sendPayment (bolt11, config, context) { - // ... -} - -// If this wallet supports receiving, you need to implement this object. -// [required for receiving] -export const server = { - // This must match a WalletType enum value in the database - // since it will be used to fetch this wallet using the WALLET_BY_TYPE GraphQL query, - // see `useServerConfig` in components/wallet/index.js. - // [required] - walletType: 'LNBITS', - - // This used must match a column of the 'Wallet' table - // since it will be used to save the wallet configuration. - // [required] - walletField: 'walletLNbits', - - // Similar to validate above, this function should throw an error if the connection test fails. - // It is called on save on the server before writing the configuration to the database. - // As the name suggests, a good idea is to try to connect to the wallet and create an invoice in this function. - // [required] - testConnect: async ( - // Wallet configuration as entered by the user - config, - // Context object with useful stuff, see `injectResolvers` in pages/api/resolvers/wallet.js. - { - me, - models, - addWalletLog, - lnService: { authenticatedLndGrpc, createInvoice }, - cln: { createInvoice: clnCreateInvoice } - } - ) => { - - // ... - // throw error if validation failed - // (logging errors is handled by calling context but you can add custom logging on success here) - }, - - // This function is called during autowithdrawals. - // It should return a bolt11 payment request. - // - // createInvoice: ({ amount, maxFee }, config, context) => Promise - // - createInvoice: async ( - { amount, maxFee }, - { socket, rune, cert }, - // Context object with useful stuff, see `autowithdraw` function in worker/autowithdraw.js. - { - me, - models, - // SN LND node instance - lnd, - lnService: { - authenticatedLndGrpc, - createInvoice: lndCreateInvoice, - getIdentity, - decodePaymentRequest - }, - cln: { - createInvoice: clnCreateInvoice - } - } - ) => { - // ... create invoice and return bolt11 that the SN node will pay - } -} diff --git a/wallets/client.js b/wallets/client.js new file mode 100644 index 00000000..2cfffcec --- /dev/null +++ b/wallets/client.js @@ -0,0 +1,8 @@ +import * as nwc from 'wallets/nwc/client' +import * as lnbits from 'wallets/lnbits/client' +import * as lnc from 'wallets/lnc/client' +import * as lnAddr from 'wallets/lightning-address/client' +import * as cln from 'wallets/cln/client' +import * as lnd from 'wallets/lnd/client' + +export default [nwc, lnbits, lnc, lnAddr, cln, lnd] diff --git a/wallets/cln/client.js b/wallets/cln/client.js new file mode 100644 index 00000000..d9192b05 --- /dev/null +++ b/wallets/cln/client.js @@ -0,0 +1 @@ +export * from 'wallets/cln' diff --git a/wallets/cln/index.js b/wallets/cln/index.js index 9644ca04..04a3bc5b 100644 --- a/wallets/cln/index.js +++ b/wallets/cln/index.js @@ -1,5 +1,4 @@ import { CLNAutowithdrawSchema } from '@/lib/validate' -import { server } from 'wallets/cln/server' export const name = 'cln' @@ -41,5 +40,3 @@ export const card = { } export const schema = CLNAutowithdrawSchema - -export { server } diff --git a/wallets/cln/server.js b/wallets/cln/server.js index 64fe379b..a8ca224f 100644 --- a/wallets/cln/server.js +++ b/wallets/cln/server.js @@ -1,39 +1,44 @@ import { ensureB64 } from '@/lib/format' +import { createInvoice as clnCreateInvoice } from '@/lib/cln' +import { addWalletLog } from '@/api/resolvers/wallet' -export const server = { - walletType: 'CLN', - walletField: 'walletCLN', - testConnect: async ( - { socket, rune, cert }, - { me, models, addWalletLog, cln: { createInvoice } } - ) => { - cert = ensureB64(cert) - const inv = await createInvoice({ - socket, - rune, - cert, - description: 'SN connection test', - msats: 'any', - expiry: 0 - }) - await addWalletLog({ wallet: { type: 'CLN' }, level: 'SUCCESS', message: 'connected to CLN' }, { me, models }) - return inv - }, - createInvoice: async ( - { amount }, - { socket, rune, cert }, - { me, models, lnd, cln: { createInvoice } } - ) => { - cert = ensureB64(cert) +export * from 'wallets/cln' - const inv = await createInvoice({ - socket, - rune, - cert, - description: me.hideInvoiceDesc ? undefined : 'autowithdraw to CLN from SN', - msats: amount + 'sat', - expiry: 360 - }) - return inv.bolt11 - } +export const walletType = 'CLN' + +export const walletField = 'walletCLN' + +export const testConnect = async ( + { socket, rune, cert }, + { me, models } +) => { + cert = ensureB64(cert) + const inv = await clnCreateInvoice({ + socket, + rune, + cert, + description: 'SN connection test', + msats: 'any', + expiry: 0 + }) + await addWalletLog({ wallet: { type: 'CLN' }, level: 'SUCCESS', message: 'connected to CLN' }, { me, models }) + return inv +} + +export const createInvoice = async ( + { amount }, + { socket, rune, cert }, + { me, models, lnd } +) => { + cert = ensureB64(cert) + + const inv = await clnCreateInvoice({ + socket, + rune, + cert, + description: me.hideInvoiceDesc ? undefined : 'autowithdraw to CLN from SN', + msats: amount + 'sat', + expiry: 360 + }) + return inv.bolt11 } diff --git a/wallets/index.js b/wallets/index.js index 05c6ca65..d2665d48 100644 --- a/wallets/index.js +++ b/wallets/index.js @@ -5,12 +5,7 @@ import { useWalletLogger } from '@/components/wallet-logger' import { SSR } from '@/lib/constants' import { bolt11Tags } from '@/lib/bolt11' -import * as lnbits from 'wallets/lnbits' -import * as nwc from 'wallets/nwc' -import * as lnc from 'wallets/lnc' -import * as lnd from 'wallets/lnd' -import * as lnAddr from 'wallets/lightning-address' -import * as cln from 'wallets/cln' +import walletDefs from 'wallets/client' import { gql, useApolloClient, useQuery } from '@apollo/client' import { REMOVE_WALLET, WALLET_BY_TYPE } from '@/fragments/wallet' import { autowithdrawInitial } from '@/components/autowithdraw-shared' @@ -18,9 +13,6 @@ import { useShowModal } from '@/components/modal' import { useToast } from '../components/toast' import { generateResolverName } from '@/lib/wallet' -// wallet definitions -export const WALLET_DEFS = [lnbits, nwc, lnc, lnd, lnAddr, cln] - export const Status = { Initialized: 'Initialized', Enabled: 'Enabled', @@ -249,15 +241,15 @@ function generateMutation (wallet) { } export function getWalletByName (name) { - return WALLET_DEFS.find(def => def.name === name) + return walletDefs.find(def => def.name === name) } -export function getServerWallet (type) { - return WALLET_DEFS.find(def => def.server?.walletType === type) +export function getWalletByType (type) { + return walletDefs.find(def => def.server?.walletType === type) } export function getEnabledWallet (me) { - return WALLET_DEFS + return walletDefs .filter(def => !!def.sendPayment) .map(def => { // populate definition with properties from useWallet that are required for sorting @@ -290,7 +282,7 @@ export function walletPrioritySort (w1, w2) { } export function useWallets () { - const wallets = WALLET_DEFS.map(def => useWallet(def.name)) + const wallets = walletDefs.map(def => useWallet(def.name)) const resetClient = useCallback(async (wallet) => { for (const w of wallets) { diff --git a/wallets/lightning-address/client.js b/wallets/lightning-address/client.js new file mode 100644 index 00000000..004c4e76 --- /dev/null +++ b/wallets/lightning-address/client.js @@ -0,0 +1 @@ +export * from 'wallets/lightning-address' diff --git a/wallets/lightning-address/index.js b/wallets/lightning-address/index.js index 4543d608..496a248f 100644 --- a/wallets/lightning-address/index.js +++ b/wallets/lightning-address/index.js @@ -1,5 +1,4 @@ import { lnAddrAutowithdrawSchema } from '@/lib/validate' -import { server } from 'wallets/lightning-address/server' export const name = 'lightning-address' export const shortName = 'lnAddr' @@ -20,5 +19,3 @@ export const card = { } export const schema = lnAddrAutowithdrawSchema - -export { server } diff --git a/wallets/lightning-address/server.js b/wallets/lightning-address/server.js index b06099cf..22b184ad 100644 --- a/wallets/lightning-address/server.js +++ b/wallets/lightning-address/server.js @@ -1,29 +1,32 @@ +import { fetchLnAddrInvoice } from '@/api/resolvers/wallet' import { lnAddrOptions } from '@/lib/lnurl' -import { fetchLnAddrInvoice } from '@/lib/wallet' -export const server = { - walletType: 'LIGHTNING_ADDRESS', - walletField: 'walletLightningAddress', - testConnect: async ( - { address }, - { me, models, addWalletLog } - ) => { - const options = await lnAddrOptions(address) - await addWalletLog({ wallet: { type: 'LIGHTNING_ADDRESS' }, level: 'SUCCESS', message: 'fetched payment details' }, { me, models }) - return options - }, - createInvoice: async ( - { amount, maxFee }, - { address }, - { me, models, lnd, lnService } - ) => { - const res = await fetchLnAddrInvoice({ addr: address, amount, maxFee }, { - me, - models, - lnd, - lnService, - autoWithdraw: true - }) - return res.pr - } +export * from 'wallets/lightning-address' + +export const walletType = 'LIGHTNING_ADDRESS' + +export const walletField = 'walletLightningAddress' + +export const testConnect = async ( + { address }, + { me, models, addWalletLog } +) => { + const options = await lnAddrOptions(address) + await addWalletLog({ wallet: { type: 'LIGHTNING_ADDRESS' }, level: 'SUCCESS', message: 'fetched payment details' }, { me, models }) + return options +} + +export const createInvoice = async ( + { amount, maxFee }, + { address }, + { me, models, lnd, lnService } +) => { + const res = await fetchLnAddrInvoice({ addr: address, amount, maxFee }, { + me, + models, + lnd, + lnService, + autoWithdraw: true + }) + return res.pr } diff --git a/wallets/lnbits/client.js b/wallets/lnbits/client.js index 1cbfa224..8598c574 100644 --- a/wallets/lnbits/client.js +++ b/wallets/lnbits/client.js @@ -1,3 +1,5 @@ +export * from 'wallets/lnbits' + export async function validate ({ url, adminKey }, { logger }) { logger.info('trying to fetch wallet') diff --git a/wallets/lnbits/index.js b/wallets/lnbits/index.js index 2194bfa6..f3f23aa8 100644 --- a/wallets/lnbits/index.js +++ b/wallets/lnbits/index.js @@ -1,5 +1,4 @@ import { lnbitsSchema } from '@/lib/validate' -import { sendPayment, validate } from 'wallets/lnbits/client' export const name = 'lnbits' @@ -23,5 +22,3 @@ export const card = { } export const schema = lnbitsSchema - -export { sendPayment, validate } diff --git a/wallets/lnc/client.js b/wallets/lnc/client.js index 2e6e1266..34f7114e 100644 --- a/wallets/lnc/client.js +++ b/wallets/lnc/client.js @@ -6,6 +6,8 @@ import LNC from '@lightninglabs/lnc-web' import { Mutex } from 'async-mutex' import { Status } from 'wallets' +export * from 'wallets/lnc' + const XXX_DEFAULT_PASSWORD = 'password' export async function validate ({ pairingPhrase, password }, { me, logger }) { diff --git a/wallets/lnc/index.js b/wallets/lnc/index.js index 20790bfc..db202948 100644 --- a/wallets/lnc/index.js +++ b/wallets/lnc/index.js @@ -1,5 +1,4 @@ import { lncSchema } from '@/lib/validate' -import { sendPayment, validate } from 'wallets/lnc/client' export const name = 'lnc' @@ -26,5 +25,3 @@ export const card = { } export const schema = lncSchema - -export { sendPayment, validate } diff --git a/wallets/lnd/client.js b/wallets/lnd/client.js new file mode 100644 index 00000000..2aeb5534 --- /dev/null +++ b/wallets/lnd/client.js @@ -0,0 +1 @@ +export * from 'wallets/lnd' diff --git a/wallets/lnd/index.js b/wallets/lnd/index.js index c7c872c2..2ecf32ee 100644 --- a/wallets/lnd/index.js +++ b/wallets/lnd/index.js @@ -1,5 +1,4 @@ import { LNDAutowithdrawSchema } from '@/lib/validate' -import { server } from 'wallets/lnd/server' export const name = 'lnd' @@ -42,5 +41,3 @@ export const card = { } export const schema = LNDAutowithdrawSchema - -export { server } diff --git a/wallets/lnd/server.js b/wallets/lnd/server.js index 9ba31a41..8d8d4a70 100644 --- a/wallets/lnd/server.js +++ b/wallets/lnd/server.js @@ -1,58 +1,63 @@ import { ensureB64 } from '@/lib/format' import { datePivot } from '@/lib/time' +import { authenticatedLndGrpc, createInvoice as lndCreateInvoice } from 'ln-service' +import { addWalletLog } from '@/api/resolvers/wallet' -export const server = { - walletType: 'LND', - walletField: 'walletLND', - testConnect: async ( - { cert, macaroon, socket }, - { me, models, addWalletLog, lnService: { authenticatedLndGrpc, createInvoice } } - ) => { - try { - cert = ensureB64(cert) - macaroon = ensureB64(macaroon) +export * from 'wallets/lnd' - const { lnd } = await authenticatedLndGrpc({ - cert, - macaroon, - socket - }) +export const walletType = 'LND' - const inv = await createInvoice({ - description: 'SN connection test', - lnd, - tokens: 0, - expires_at: new Date() - }) +export const walletField = 'walletLND' - // we wrap both calls in one try/catch since connection attempts happen on RPC calls - await addWalletLog({ wallet: { type: 'LND' }, level: 'SUCCESS', message: 'connected to LND' }, { me, models }) +export const testConnect = async ( + { cert, macaroon, socket }, + { me, models } +) => { + try { + cert = ensureB64(cert) + macaroon = ensureB64(macaroon) - return inv - } catch (err) { - // LND errors are in this shape: [code, type, { err: { code, details, metadata } }] - const details = err[2]?.err?.details || err.message || err.toString?.() - throw new Error(details) - } - }, - createInvoice: async ( - { amount }, - { cert, macaroon, socket }, - { me, lnService: { authenticatedLndGrpc, createInvoice } } - ) => { const { lnd } = await authenticatedLndGrpc({ cert, macaroon, socket }) - const invoice = await createInvoice({ - description: me.hideInvoiceDesc ? undefined : 'autowithdraw to LND from SN', + const inv = await lndCreateInvoice({ + description: 'SN connection test', lnd, - tokens: amount, - expires_at: datePivot(new Date(), { seconds: 360 }) + tokens: 0, + expires_at: new Date() }) - return invoice.request + // we wrap both calls in one try/catch since connection attempts happen on RPC calls + await addWalletLog({ wallet: { type: 'LND' }, level: 'SUCCESS', message: 'connected to LND' }, { me, models }) + + return inv + } catch (err) { + // LND errors are in this shape: [code, type, { err: { code, details, metadata } }] + const details = err[2]?.err?.details || err.message || err.toString?.() + throw new Error(details) } } + +export const createInvoice = async ( + { amount }, + { cert, macaroon, socket }, + { me } +) => { + const { lnd } = await authenticatedLndGrpc({ + cert, + macaroon, + socket + }) + + const invoice = await lndCreateInvoice({ + description: me.hideInvoiceDesc ? undefined : 'autowithdraw to LND from SN', + lnd, + tokens: amount, + expires_at: datePivot(new Date(), { seconds: 360 }) + }) + + return invoice.request +} diff --git a/wallets/nwc/client.js b/wallets/nwc/client.js index eac7482c..3123d839 100644 --- a/wallets/nwc/client.js +++ b/wallets/nwc/client.js @@ -1,6 +1,8 @@ import { parseNwcUrl } from '@/lib/url' import { Relay, finalizeEvent, nip04 } from 'nostr-tools' +export * from 'wallets/nwc' + export async function validate ({ nwcUrl }, { logger }) { const { relayUrl, walletPubkey } = parseNwcUrl(nwcUrl) diff --git a/wallets/nwc/index.js b/wallets/nwc/index.js index 476eb42b..36ae28aa 100644 --- a/wallets/nwc/index.js +++ b/wallets/nwc/index.js @@ -1,5 +1,4 @@ import { nwcSchema } from '@/lib/validate' -import { sendPayment, validate } from 'wallets/nwc/client' export const name = 'nwc' @@ -18,5 +17,3 @@ export const card = { } export const schema = nwcSchema - -export { sendPayment, validate } diff --git a/wallets/server.js b/wallets/server.js new file mode 100644 index 00000000..1b47606e --- /dev/null +++ b/wallets/server.js @@ -0,0 +1,8 @@ +import * as lnd from 'wallets/lnd/server' +import * as cln from 'wallets/cln/server' +import * as lnAddr from 'wallets/lightning-address/server' + +// worker and app import modules differently +// const resolveImport = i => i.default || i + +export default [lnd, cln, lnAddr] diff --git a/worker/autowithdraw.js b/worker/autowithdraw.js index e678e9b1..6b310269 100644 --- a/worker/autowithdraw.js +++ b/worker/autowithdraw.js @@ -1,7 +1,6 @@ -import { authenticatedLndGrpc, createInvoice as lndCreateInvoice, getIdentity, decodePaymentRequest } from 'ln-service' import { msatsToSats, satsToMsats } from '@/lib/format' -import { createWithdrawal, addWalletLog, SERVER_WALLET_DEFS } from '@/api/resolvers/wallet' -import { createInvoice as clnCreateInvoice } from '@/lib/cln' +import { createWithdrawal, addWalletLog } from '@/api/resolvers/wallet' +import walletDefs from 'wallets/server' export async function autoWithdraw ({ data: { id }, models, lnd }) { const user = await models.user.findUnique({ where: { id } }) @@ -48,9 +47,9 @@ export async function autoWithdraw ({ data: { id }, models, lnd }) { }) for (const wallet of wallets) { - const w = SERVER_WALLET_DEFS.find(({ default: w }) => w.server.walletType === wallet.type) + const w = walletDefs.find(({ default: w }) => w.walletType === wallet.type) try { - const { server: { walletType, walletField, createInvoice } } = w.default + const { walletType, walletField, createInvoice } = w.default return await autowithdraw( { walletType, walletField, createInvoice }, { amount, maxFee }, @@ -58,6 +57,9 @@ export async function autoWithdraw ({ data: { id }, models, lnd }) { ) } catch (error) { console.error(error) + + // TODO: I think this is a bug, `walletCreateInvoice` in `autowithdraw` should parse the error + // LND errors are in this shape: [code, type, { err: { code, details, metadata } }] const details = error[2]?.err?.details || error.message || error.toString?.() await addWalletLog({ @@ -99,16 +101,7 @@ async function autowithdraw ( { me, models, - lnd, - lnService: { - authenticatedLndGrpc, - createInvoice: lndCreateInvoice, - getIdentity, - decodePaymentRequest - }, - cln: { - createInvoice: clnCreateInvoice - } + lnd }) return await createWithdrawal(null, { invoice: bolt11, maxFee }, { me, models, lnd, walletId: wallet.id })