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
This commit is contained in:
ekzyis 2024-07-16 15:45:32 +02:00
parent 259ebef971
commit 5b2e835722
25 changed files with 234 additions and 381 deletions

View File

@ -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
}

View File

@ -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

View File

@ -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)
})
}
}

View File

@ -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}`

View File

@ -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(

View File

@ -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 <WalletFields /> in pages/settings/wallets/[wallet].js.
//
//
// If not handled otherwise in <WalletFields />, field properties are simply
// passed into <ClientInput /> or <PasswordInput /> 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<bolt11>
//
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
}
}

8
wallets/client.js Normal file
View File

@ -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]

1
wallets/cln/client.js Normal file
View File

@ -0,0 +1 @@
export * from 'wallets/cln'

View File

@ -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 }

View File

@ -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
}

View File

@ -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) {

View File

@ -0,0 +1 @@
export * from 'wallets/lightning-address'

View File

@ -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 }

View File

@ -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
}

View File

@ -1,3 +1,5 @@
export * from 'wallets/lnbits'
export async function validate ({ url, adminKey }, { logger }) {
logger.info('trying to fetch wallet')

View File

@ -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 }

View File

@ -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 }) {

View File

@ -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 }

1
wallets/lnd/client.js Normal file
View File

@ -0,0 +1 @@
export * from 'wallets/lnd'

View File

@ -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 }

View File

@ -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
}

View File

@ -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)

View File

@ -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 }

8
wallets/server.js Normal file
View File

@ -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]

View File

@ -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 })