Merge pull request #849 from shocknet/dynamic-settings
dynamic settings
This commit is contained in:
commit
c4644b5701
43 changed files with 947 additions and 621 deletions
|
|
@ -20,6 +20,7 @@ import { UserOffer } from "./build/src/services/storage/entity/UserOffer.js"
|
||||||
import { ManagementGrant } from "./build/src/services/storage/entity/ManagementGrant.js"
|
import { ManagementGrant } from "./build/src/services/storage/entity/ManagementGrant.js"
|
||||||
import { AppUserDevice } from "./build/src/services/storage/entity/AppUserDevice.js"
|
import { AppUserDevice } from "./build/src/services/storage/entity/AppUserDevice.js"
|
||||||
import { UserAccess } from "./build/src/services/storage/entity/UserAccess.js"
|
import { UserAccess } from "./build/src/services/storage/entity/UserAccess.js"
|
||||||
|
import { AdminSettings } from "./build/src/services/storage/entity/AdminSettings.js"
|
||||||
|
|
||||||
import { Initial1703170309875 } from './build/src/services/storage/migrations/1703170309875-initial.js'
|
import { Initial1703170309875 } from './build/src/services/storage/migrations/1703170309875-initial.js'
|
||||||
import { LspOrder1718387847693 } from './build/src/services/storage/migrations/1718387847693-lsp_order.js'
|
import { LspOrder1718387847693 } from './build/src/services/storage/migrations/1718387847693-lsp_order.js'
|
||||||
|
|
@ -37,6 +38,9 @@ import { InvoiceCallbackUrls1752425992291 } from './build/src/services/storage/m
|
||||||
import { OldSomethingLeftover1753106599604 } from './build/src/services/storage/migrations/1753106599604-old_something_leftover.js'
|
import { OldSomethingLeftover1753106599604 } from './build/src/services/storage/migrations/1753106599604-old_something_leftover.js'
|
||||||
import { UserReceivingInvoiceIdx1753109184611 } from './build/src/services/storage/migrations/1753109184611-user_receiving_invoice_idx.js'
|
import { UserReceivingInvoiceIdx1753109184611 } from './build/src/services/storage/migrations/1753109184611-user_receiving_invoice_idx.js'
|
||||||
import { AppUserDevice1753285173175 } from './build/src/services/storage/migrations/1753285173175-app_user_device.js'
|
import { AppUserDevice1753285173175 } from './build/src/services/storage/migrations/1753285173175-app_user_device.js'
|
||||||
|
import { UserAccess1759426050669 } from './build/src/services/storage/migrations/1759426050669-user_access.js'
|
||||||
|
import { AddBlindToUserOffer1760000000000 } from './build/src/services/storage/migrations/1760000000000-add_blind_to_user_offer.js'
|
||||||
|
import { ApplicationAvatarUrl1761000001000 } from './build/src/services/storage/migrations/1761000001000-application_avatar_url.js'
|
||||||
|
|
||||||
export default new DataSource({
|
export default new DataSource({
|
||||||
type: "better-sqlite3",
|
type: "better-sqlite3",
|
||||||
|
|
@ -45,10 +49,10 @@ export default new DataSource({
|
||||||
migrations: [Initial1703170309875, LspOrder1718387847693, LiquidityProvider1719335699480, LndNodeInfo1720187506189, CreateInviteTokenTable1721751414878,
|
migrations: [Initial1703170309875, LspOrder1718387847693, LiquidityProvider1719335699480, LndNodeInfo1720187506189, CreateInviteTokenTable1721751414878,
|
||||||
PaymentIndex1721760297610, DebitAccess1726496225078, DebitAccessFixes1726685229264, DebitToPub1727105758354, UserCbUrl1727112281043,
|
PaymentIndex1721760297610, DebitAccess1726496225078, DebitAccessFixes1726685229264, DebitToPub1727105758354, UserCbUrl1727112281043,
|
||||||
UserOffer1733502626042, ManagementGrant1751307732346, InvoiceCallbackUrls1752425992291, OldSomethingLeftover1753106599604, UserReceivingInvoiceIdx1753109184611,
|
UserOffer1733502626042, ManagementGrant1751307732346, InvoiceCallbackUrls1752425992291, OldSomethingLeftover1753106599604, UserReceivingInvoiceIdx1753109184611,
|
||||||
AppUserDevice1753285173175],
|
AppUserDevice1753285173175, UserAccess1759426050669, AddBlindToUserOffer1760000000000, ApplicationAvatarUrl1761000001000],
|
||||||
entities: [User, UserReceivingInvoice, UserReceivingAddress, AddressReceivingTransaction, UserInvoicePayment, UserTransactionPayment,
|
entities: [User, UserReceivingInvoice, UserReceivingAddress, AddressReceivingTransaction, UserInvoicePayment, UserTransactionPayment,
|
||||||
UserBasicAuth, UserEphemeralKey, Product, UserToUserPayment, Application, ApplicationUser, UserToUserPayment, LspOrder, LndNodeInfo,
|
UserBasicAuth, UserEphemeralKey, Product, UserToUserPayment, Application, ApplicationUser, UserToUserPayment, LspOrder, LndNodeInfo,
|
||||||
TrackedProvider, InviteToken, DebitAccess, UserOffer, ManagementGrant, AppUserDevice, UserAccess],
|
TrackedProvider, InviteToken, DebitAccess, UserOffer, ManagementGrant, AppUserDevice, UserAccess, AdminSettings],
|
||||||
// synchronize: true,
|
// synchronize: true,
|
||||||
})
|
})
|
||||||
//npx typeorm migration:generate ./src/services/storage/migrations/user_access -d ./datasource.js
|
//npx typeorm migration:generate ./src/services/storage/migrations/admin_settings -d ./datasource.js
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
# The developer is used by default or you may specify your own
|
# The developer is used by default or you may specify your own
|
||||||
# To disable this feature entirely overwrite the env with "null"
|
# To disable this feature entirely overwrite the env with "null"
|
||||||
#LIQUIDITY_PROVIDER_PUB=null
|
#LIQUIDITY_PROVIDER_PUB=null
|
||||||
|
#DISABLE_LIQUIDITY_PROVIDER=false
|
||||||
|
|
||||||
#DB
|
#DB
|
||||||
#DATABASE_FILE=db.sqlite
|
#DATABASE_FILE=db.sqlite
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ const serverOptions = (mainHandler: Main): ServerOptions => {
|
||||||
UserAuthGuard: async (authHeader) => { return mainHandler.appUserManager.DecodeUserToken(stripBearer(authHeader)) },
|
UserAuthGuard: async (authHeader) => { return mainHandler.appUserManager.DecodeUserToken(stripBearer(authHeader)) },
|
||||||
GuestWithPubAuthGuard: async (_) => { throw new Error("Nostr only route") },
|
GuestWithPubAuthGuard: async (_) => { throw new Error("Nostr only route") },
|
||||||
GuestAuthGuard: async (_) => ({}),
|
GuestAuthGuard: async (_) => ({}),
|
||||||
metricsCallback: metrics => mainHandler.settings.recordPerformance ? mainHandler.metricsManager.AddMetrics(metrics) : null,
|
metricsCallback: metrics => mainHandler.settings.getSettings().serviceSettings.recordPerformance ? mainHandler.metricsManager.AddMetrics(metrics) : null,
|
||||||
allowCors: true,
|
allowCors: true,
|
||||||
logMethod: true,
|
logMethod: true,
|
||||||
logBody: true
|
logBody: true
|
||||||
|
|
|
||||||
13
src/e2e.ts
13
src/e2e.ts
|
|
@ -4,16 +4,17 @@ import GetServerMethods from './services/serverMethods/index.js'
|
||||||
import serverOptions from './auth.js';
|
import serverOptions from './auth.js';
|
||||||
import nostrMiddleware from './nostrMiddleware.js'
|
import nostrMiddleware from './nostrMiddleware.js'
|
||||||
import { getLogger } from './services/helpers/logger.js';
|
import { getLogger } from './services/helpers/logger.js';
|
||||||
import { initMainHandler } from './services/main/init.js';
|
import { initMainHandler, initSettings } from './services/main/init.js';
|
||||||
import { LoadMainSettingsFromEnv } from './services/main/settings.js';
|
|
||||||
import { nip19 } from 'nostr-tools'
|
import { nip19 } from 'nostr-tools'
|
||||||
|
import { LoadStorageSettingsFromEnv } from './services/storage/index.js';
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
const { nprofileEncode } = nip19
|
const { nprofileEncode } = nip19
|
||||||
|
|
||||||
const start = async () => {
|
const start = async () => {
|
||||||
const log = getLogger({})
|
const log = getLogger({})
|
||||||
const mainSettings = LoadMainSettingsFromEnv()
|
const storageSettings = LoadStorageSettingsFromEnv()
|
||||||
const keepOn = await initMainHandler(log, mainSettings)
|
const settingsManager = await initSettings(log, storageSettings)
|
||||||
|
const keepOn = await initMainHandler(log, settingsManager)
|
||||||
if (!keepOn) {
|
if (!keepOn) {
|
||||||
log("manual process ended")
|
log("manual process ended")
|
||||||
return
|
return
|
||||||
|
|
@ -21,7 +22,7 @@ const start = async () => {
|
||||||
|
|
||||||
const { apps, mainHandler, liquidityProviderInfo, wizard, adminManager } = keepOn
|
const { apps, mainHandler, liquidityProviderInfo, wizard, adminManager } = keepOn
|
||||||
const serverMethods = GetServerMethods(mainHandler)
|
const serverMethods = GetServerMethods(mainHandler)
|
||||||
const nostrSettings = mainSettings.nostrRelaySettings
|
const nostrSettings = settingsManager.getSettings().nostrRelaySettings
|
||||||
log("initializing nostr middleware")
|
log("initializing nostr middleware")
|
||||||
const { Send } = nostrMiddleware(serverMethods, mainHandler,
|
const { Send } = nostrMiddleware(serverMethods, mainHandler,
|
||||||
{ ...nostrSettings, apps, clients: [liquidityProviderInfo] },
|
{ ...nostrSettings, apps, clients: [liquidityProviderInfo] },
|
||||||
|
|
@ -36,6 +37,6 @@ const start = async () => {
|
||||||
}
|
}
|
||||||
adminManager.setAppNprofile(appNprofile)
|
adminManager.setAppNprofile(appNprofile)
|
||||||
const Server = NewServer(serverMethods, serverOptions(mainHandler))
|
const Server = NewServer(serverMethods, serverOptions(mainHandler))
|
||||||
Server.Listen(mainSettings.servicePort)
|
Server.Listen(settingsManager.getSettings().serviceSettings.servicePort)
|
||||||
}
|
}
|
||||||
start()
|
start()
|
||||||
|
|
|
||||||
24
src/index.ts
24
src/index.ts
|
|
@ -4,16 +4,19 @@ import GetServerMethods from './services/serverMethods/index.js'
|
||||||
import serverOptions from './auth.js';
|
import serverOptions from './auth.js';
|
||||||
import nostrMiddleware from './nostrMiddleware.js'
|
import nostrMiddleware from './nostrMiddleware.js'
|
||||||
import { getLogger } from './services/helpers/logger.js';
|
import { getLogger } from './services/helpers/logger.js';
|
||||||
import { initMainHandler } from './services/main/init.js';
|
import { initMainHandler, initSettings } from './services/main/init.js';
|
||||||
import { LoadMainSettingsFromEnv } from './services/main/settings.js';
|
|
||||||
import { nip19 } from 'nostr-tools'
|
import { nip19 } from 'nostr-tools'
|
||||||
|
import { LoadStorageSettingsFromEnv } from './services/storage/index.js';
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
const { nprofileEncode } = nip19
|
const { nprofileEncode } = nip19
|
||||||
|
|
||||||
|
|
||||||
const start = async () => {
|
const start = async () => {
|
||||||
const log = getLogger({})
|
const log = getLogger({})
|
||||||
const mainSettings = LoadMainSettingsFromEnv()
|
//const mainSettings = LoadMainSettingsFromEnv()
|
||||||
const keepOn = await initMainHandler(log, mainSettings)
|
const storageSettings = LoadStorageSettingsFromEnv()
|
||||||
|
const settingsManager = await initSettings(log, storageSettings)
|
||||||
|
const keepOn = await initMainHandler(log, settingsManager)
|
||||||
if (!keepOn) {
|
if (!keepOn) {
|
||||||
log("manual process ended")
|
log("manual process ended")
|
||||||
return
|
return
|
||||||
|
|
@ -22,22 +25,25 @@ const start = async () => {
|
||||||
const { apps, mainHandler, liquidityProviderInfo, wizard, adminManager } = keepOn
|
const { apps, mainHandler, liquidityProviderInfo, wizard, adminManager } = keepOn
|
||||||
const serverMethods = GetServerMethods(mainHandler)
|
const serverMethods = GetServerMethods(mainHandler)
|
||||||
log("initializing nostr middleware")
|
log("initializing nostr middleware")
|
||||||
const { Send, Stop, Ping } = nostrMiddleware(serverMethods, mainHandler,
|
const relays = mainHandler.settings.getSettings().nostrRelaySettings.relays
|
||||||
{ ...mainSettings.nostrRelaySettings, apps, clients: [liquidityProviderInfo] },
|
const maxEventContentLength = mainHandler.settings.getSettings().nostrRelaySettings.maxEventContentLength
|
||||||
|
const { Send, Stop, Ping, Reset } = nostrMiddleware(serverMethods, mainHandler,
|
||||||
|
{ relays, maxEventContentLength, apps, clients: [liquidityProviderInfo] },
|
||||||
(e, p) => mainHandler.liquidityProvider.onEvent(e, p)
|
(e, p) => mainHandler.liquidityProvider.onEvent(e, p)
|
||||||
)
|
)
|
||||||
exitHandler(() => { Stop(); mainHandler.Stop() })
|
exitHandler(() => { Stop(); mainHandler.Stop() })
|
||||||
log("starting server")
|
log("starting server")
|
||||||
mainHandler.attachNostrSend(Send)
|
mainHandler.attachNostrSend(Send)
|
||||||
mainHandler.attachNostrProcessPing(Ping)
|
mainHandler.attachNostrProcessPing(Ping)
|
||||||
|
mainHandler.attachNostrReset(Reset)
|
||||||
mainHandler.StartBeacons()
|
mainHandler.StartBeacons()
|
||||||
const appNprofile = nprofileEncode({ pubkey: liquidityProviderInfo.publicKey, relays: mainSettings.nostrRelaySettings.relays })
|
const appNprofile = nprofileEncode({ pubkey: liquidityProviderInfo.publicKey, relays })
|
||||||
if (wizard) {
|
if (wizard) {
|
||||||
wizard.AddConnectInfo(appNprofile, mainSettings.nostrRelaySettings.relays)
|
wizard.AddConnectInfo(appNprofile, relays)
|
||||||
}
|
}
|
||||||
adminManager.setAppNprofile(appNprofile)
|
adminManager.setAppNprofile(appNprofile)
|
||||||
const Server = NewServer(serverMethods, serverOptions(mainHandler))
|
const Server = NewServer(serverMethods, serverOptions(mainHandler))
|
||||||
Server.Listen(mainSettings.servicePort)
|
Server.Listen(mainHandler.settings.getSettings().serviceSettings.servicePort)
|
||||||
}
|
}
|
||||||
start()
|
start()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,9 @@ import * as Types from '../proto/autogenerated/ts/types.js'
|
||||||
import NewNostrTransport, { NostrRequest } from '../proto/autogenerated/ts/nostr_transport.js';
|
import NewNostrTransport, { NostrRequest } from '../proto/autogenerated/ts/nostr_transport.js';
|
||||||
import { ERROR, getLogger } from "./services/helpers/logger.js";
|
import { ERROR, getLogger } from "./services/helpers/logger.js";
|
||||||
import { NdebitData, NofferData, NmanageRequest } from "@shocknet/clink-sdk";
|
import { NdebitData, NofferData, NmanageRequest } from "@shocknet/clink-sdk";
|
||||||
|
type ExportedCalls = { Stop: () => void, Send: NostrSend, Ping: () => Promise<void>, Reset: (settings: NostrSettings) => void }
|
||||||
export default (serverMethods: Types.ServerMethods, mainHandler: Main, nostrSettings: NostrSettings, onClientEvent: (e: { requestId: string }, fromPub: string) => void): { Stop: () => void, Send: NostrSend, Ping: () => Promise<void> } => {
|
type ClientEventCallback = (e: { requestId: string }, fromPub: string) => void
|
||||||
|
export default (serverMethods: Types.ServerMethods, mainHandler: Main, nostrSettings: NostrSettings, onClientEvent: ClientEventCallback): ExportedCalls => {
|
||||||
const log = getLogger({})
|
const log = getLogger({})
|
||||||
const nostrTransport = NewNostrTransport(serverMethods, {
|
const nostrTransport = NewNostrTransport(serverMethods, {
|
||||||
NostrUserAuthGuard: async (appId, pub) => {
|
NostrUserAuthGuard: async (appId, pub) => {
|
||||||
|
|
@ -29,7 +30,7 @@ export default (serverMethods: Types.ServerMethods, mainHandler: Main, nostrSett
|
||||||
log("operator access from", pub)
|
log("operator access from", pub)
|
||||||
return { operator_id: pub, app_id: appId || "" }
|
return { operator_id: pub, app_id: appId || "" }
|
||||||
},
|
},
|
||||||
metricsCallback: metrics => mainHandler.settings.recordPerformance ? mainHandler.metricsManager.AddMetrics(metrics) : null,
|
metricsCallback: metrics => mainHandler.settings.getSettings().serviceSettings.recordPerformance ? mainHandler.metricsManager.AddMetrics(metrics) : null,
|
||||||
NostrGuestWithPubAuthGuard: async (appId, pub) => {
|
NostrGuestWithPubAuthGuard: async (appId, pub) => {
|
||||||
if (!pub || !appId) {
|
if (!pub || !appId) {
|
||||||
throw new Error("Unknown error occured")
|
throw new Error("Unknown error occured")
|
||||||
|
|
@ -83,7 +84,12 @@ export default (serverMethods: Types.ServerMethods, mainHandler: Main, nostrSett
|
||||||
// Mark nostr connected/ready after initial subscription tick
|
// Mark nostr connected/ready after initial subscription tick
|
||||||
mainHandler.adminManager.setNostrConnected(true)
|
mainHandler.adminManager.setNostrConnected(true)
|
||||||
|
|
||||||
return { Stop: () => { mainHandler.adminManager.setNostrConnected(false); return nostr.Stop }, Send: (...args) => nostr.Send(...args), Ping: () => nostr.Ping() }
|
return {
|
||||||
|
Stop: () => { mainHandler.adminManager.setNostrConnected(false); return nostr.Stop },
|
||||||
|
Send: (...args) => nostr.Send(...args),
|
||||||
|
Ping: () => nostr.Ping(),
|
||||||
|
Reset: (settings: NostrSettings) => nostr.Reset(settings)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,3 +26,32 @@ export const EnvCanBeBoolean = (name: string): boolean => {
|
||||||
if (!env) return false
|
if (!env) return false
|
||||||
return env.toLowerCase() === 'true'
|
return env.toLowerCase() === 'true'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const IntOrUndefinedEnv = (v: string | undefined): number | undefined => {
|
||||||
|
if (!v) return undefined
|
||||||
|
const num = +v
|
||||||
|
if (isNaN(num) || !Number.isInteger(num)) return undefined
|
||||||
|
return num
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EnvCacher = (key: string, value: string) => void
|
||||||
|
|
||||||
|
export const chooseEnv = (key: string, dbEnv: Record<string, string | undefined>, defaultValue: string, addToDb?: EnvCacher): string => {
|
||||||
|
const fromProcess = process.env[key]
|
||||||
|
if (fromProcess) {
|
||||||
|
if (fromProcess !== dbEnv[key] && addToDb) addToDb(key, fromProcess)
|
||||||
|
return fromProcess
|
||||||
|
}
|
||||||
|
return dbEnv[key] || defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
export const chooseEnvInt = (key: string, dbEnv: Record<string, string | undefined>, defaultValue: number, addToDb?: EnvCacher): number => {
|
||||||
|
const v = IntOrUndefinedEnv(chooseEnv(key, dbEnv, defaultValue.toString(), addToDb))
|
||||||
|
if (v === undefined) return defaultValue
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
export const chooseEnvBool = (key: string, dbEnv: Record<string, string | undefined>, defaultValue: boolean, addToDb?: EnvCacher): boolean => {
|
||||||
|
const v = chooseEnv(key, dbEnv, defaultValue.toString(), addToDb)
|
||||||
|
return v.toLowerCase() === 'true'
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import { MainSettings } from "../main/settings.js";
|
|
||||||
import { StateBundler } from "../storage/tlv/stateBundler.js";
|
import { StateBundler } from "../storage/tlv/stateBundler.js";
|
||||||
import { TlvStorageFactory } from "../storage/tlv/tlvFilesStorageFactory.js";
|
import { TlvStorageFactory } from "../storage/tlv/tlvFilesStorageFactory.js";
|
||||||
import { NostrSend } from "../nostr/handler.js";
|
import { NostrSend } from "../nostr/handler.js";
|
||||||
|
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
import { EnvMustBeNonEmptyString, EnvMustBeInteger, EnvCanBeBoolean, EnvCanBeInteger } from '../helpers/envParser.js'
|
|
||||||
import { LndSettings } from './settings.js'
|
|
||||||
import os from 'os'
|
|
||||||
import path from 'path'
|
|
||||||
|
|
||||||
const resolveHome = (filepath: string) => {
|
|
||||||
let homeDir;
|
|
||||||
if (process.env.SUDO_USER) {
|
|
||||||
homeDir = path.join('/home', process.env.SUDO_USER);
|
|
||||||
} else {
|
|
||||||
homeDir = os.homedir();
|
|
||||||
}
|
|
||||||
return path.join(homeDir, filepath);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const LoadLndSettingsFromEnv = (): LndSettings => {
|
|
||||||
const lndAddr = process.env.LND_ADDRESS || "127.0.0.1:10009"
|
|
||||||
const lndCertPath = process.env.LND_CERT_PATH || resolveHome("/.lnd/tls.cert")
|
|
||||||
const lndMacaroonPath = process.env.LND_MACAROON_PATH || resolveHome("/.lnd/data/chain/bitcoin/mainnet/admin.macaroon")
|
|
||||||
const lndLogDir = process.env.LND_LOG_DIR || resolveHome("/.lnd/logs/bitcoin/mainnet/lnd.log")
|
|
||||||
const feeRateBps = EnvCanBeInteger("OUTBOUND_MAX_FEE_BPS", 60)
|
|
||||||
const feeRateLimit = feeRateBps / 10000
|
|
||||||
const feeFixedLimit = EnvCanBeInteger("OUTBOUND_MAX_FEE_EXTRA_SATS", 100)
|
|
||||||
const mockLnd = EnvCanBeBoolean("MOCK_LND")
|
|
||||||
return { mainNode: { lndAddr, lndCertPath, lndMacaroonPath }, lndLogDir, feeRateLimit, feeFixedLimit, feeRateBps, mockLnd }
|
|
||||||
}
|
|
||||||
|
|
@ -13,23 +13,31 @@ import { OpenChannelReq } from './openChannelReq.js';
|
||||||
import { AddInvoiceReq } from './addInvoiceReq.js';
|
import { AddInvoiceReq } from './addInvoiceReq.js';
|
||||||
import { PayInvoiceReq } from './payInvoiceReq.js';
|
import { PayInvoiceReq } from './payInvoiceReq.js';
|
||||||
import { SendCoinsReq } from './sendCoinsReq.js';
|
import { SendCoinsReq } from './sendCoinsReq.js';
|
||||||
import { LndSettings, AddressPaidCb, InvoicePaidCb, NodeInfo, Invoice, DecodedInvoice, PaidInvoice, NewBlockCb, HtlcCb, BalanceInfo, ChannelEventCb } from './settings.js';
|
import { AddressPaidCb, InvoicePaidCb, NodeInfo, Invoice, DecodedInvoice, PaidInvoice, NewBlockCb, HtlcCb, BalanceInfo, ChannelEventCb } from './settings.js';
|
||||||
import { ERROR, getLogger } from '../helpers/logger.js';
|
import { ERROR, getLogger } from '../helpers/logger.js';
|
||||||
import { HtlcEvent_EventType } from '../../../proto/lnd/router.js';
|
import { HtlcEvent_EventType } from '../../../proto/lnd/router.js';
|
||||||
import { LiquidityProvider, LiquidityRequest } from '../main/liquidityProvider.js';
|
import { LiquidityProvider, LiquidityRequest } from '../main/liquidityProvider.js';
|
||||||
import { Utils } from '../helpers/utilsWrapper.js';
|
import { Utils } from '../helpers/utilsWrapper.js';
|
||||||
import { TxPointSettings } from '../storage/tlv/stateBundler.js';
|
import { TxPointSettings } from '../storage/tlv/stateBundler.js';
|
||||||
import { WalletKitClient } from '../../../proto/lnd/walletkit.client.js';
|
import { WalletKitClient } from '../../../proto/lnd/walletkit.client.js';
|
||||||
|
import SettingsManager from '../main/settingsManager.js';
|
||||||
|
import { LndNodeSettings, LndSettings } from '../main/settings.js';
|
||||||
|
|
||||||
const DeadLineMetadata = (deadline = 10 * 1000) => ({ deadline: Date.now() + deadline })
|
const DeadLineMetadata = (deadline = 10 * 1000) => ({ deadline: Date.now() + deadline })
|
||||||
const deadLndRetrySeconds = 5
|
const deadLndRetrySeconds = 5
|
||||||
type TxActionOptions = { useProvider: boolean, from: 'user' | 'system' }
|
type TxActionOptions = { useProvider: boolean, from: 'user' | 'system' }
|
||||||
|
type NodeSettingsOverride = {
|
||||||
|
lndAddr: string
|
||||||
|
lndCertPath: string
|
||||||
|
lndMacaroonPath: string
|
||||||
|
}
|
||||||
export default class {
|
export default class {
|
||||||
lightning: LightningClient
|
lightning: LightningClient
|
||||||
invoices: InvoicesClient
|
invoices: InvoicesClient
|
||||||
router: RouterClient
|
router: RouterClient
|
||||||
chainNotifier: ChainNotifierClient
|
chainNotifier: ChainNotifierClient
|
||||||
walletKit: WalletKitClient
|
walletKit: WalletKitClient
|
||||||
settings: LndSettings
|
getSettings: () => { lndSettings: LndSettings, lndNodeSettings: LndNodeSettings }
|
||||||
ready = false
|
ready = false
|
||||||
latestKnownBlockHeigh = 0
|
latestKnownBlockHeigh = 0
|
||||||
latestKnownSettleIndex = 0
|
latestKnownSettleIndex = 0
|
||||||
|
|
@ -43,15 +51,15 @@ export default class {
|
||||||
outgoingOpsLocked = false
|
outgoingOpsLocked = false
|
||||||
liquidProvider: LiquidityProvider
|
liquidProvider: LiquidityProvider
|
||||||
utils: Utils
|
utils: Utils
|
||||||
constructor(settings: LndSettings, liquidProvider: LiquidityProvider, utils: Utils, addressPaidCb: AddressPaidCb, invoicePaidCb: InvoicePaidCb, newBlockCb: NewBlockCb, htlcCb: HtlcCb, channelEventCb: ChannelEventCb) {
|
constructor(getSettings: () => { lndSettings: LndSettings, lndNodeSettings: LndNodeSettings }, liquidProvider: LiquidityProvider, utils: Utils, addressPaidCb: AddressPaidCb, invoicePaidCb: InvoicePaidCb, newBlockCb: NewBlockCb, htlcCb: HtlcCb, channelEventCb: ChannelEventCb) {
|
||||||
this.settings = settings
|
this.getSettings = getSettings
|
||||||
this.utils = utils
|
this.utils = utils
|
||||||
this.addressPaidCb = addressPaidCb
|
this.addressPaidCb = addressPaidCb
|
||||||
this.invoicePaidCb = invoicePaidCb
|
this.invoicePaidCb = invoicePaidCb
|
||||||
this.newBlockCb = newBlockCb
|
this.newBlockCb = newBlockCb
|
||||||
this.htlcCb = htlcCb
|
this.htlcCb = htlcCb
|
||||||
this.channelEventCb = channelEventCb
|
this.channelEventCb = channelEventCb
|
||||||
const { lndAddr, lndCertPath, lndMacaroonPath } = this.settings.mainNode
|
const { lndAddr, lndCertPath, lndMacaroonPath } = this.getSettings().lndNodeSettings
|
||||||
const lndCert = fs.readFileSync(lndCertPath);
|
const lndCert = fs.readFileSync(lndCertPath);
|
||||||
const macaroon = fs.readFileSync(lndMacaroonPath).toString('hex');
|
const macaroon = fs.readFileSync(lndMacaroonPath).toString('hex');
|
||||||
const sslCreds = credentials.createSsl(lndCert);
|
const sslCreds = credentials.createSsl(lndCert);
|
||||||
|
|
@ -311,11 +319,11 @@ export default class {
|
||||||
}
|
}
|
||||||
|
|
||||||
GetFeeLimitAmount(amount: number): number {
|
GetFeeLimitAmount(amount: number): number {
|
||||||
return Math.ceil(amount * this.settings.feeRateLimit + this.settings.feeFixedLimit);
|
return Math.ceil(amount * this.getSettings().lndSettings.feeRateLimit + this.getSettings().lndSettings.feeFixedLimit);
|
||||||
}
|
}
|
||||||
|
|
||||||
GetMaxWithinLimit(amount: number): number {
|
GetMaxWithinLimit(amount: number): number {
|
||||||
return Math.max(0, Math.floor(amount * (1 - this.settings.feeRateLimit) - this.settings.feeFixedLimit))
|
return Math.max(0, Math.floor(amount * (1 - this.getSettings().lndSettings.feeRateLimit) - this.getSettings().lndSettings.feeFixedLimit))
|
||||||
}
|
}
|
||||||
|
|
||||||
async ChannelBalance(): Promise<{ local: number, remote: number }> {
|
async ChannelBalance(): Promise<{ local: number, remote: number }> {
|
||||||
|
|
|
||||||
|
|
@ -3,24 +3,8 @@ import { LiquidityProvider } from "../main/liquidityProvider.js"
|
||||||
import { getLogger, PubLogger } from '../helpers/logger.js'
|
import { getLogger, PubLogger } from '../helpers/logger.js'
|
||||||
import LND from "./lnd.js"
|
import LND from "./lnd.js"
|
||||||
import { AddressType } from "../../../proto/autogenerated/ts/types.js"
|
import { AddressType } from "../../../proto/autogenerated/ts/types.js"
|
||||||
import { EnvCanBeInteger } from "../helpers/envParser.js"
|
import SettingsManager from "../main/settingsManager.js"
|
||||||
export type LSPSettings = {
|
|
||||||
olympusServiceUrl: string
|
|
||||||
voltageServiceUrl: string
|
|
||||||
flashsatsServiceUrl: string
|
|
||||||
channelThreshold: number
|
|
||||||
maxRelativeFee: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export const LoadLSPSettingsFromEnv = (): LSPSettings => {
|
|
||||||
const olympusServiceUrl = process.env.OLYMPUS_LSP_URL || "https://lsps1.lnolymp.us/api/v1"
|
|
||||||
const voltageServiceUrl = process.env.VOLTAGE_LSP_URL || "https://lsp.voltageapi.com/api/v1"
|
|
||||||
const flashsatsServiceUrl = process.env.FLASHSATS_LSP_URL || "https://lsp.flashsats.xyz/lsp/channel"
|
|
||||||
const channelThreshold = EnvCanBeInteger("LSP_CHANNEL_THRESHOLD", 1000000)
|
|
||||||
const maxRelativeFee = EnvCanBeInteger("LSP_MAX_FEE_BPS", 100) / 10000
|
|
||||||
return { olympusServiceUrl, voltageServiceUrl, channelThreshold, maxRelativeFee, flashsatsServiceUrl }
|
|
||||||
|
|
||||||
}
|
|
||||||
type OlympusOrder = {
|
type OlympusOrder = {
|
||||||
"lsp_balance_sat": string,
|
"lsp_balance_sat": string,
|
||||||
"client_balance_sat": string,
|
"client_balance_sat": string,
|
||||||
|
|
@ -50,11 +34,11 @@ type OrderResponse = {
|
||||||
}
|
}
|
||||||
|
|
||||||
class LSP {
|
class LSP {
|
||||||
settings: LSPSettings
|
settings: SettingsManager
|
||||||
liquidityProvider: LiquidityProvider
|
liquidityProvider: LiquidityProvider
|
||||||
lnd: LND
|
lnd: LND
|
||||||
log: PubLogger
|
log: PubLogger
|
||||||
constructor(serviceName: string, settings: LSPSettings, lnd: LND, liquidityProvider: LiquidityProvider) {
|
constructor(serviceName: string, settings: SettingsManager, lnd: LND, liquidityProvider: LiquidityProvider) {
|
||||||
this.settings = settings
|
this.settings = settings
|
||||||
this.lnd = lnd
|
this.lnd = lnd
|
||||||
this.liquidityProvider = liquidityProvider
|
this.liquidityProvider = liquidityProvider
|
||||||
|
|
@ -71,12 +55,15 @@ class LSP {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class FlashsatsLSP extends LSP {
|
export class FlashsatsLSP extends LSP {
|
||||||
constructor(settings: LSPSettings, lnd: LND, liquidityProvider: LiquidityProvider) {
|
constructor(settings: SettingsManager, lnd: LND, liquidityProvider: LiquidityProvider) {
|
||||||
super("FlashsatsLSP", settings, lnd, liquidityProvider)
|
super("FlashsatsLSP", settings, lnd, liquidityProvider)
|
||||||
}
|
}
|
||||||
|
|
||||||
requestChannel = async (maxSpendable: number): Promise<OrderResponse | null> => {
|
requestChannel = async (maxSpendable: number): Promise<OrderResponse | null> => {
|
||||||
if (!this.settings.flashsatsServiceUrl) {
|
const s = this.settings.getSettings().lspSettings
|
||||||
|
const flashsatsServiceUrl = s.flashsatsServiceUrl
|
||||||
|
const maxRelativeFee = s.maxRelativeFee
|
||||||
|
if (!flashsatsServiceUrl) {
|
||||||
this.log("no flashsats service url provided")
|
this.log("no flashsats service url provided")
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
@ -91,7 +78,7 @@ export class FlashsatsLSP extends LSP {
|
||||||
this.log("no uri found for this node,uri is required to use flashsats")
|
this.log("no uri found for this node,uri is required to use flashsats")
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
const channelSize = Math.floor(maxSpendable * (1 - this.settings.maxRelativeFee)) * 2
|
const channelSize = Math.floor(maxSpendable * (1 - maxRelativeFee)) * 2
|
||||||
const lspBalance = channelSize.toString()
|
const lspBalance = channelSize.toString()
|
||||||
const chanExpiryBlocks = serviceInfo.options.max_channel_expiry_blocks
|
const chanExpiryBlocks = serviceInfo.options.max_channel_expiry_blocks
|
||||||
const order = await this.createOrder({ nodeUri: myUri, lspBalance, clientBalance: "0", chanExpiryBlocks })
|
const order = await this.createOrder({ nodeUri: myUri, lspBalance, clientBalance: "0", chanExpiryBlocks })
|
||||||
|
|
@ -109,8 +96,8 @@ export class FlashsatsLSP extends LSP {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
const relativeFee = +order.payment.fee_total_sat / channelSize
|
const relativeFee = +order.payment.fee_total_sat / channelSize
|
||||||
if (relativeFee > this.settings.maxRelativeFee) {
|
if (relativeFee > maxRelativeFee) {
|
||||||
this.log("invoice relative fee of", relativeFee, "exceeds max relative fee of", this.settings.maxRelativeFee)
|
this.log("invoice relative fee of", relativeFee, "exceeds max relative fee of", maxRelativeFee)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
const res = await this.liquidityProvider.PayInvoice(order.payment.bolt11_invoice, decoded.numSatoshis, 'system')
|
const res = await this.liquidityProvider.PayInvoice(order.payment.bolt11_invoice, decoded.numSatoshis, 'system')
|
||||||
|
|
@ -120,7 +107,7 @@ export class FlashsatsLSP extends LSP {
|
||||||
|
|
||||||
}
|
}
|
||||||
getInfo = async () => {
|
getInfo = async () => {
|
||||||
const res = await fetch(`${this.settings.flashsatsServiceUrl}/info`)
|
const res = await fetch(`${this.settings.getSettings().lspSettings.flashsatsServiceUrl}/info`)
|
||||||
const json = await res.json() as { options: { min_initial_client_balance_sat: string, max_channel_expiry_blocks: number } }
|
const json = await res.json() as { options: { min_initial_client_balance_sat: string, max_channel_expiry_blocks: number } }
|
||||||
return json
|
return json
|
||||||
}
|
}
|
||||||
|
|
@ -134,7 +121,7 @@ export class FlashsatsLSP extends LSP {
|
||||||
confirms_within_blocks: 6,
|
confirms_within_blocks: 6,
|
||||||
token: "flashsats"
|
token: "flashsats"
|
||||||
}
|
}
|
||||||
const res = await fetch(`${this.settings.flashsatsServiceUrl}/channel`, {
|
const res = await fetch(`${this.settings.getSettings().lspSettings.flashsatsServiceUrl}/channel`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify(req),
|
body: JSON.stringify(req),
|
||||||
headers: { "Content-Type": "application/json" }
|
headers: { "Content-Type": "application/json" }
|
||||||
|
|
@ -145,12 +132,15 @@ export class FlashsatsLSP extends LSP {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class OlympusLSP extends LSP {
|
export class OlympusLSP extends LSP {
|
||||||
constructor(settings: LSPSettings, lnd: LND, liquidityProvider: LiquidityProvider) {
|
constructor(settings: SettingsManager, lnd: LND, liquidityProvider: LiquidityProvider) {
|
||||||
super("OlympusLSP", settings, lnd, liquidityProvider)
|
super("OlympusLSP", settings, lnd, liquidityProvider)
|
||||||
}
|
}
|
||||||
|
|
||||||
requestChannel = async (maxSpendable: number): Promise<OrderResponse | null> => {
|
requestChannel = async (maxSpendable: number): Promise<OrderResponse | null> => {
|
||||||
if (!this.settings.olympusServiceUrl) {
|
const s = this.settings.getSettings().lspSettings
|
||||||
|
const olympusServiceUrl = s.olympusServiceUrl
|
||||||
|
const maxRelativeFee = s.maxRelativeFee
|
||||||
|
if (!olympusServiceUrl) {
|
||||||
this.log("no olympus service url provided")
|
this.log("no olympus service url provided")
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
@ -164,7 +154,7 @@ export class OlympusLSP extends LSP {
|
||||||
const lndInfo = await this.lnd.GetInfo()
|
const lndInfo = await this.lnd.GetInfo()
|
||||||
const myPub = lndInfo.identityPubkey
|
const myPub = lndInfo.identityPubkey
|
||||||
const refundAddr = await this.lnd.NewAddress(AddressType.WITNESS_PUBKEY_HASH, { useProvider: false, from: 'system' })
|
const refundAddr = await this.lnd.NewAddress(AddressType.WITNESS_PUBKEY_HASH, { useProvider: false, from: 'system' })
|
||||||
const channelSize = Math.floor(maxSpendable * (1 - this.settings.maxRelativeFee)) * 2
|
const channelSize = Math.floor(maxSpendable * (1 - maxRelativeFee)) * 2
|
||||||
const lspBalance = channelSize.toString()
|
const lspBalance = channelSize.toString()
|
||||||
const chanExpiryBlocks = serviceInfo.max_channel_expiry_blocks
|
const chanExpiryBlocks = serviceInfo.max_channel_expiry_blocks
|
||||||
const order = await this.createOrder({ pubKey: myPub, refundAddr: refundAddr.address, lspBalance, clientBalance: "0", chanExpiryBlocks })
|
const order = await this.createOrder({ pubKey: myPub, refundAddr: refundAddr.address, lspBalance, clientBalance: "0", chanExpiryBlocks })
|
||||||
|
|
@ -182,8 +172,8 @@ export class OlympusLSP extends LSP {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
const relativeFee = +order.payment.bolt11.fee_total_sat / channelSize
|
const relativeFee = +order.payment.bolt11.fee_total_sat / channelSize
|
||||||
if (relativeFee > this.settings.maxRelativeFee) {
|
if (relativeFee > maxRelativeFee) {
|
||||||
this.log("invoice relative fee of", relativeFee, "exceeds max relative fee of", this.settings.maxRelativeFee)
|
this.log("invoice relative fee of", relativeFee, "exceeds max relative fee of", maxRelativeFee)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
const res = await this.liquidityProvider.PayInvoice(order.payment.bolt11.invoice, decoded.numSatoshis, 'system')
|
const res = await this.liquidityProvider.PayInvoice(order.payment.bolt11.invoice, decoded.numSatoshis, 'system')
|
||||||
|
|
@ -193,7 +183,7 @@ export class OlympusLSP extends LSP {
|
||||||
}
|
}
|
||||||
|
|
||||||
getInfo = async () => {
|
getInfo = async () => {
|
||||||
const res = await fetch(`${this.settings.olympusServiceUrl}/get_info`)
|
const res = await fetch(`${this.settings.getSettings().lspSettings.olympusServiceUrl}/get_info`)
|
||||||
const json = await res.json() as { min_initial_client_balance_sat: string, max_channel_expiry_blocks: number, uris: string[] }
|
const json = await res.json() as { min_initial_client_balance_sat: string, max_channel_expiry_blocks: number, uris: string[] }
|
||||||
return json
|
return json
|
||||||
}
|
}
|
||||||
|
|
@ -209,7 +199,7 @@ export class OlympusLSP extends LSP {
|
||||||
funding_confirms_within_blocks: 6,
|
funding_confirms_within_blocks: 6,
|
||||||
required_channel_confirmations: 0
|
required_channel_confirmations: 0
|
||||||
}
|
}
|
||||||
const res = await fetch(`${this.settings.olympusServiceUrl}/create_order`, {
|
const res = await fetch(`${this.settings.getSettings().lspSettings.olympusServiceUrl}/create_order`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify(req),
|
body: JSON.stringify(req),
|
||||||
headers: { "Content-Type": "application/json" }
|
headers: { "Content-Type": "application/json" }
|
||||||
|
|
@ -219,7 +209,7 @@ export class OlympusLSP extends LSP {
|
||||||
}
|
}
|
||||||
|
|
||||||
getOrder = async (orderId: string) => {
|
getOrder = async (orderId: string) => {
|
||||||
const res = await fetch(`${this.settings.olympusServiceUrl}/get_order&order_id=${orderId}`)
|
const res = await fetch(`${this.settings.getSettings().lspSettings.olympusServiceUrl}/get_order&order_id=${orderId}`)
|
||||||
const json = await res.json() as {}
|
const json = await res.json() as {}
|
||||||
return json
|
return json
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,5 @@
|
||||||
import { Channel, ChannelEventUpdate } from "../../../proto/lnd/lightning"
|
import { Channel, ChannelEventUpdate } from "../../../proto/lnd/lightning"
|
||||||
import { HtlcEvent } from "../../../proto/lnd/router"
|
import { HtlcEvent } from "../../../proto/lnd/router"
|
||||||
export type NodeSettings = {
|
|
||||||
lndAddr: string
|
|
||||||
lndCertPath: string
|
|
||||||
lndMacaroonPath: string
|
|
||||||
}
|
|
||||||
export type LndSettings = {
|
|
||||||
mainNode: NodeSettings
|
|
||||||
lndLogDir: string
|
|
||||||
feeRateLimit: number
|
|
||||||
feeFixedLimit: number
|
|
||||||
feeRateBps: number
|
|
||||||
mockLnd: boolean
|
|
||||||
|
|
||||||
otherNode?: NodeSettings
|
|
||||||
thirdNode?: NodeSettings
|
|
||||||
}
|
|
||||||
|
|
||||||
type TxOutput = {
|
type TxOutput = {
|
||||||
hash: string
|
hash: string
|
||||||
|
|
@ -63,3 +47,6 @@ export type PaidInvoice = {
|
||||||
paymentPreimage: string
|
paymentPreimage: string
|
||||||
providerDst?: string
|
providerDst?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,11 @@
|
||||||
import fs, { watchFile } from "fs";
|
import fs, { watchFile } from "fs";
|
||||||
import crypto from 'crypto'
|
import crypto from 'crypto'
|
||||||
import { ERROR, getLogger } from "../helpers/logger.js";
|
import { ERROR, getLogger } from "../helpers/logger.js";
|
||||||
import { MainSettings, getDataPath } from "./settings.js";
|
|
||||||
import Storage from "../storage/index.js";
|
import Storage from "../storage/index.js";
|
||||||
import * as Types from '../../../proto/autogenerated/ts/types.js'
|
import * as Types from '../../../proto/autogenerated/ts/types.js'
|
||||||
import LND from "../lnd/lnd.js";
|
import LND from "../lnd/lnd.js";
|
||||||
|
import SettingsManager from "./settingsManager.js";
|
||||||
export class AdminManager {
|
export class AdminManager {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
storage: Storage
|
storage: Storage
|
||||||
log = getLogger({ component: "adminManager" })
|
log = getLogger({ component: "adminManager" })
|
||||||
adminNpub = ""
|
adminNpub = ""
|
||||||
|
|
@ -23,9 +18,10 @@ export class AdminManager {
|
||||||
appNprofile: string
|
appNprofile: string
|
||||||
lnd: LND
|
lnd: LND
|
||||||
nostrConnected: boolean = false
|
nostrConnected: boolean = false
|
||||||
constructor(mainSettings: MainSettings, storage: Storage) {
|
private nostrReset: () => Promise<void> = async () => { this.log("nostr reset not initialized yet") }
|
||||||
|
constructor(settings: SettingsManager, storage: Storage) {
|
||||||
this.storage = storage
|
this.storage = storage
|
||||||
this.dataDir = mainSettings.storageSettings.dataDir
|
this.dataDir = settings.getStorageSettings().dataDir
|
||||||
this.adminNpubPath = getDataPath(this.dataDir, 'admin.npub')
|
this.adminNpubPath = getDataPath(this.dataDir, 'admin.npub')
|
||||||
this.adminEnrollTokenPath = getDataPath(this.dataDir, 'admin.enroll')
|
this.adminEnrollTokenPath = getDataPath(this.dataDir, 'admin.enroll')
|
||||||
this.adminConnectPath = getDataPath(this.dataDir, 'admin.connect')
|
this.adminConnectPath = getDataPath(this.dataDir, 'admin.connect')
|
||||||
|
|
@ -39,6 +35,14 @@ export class AdminManager {
|
||||||
this.start()
|
this.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
attachNostrReset(f: () => Promise<void>) {
|
||||||
|
this.nostrReset = f
|
||||||
|
}
|
||||||
|
|
||||||
|
async ResetNostr() {
|
||||||
|
await this.nostrReset()
|
||||||
|
}
|
||||||
|
|
||||||
setLND = (lnd: LND) => {
|
setLND = (lnd: LND) => {
|
||||||
this.lnd = lnd
|
this.lnd = lnd
|
||||||
}
|
}
|
||||||
|
|
@ -250,3 +254,7 @@ export class AdminManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getDataPath = (dataDir: string, dataPath: string) => {
|
||||||
|
return dataDir !== "" ? `${dataDir}/${dataPath}` : dataPath
|
||||||
|
}
|
||||||
|
|
@ -2,23 +2,23 @@ import jwt from 'jsonwebtoken'
|
||||||
import Storage from '../storage/index.js'
|
import Storage from '../storage/index.js'
|
||||||
import * as Types from '../../../proto/autogenerated/ts/types.js'
|
import * as Types from '../../../proto/autogenerated/ts/types.js'
|
||||||
|
|
||||||
import { MainSettings } from './settings.js'
|
|
||||||
import ApplicationManager from './applicationManager.js'
|
import ApplicationManager from './applicationManager.js'
|
||||||
import { OfferPriceType, ndebitEncode, nmanageEncode, nofferEncode } from '@shocknet/clink-sdk'
|
import { OfferPriceType, ndebitEncode, nmanageEncode, nofferEncode } from '@shocknet/clink-sdk'
|
||||||
import { getLogger } from '../helpers/logger.js'
|
import { getLogger } from '../helpers/logger.js'
|
||||||
|
import SettingsManager from './settingsManager.js'
|
||||||
export default class {
|
export default class {
|
||||||
|
|
||||||
storage: Storage
|
storage: Storage
|
||||||
settings: MainSettings
|
settings: SettingsManager
|
||||||
applicationManager: ApplicationManager
|
applicationManager: ApplicationManager
|
||||||
log = getLogger({ component: 'AppUserManager' })
|
log = getLogger({ component: 'AppUserManager' })
|
||||||
constructor(storage: Storage, settings: MainSettings, applicationManager: ApplicationManager) {
|
constructor(storage: Storage, settings: SettingsManager, applicationManager: ApplicationManager) {
|
||||||
this.storage = storage
|
this.storage = storage
|
||||||
this.settings = settings
|
this.settings = settings
|
||||||
this.applicationManager = applicationManager
|
this.applicationManager = applicationManager
|
||||||
}
|
}
|
||||||
SignUserToken(userId: string, appId: string, userIdentifier: string): string {
|
SignUserToken(userId: string, appId: string, userIdentifier: string): string {
|
||||||
return jwt.sign({ user_id: userId, app_id: appId, app_user_id: userIdentifier }, this.settings.jwtSecret);
|
return jwt.sign({ user_id: userId, app_id: appId, app_user_id: userIdentifier }, this.settings.getStorageSettings().jwtSecret);
|
||||||
}
|
}
|
||||||
|
|
||||||
DecodeUserToken(token?: string): { user_id: string, app_id: string, app_user_id: string } {
|
DecodeUserToken(token?: string): { user_id: string, app_id: string, app_user_id: string } {
|
||||||
|
|
@ -28,7 +28,7 @@ export default class {
|
||||||
t = token.substring("Bearer ".length)
|
t = token.substring("Bearer ".length)
|
||||||
}
|
}
|
||||||
if (!t) throw new Error("no user token provided")
|
if (!t) throw new Error("no user token provided")
|
||||||
const decoded = jwt.verify(token, this.settings.jwtSecret) as { user_id: string, app_id: string, app_user_id: string }
|
const decoded = jwt.verify(token, this.settings.getStorageSettings().jwtSecret) as { user_id: string, app_id: string, app_user_id: string }
|
||||||
if (!decoded.user_id || !decoded.app_id || !decoded.app_user_id) {
|
if (!decoded.user_id || !decoded.app_id || !decoded.app_user_id) {
|
||||||
throw new Error("the provided token is not a valid app user token token")
|
throw new Error("the provided token is not a valid app user token token")
|
||||||
}
|
}
|
||||||
|
|
@ -37,11 +37,11 @@ export default class {
|
||||||
}
|
}
|
||||||
|
|
||||||
GetHttpCreds(ctx: Types.UserContext): Types.HttpCreds {
|
GetHttpCreds(ctx: Types.UserContext): Types.HttpCreds {
|
||||||
if (!this.settings.allowHttpUpgrade) {
|
if (!this.settings.getSettings().serviceSettings.allowHttpUpgrade) {
|
||||||
throw new Error("http upgrade not allowed")
|
throw new Error("http upgrade not allowed")
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
url: this.settings.serviceUrl,
|
url: this.settings.getSettings().serviceSettings.serviceUrl,
|
||||||
token: this.SignUserToken(ctx.user_id, ctx.app_id, ctx.app_user_id)
|
token: this.SignUserToken(ctx.user_id, ctx.app_id, ctx.app_user_id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -68,20 +68,20 @@ export default class {
|
||||||
if (!appUser) {
|
if (!appUser) {
|
||||||
throw new Error(`app user ${ctx.user_id} not found`) // TODO: fix logs doxing
|
throw new Error(`app user ${ctx.user_id} not found`) // TODO: fix logs doxing
|
||||||
}
|
}
|
||||||
const nostrSettings = this.settings.nostrRelaySettings
|
const nostrSettings = this.settings.getSettings().nostrRelaySettings
|
||||||
return {
|
return {
|
||||||
userId: ctx.user_id,
|
userId: ctx.user_id,
|
||||||
balance: user.balance_sats,
|
balance: user.balance_sats,
|
||||||
max_withdrawable: this.applicationManager.paymentManager.GetMaxPayableInvoice(user.balance_sats, true),
|
max_withdrawable: this.applicationManager.paymentManager.GetMaxPayableInvoice(user.balance_sats, true),
|
||||||
user_identifier: appUser.identifier,
|
user_identifier: appUser.identifier,
|
||||||
network_max_fee_bps: this.settings.lndSettings.feeRateBps,
|
network_max_fee_bps: this.settings.getSettings().lndSettings.feeRateBps,
|
||||||
network_max_fee_fixed: this.settings.lndSettings.feeFixedLimit,
|
network_max_fee_fixed: this.settings.getSettings().lndSettings.feeFixedLimit,
|
||||||
service_fee_bps: this.settings.outgoingAppUserInvoiceFeeBps,
|
service_fee_bps: this.settings.getSettings().serviceFeeSettings.outgoingAppUserInvoiceFeeBps,
|
||||||
noffer: nofferEncode({ pubkey: app.nostr_public_key!, offer: appUser.identifier, priceType: OfferPriceType.Spontaneous, relay: nostrSettings.relays[0] }),
|
noffer: nofferEncode({ pubkey: app.nostr_public_key!, offer: appUser.identifier, priceType: OfferPriceType.Spontaneous, relay: nostrSettings.relays[0] }),
|
||||||
ndebit: ndebitEncode({ pubkey: app.nostr_public_key!, pointer: appUser.identifier, relay: nostrSettings.relays[0] }),
|
ndebit: ndebitEncode({ pubkey: app.nostr_public_key!, pointer: appUser.identifier, relay: nostrSettings.relays[0] }),
|
||||||
nmanage: nmanageEncode({ pubkey: app.nostr_public_key!, pointer: appUser.identifier, relay: nostrSettings.relays[0] }),
|
nmanage: nmanageEncode({ pubkey: app.nostr_public_key!, pointer: appUser.identifier, relay: nostrSettings.relays[0] }),
|
||||||
callback_url: appUser.callback_url,
|
callback_url: appUser.callback_url,
|
||||||
bridge_url: this.settings.bridgeUrl
|
bridge_url: this.settings.getSettings().serviceSettings.bridgeUrl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -124,24 +124,24 @@ export default class {
|
||||||
async CleanupInactiveUsers() {
|
async CleanupInactiveUsers() {
|
||||||
this.log("Cleaning up inactive users")
|
this.log("Cleaning up inactive users")
|
||||||
const inactiveUsers = await this.storage.userStorage.GetInactiveUsers(365)
|
const inactiveUsers = await this.storage.userStorage.GetInactiveUsers(365)
|
||||||
const toDelete:{userId: string, appUserIds: string[]}[] = []
|
const toDelete: { userId: string, appUserIds: string[] }[] = []
|
||||||
for (const u of inactiveUsers) {
|
for (const u of inactiveUsers) {
|
||||||
const user = await this.storage.userStorage.GetUser(u.user_id)
|
const user = await this.storage.userStorage.GetUser(u.user_id)
|
||||||
if (user.balance_sats > 10_000) {
|
if (user.balance_sats > 10_000) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
const appUsers = await this.storage.applicationStorage.GetAllAppUsersFromUser(u.user_id)
|
const appUsers = await this.storage.applicationStorage.GetAllAppUsersFromUser(u.user_id)
|
||||||
toDelete.push({userId: u.user_id, appUserIds: appUsers.map(a => a.identifier)})
|
toDelete.push({ userId: u.user_id, appUserIds: appUsers.map(a => a.identifier) })
|
||||||
}
|
}
|
||||||
|
|
||||||
this.log("Found",toDelete.length, "inactive users to delete")
|
this.log("Found", toDelete.length, "inactive users to delete")
|
||||||
// await this.RemoveUsers(toDelete)
|
// await this.RemoveUsers(toDelete)
|
||||||
}
|
}
|
||||||
|
|
||||||
async CleanupNeverActiveUsers() {
|
async CleanupNeverActiveUsers() {
|
||||||
this.log("Cleaning up never active users")
|
this.log("Cleaning up never active users")
|
||||||
const inactiveUsers = await this.storage.userStorage.GetInactiveUsers(30)
|
const inactiveUsers = await this.storage.userStorage.GetInactiveUsers(30)
|
||||||
const toDelete:{userId: string, appUserIds: string[]}[] = []
|
const toDelete: { userId: string, appUserIds: string[] }[] = []
|
||||||
for (const u of inactiveUsers) {
|
for (const u of inactiveUsers) {
|
||||||
const user = await this.storage.userStorage.GetUser(u.user_id)
|
const user = await this.storage.userStorage.GetUser(u.user_id)
|
||||||
if (user.balance_sats > 0) {
|
if (user.balance_sats > 0) {
|
||||||
|
|
@ -160,18 +160,18 @@ export default class {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
const appUsers = await this.storage.applicationStorage.GetAllAppUsersFromUser(u.user_id)
|
const appUsers = await this.storage.applicationStorage.GetAllAppUsersFromUser(u.user_id)
|
||||||
toDelete.push({userId: u.user_id, appUserIds: appUsers.map(a => a.identifier)})
|
toDelete.push({ userId: u.user_id, appUserIds: appUsers.map(a => a.identifier) })
|
||||||
}
|
}
|
||||||
|
|
||||||
this.log("Found",toDelete.length, "never active users to delete")
|
this.log("Found", toDelete.length, "never active users to delete")
|
||||||
// await this.RemoveUsers(toDelete) TODO: activate deletion
|
// await this.RemoveUsers(toDelete) TODO: activate deletion
|
||||||
}
|
}
|
||||||
|
|
||||||
async RemoveUsers(toDelete: { userId: string, appUserIds: string[] }[]) {
|
async RemoveUsers(toDelete: { userId: string, appUserIds: string[] }[]) {
|
||||||
this.log("Deleting",toDelete.length, "inactive users")
|
this.log("Deleting", toDelete.length, "inactive users")
|
||||||
for (let i = 0; i < toDelete.length; i++) {
|
for (let i = 0; i < toDelete.length; i++) {
|
||||||
const {userId,appUserIds} = toDelete[i]
|
const { userId, appUserIds } = toDelete[i]
|
||||||
this.log("Deleting user", userId, "progress", i+1, "/", toDelete.length)
|
this.log("Deleting user", userId, "progress", i + 1, "/", toDelete.length)
|
||||||
await this.storage.StartTransaction(async tx => {
|
await this.storage.StartTransaction(async tx => {
|
||||||
for (const appUserId of appUserIds) {
|
for (const appUserId of appUserIds) {
|
||||||
await this.storage.managementStorage.removeUserGrants(appUserId, tx)
|
await this.storage.managementStorage.removeUserGrants(appUserId, tx)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import jwt from 'jsonwebtoken'
|
import jwt from 'jsonwebtoken'
|
||||||
import Storage from '../storage/index.js'
|
import Storage from '../storage/index.js'
|
||||||
import * as Types from '../../../proto/autogenerated/ts/types.js'
|
import * as Types from '../../../proto/autogenerated/ts/types.js'
|
||||||
import { MainSettings } from './settings.js'
|
|
||||||
import PaymentManager from './paymentManager.js'
|
import PaymentManager from './paymentManager.js'
|
||||||
import { InboundOptionals, defaultInvoiceExpiry } from '../storage/paymentStorage.js'
|
import { InboundOptionals, defaultInvoiceExpiry } from '../storage/paymentStorage.js'
|
||||||
import { ApplicationUser } from '../storage/entity/ApplicationUser.js'
|
import { ApplicationUser } from '../storage/entity/ApplicationUser.js'
|
||||||
|
|
@ -10,6 +9,7 @@ import crypto from 'crypto'
|
||||||
import { Application } from '../storage/entity/Application.js'
|
import { Application } from '../storage/entity/Application.js'
|
||||||
import { ZapInfo } from '../storage/entity/UserReceivingInvoice.js'
|
import { ZapInfo } from '../storage/entity/UserReceivingInvoice.js'
|
||||||
import { nofferEncode, ndebitEncode, OfferPriceType, nmanageEncode } from '@shocknet/clink-sdk'
|
import { nofferEncode, ndebitEncode, OfferPriceType, nmanageEncode } from '@shocknet/clink-sdk'
|
||||||
|
import SettingsManager from './settingsManager.js'
|
||||||
const TOKEN_EXPIRY_TIME = 2 * 60 * 1000 // 2 minutes, in milliseconds
|
const TOKEN_EXPIRY_TIME = 2 * 60 * 1000 // 2 minutes, in milliseconds
|
||||||
|
|
||||||
type NsecLinkingData = {
|
type NsecLinkingData = {
|
||||||
|
|
@ -19,13 +19,13 @@ type NsecLinkingData = {
|
||||||
export default class {
|
export default class {
|
||||||
|
|
||||||
storage: Storage
|
storage: Storage
|
||||||
settings: MainSettings
|
settings: SettingsManager
|
||||||
paymentManager: PaymentManager
|
paymentManager: PaymentManager
|
||||||
nPubLinkingTokens = new Map<string, NsecLinkingData>();
|
nPubLinkingTokens = new Map<string, NsecLinkingData>();
|
||||||
linkingTokenInterval: NodeJS.Timeout | null = null
|
linkingTokenInterval: NodeJS.Timeout | null = null
|
||||||
serviceBeaconInterval: NodeJS.Timeout | null = null
|
serviceBeaconInterval: NodeJS.Timeout | null = null
|
||||||
log: PubLogger
|
log: PubLogger
|
||||||
constructor(storage: Storage, settings: MainSettings, paymentManager: PaymentManager) {
|
constructor(storage: Storage, settings: SettingsManager, paymentManager: PaymentManager) {
|
||||||
this.log = getLogger({ component: "ApplicationManager" })
|
this.log = getLogger({ component: "ApplicationManager" })
|
||||||
this.storage = storage
|
this.storage = storage
|
||||||
this.settings = settings
|
this.settings = settings
|
||||||
|
|
@ -69,7 +69,7 @@ export default class {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SignAppToken(appId: string): string {
|
SignAppToken(appId: string): string {
|
||||||
return jwt.sign({ appId }, this.settings.jwtSecret);
|
return jwt.sign({ appId }, this.settings.getStorageSettings().jwtSecret);
|
||||||
}
|
}
|
||||||
DecodeAppToken(token?: string): string {
|
DecodeAppToken(token?: string): string {
|
||||||
if (!token) throw new Error("empty app token provided")
|
if (!token) throw new Error("empty app token provided")
|
||||||
|
|
@ -78,7 +78,7 @@ export default class {
|
||||||
t = token.substring("Bearer ".length)
|
t = token.substring("Bearer ".length)
|
||||||
}
|
}
|
||||||
if (!t) throw new Error("no app token provided")
|
if (!t) throw new Error("no app token provided")
|
||||||
const decoded = jwt.verify(token, this.settings.jwtSecret) as { appId?: string }
|
const decoded = jwt.verify(token, this.settings.getStorageSettings().jwtSecret) as { appId?: string }
|
||||||
if (!decoded.appId) {
|
if (!decoded.appId) {
|
||||||
throw new Error("the provided token is not an app token")
|
throw new Error("the provided token is not an app token")
|
||||||
}
|
}
|
||||||
|
|
@ -150,7 +150,7 @@ export default class {
|
||||||
u = user
|
u = user
|
||||||
if (created) log(u.identifier, u.user.user_id, "user created")
|
if (created) log(u.identifier, u.user.user_id, "user created")
|
||||||
}
|
}
|
||||||
const nostrSettings = this.settings.nostrRelaySettings
|
const nostrSettings = this.settings.getSettings().nostrRelaySettings
|
||||||
|
|
||||||
const ndebitString = ndebitEncode({ pubkey: app.nostr_public_key!, pointer: u.identifier, relay: nostrSettings.relays[0] })
|
const ndebitString = ndebitEncode({ pubkey: app.nostr_public_key!, pointer: u.identifier, relay: nostrSettings.relays[0] })
|
||||||
log("🔗 [DEBUG] Generated ndebit for user", { userId: u.user.user_id, ndebit: ndebitString })
|
log("🔗 [DEBUG] Generated ndebit for user", { userId: u.user.user_id, ndebit: ndebitString })
|
||||||
|
|
@ -162,14 +162,14 @@ export default class {
|
||||||
balance: u.user.balance_sats,
|
balance: u.user.balance_sats,
|
||||||
max_withdrawable: this.paymentManager.GetMaxPayableInvoice(u.user.balance_sats, true),
|
max_withdrawable: this.paymentManager.GetMaxPayableInvoice(u.user.balance_sats, true),
|
||||||
user_identifier: u.identifier,
|
user_identifier: u.identifier,
|
||||||
network_max_fee_bps: this.settings.lndSettings.feeRateBps,
|
network_max_fee_bps: this.settings.getSettings().lndSettings.feeRateBps,
|
||||||
network_max_fee_fixed: this.settings.lndSettings.feeFixedLimit,
|
network_max_fee_fixed: this.settings.getSettings().lndSettings.feeFixedLimit,
|
||||||
service_fee_bps: this.settings.outgoingAppUserInvoiceFeeBps,
|
service_fee_bps: this.settings.getSettings().serviceFeeSettings.outgoingAppUserInvoiceFeeBps,
|
||||||
noffer: nofferEncode({ pubkey: app.nostr_public_key!, offer: u.identifier, priceType: OfferPriceType.Spontaneous, relay: nostrSettings.relays[0] }),
|
noffer: nofferEncode({ pubkey: app.nostr_public_key!, offer: u.identifier, priceType: OfferPriceType.Spontaneous, relay: nostrSettings.relays[0] }),
|
||||||
ndebit: ndebitEncode({ pubkey: app.nostr_public_key!, pointer: u.identifier, relay: nostrSettings.relays[0] }),
|
ndebit: ndebitEncode({ pubkey: app.nostr_public_key!, pointer: u.identifier, relay: nostrSettings.relays[0] }),
|
||||||
nmanage: nmanageEncode({ pubkey: app.nostr_public_key!, pointer: u.identifier, relay: nostrSettings.relays[0] }),
|
nmanage: nmanageEncode({ pubkey: app.nostr_public_key!, pointer: u.identifier, relay: nostrSettings.relays[0] }),
|
||||||
callback_url: u.callback_url,
|
callback_url: u.callback_url,
|
||||||
bridge_url: this.settings.bridgeUrl
|
bridge_url: this.settings.getSettings().serviceSettings.bridgeUrl
|
||||||
|
|
||||||
},
|
},
|
||||||
max_withdrawable: this.paymentManager.GetMaxPayableInvoice(u.user.balance_sats, true)
|
max_withdrawable: this.paymentManager.GetMaxPayableInvoice(u.user.balance_sats, true)
|
||||||
|
|
@ -212,20 +212,20 @@ export default class {
|
||||||
const app = await this.storage.applicationStorage.GetApplication(appId)
|
const app = await this.storage.applicationStorage.GetApplication(appId)
|
||||||
const user = await this.storage.applicationStorage.GetApplicationUser(app, req.user_identifier)
|
const user = await this.storage.applicationStorage.GetApplicationUser(app, req.user_identifier)
|
||||||
const max = this.paymentManager.GetMaxPayableInvoice(user.user.balance_sats, true)
|
const max = this.paymentManager.GetMaxPayableInvoice(user.user.balance_sats, true)
|
||||||
const nostrSettings = this.settings.nostrRelaySettings
|
const nostrSettings = this.settings.getSettings().nostrRelaySettings
|
||||||
return {
|
return {
|
||||||
max_withdrawable: max, identifier: req.user_identifier, info: {
|
max_withdrawable: max, identifier: req.user_identifier, info: {
|
||||||
userId: user.user.user_id, balance: user.user.balance_sats,
|
userId: user.user.user_id, balance: user.user.balance_sats,
|
||||||
max_withdrawable: this.paymentManager.GetMaxPayableInvoice(user.user.balance_sats, true),
|
max_withdrawable: this.paymentManager.GetMaxPayableInvoice(user.user.balance_sats, true),
|
||||||
user_identifier: user.identifier,
|
user_identifier: user.identifier,
|
||||||
network_max_fee_bps: this.settings.lndSettings.feeRateBps,
|
network_max_fee_bps: this.settings.getSettings().lndSettings.feeRateBps,
|
||||||
network_max_fee_fixed: this.settings.lndSettings.feeFixedLimit,
|
network_max_fee_fixed: this.settings.getSettings().lndSettings.feeFixedLimit,
|
||||||
service_fee_bps: this.settings.outgoingAppUserInvoiceFeeBps,
|
service_fee_bps: this.settings.getSettings().serviceFeeSettings.outgoingAppUserInvoiceFeeBps,
|
||||||
noffer: nofferEncode({ pubkey: app.nostr_public_key!, offer: user.identifier, priceType: OfferPriceType.Spontaneous, relay: nostrSettings.relays[0] }),
|
noffer: nofferEncode({ pubkey: app.nostr_public_key!, offer: user.identifier, priceType: OfferPriceType.Spontaneous, relay: nostrSettings.relays[0] }),
|
||||||
ndebit: ndebitEncode({ pubkey: app.nostr_public_key!, pointer: user.identifier, relay: nostrSettings.relays[0] }),
|
ndebit: ndebitEncode({ pubkey: app.nostr_public_key!, pointer: user.identifier, relay: nostrSettings.relays[0] }),
|
||||||
nmanage: nmanageEncode({ pubkey: app.nostr_public_key!, pointer: user.identifier, relay: nostrSettings.relays[0] }),
|
nmanage: nmanageEncode({ pubkey: app.nostr_public_key!, pointer: user.identifier, relay: nostrSettings.relays[0] }),
|
||||||
callback_url: user.callback_url,
|
callback_url: user.callback_url,
|
||||||
bridge_url: this.settings.bridgeUrl
|
bridge_url: this.settings.getSettings().serviceSettings.bridgeUrl
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import * as Types from '../../../proto/autogenerated/ts/types.js'
|
||||||
import ProductManager from './productManager.js'
|
import ProductManager from './productManager.js'
|
||||||
import ApplicationManager from './applicationManager.js'
|
import ApplicationManager from './applicationManager.js'
|
||||||
import PaymentManager, { PendingTx } from './paymentManager.js'
|
import PaymentManager, { PendingTx } from './paymentManager.js'
|
||||||
import { MainSettings } from './settings.js'
|
|
||||||
import LND from "../lnd/lnd.js"
|
import LND from "../lnd/lnd.js"
|
||||||
import { AddressPaidCb, ChannelEventCb, HtlcCb, InvoicePaidCb, NewBlockCb } from "../lnd/settings.js"
|
import { AddressPaidCb, ChannelEventCb, HtlcCb, InvoicePaidCb, NewBlockCb } from "../lnd/settings.js"
|
||||||
import { ERROR, getLogger, PubLogger } from "../helpers/logger.js"
|
import { ERROR, getLogger, PubLogger } from "../helpers/logger.js"
|
||||||
|
|
@ -31,7 +30,8 @@ import { ManagementManager } from "./managementManager.js"
|
||||||
import { Agent } from "https"
|
import { Agent } from "https"
|
||||||
import { NotificationsManager } from "./notificationsManager.js"
|
import { NotificationsManager } from "./notificationsManager.js"
|
||||||
import { ApplicationUser } from '../storage/entity/ApplicationUser.js'
|
import { ApplicationUser } from '../storage/entity/ApplicationUser.js'
|
||||||
|
import SettingsManager from './settingsManager.js'
|
||||||
|
import { NostrSettings } from '../nostr/handler.js'
|
||||||
type UserOperationsSub = {
|
type UserOperationsSub = {
|
||||||
id: string
|
id: string
|
||||||
newIncomingInvoice: (operation: Types.UserOperation) => void
|
newIncomingInvoice: (operation: Types.UserOperation) => void
|
||||||
|
|
@ -44,7 +44,7 @@ const appTag = "Lightning.Pub"
|
||||||
export default class {
|
export default class {
|
||||||
storage: Storage
|
storage: Storage
|
||||||
lnd: LND
|
lnd: LND
|
||||||
settings: MainSettings
|
settings: SettingsManager
|
||||||
userOperationsSub: UserOperationsSub | null = null
|
userOperationsSub: UserOperationsSub | null = null
|
||||||
adminManager: AdminManager
|
adminManager: AdminManager
|
||||||
productManager: ProductManager
|
productManager: ProductManager
|
||||||
|
|
@ -65,17 +65,22 @@ export default class {
|
||||||
//webRTC: webRTC
|
//webRTC: webRTC
|
||||||
nostrSend: NostrSend = () => { getLogger({})("nostr send not initialized yet") }
|
nostrSend: NostrSend = () => { getLogger({})("nostr send not initialized yet") }
|
||||||
nostrProcessPing: (() => Promise<void>) | null = null
|
nostrProcessPing: (() => Promise<void>) | null = null
|
||||||
constructor(settings: MainSettings, storage: Storage, adminManager: AdminManager, utils: Utils, unlocker: Unlocker) {
|
nostrReset: (settings: NostrSettings) => void = () => { getLogger({})("nostr reset not initialized yet") }
|
||||||
|
constructor(settings: SettingsManager, storage: Storage, adminManager: AdminManager, utils: Utils, unlocker: Unlocker) {
|
||||||
this.settings = settings
|
this.settings = settings
|
||||||
this.storage = storage
|
this.storage = storage
|
||||||
this.adminManager = adminManager
|
this.adminManager = adminManager
|
||||||
this.utils = utils
|
this.utils = utils
|
||||||
this.unlocker = unlocker
|
this.unlocker = unlocker
|
||||||
const updateProviderBalance = (b: number) => this.storage.liquidityStorage.IncrementTrackedProviderBalance('lnPub', settings.liquiditySettings.liquidityProviderPub, b)
|
const updateProviderBalance = (b: number) => this.storage.liquidityStorage.IncrementTrackedProviderBalance('lnPub', settings.getSettings().liquiditySettings.liquidityProviderPub, b)
|
||||||
this.liquidityProvider = new LiquidityProvider(settings.liquiditySettings.liquidityProviderPub, this.utils, this.invoicePaidCb, updateProviderBalance)
|
this.liquidityProvider = new LiquidityProvider(() => this.settings.getSettings().liquiditySettings, this.utils, this.invoicePaidCb, updateProviderBalance)
|
||||||
this.rugPullTracker = new RugPullTracker(this.storage, this.liquidityProvider)
|
this.rugPullTracker = new RugPullTracker(this.storage, this.liquidityProvider)
|
||||||
this.lnd = new LND(settings.lndSettings, this.liquidityProvider, this.utils, this.addressPaidCb, this.invoicePaidCb, this.newBlockCb, this.htlcCb, this.channelEventCb)
|
const lndGetSettings = () => ({
|
||||||
this.liquidityManager = new LiquidityManager(this.settings.liquiditySettings, this.storage, this.utils, this.liquidityProvider, this.lnd, this.rugPullTracker)
|
lndSettings: settings.getSettings().lndSettings,
|
||||||
|
lndNodeSettings: settings.getSettings().lndNodeSettings
|
||||||
|
})
|
||||||
|
this.lnd = new LND(lndGetSettings, this.liquidityProvider, this.utils, this.addressPaidCb, this.invoicePaidCb, this.newBlockCb, this.htlcCb, this.channelEventCb)
|
||||||
|
this.liquidityManager = new LiquidityManager(this.settings, this.storage, this.utils, this.liquidityProvider, this.lnd, this.rugPullTracker)
|
||||||
this.metricsManager = new MetricsManager(this.storage, this.lnd)
|
this.metricsManager = new MetricsManager(this.storage, this.lnd)
|
||||||
|
|
||||||
this.paymentManager = new PaymentManager(this.storage, this.lnd, this.settings, this.liquidityManager, this.utils, this.addressPaidCb, this.invoicePaidCb)
|
this.paymentManager = new PaymentManager(this.storage, this.lnd, this.settings, this.liquidityManager, this.utils, this.addressPaidCb, this.invoicePaidCb)
|
||||||
|
|
@ -85,7 +90,7 @@ export default class {
|
||||||
this.debitManager = new DebitManager(this.storage, this.lnd, this.applicationManager)
|
this.debitManager = new DebitManager(this.storage, this.lnd, this.applicationManager)
|
||||||
this.offerManager = new OfferManager(this.storage, this.settings, this.lnd, this.applicationManager, this.productManager, this.liquidityManager)
|
this.offerManager = new OfferManager(this.storage, this.settings, this.lnd, this.applicationManager, this.productManager, this.liquidityManager)
|
||||||
this.managementManager = new ManagementManager(this.storage, this.settings)
|
this.managementManager = new ManagementManager(this.storage, this.settings)
|
||||||
this.notificationsManager = new NotificationsManager(this.settings.shockPushBaseUrl)
|
this.notificationsManager = new NotificationsManager(this.settings)
|
||||||
//this.webRTC = new webRTC(this.storage, this.utils)
|
//this.webRTC = new webRTC(this.storage, this.utils)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -99,7 +104,7 @@ export default class {
|
||||||
|
|
||||||
StartBeacons() {
|
StartBeacons() {
|
||||||
this.applicationManager.StartAppsServiceBeacon(app => {
|
this.applicationManager.StartAppsServiceBeacon(app => {
|
||||||
this.UpdateBeacon(app, { type: 'service', name: app.name, avatarUrl: (app as any).avatar_url })
|
this.UpdateBeacon(app, { type: 'service', name: app.name, avatarUrl: app.avatar_url })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -117,6 +122,11 @@ export default class {
|
||||||
this.nostrProcessPing = f
|
this.nostrProcessPing = f
|
||||||
}
|
}
|
||||||
|
|
||||||
|
attachNostrReset(f: (settings: NostrSettings) => void) {
|
||||||
|
this.nostrReset = f
|
||||||
|
this.adminManager.attachNostrReset(() => this.ResetNostr())
|
||||||
|
}
|
||||||
|
|
||||||
async pingSubProcesses() {
|
async pingSubProcesses() {
|
||||||
if (!this.nostrProcessPing) {
|
if (!this.nostrProcessPing) {
|
||||||
throw new Error("nostr process ping not initialized")
|
throw new Error("nostr process ping not initialized")
|
||||||
|
|
@ -386,7 +396,7 @@ export default class {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async UpdateBeacon(app: Application, content: { type: 'service', name: string, avatarUrl?: string }) {
|
async UpdateBeacon(app: Application, content: { type: 'service', name: string, avatarUrl?: string, nextRelay?: string }) {
|
||||||
if (!app.nostr_public_key) {
|
if (!app.nostr_public_key) {
|
||||||
getLogger({ appName: app.name })("cannot update beacon, public key not set")
|
getLogger({ appName: app.name })("cannot update beacon, public key not set")
|
||||||
return
|
return
|
||||||
|
|
@ -421,6 +431,32 @@ export default class {
|
||||||
log({ unsigned: event })
|
log({ unsigned: event })
|
||||||
this.nostrSend({ type: 'app', appId: invoice.linkedApplication.app_id }, { type: 'event', event }, zapInfo.relays || undefined)
|
this.nostrSend({ type: 'app', appId: invoice.linkedApplication.app_id }, { type: 'event', event }, zapInfo.relays || undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async ResetNostr() {
|
||||||
|
const apps = await this.storage.applicationStorage.GetApplications()
|
||||||
|
const nextRelay = this.settings.getSettings().nostrRelaySettings.relays[0]
|
||||||
|
for (const app of apps) {
|
||||||
|
await this.UpdateBeacon(app, { type: 'service', name: app.name, avatarUrl: app.avatar_url, nextRelay })
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultNames = ['wallet', 'wallet-test', this.settings.getSettings().serviceSettings.defaultAppName]
|
||||||
|
const liquidityProviderApp = apps.find(app => defaultNames.includes(app.name))
|
||||||
|
if (!liquidityProviderApp) {
|
||||||
|
throw new Error("wallet app not initialized correctly")
|
||||||
|
}
|
||||||
|
const liquidityProviderInfo = {
|
||||||
|
privateKey: liquidityProviderApp.nostr_private_key || "",
|
||||||
|
publicKey: liquidityProviderApp.nostr_public_key || "",
|
||||||
|
name: "liquidity_provider", clientId: `client_${liquidityProviderApp.app_id}`
|
||||||
|
}
|
||||||
|
const s: NostrSettings = {
|
||||||
|
apps: apps.map(a => ({ appId: a.app_id, name: a.name, privateKey: a.nostr_private_key || "", publicKey: a.nostr_public_key || "" })),
|
||||||
|
relays: this.settings.getSettings().nostrRelaySettings.relays,
|
||||||
|
maxEventContentLength: this.settings.getSettings().nostrRelaySettings.maxEventContentLength,
|
||||||
|
clients: [liquidityProviderInfo]
|
||||||
|
}
|
||||||
|
this.nostrReset(s)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,55 +1,56 @@
|
||||||
import { PubLogger, getLogger } from "../helpers/logger.js"
|
import { PubLogger, getLogger } from "../helpers/logger.js"
|
||||||
import { LiquidityProvider } from "./liquidityProvider.js"
|
import { LiquidityProvider } from "./liquidityProvider.js"
|
||||||
import { Unlocker } from "./unlocker.js"
|
import { Unlocker } from "./unlocker.js"
|
||||||
import Storage from "../storage/index.js"
|
import Storage, { StorageSettings } from "../storage/index.js"
|
||||||
/* import { TypeOrmMigrationRunner } from "../storage/migrations/runner.js" */
|
/* import { TypeOrmMigrationRunner } from "../storage/migrations/runner.js" */
|
||||||
import Main from "./index.js"
|
import Main from "./index.js"
|
||||||
import SanityChecker from "./sanityChecker.js"
|
import SanityChecker from "./sanityChecker.js"
|
||||||
import { LoadMainSettingsFromEnv, MainSettings } from "./settings.js"
|
|
||||||
import { Utils } from "../helpers/utilsWrapper.js"
|
import { Utils } from "../helpers/utilsWrapper.js"
|
||||||
import { Wizard } from "../wizard/index.js"
|
import { Wizard } from "../wizard/index.js"
|
||||||
import { AdminManager } from "./adminManager.js"
|
import { AdminManager } from "./adminManager.js"
|
||||||
import { TlvStorageFactory } from "../storage/tlv/tlvFilesStorageFactory.js"
|
import SettingsManager from "./settingsManager.js"
|
||||||
|
import { LoadStorageSettingsFromEnv } from "../storage/index.js"
|
||||||
export type AppData = {
|
export type AppData = {
|
||||||
privateKey: string;
|
privateKey: string;
|
||||||
publicKey: string;
|
publicKey: string;
|
||||||
appId: string;
|
appId: string;
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
export const initMainHandler = async (log: PubLogger, mainSettings: MainSettings) => {
|
|
||||||
const utils = new Utils({ dataDir: mainSettings.storageSettings.dataDir, allowResetMetricsStorages: mainSettings.allowResetMetricsStorages })
|
export const initSettings = async (log: PubLogger, storageSettings: StorageSettings): Promise<SettingsManager> => {
|
||||||
const storageManager = new Storage(mainSettings.storageSettings, utils)
|
const utils = new Utils({ dataDir: storageSettings.dataDir, allowResetMetricsStorages: storageSettings.allowResetMetricsStorages })
|
||||||
|
const storageManager = new Storage(storageSettings, utils)
|
||||||
await storageManager.Connect(log)
|
await storageManager.Connect(log)
|
||||||
/* const manualMigration = await TypeOrmMigrationRunner(log, storageManager, mainSettings.storageSettings.dbSettings, process.argv[2])
|
const settingsManager = new SettingsManager(storageManager)
|
||||||
if (manualMigration) {
|
await settingsManager.InitSettings()
|
||||||
return
|
return settingsManager
|
||||||
} */
|
}
|
||||||
const unlocker = new Unlocker(mainSettings, storageManager)
|
export const initMainHandler = async (log: PubLogger, settingsManager: SettingsManager) => {
|
||||||
|
const storageManager = settingsManager.storage
|
||||||
|
const utils = storageManager.utils
|
||||||
|
const unlocker = new Unlocker(settingsManager, storageManager)
|
||||||
await unlocker.Unlock()
|
await unlocker.Unlock()
|
||||||
const adminManager = new AdminManager(mainSettings, storageManager)
|
const adminManager = new AdminManager(settingsManager, storageManager)
|
||||||
let reloadedSettings = mainSettings
|
|
||||||
let wizard: Wizard | null = null
|
let wizard: Wizard | null = null
|
||||||
if (mainSettings.wizard) {
|
if (settingsManager.getSettings().serviceSettings.wizard) {
|
||||||
wizard = new Wizard(mainSettings, storageManager, adminManager)
|
wizard = new Wizard(settingsManager, storageManager, adminManager)
|
||||||
const reload = await wizard.Configure()
|
await wizard.Configure()
|
||||||
if (reload) {
|
|
||||||
reloadedSettings = LoadMainSettingsFromEnv()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const mainHandler = new Main(reloadedSettings, storageManager, adminManager, utils, unlocker)
|
const mainHandler = new Main(settingsManager, storageManager, adminManager, utils, unlocker)
|
||||||
adminManager.setLND(mainHandler.lnd)
|
adminManager.setLND(mainHandler.lnd)
|
||||||
await mainHandler.lnd.Warmup()
|
await mainHandler.lnd.Warmup()
|
||||||
if (!reloadedSettings.skipSanityCheck) {
|
if (!settingsManager.getSettings().serviceSettings.skipSanityCheck) {
|
||||||
const sanityChecker = new SanityChecker(storageManager, mainHandler.lnd)
|
const sanityChecker = new SanityChecker(storageManager, mainHandler.lnd)
|
||||||
await sanityChecker.VerifyEventsLog()
|
await sanityChecker.VerifyEventsLog()
|
||||||
}
|
}
|
||||||
|
const defaultAppName = settingsManager.getSettings().serviceSettings.defaultAppName
|
||||||
const appsData = await mainHandler.storage.applicationStorage.GetApplications()
|
const appsData = await mainHandler.storage.applicationStorage.GetApplications()
|
||||||
const defaultNames = ['wallet', 'wallet-test', reloadedSettings.defaultAppName]
|
const defaultNames = ['wallet', 'wallet-test', defaultAppName]
|
||||||
const existingWalletApp = await appsData.find(app => defaultNames.includes(app.name))
|
const existingWalletApp = await appsData.find(app => defaultNames.includes(app.name))
|
||||||
if (!existingWalletApp) {
|
if (!existingWalletApp) {
|
||||||
log("no default wallet app found, creating one...")
|
log("no default wallet app found, creating one...")
|
||||||
const newWalletApp = await mainHandler.storage.applicationStorage.AddApplication(reloadedSettings.defaultAppName, true)
|
const newWalletApp = await mainHandler.storage.applicationStorage.AddApplication(defaultAppName, true)
|
||||||
appsData.push(newWalletApp)
|
appsData.push(newWalletApp)
|
||||||
}
|
}
|
||||||
const apps: AppData[] = await Promise.all(appsData.map(app => {
|
const apps: AppData[] = await Promise.all(appsData.map(app => {
|
||||||
|
|
|
||||||
|
|
@ -2,22 +2,14 @@ import { getLogger } from "../helpers/logger.js"
|
||||||
import { Utils } from "../helpers/utilsWrapper.js"
|
import { Utils } from "../helpers/utilsWrapper.js"
|
||||||
import { LiquidityProvider } from "./liquidityProvider.js"
|
import { LiquidityProvider } from "./liquidityProvider.js"
|
||||||
import LND from "../lnd/lnd.js"
|
import LND from "../lnd/lnd.js"
|
||||||
import { FlashsatsLSP, LoadLSPSettingsFromEnv, LSPSettings, OlympusLSP, /* VoltageLSP */ } from "../lnd/lsp.js"
|
import { FlashsatsLSP, OlympusLSP, /* VoltageLSP */ } from "../lnd/lsp.js"
|
||||||
import Storage from '../storage/index.js'
|
import Storage from '../storage/index.js'
|
||||||
import { defaultInvoiceExpiry } from "../storage/paymentStorage.js"
|
import { defaultInvoiceExpiry } from "../storage/paymentStorage.js"
|
||||||
import { RugPullTracker } from "./rugPullTracker.js"
|
import { RugPullTracker } from "./rugPullTracker.js"
|
||||||
export type LiquiditySettings = {
|
import SettingsManager from "./settingsManager.js"
|
||||||
lspSettings: LSPSettings
|
|
||||||
liquidityProviderPub: string
|
|
||||||
useOnlyLiquidityProvider: boolean
|
|
||||||
}
|
|
||||||
export const LoadLiquiditySettingsFromEnv = (): LiquiditySettings => {
|
|
||||||
const lspSettings = LoadLSPSettingsFromEnv()
|
|
||||||
const liquidityProviderPub = process.env.LIQUIDITY_PROVIDER_PUB === "null" ? "" : (process.env.LIQUIDITY_PROVIDER_PUB || "76ed45f00cea7bac59d8d0b7d204848f5319d7b96c140ffb6fcbaaab0a13d44e")
|
|
||||||
return { lspSettings, liquidityProviderPub, useOnlyLiquidityProvider: false }
|
|
||||||
}
|
|
||||||
export class LiquidityManager {
|
export class LiquidityManager {
|
||||||
settings: LiquiditySettings
|
settings: SettingsManager
|
||||||
storage: Storage
|
storage: Storage
|
||||||
liquidityProvider: LiquidityProvider
|
liquidityProvider: LiquidityProvider
|
||||||
rugPullTracker: RugPullTracker
|
rugPullTracker: RugPullTracker
|
||||||
|
|
@ -32,16 +24,16 @@ export class LiquidityManager {
|
||||||
utils: Utils
|
utils: Utils
|
||||||
latestDrain: ({ success: true, amt: number } | { success: false, amt: number, attempt: number, at: Date }) = { success: true, amt: 0 }
|
latestDrain: ({ success: true, amt: number } | { success: false, amt: number, attempt: number, at: Date }) = { success: true, amt: 0 }
|
||||||
drainsSkipped = 0
|
drainsSkipped = 0
|
||||||
constructor(settings: LiquiditySettings, storage: Storage, utils: Utils, liquidityProvider: LiquidityProvider, lnd: LND, rugPullTracker: RugPullTracker) {
|
constructor(settings: SettingsManager, storage: Storage, utils: Utils, liquidityProvider: LiquidityProvider, lnd: LND, rugPullTracker: RugPullTracker) {
|
||||||
this.settings = settings
|
this.settings = settings
|
||||||
this.storage = storage
|
this.storage = storage
|
||||||
this.liquidityProvider = liquidityProvider
|
this.liquidityProvider = liquidityProvider
|
||||||
this.lnd = lnd
|
this.lnd = lnd
|
||||||
this.rugPullTracker = rugPullTracker
|
this.rugPullTracker = rugPullTracker
|
||||||
this.utils = utils
|
this.utils = utils
|
||||||
this.olympusLSP = new OlympusLSP(settings.lspSettings, lnd, liquidityProvider)
|
this.olympusLSP = new OlympusLSP(settings, lnd, liquidityProvider)
|
||||||
/* this.voltageLSP = new VoltageLSP(settings.lspSettings, lnd, liquidityProvider) */
|
/* this.voltageLSP = new VoltageLSP(settings.lspSettings, lnd, liquidityProvider) */
|
||||||
this.flashsatsLSP = new FlashsatsLSP(settings.lspSettings, lnd, liquidityProvider)
|
this.flashsatsLSP = new FlashsatsLSP(settings, lnd, liquidityProvider)
|
||||||
}
|
}
|
||||||
|
|
||||||
GetPaidFees = () => {
|
GetPaidFees = () => {
|
||||||
|
|
@ -58,7 +50,8 @@ export class LiquidityManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeInvoiceCreation = async (amount: number): Promise<'lnd' | 'provider'> => {
|
beforeInvoiceCreation = async (amount: number): Promise<'lnd' | 'provider'> => {
|
||||||
if (this.settings.useOnlyLiquidityProvider) {
|
|
||||||
|
if (this.settings.getSettings().liquiditySettings.useOnlyLiquidityProvider) {
|
||||||
return 'provider'
|
return 'provider'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -86,7 +79,7 @@ export class LiquidityManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeOutInvoicePayment = async (amount: number): Promise<'lnd' | 'provider'> => {
|
beforeOutInvoicePayment = async (amount: number): Promise<'lnd' | 'provider'> => {
|
||||||
if (this.settings.useOnlyLiquidityProvider) {
|
if (this.settings.getSettings().liquiditySettings.useOnlyLiquidityProvider) {
|
||||||
return 'provider'
|
return 'provider'
|
||||||
}
|
}
|
||||||
const canHandle = await this.liquidityProvider.CanProviderHandle({ action: 'spend', amount })
|
const canHandle = await this.liquidityProvider.CanProviderHandle({ action: 'spend', amount })
|
||||||
|
|
@ -155,7 +148,7 @@ export class LiquidityManager {
|
||||||
|
|
||||||
|
|
||||||
shouldOpenChannel = async (): Promise<{ shouldOpen: false } | { shouldOpen: true, maxSpendable: number }> => {
|
shouldOpenChannel = async (): Promise<{ shouldOpen: false } | { shouldOpen: true, maxSpendable: number }> => {
|
||||||
const threshold = this.settings.lspSettings.channelThreshold
|
const threshold = this.settings.getSettings().lspSettings.channelThreshold
|
||||||
if (threshold === 0) {
|
if (threshold === 0) {
|
||||||
return { shouldOpen: false }
|
return { shouldOpen: false }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,13 @@ import { Utils } from '../helpers/utilsWrapper.js'
|
||||||
import { NostrEvent, NostrSend } from '../nostr/handler.js'
|
import { NostrEvent, NostrSend } from '../nostr/handler.js'
|
||||||
import { InvoicePaidCb } from '../lnd/settings.js'
|
import { InvoicePaidCb } from '../lnd/settings.js'
|
||||||
import Storage from '../storage/index.js'
|
import Storage from '../storage/index.js'
|
||||||
|
import SettingsManager from './settingsManager.js'
|
||||||
|
import { LiquiditySettings } from './settings.js'
|
||||||
export type LiquidityRequest = { action: 'spend' | 'receive', amount: number }
|
export type LiquidityRequest = { action: 'spend' | 'receive', amount: number }
|
||||||
|
|
||||||
export type nostrCallback<T> = { startedAtMillis: number, type: 'single' | 'stream', f: (res: T) => void }
|
export type nostrCallback<T> = { startedAtMillis: number, type: 'single' | 'stream', f: (res: T) => void }
|
||||||
export class LiquidityProvider {
|
export class LiquidityProvider {
|
||||||
|
getSettings: () => LiquiditySettings
|
||||||
client: ReturnType<typeof newNostrClient>
|
client: ReturnType<typeof newNostrClient>
|
||||||
clientCbs: Record<string, nostrCallback<any>> = {}
|
clientCbs: Record<string, nostrCallback<any>> = {}
|
||||||
clientId: string = ""
|
clientId: string = ""
|
||||||
|
|
@ -28,12 +30,19 @@ export class LiquidityProvider {
|
||||||
pendingPayments: Record<string, number> = {}
|
pendingPayments: Record<string, number> = {}
|
||||||
incrementProviderBalance: (balance: number) => Promise<void>
|
incrementProviderBalance: (balance: number) => Promise<void>
|
||||||
// make the sub process accept client
|
// make the sub process accept client
|
||||||
constructor(pubDestination: string, utils: Utils, invoicePaidCb: InvoicePaidCb, incrementProviderBalance: (balance: number) => Promise<any>) {
|
constructor(getSettings: () => LiquiditySettings, utils: Utils, invoicePaidCb: InvoicePaidCb, incrementProviderBalance: (balance: number) => Promise<any>) {
|
||||||
this.utils = utils
|
this.utils = utils
|
||||||
|
this.getSettings = getSettings
|
||||||
|
const pubDestination = getSettings().liquidityProviderPub
|
||||||
|
const disableLiquidityProvider = getSettings().disableLiquidityProvider
|
||||||
if (!pubDestination) {
|
if (!pubDestination) {
|
||||||
this.log("No pub provider to liquidity provider, will not be initialized")
|
this.log("No pub provider to liquidity provider, will not be initialized")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (disableLiquidityProvider) {
|
||||||
|
this.log("Liquidity provider is disabled, will not be initialized")
|
||||||
|
return
|
||||||
|
}
|
||||||
this.log("connecting to liquidity provider:", pubDestination)
|
this.log("connecting to liquidity provider:", pubDestination)
|
||||||
this.pubDestination = pubDestination
|
this.pubDestination = pubDestination
|
||||||
this.invoicePaidCb = invoicePaidCb
|
this.invoicePaidCb = invoicePaidCb
|
||||||
|
|
@ -59,14 +68,14 @@ export class LiquidityProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
IsReady = () => {
|
IsReady = () => {
|
||||||
return this.ready
|
return this.ready && !this.getSettings().disableLiquidityProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
AwaitProviderReady = async (): Promise<'inactive' | 'ready'> => {
|
AwaitProviderReady = async (): Promise<'inactive' | 'ready'> => {
|
||||||
if (!this.pubDestination) {
|
if (!this.pubDestination || this.getSettings().disableLiquidityProvider) {
|
||||||
return 'inactive'
|
return 'inactive'
|
||||||
}
|
}
|
||||||
if (this.ready) {
|
if (this.IsReady()) {
|
||||||
return 'ready'
|
return 'ready'
|
||||||
}
|
}
|
||||||
return new Promise<'ready'>(res => {
|
return new Promise<'ready'>(res => {
|
||||||
|
|
@ -119,7 +128,7 @@ export class LiquidityProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
GetLatestMaxWithdrawable = async () => {
|
GetLatestMaxWithdrawable = async () => {
|
||||||
if (!this.ready) {
|
if (!this.IsReady()) {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
const res = await this.GetUserState()
|
const res = await this.GetUserState()
|
||||||
|
|
@ -131,7 +140,7 @@ export class LiquidityProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
GetLatestBalance = async () => {
|
GetLatestBalance = async () => {
|
||||||
if (!this.ready) {
|
if (!this.IsReady()) {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
const res = await this.GetUserState()
|
const res = await this.GetUserState()
|
||||||
|
|
@ -155,7 +164,7 @@ export class LiquidityProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
CanProviderHandle = async (req: LiquidityRequest) => {
|
CanProviderHandle = async (req: LiquidityRequest) => {
|
||||||
if (!this.ready) {
|
if (!this.IsReady()) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
const maxW = await this.GetLatestMaxWithdrawable()
|
const maxW = await this.GetLatestMaxWithdrawable()
|
||||||
|
|
@ -167,8 +176,8 @@ export class LiquidityProvider {
|
||||||
|
|
||||||
AddInvoice = async (amount: number, memo: string, from: 'user' | 'system', expiry: number) => {
|
AddInvoice = async (amount: number, memo: string, from: 'user' | 'system', expiry: number) => {
|
||||||
try {
|
try {
|
||||||
if (!this.ready) {
|
if (!this.IsReady()) {
|
||||||
throw new Error("liquidity provider is not ready yet")
|
throw new Error("liquidity provider is not ready yet or disabled")
|
||||||
}
|
}
|
||||||
const res = await this.client.NewInvoice({ amountSats: amount, memo, expiry })
|
const res = await this.client.NewInvoice({ amountSats: amount, memo, expiry })
|
||||||
if (res.status === 'ERROR') {
|
if (res.status === 'ERROR') {
|
||||||
|
|
@ -186,8 +195,8 @@ export class LiquidityProvider {
|
||||||
|
|
||||||
PayInvoice = async (invoice: string, decodedAmount: number, from: 'user' | 'system') => {
|
PayInvoice = async (invoice: string, decodedAmount: number, from: 'user' | 'system') => {
|
||||||
try {
|
try {
|
||||||
if (!this.ready) {
|
if (!this.IsReady()) {
|
||||||
throw new Error("liquidity provider is not ready yet")
|
throw new Error("liquidity provider is not ready yet or disabled")
|
||||||
}
|
}
|
||||||
const userInfo = await this.GetUserState()
|
const userInfo = await this.GetUserState()
|
||||||
if (userInfo.status === 'ERROR') {
|
if (userInfo.status === 'ERROR') {
|
||||||
|
|
@ -211,8 +220,8 @@ export class LiquidityProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
GetPaymentState = async (invoice: string) => {
|
GetPaymentState = async (invoice: string) => {
|
||||||
if (!this.ready) {
|
if (!this.IsReady()) {
|
||||||
throw new Error("liquidity provider is not ready yet")
|
throw new Error("liquidity provider is not ready yet or disabled")
|
||||||
}
|
}
|
||||||
const res = await this.client.GetPaymentState({ invoice })
|
const res = await this.client.GetPaymentState({ invoice })
|
||||||
if (res.status === 'ERROR') {
|
if (res.status === 'ERROR') {
|
||||||
|
|
@ -223,8 +232,8 @@ export class LiquidityProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
GetOperations = async () => {
|
GetOperations = async () => {
|
||||||
if (!this.ready) {
|
if (!this.IsReady()) {
|
||||||
throw new Error("liquidity provider is not ready yet")
|
throw new Error("liquidity provider is not ready yet or disabled")
|
||||||
}
|
}
|
||||||
const res = await this.client.GetUserOperations({
|
const res = await this.client.GetUserOperations({
|
||||||
latestIncomingInvoice: { ts: 0, id: 0 }, latestOutgoingInvoice: { ts: 0, id: 0 },
|
latestIncomingInvoice: { ts: 0, id: 0 }, latestOutgoingInvoice: { ts: 0, id: 0 },
|
||||||
|
|
|
||||||
|
|
@ -6,19 +6,19 @@ import { NostrEvent, NostrSend } from "../nostr/handler.js";
|
||||||
import Storage from "../storage/index.js";
|
import Storage from "../storage/index.js";
|
||||||
import { OfferManager } from "./offerManager.js";
|
import { OfferManager } from "./offerManager.js";
|
||||||
import * as Types from "../../../proto/autogenerated/ts/types.js";
|
import * as Types from "../../../proto/autogenerated/ts/types.js";
|
||||||
import { MainSettings } from "./settings.js";
|
|
||||||
import { nofferEncode, OfferPointer, OfferPriceType, NmanageRequest, NmanageResponse, NmanageCreateOffer, NmanageUpdateOffer, NmanageDeleteOffer, NmanageGetOffer, NmanageListOffers, OfferData, OfferFields, NmanageFailure } from "@shocknet/clink-sdk";
|
import { nofferEncode, OfferPointer, OfferPriceType, NmanageRequest, NmanageResponse, NmanageCreateOffer, NmanageUpdateOffer, NmanageDeleteOffer, NmanageGetOffer, NmanageListOffers, OfferData, OfferFields, NmanageFailure } from "@shocknet/clink-sdk";
|
||||||
import { UnsignedEvent } from "nostr-tools";
|
import { UnsignedEvent } from "nostr-tools";
|
||||||
import { getLogger, PubLogger, ERROR } from "../helpers/logger.js";
|
import { getLogger, PubLogger, ERROR } from "../helpers/logger.js";
|
||||||
|
import SettingsManager from "./settingsManager.js";
|
||||||
type Result<T> = { state: 'success', result: T } | { state: 'error', err: NmanageFailure } | { state: 'authRequired' }
|
type Result<T> = { state: 'success', result: T } | { state: 'error', err: NmanageFailure } | { state: 'authRequired' }
|
||||||
|
|
||||||
export class ManagementManager {
|
export class ManagementManager {
|
||||||
private nostrSend: NostrSend;
|
private nostrSend: NostrSend;
|
||||||
private storage: Storage;
|
private storage: Storage;
|
||||||
private settings: MainSettings;
|
private settings: SettingsManager;
|
||||||
private awaitingRequests: Record<string, { request: NmanageRequest, event: NostrEvent }> = {}
|
private awaitingRequests: Record<string, { request: NmanageRequest, event: NostrEvent }> = {}
|
||||||
private logger: PubLogger
|
private logger: PubLogger
|
||||||
constructor(storage: Storage, settings: MainSettings) {
|
constructor(storage: Storage, settings: SettingsManager) {
|
||||||
this.storage = storage;
|
this.storage = storage;
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
this.logger = getLogger({ component: 'ManagementManager' })
|
this.logger = getLogger({ component: 'ManagementManager' })
|
||||||
|
|
@ -141,7 +141,7 @@ export class ManagementManager {
|
||||||
const pointer: OfferPointer = {
|
const pointer: OfferPointer = {
|
||||||
offer: offer.offer_id,
|
offer: offer.offer_id,
|
||||||
pubkey: appPub,
|
pubkey: appPub,
|
||||||
relay: this.settings.nostrRelaySettings.relays[0],
|
relay: this.settings.getSettings().nostrRelaySettings.relays[0],
|
||||||
priceType: offer.price_sats > 0 ? OfferPriceType.Fixed : OfferPriceType.Spontaneous,
|
priceType: offer.price_sats > 0 ? OfferPriceType.Fixed : OfferPriceType.Spontaneous,
|
||||||
price: offer.price_sats,
|
price: offer.price_sats,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
import { PushPair, ShockPush } from "../ShockPush/index.js"
|
import { PushPair, ShockPush } from "../ShockPush/index.js"
|
||||||
import { getLogger, PubLogger } from "../helpers/logger.js"
|
import { getLogger, PubLogger } from "../helpers/logger.js"
|
||||||
|
import SettingsManager from "./settingsManager.js"
|
||||||
|
|
||||||
export class NotificationsManager {
|
export class NotificationsManager {
|
||||||
private shockPushBaseUrl: string
|
private settings: SettingsManager
|
||||||
private clients: Record<string, ShockPush> = {}
|
private clients: Record<string, ShockPush> = {}
|
||||||
private logger: PubLogger
|
private logger: PubLogger
|
||||||
constructor(shockPushBaseUrl: string) {
|
constructor(settings: SettingsManager) {
|
||||||
this.shockPushBaseUrl = shockPushBaseUrl
|
this.settings = settings
|
||||||
this.logger = getLogger({ component: 'notificationsManager' })
|
this.logger = getLogger({ component: 'notificationsManager' })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -15,13 +16,13 @@ export class NotificationsManager {
|
||||||
if (client) {
|
if (client) {
|
||||||
return client
|
return client
|
||||||
}
|
}
|
||||||
const newClient = new ShockPush(this.shockPushBaseUrl, pair)
|
const newClient = new ShockPush(this.settings.getSettings().serviceSettings.shockPushBaseUrl, pair)
|
||||||
this.clients[pair.pubkey] = newClient
|
this.clients[pair.pubkey] = newClient
|
||||||
return newClient
|
return newClient
|
||||||
}
|
}
|
||||||
|
|
||||||
SendNotification = async (message: string, messagingTokens: string[], pair: PushPair) => {
|
SendNotification = async (message: string, messagingTokens: string[], pair: PushPair) => {
|
||||||
if (!this.shockPushBaseUrl) {
|
if (!this.settings.getSettings().serviceSettings.shockPushBaseUrl) {
|
||||||
this.logger("ShockPush is not configured, skipping notification")
|
this.logger("ShockPush is not configured, skipping notification")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import { UnsignedEvent } from 'nostr-tools';
|
||||||
import { UserOffer } from '../storage/entity/UserOffer.js';
|
import { UserOffer } from '../storage/entity/UserOffer.js';
|
||||||
import { LiquidityManager } from "./liquidityManager.js"
|
import { LiquidityManager } from "./liquidityManager.js"
|
||||||
import { NofferData, OfferPriceType, nofferEncode } from '@shocknet/clink-sdk';
|
import { NofferData, OfferPriceType, nofferEncode } from '@shocknet/clink-sdk';
|
||||||
import { MainSettings } from "./settings.js";
|
import SettingsManager from "./settingsManager.js";
|
||||||
|
|
||||||
const mapToOfferConfig = (appUserId: string, offer: UserOffer, { pubkey, relay }: { pubkey: string, relay: string }): Types.OfferConfig => {
|
const mapToOfferConfig = (appUserId: string, offer: UserOffer, { pubkey, relay }: { pubkey: string, relay: string }): Types.OfferConfig => {
|
||||||
const offerStr = offer.offer_id
|
const offerStr = offer.offer_id
|
||||||
|
|
@ -34,14 +34,14 @@ export class OfferManager {
|
||||||
|
|
||||||
|
|
||||||
_nostrSend: NostrSend | null = null
|
_nostrSend: NostrSend | null = null
|
||||||
settings: MainSettings
|
settings: SettingsManager
|
||||||
applicationManager: ApplicationManager
|
applicationManager: ApplicationManager
|
||||||
productManager: ProductManager
|
productManager: ProductManager
|
||||||
storage: Storage
|
storage: Storage
|
||||||
lnd: LND
|
lnd: LND
|
||||||
liquidityManager: LiquidityManager
|
liquidityManager: LiquidityManager
|
||||||
logger = getLogger({ component: 'OfferManager' })
|
logger = getLogger({ component: 'OfferManager' })
|
||||||
constructor(storage: Storage, settings: MainSettings, lnd: LND, applicationManager: ApplicationManager, productManager: ProductManager, liquidityManager: LiquidityManager) {
|
constructor(storage: Storage, settings: SettingsManager, lnd: LND, applicationManager: ApplicationManager, productManager: ProductManager, liquidityManager: LiquidityManager) {
|
||||||
this.storage = storage
|
this.storage = storage
|
||||||
this.settings = settings
|
this.settings = settings
|
||||||
this.lnd = lnd
|
this.lnd = lnd
|
||||||
|
|
@ -112,7 +112,7 @@ export class OfferManager {
|
||||||
if (!offer) {
|
if (!offer) {
|
||||||
throw new Error("Offer not found")
|
throw new Error("Offer not found")
|
||||||
}
|
}
|
||||||
const nostrSettings = this.settings.nostrRelaySettings
|
const nostrSettings = this.settings.getSettings().nostrRelaySettings
|
||||||
return mapToOfferConfig(ctx.app_user_id, offer, { pubkey: app.npub, relay: nostrSettings.relays[0] })
|
return mapToOfferConfig(ctx.app_user_id, offer, { pubkey: app.npub, relay: nostrSettings.relays[0] })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -130,7 +130,7 @@ export class OfferManager {
|
||||||
if (toAppend) {
|
if (toAppend) {
|
||||||
offers.push(toAppend)
|
offers.push(toAppend)
|
||||||
}
|
}
|
||||||
const nostrSettings = this.settings.nostrRelaySettings
|
const nostrSettings = this.settings.getSettings().nostrRelaySettings
|
||||||
return {
|
return {
|
||||||
offers: offers.map(o => mapToOfferConfig(ctx.app_user_id, o, { pubkey: app.npub, relay: nostrSettings.relays[0] }))
|
offers: offers.map(o => mapToOfferConfig(ctx.app_user_id, o, { pubkey: app.npub, relay: nostrSettings.relays[0] }))
|
||||||
}
|
}
|
||||||
|
|
@ -205,7 +205,7 @@ export class OfferManager {
|
||||||
}
|
}
|
||||||
const res = await this.applicationManager.AddAppUserInvoice(appId, {
|
const res = await this.applicationManager.AddAppUserInvoice(appId, {
|
||||||
http_callback_url: "", payer_identifier: offer, receiver_identifier: offer,
|
http_callback_url: "", payer_identifier: offer, receiver_identifier: offer,
|
||||||
invoice_req: { amountSats: amount, memo: memo ||"Default CLINK Offer", zap: offerReq.zap, expiry },
|
invoice_req: { amountSats: amount, memo: memo || "Default CLINK Offer", zap: offerReq.zap, expiry },
|
||||||
offer_string: 'offer'
|
offer_string: 'offer'
|
||||||
})
|
})
|
||||||
return { success: true, invoice: res.invoice }
|
return { success: true, invoice: res.invoice }
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ import { bech32 } from 'bech32'
|
||||||
import crypto from 'crypto'
|
import crypto from 'crypto'
|
||||||
import Storage from '../storage/index.js'
|
import Storage from '../storage/index.js'
|
||||||
import * as Types from '../../../proto/autogenerated/ts/types.js'
|
import * as Types from '../../../proto/autogenerated/ts/types.js'
|
||||||
import { MainSettings } from './settings.js'
|
|
||||||
import { InboundOptionals, defaultInvoiceExpiry } from '../storage/paymentStorage.js'
|
import { InboundOptionals, defaultInvoiceExpiry } from '../storage/paymentStorage.js'
|
||||||
import LND from '../lnd/lnd.js'
|
import LND from '../lnd/lnd.js'
|
||||||
import { Application } from '../storage/entity/Application.js'
|
import { Application } from '../storage/entity/Application.js'
|
||||||
|
|
@ -17,6 +16,7 @@ import { Watchdog } from './watchdog.js'
|
||||||
import { LiquidityManager } from './liquidityManager.js'
|
import { LiquidityManager } from './liquidityManager.js'
|
||||||
import { Utils } from '../helpers/utilsWrapper.js'
|
import { Utils } from '../helpers/utilsWrapper.js'
|
||||||
import { UserInvoicePayment } from '../storage/entity/UserInvoicePayment.js'
|
import { UserInvoicePayment } from '../storage/entity/UserInvoicePayment.js'
|
||||||
|
import SettingsManager from './settingsManager.js'
|
||||||
interface UserOperationInfo {
|
interface UserOperationInfo {
|
||||||
serial_id: number
|
serial_id: number
|
||||||
paid_amount: number
|
paid_amount: number
|
||||||
|
|
@ -43,7 +43,7 @@ const confInOne = 1000 * 1000
|
||||||
const confInTwo = 100 * 1000 * 1000
|
const confInTwo = 100 * 1000 * 1000
|
||||||
export default class {
|
export default class {
|
||||||
storage: Storage
|
storage: Storage
|
||||||
settings: MainSettings
|
settings: SettingsManager
|
||||||
lnd: LND
|
lnd: LND
|
||||||
addressPaidCb: AddressPaidCb
|
addressPaidCb: AddressPaidCb
|
||||||
invoicePaidCb: InvoicePaidCb
|
invoicePaidCb: InvoicePaidCb
|
||||||
|
|
@ -51,13 +51,13 @@ export default class {
|
||||||
watchDog: Watchdog
|
watchDog: Watchdog
|
||||||
liquidityManager: LiquidityManager
|
liquidityManager: LiquidityManager
|
||||||
utils: Utils
|
utils: Utils
|
||||||
constructor(storage: Storage, lnd: LND, settings: MainSettings, liquidityManager: LiquidityManager, utils: Utils, addressPaidCb: AddressPaidCb, invoicePaidCb: InvoicePaidCb) {
|
constructor(storage: Storage, lnd: LND, settings: SettingsManager, liquidityManager: LiquidityManager, utils: Utils, addressPaidCb: AddressPaidCb, invoicePaidCb: InvoicePaidCb) {
|
||||||
this.storage = storage
|
this.storage = storage
|
||||||
this.settings = settings
|
this.settings = settings
|
||||||
this.lnd = lnd
|
this.lnd = lnd
|
||||||
this.liquidityManager = liquidityManager
|
this.liquidityManager = liquidityManager
|
||||||
this.utils = utils
|
this.utils = utils
|
||||||
this.watchDog = new Watchdog(settings.watchDogSettings, this.liquidityManager, this.lnd, this.storage, this.utils, this.liquidityManager.rugPullTracker)
|
this.watchDog = new Watchdog(settings, this.liquidityManager, this.lnd, this.storage, this.utils, this.liquidityManager.rugPullTracker)
|
||||||
this.addressPaidCb = addressPaidCb
|
this.addressPaidCb = addressPaidCb
|
||||||
this.invoicePaidCb = invoicePaidCb
|
this.invoicePaidCb = invoicePaidCb
|
||||||
}
|
}
|
||||||
|
|
@ -163,38 +163,38 @@ export default class {
|
||||||
getServiceFee(action: Types.UserOperationType, amount: number, appUser: boolean): number {
|
getServiceFee(action: Types.UserOperationType, amount: number, appUser: boolean): number {
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case Types.UserOperationType.INCOMING_TX:
|
case Types.UserOperationType.INCOMING_TX:
|
||||||
return Math.ceil(this.settings.incomingTxFee * amount)
|
return Math.ceil(this.settings.getSettings().serviceFeeSettings.incomingTxFee * amount)
|
||||||
case Types.UserOperationType.OUTGOING_TX:
|
case Types.UserOperationType.OUTGOING_TX:
|
||||||
return Math.ceil(this.settings.outgoingTxFee * amount)
|
return Math.ceil(this.settings.getSettings().serviceFeeSettings.outgoingTxFee * amount)
|
||||||
case Types.UserOperationType.INCOMING_INVOICE:
|
case Types.UserOperationType.INCOMING_INVOICE:
|
||||||
if (appUser) {
|
if (appUser) {
|
||||||
return Math.ceil(this.settings.incomingAppUserInvoiceFee * amount)
|
return Math.ceil(this.settings.getSettings().serviceFeeSettings.incomingAppUserInvoiceFee * amount)
|
||||||
}
|
}
|
||||||
return Math.ceil(this.settings.incomingAppInvoiceFee * amount)
|
return Math.ceil(this.settings.getSettings().serviceFeeSettings.incomingAppInvoiceFee * amount)
|
||||||
case Types.UserOperationType.OUTGOING_INVOICE:
|
case Types.UserOperationType.OUTGOING_INVOICE:
|
||||||
if (appUser) {
|
if (appUser) {
|
||||||
return Math.ceil(this.settings.outgoingAppUserInvoiceFee * amount)
|
return Math.ceil(this.settings.getSettings().serviceFeeSettings.outgoingAppUserInvoiceFee * amount)
|
||||||
}
|
}
|
||||||
return Math.ceil(this.settings.outgoingAppInvoiceFee * amount)
|
return Math.ceil(this.settings.getSettings().serviceFeeSettings.outgoingAppInvoiceFee * amount)
|
||||||
case Types.UserOperationType.OUTGOING_USER_TO_USER || Types.UserOperationType.INCOMING_USER_TO_USER:
|
case Types.UserOperationType.OUTGOING_USER_TO_USER || Types.UserOperationType.INCOMING_USER_TO_USER:
|
||||||
if (appUser) {
|
if (appUser) {
|
||||||
return Math.ceil(this.settings.userToUserFee * amount)
|
return Math.ceil(this.settings.getSettings().serviceFeeSettings.userToUserFee * amount)
|
||||||
}
|
}
|
||||||
return Math.ceil(this.settings.appToUserFee * amount)
|
return Math.ceil(this.settings.getSettings().serviceFeeSettings.appToUserFee * amount)
|
||||||
default:
|
default:
|
||||||
throw new Error("Unknown service action type")
|
throw new Error("Unknown service action type")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async SetMockInvoiceAsPaid(req: Types.SetMockInvoiceAsPaidRequest) {
|
async SetMockInvoiceAsPaid(req: Types.SetMockInvoiceAsPaidRequest) {
|
||||||
if (!this.settings.lndSettings.mockLnd) {
|
if (!this.settings.getSettings().lndSettings.mockLnd) {
|
||||||
throw new Error("mock disabled, cannot set invoice as paid")
|
throw new Error("mock disabled, cannot set invoice as paid")
|
||||||
}
|
}
|
||||||
await this.lnd.SetMockInvoiceAsPaid(req.invoice, req.amount)
|
await this.lnd.SetMockInvoiceAsPaid(req.invoice, req.amount)
|
||||||
}
|
}
|
||||||
|
|
||||||
async SetMockUserBalance(userId: string, balance: number) {
|
async SetMockUserBalance(userId: string, balance: number) {
|
||||||
if (!this.settings.lndSettings.mockLnd) {
|
if (!this.settings.getSettings().lndSettings.mockLnd) {
|
||||||
throw new Error("mock disabled, cannot set invoice as paid")
|
throw new Error("mock disabled, cannot set invoice as paid")
|
||||||
}
|
}
|
||||||
getLogger({})("setting mock balance...")
|
getLogger({})("setting mock balance...")
|
||||||
|
|
@ -235,9 +235,9 @@ export default class {
|
||||||
GetMaxPayableInvoice(balance: number, appUser: boolean): number {
|
GetMaxPayableInvoice(balance: number, appUser: boolean): number {
|
||||||
let maxWithinServiceFee = 0
|
let maxWithinServiceFee = 0
|
||||||
if (appUser) {
|
if (appUser) {
|
||||||
maxWithinServiceFee = Math.max(0, Math.floor(balance * (1 - this.settings.outgoingAppUserInvoiceFee)))
|
maxWithinServiceFee = Math.max(0, Math.floor(balance * (1 - this.settings.getSettings().serviceFeeSettings.outgoingAppUserInvoiceFee)))
|
||||||
} else {
|
} else {
|
||||||
maxWithinServiceFee = Math.max(0, Math.floor(balance * (1 - this.settings.outgoingAppInvoiceFee)))
|
maxWithinServiceFee = Math.max(0, Math.floor(balance * (1 - this.settings.getSettings().serviceFeeSettings.outgoingAppInvoiceFee)))
|
||||||
}
|
}
|
||||||
return this.lnd.GetMaxWithinLimit(maxWithinServiceFee)
|
return this.lnd.GetMaxWithinLimit(maxWithinServiceFee)
|
||||||
}
|
}
|
||||||
|
|
@ -293,7 +293,7 @@ export default class {
|
||||||
}
|
}
|
||||||
|
|
||||||
async PayExternalInvoice(userId: string, invoice: string, amounts: { payAmount: number, serviceFee: number, amountForLnd: number }, linkedApplication: Application, debitNpub?: string) {
|
async PayExternalInvoice(userId: string, invoice: string, amounts: { payAmount: number, serviceFee: number, amountForLnd: number }, linkedApplication: Application, debitNpub?: string) {
|
||||||
if (this.settings.disableExternalPayments) {
|
if (this.settings.getSettings().serviceSettings.disableExternalPayments) {
|
||||||
throw new Error("something went wrong sending payment, please try again later")
|
throw new Error("something went wrong sending payment, please try again later")
|
||||||
}
|
}
|
||||||
const existingPendingPayment = await this.storage.paymentStorage.GetPaymentOwner(invoice)
|
const existingPendingPayment = await this.storage.paymentStorage.GetPaymentOwner(invoice)
|
||||||
|
|
@ -412,14 +412,14 @@ export default class {
|
||||||
}
|
}
|
||||||
|
|
||||||
balanceCheckUrl(k1: string): string {
|
balanceCheckUrl(k1: string): string {
|
||||||
return `${this.settings.serviceUrl}/api/guest/lnurl_withdraw/info?k1=${k1}`
|
return `${this.settings.getSettings().serviceSettings.serviceUrl}/api/guest/lnurl_withdraw/info?k1=${k1}`
|
||||||
}
|
}
|
||||||
|
|
||||||
isDefaultServiceUrl(): boolean {
|
isDefaultServiceUrl(): boolean {
|
||||||
if (
|
if (
|
||||||
this.settings.serviceUrl.includes("localhost")
|
this.settings.getSettings().serviceSettings.serviceUrl.includes("localhost")
|
||||||
||
|
||
|
||||||
this.settings.serviceUrl.includes("127.0.0.1")
|
this.settings.getSettings().serviceSettings.serviceUrl.includes("127.0.0.1")
|
||||||
) {
|
) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
@ -471,7 +471,7 @@ export default class {
|
||||||
}
|
}
|
||||||
|
|
||||||
lnurlPayUrl(k1: string): string {
|
lnurlPayUrl(k1: string): string {
|
||||||
return `${this.settings.serviceUrl}/api/guest/lnurl_pay/info?k1=${k1}`
|
return `${this.settings.getSettings().serviceSettings.serviceUrl}/api/guest/lnurl_pay/info?k1=${k1}`
|
||||||
}
|
}
|
||||||
|
|
||||||
async GetLnurlPayLink(ctx: Types.UserContext): Promise<Types.LnurlLinkResponse> {
|
async GetLnurlPayLink(ctx: Types.UserContext): Promise<Types.LnurlLinkResponse> {
|
||||||
|
|
@ -493,7 +493,7 @@ export default class {
|
||||||
}
|
}
|
||||||
const { baseUrl, metadata } = opts
|
const { baseUrl, metadata } = opts
|
||||||
const payK1 = await this.storage.paymentStorage.AddUserEphemeralKey(userId, 'pay', linkedApplication)
|
const payK1 = await this.storage.paymentStorage.AddUserEphemeralKey(userId, 'pay', linkedApplication)
|
||||||
const url = baseUrl ? baseUrl : `${this.settings.serviceUrl}/api/guest/lnurl_pay/handle`
|
const url = baseUrl ? baseUrl : `${this.settings.getSettings().serviceSettings.serviceUrl}/api/guest/lnurl_pay/handle`
|
||||||
const { remote } = await this.lnd.ChannelBalance()
|
const { remote } = await this.lnd.ChannelBalance()
|
||||||
let maxSendable = remote * 1000
|
let maxSendable = remote * 1000
|
||||||
if (remote === 0 && (await this.liquidityManager.liquidityProvider.IsReady())) {
|
if (remote === 0 && (await this.liquidityManager.liquidityProvider.IsReady())) {
|
||||||
|
|
@ -504,7 +504,7 @@ export default class {
|
||||||
callback: `${url}?k1=${payK1.key}`,
|
callback: `${url}?k1=${payK1.key}`,
|
||||||
maxSendable: maxSendable,
|
maxSendable: maxSendable,
|
||||||
minSendable: 10000,
|
minSendable: 10000,
|
||||||
metadata: metadata ? metadata : defaultLnurlPayMetadata(this.settings.lnurlMetaText),
|
metadata: metadata ? metadata : defaultLnurlPayMetadata(this.settings.getSettings().serviceSettings.lnurlMetaText),
|
||||||
allowsNostr: !!linkedApplication.nostr_public_key,
|
allowsNostr: !!linkedApplication.nostr_public_key,
|
||||||
nostrPubkey: linkedApplication.nostr_public_key || ""
|
nostrPubkey: linkedApplication.nostr_public_key || ""
|
||||||
}
|
}
|
||||||
|
|
@ -525,10 +525,10 @@ export default class {
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
tag: 'payRequest',
|
tag: 'payRequest',
|
||||||
callback: `${this.settings.serviceUrl}/api/guest/lnurl_pay/handle?k1=${payInfoK1}`,
|
callback: `${this.settings.getSettings().serviceSettings.serviceUrl}/api/guest/lnurl_pay/handle?k1=${payInfoK1}`,
|
||||||
maxSendable: maxSendable,
|
maxSendable: maxSendable,
|
||||||
minSendable: 10000,
|
minSendable: 10000,
|
||||||
metadata: defaultLnurlPayMetadata(this.settings.lnurlMetaText),
|
metadata: defaultLnurlPayMetadata(this.settings.getSettings().serviceSettings.lnurlMetaText),
|
||||||
allowsNostr: !!key.linkedApplication.nostr_public_key,
|
allowsNostr: !!key.linkedApplication.nostr_public_key,
|
||||||
nostrPubkey: key.linkedApplication.nostr_public_key || ""
|
nostrPubkey: key.linkedApplication.nostr_public_key || ""
|
||||||
}
|
}
|
||||||
|
|
@ -607,7 +607,7 @@ export default class {
|
||||||
}
|
}
|
||||||
const invoice = await this.NewInvoice(key.user.user_id, {
|
const invoice = await this.NewInvoice(key.user.user_id, {
|
||||||
amountSats: sats,
|
amountSats: sats,
|
||||||
memo: zapInfo ? zapInfo.description : defaultLnurlPayMetadata(this.settings.lnurlMetaText)
|
memo: zapInfo ? zapInfo.description : defaultLnurlPayMetadata(this.settings.getSettings().serviceSettings.lnurlMetaText)
|
||||||
}, { expiry: defaultInvoiceExpiry, linkedApplication: key.linkedApplication, zapInfo })
|
}, { expiry: defaultInvoiceExpiry, linkedApplication: key.linkedApplication, zapInfo })
|
||||||
return {
|
return {
|
||||||
pr: invoice.invoice,
|
pr: invoice.invoice,
|
||||||
|
|
@ -620,7 +620,9 @@ export default class {
|
||||||
if (!linkedUser) {
|
if (!linkedUser) {
|
||||||
throw new Error("this address is not linked to any user")
|
throw new Error("this address is not linked to any user")
|
||||||
}
|
}
|
||||||
return this.GetLnurlPayInfoFromUser(linkedUser.user.user_id, linkedUser.application, { metadata: defaultLnAddressMetadata(this.settings.lnurlMetaText, addressName) })
|
return this.GetLnurlPayInfoFromUser(linkedUser.user.user_id, linkedUser.application, {
|
||||||
|
metadata: defaultLnAddressMetadata(this.settings.getSettings().serviceSettings.lnurlMetaText, addressName)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
mapOperations(operations: UserOperationInfo[], type: Types.UserOperationType, inbound: boolean): Types.UserOperations {
|
mapOperations(operations: UserOperationInfo[], type: Types.UserOperationType, inbound: boolean): Types.UserOperations {
|
||||||
|
|
@ -633,7 +635,7 @@ export default class {
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
// We fetch in ascending order
|
// We fetch in ascending order
|
||||||
toIndex: { ts: operations.at(-1)!.paid_at_unix, id: operations.at(-1)!.serial_id } ,
|
toIndex: { ts: operations.at(-1)!.paid_at_unix, id: operations.at(-1)!.serial_id },
|
||||||
fromIndex: { ts: operations[0].paid_at_unix, id: operations[0]!.serial_id },
|
fromIndex: { ts: operations[0].paid_at_unix, id: operations[0]!.serial_id },
|
||||||
operations: operations.map((o: UserOperationInfo): Types.UserOperation => {
|
operations: operations.map((o: UserOperationInfo): Types.UserOperation => {
|
||||||
let identifier = "";
|
let identifier = "";
|
||||||
|
|
@ -762,7 +764,7 @@ export default class {
|
||||||
async CleanupOldUnpaidInvoices() {
|
async CleanupOldUnpaidInvoices() {
|
||||||
this.log("Cleaning up old unpaid invoices")
|
this.log("Cleaning up old unpaid invoices")
|
||||||
const affected = await this.storage.paymentStorage.RemoveOldUnpaidInvoices()
|
const affected = await this.storage.paymentStorage.RemoveOldUnpaidInvoices()
|
||||||
this.log("Cleaned up",affected, "old unpaid invoices")
|
this.log("Cleaned up", affected, "old unpaid invoices")
|
||||||
}
|
}
|
||||||
|
|
||||||
async GetLndBalance() {
|
async GetLndBalance() {
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,17 @@ import Storage from '../storage/index.js'
|
||||||
import * as Types from '../../../proto/autogenerated/ts/types.js'
|
import * as Types from '../../../proto/autogenerated/ts/types.js'
|
||||||
|
|
||||||
|
|
||||||
import { MainSettings } from './settings.js'
|
|
||||||
import PaymentManager from './paymentManager.js'
|
import PaymentManager from './paymentManager.js'
|
||||||
import { defaultInvoiceExpiry } from '../storage/paymentStorage.js'
|
import { defaultInvoiceExpiry } from '../storage/paymentStorage.js'
|
||||||
import { nofferEncode, OfferPriceType } from '@shocknet/clink-sdk'
|
import { nofferEncode, OfferPriceType } from '@shocknet/clink-sdk'
|
||||||
|
import SettingsManager from './settingsManager.js'
|
||||||
|
|
||||||
export default class {
|
export default class {
|
||||||
storage: Storage
|
storage: Storage
|
||||||
settings: MainSettings
|
settings: SettingsManager
|
||||||
paymentManager: PaymentManager
|
paymentManager: PaymentManager
|
||||||
|
|
||||||
constructor(storage: Storage, paymentManager: PaymentManager, settings: MainSettings) {
|
constructor(storage: Storage, paymentManager: PaymentManager, settings: SettingsManager) {
|
||||||
this.storage = storage
|
this.storage = storage
|
||||||
this.settings = settings
|
this.settings = settings
|
||||||
this.paymentManager = paymentManager
|
this.paymentManager = paymentManager
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,8 @@
|
||||||
import { LoadStorageSettingsFromEnv, StorageSettings } from '../storage/index.js'
|
import { EnvCacher, EnvMustBeNonEmptyString, EnvMustBeInteger, chooseEnv, chooseEnvBool, chooseEnvInt } from '../helpers/envParser.js'
|
||||||
import { LndSettings, NodeSettings } from '../lnd/settings.js'
|
import os from 'os'
|
||||||
import { LoadWatchdogSettingsFromEnv, WatchdogSettings } from './watchdog.js'
|
import path from 'path'
|
||||||
import { LoadLndSettingsFromEnv } from '../lnd/index.js'
|
|
||||||
import { EnvCanBeInteger, EnvMustBeInteger, EnvMustBeNonEmptyString } from '../helpers/envParser.js'
|
|
||||||
import { getLogger } from '../helpers/logger.js'
|
|
||||||
import fs from 'fs'
|
|
||||||
import crypto from 'crypto';
|
|
||||||
import { LiquiditySettings, LoadLiquiditySettingsFromEnv } from './liquidityManager.js'
|
|
||||||
import { LoadNosrtRelaySettingsFromEnv, NostrRelaySettings } from '../nostr/handler.js'
|
|
||||||
|
|
||||||
export type MainSettings = {
|
export type ServiceFeeSettings = {
|
||||||
storageSettings: StorageSettings,
|
|
||||||
lndSettings: LndSettings,
|
|
||||||
watchDogSettings: WatchdogSettings,
|
|
||||||
liquiditySettings: LiquiditySettings,
|
|
||||||
nostrRelaySettings: NostrRelaySettings,
|
|
||||||
jwtSecret: string
|
|
||||||
walletPasswordPath: string
|
|
||||||
walletSecretPath: string
|
|
||||||
incomingTxFee: number
|
incomingTxFee: number
|
||||||
outgoingTxFee: number
|
outgoingTxFee: number
|
||||||
incomingAppInvoiceFee: number
|
incomingAppInvoiceFee: number
|
||||||
|
|
@ -27,19 +12,57 @@ export type MainSettings = {
|
||||||
outgoingAppUserInvoiceFeeBps: number
|
outgoingAppUserInvoiceFeeBps: number
|
||||||
userToUserFee: number
|
userToUserFee: number
|
||||||
appToUserFee: number
|
appToUserFee: number
|
||||||
serviceUrl: string
|
}
|
||||||
|
|
||||||
|
export const LoadServiceFeeSettingsFromEnv = (dbEnv: Record<string, string | undefined>, addToDb?: EnvCacher): ServiceFeeSettings => {
|
||||||
|
const outgoingAppUserInvoiceFeeBps = chooseEnvInt("OUTGOING_INVOICE_FEE_USER_BPS", dbEnv, 0, addToDb)
|
||||||
|
return {
|
||||||
|
incomingTxFee: chooseEnvInt("INCOMING_CHAIN_FEE_ROOT_BPS", dbEnv, 0, addToDb) / 10000,
|
||||||
|
outgoingTxFee: chooseEnvInt("OUTGOING_CHAIN_FEE_ROOT_BPS", dbEnv, 60, addToDb) / 10000,
|
||||||
|
incomingAppInvoiceFee: chooseEnvInt("INCOMING_INVOICE_FEE_ROOT_BPS", dbEnv, 0, addToDb) / 10000,
|
||||||
|
outgoingAppInvoiceFee: chooseEnvInt("OUTGOING_INVOICE_FEE_ROOT_BPS", dbEnv, 60, addToDb) / 10000,
|
||||||
|
incomingAppUserInvoiceFee: chooseEnvInt("INCOMING_INVOICE_FEE_USER_BPS", dbEnv, 0, addToDb) / 10000,
|
||||||
|
outgoingAppUserInvoiceFeeBps,
|
||||||
|
outgoingAppUserInvoiceFee: outgoingAppUserInvoiceFeeBps / 10000,
|
||||||
|
userToUserFee: chooseEnvInt("TX_FEE_INTERNAL_USER_BPS", dbEnv, 0, addToDb) / 10000,
|
||||||
|
appToUserFee: chooseEnvInt("TX_FEE_INTERNAL_ROOT_BPS", dbEnv, 0, addToDb) / 10000,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ServiceSettings = {
|
||||||
servicePort: number
|
servicePort: number
|
||||||
recordPerformance: boolean
|
recordPerformance: boolean
|
||||||
skipSanityCheck: boolean
|
skipSanityCheck: boolean
|
||||||
disableExternalPayments: boolean
|
|
||||||
wizard: boolean
|
wizard: boolean
|
||||||
|
bridgeUrl: string,
|
||||||
|
shockPushBaseUrl: string
|
||||||
|
|
||||||
|
serviceUrl: string
|
||||||
|
disableExternalPayments: boolean
|
||||||
defaultAppName: string
|
defaultAppName: string
|
||||||
pushBackupsToNostr: boolean
|
pushBackupsToNostr: boolean
|
||||||
lnurlMetaText: string,
|
lnurlMetaText: string,
|
||||||
bridgeUrl: string,
|
|
||||||
allowResetMetricsStorages: boolean
|
|
||||||
allowHttpUpgrade: boolean
|
allowHttpUpgrade: boolean
|
||||||
shockPushBaseUrl: string
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LoadServiceSettingsFromEnv = (dbEnv: Record<string, string | undefined>, addToDb?: EnvCacher): ServiceSettings => {
|
||||||
|
const port = chooseEnvInt("PORT", dbEnv, 1776, addToDb)
|
||||||
|
return {
|
||||||
|
serviceUrl: chooseEnv("SERVICE_URL", dbEnv, `http://localhost:${port}`, addToDb),
|
||||||
|
servicePort: port,
|
||||||
|
recordPerformance: chooseEnvBool("RECORD_PERFORMANCE", dbEnv, false, addToDb),
|
||||||
|
skipSanityCheck: chooseEnvBool("SKIP_SANITY_CHECK", dbEnv, false, addToDb),
|
||||||
|
disableExternalPayments: chooseEnvBool("DISABLE_EXTERNAL_PAYMENTS", dbEnv, false, addToDb),
|
||||||
|
wizard: chooseEnvBool("WIZARD", dbEnv, false, addToDb),
|
||||||
|
defaultAppName: chooseEnv("DEFAULT_APP_NAME", dbEnv, "wallet", addToDb),
|
||||||
|
pushBackupsToNostr: chooseEnvBool("PUSH_BACKUPS_TO_NOSTR", dbEnv, false, addToDb),
|
||||||
|
lnurlMetaText: chooseEnv("LNURL_META_TEXT", dbEnv, "LNURL via Lightning.pub", addToDb),
|
||||||
|
bridgeUrl: chooseEnv("BRIDGE_URL", dbEnv, "https://shockwallet.app", addToDb),
|
||||||
|
allowHttpUpgrade: chooseEnvBool("ALLOW_HTTP_UPGRADE", dbEnv, false, addToDb),
|
||||||
|
shockPushBaseUrl: chooseEnv("SHOCK_PUSH_URL", dbEnv, "", addToDb),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type BitcoinCoreSettings = {
|
export type BitcoinCoreSettings = {
|
||||||
|
|
@ -48,109 +71,136 @@ export type BitcoinCoreSettings = {
|
||||||
pass: string
|
pass: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TestSettings = MainSettings & { lndSettings: { otherNode: NodeSettings, thirdNode: NodeSettings, fourthNode: NodeSettings }, bitcoinCoreSettings: BitcoinCoreSettings }
|
export type LndNodeSettings = {
|
||||||
export const LoadMainSettingsFromEnv = (): MainSettings => {
|
lndAddr: string // cold setting
|
||||||
const storageSettings = LoadStorageSettingsFromEnv()
|
lndCertPath: string // cold setting
|
||||||
const outgoingAppUserInvoiceFeeBps = EnvCanBeInteger("OUTGOING_INVOICE_FEE_USER_BPS", 0)
|
lndMacaroonPath: string // cold setting
|
||||||
const nostrRelaySettings = LoadNosrtRelaySettingsFromEnv()
|
}
|
||||||
|
export type LndSettings = {
|
||||||
|
lndLogDir: string
|
||||||
|
feeRateLimit: number
|
||||||
|
feeFixedLimit: number
|
||||||
|
feeRateBps: number
|
||||||
|
mockLnd: boolean
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolveHome = (filepath: string) => {
|
||||||
|
let homeDir;
|
||||||
|
if (process.env.SUDO_USER) {
|
||||||
|
homeDir = path.join('/home', process.env.SUDO_USER);
|
||||||
|
} else {
|
||||||
|
homeDir = os.homedir();
|
||||||
|
}
|
||||||
|
return path.join(homeDir, filepath);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LoadLndNodeSettingsFromEnv = (dbEnv: Record<string, string | undefined>, addToDb?: EnvCacher): LndNodeSettings => {
|
||||||
return {
|
return {
|
||||||
watchDogSettings: LoadWatchdogSettingsFromEnv(),
|
lndAddr: chooseEnv('LND_ADDRESS', dbEnv, "127.0.0.1:10009", addToDb),
|
||||||
lndSettings: LoadLndSettingsFromEnv(),
|
lndCertPath: chooseEnv('LND_CERT_PATH', dbEnv, resolveHome("/.lnd/tls.cert"), addToDb),
|
||||||
storageSettings: storageSettings,
|
lndMacaroonPath: chooseEnv('LND_MACAROON_PATH', dbEnv, resolveHome("/.lnd/data/chain/bitcoin/mainnet/admin.macaroon"), addToDb),
|
||||||
liquiditySettings: LoadLiquiditySettingsFromEnv(),
|
|
||||||
nostrRelaySettings: nostrRelaySettings,
|
|
||||||
jwtSecret: loadJwtSecret(storageSettings.dataDir),
|
|
||||||
walletSecretPath: process.env.WALLET_SECRET_PATH || getDataPath(storageSettings.dataDir, ".wallet_secret"),
|
|
||||||
walletPasswordPath: process.env.WALLET_PASSWORD_PATH || getDataPath(storageSettings.dataDir, ".wallet_password"),
|
|
||||||
incomingTxFee: EnvCanBeInteger("INCOMING_CHAIN_FEE_ROOT_BPS", 0) / 10000,
|
|
||||||
outgoingTxFee: EnvCanBeInteger("OUTGOING_CHAIN_FEE_ROOT_BPS", 60) / 10000,
|
|
||||||
incomingAppInvoiceFee: EnvCanBeInteger("INCOMING_INVOICE_FEE_ROOT_BPS", 0) / 10000,
|
|
||||||
outgoingAppInvoiceFee: EnvCanBeInteger("OUTGOING_INVOICE_FEE_ROOT_BPS", 60) / 10000,
|
|
||||||
incomingAppUserInvoiceFee: EnvCanBeInteger("INCOMING_INVOICE_FEE_USER_BPS", 0) / 10000,
|
|
||||||
outgoingAppUserInvoiceFeeBps,
|
|
||||||
outgoingAppUserInvoiceFee: outgoingAppUserInvoiceFeeBps / 10000,
|
|
||||||
userToUserFee: EnvCanBeInteger("TX_FEE_INTERNAL_USER_BPS", 0) / 10000,
|
|
||||||
appToUserFee: EnvCanBeInteger("TX_FEE_INTERNAL_ROOT_BPS", 0) / 10000,
|
|
||||||
serviceUrl: process.env.SERVICE_URL || `http://localhost:${EnvCanBeInteger("PORT", 1776)}`,
|
|
||||||
servicePort: EnvCanBeInteger("PORT", 1776),
|
|
||||||
recordPerformance: process.env.RECORD_PERFORMANCE === 'true' || false,
|
|
||||||
skipSanityCheck: process.env.SKIP_SANITY_CHECK === 'true' || false,
|
|
||||||
disableExternalPayments: process.env.DISABLE_EXTERNAL_PAYMENTS === 'true' || false,
|
|
||||||
wizard: process.env.WIZARD === 'true' || false,
|
|
||||||
defaultAppName: process.env.DEFAULT_APP_NAME || "wallet",
|
|
||||||
pushBackupsToNostr: process.env.PUSH_BACKUPS_TO_NOSTR === 'true' || false,
|
|
||||||
lnurlMetaText: process.env.LNURL_META_TEXT || "LNURL via Lightning.pub",
|
|
||||||
bridgeUrl: process.env.BRIDGE_URL || "https://shockwallet.app",
|
|
||||||
allowResetMetricsStorages: process.env.ALLOW_RESET_METRICS_STORAGES === 'true' || false,
|
|
||||||
allowHttpUpgrade: process.env.ALLOW_HTTP_UPGRADE === 'true' || false,
|
|
||||||
shockPushBaseUrl: process.env.SHOCK_PUSH_URL || ""
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const GetTestStorageSettings = (s?: StorageSettings): StorageSettings => {
|
export const LoadLndSettingsFromEnv = (dbEnv: Record<string, string | undefined>, addToDb?: EnvCacher): LndSettings => {
|
||||||
const eventLogPath = `logs/eventLogV3Test${Date.now()}.csv`
|
const feeRateBps: number = chooseEnvInt('OUTBOUND_MAX_FEE_BPS', dbEnv, 60, addToDb)
|
||||||
if (s) {
|
return {
|
||||||
return { dbSettings: { ...s.dbSettings, databaseFile: ":memory:", metricsDatabaseFile: ":memory:" }, eventLogPath, dataDir: "test-data" }
|
lndLogDir: chooseEnv('LND_LOG_DIR', dbEnv, resolveHome("/.lnd/logs/bitcoin/mainnet/lnd.log"), addToDb),
|
||||||
|
feeRateBps: feeRateBps,
|
||||||
|
feeRateLimit: feeRateBps / 10000,
|
||||||
|
feeFixedLimit: chooseEnvInt('OUTBOUND_MAX_FEE_EXTRA_SATS', dbEnv, 100, addToDb),
|
||||||
|
mockLnd: false
|
||||||
}
|
}
|
||||||
return { dbSettings: { databaseFile: ":memory:", metricsDatabaseFile: ":memory:", migrate: true }, eventLogPath, dataDir: "test-data" }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LoadTestSettingsFromEnv = (): TestSettings => {
|
export type NostrRelaySettings = {
|
||||||
|
relays: string[],
|
||||||
|
maxEventContentLength: number
|
||||||
|
}
|
||||||
|
|
||||||
const settings = LoadMainSettingsFromEnv()
|
export const LoadNostrRelaySettingsFromEnv = (dbEnv: Record<string, string | undefined>, addToDb?: EnvCacher): NostrRelaySettings => {
|
||||||
|
const relaysEnv = chooseEnv("NOSTR_RELAYS", dbEnv, "wss://relay.lightning.pub", addToDb);
|
||||||
|
const maxEventContentLength = chooseEnvInt("NOSTR_MAX_EVENT_CONTENT_LENGTH", dbEnv, 40000, addToDb)
|
||||||
|
return {
|
||||||
|
relays: relaysEnv.split(' '),
|
||||||
|
maxEventContentLength
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type WatchdogSettings = {
|
||||||
|
maxDiffSats: number // hot setting
|
||||||
|
}
|
||||||
|
export const LoadWatchdogSettingsFromEnv = (dbEnv: Record<string, string | undefined>, addToDb?: EnvCacher): WatchdogSettings => {
|
||||||
|
return {
|
||||||
|
maxDiffSats: chooseEnvInt("WATCHDOG_MAX_DIFF_SATS", dbEnv, 0, addToDb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LSPSettings = {
|
||||||
|
olympusServiceUrl: string // hot setting
|
||||||
|
voltageServiceUrl: string // unused?
|
||||||
|
flashsatsServiceUrl: string // hot setting
|
||||||
|
channelThreshold: number // hot setting
|
||||||
|
maxRelativeFee: number // hot setting
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LoadLSPSettingsFromEnv = (dbEnv: Record<string, string | undefined>, addToDb?: EnvCacher): LSPSettings => {
|
||||||
|
const olympusServiceUrl = chooseEnv("OLYMPUS_LSP_URL", dbEnv, "https://lsps1.lnolymp.us/api/v1", addToDb)
|
||||||
|
const voltageServiceUrl = chooseEnv("VOLTAGE_LSP_URL", dbEnv, "https://lsp.voltageapi.com/api/v1", addToDb)
|
||||||
|
const flashsatsServiceUrl = chooseEnv("FLASHSATS_LSP_URL", dbEnv, "https://lsp.flashsats.xyz/lsp/channel", addToDb)
|
||||||
|
const channelThreshold = chooseEnvInt("LSP_CHANNEL_THRESHOLD", dbEnv, 1000000, addToDb)
|
||||||
|
const maxRelativeFee = chooseEnvInt("LSP_MAX_FEE_BPS", dbEnv, 100, addToDb) / 10000
|
||||||
|
return { olympusServiceUrl, voltageServiceUrl, channelThreshold, maxRelativeFee, flashsatsServiceUrl }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LiquiditySettings = {
|
||||||
|
|
||||||
|
liquidityProviderPub: string // cold setting
|
||||||
|
useOnlyLiquidityProvider: boolean // hot setting
|
||||||
|
disableLiquidityProvider: boolean // hot setting
|
||||||
|
}
|
||||||
|
export const LoadLiquiditySettingsFromEnv = (dbEnv: Record<string, string | undefined>, addToDb?: EnvCacher): LiquiditySettings => {
|
||||||
|
//const liquidityProviderPub = process.env.LIQUIDITY_PROVIDER_PUB === "null" ? "" : (process.env.LIQUIDITY_PROVIDER_PUB || "76ed45f00cea7bac59d8d0b7d204848f5319d7b96c140ffb6fcbaaab0a13d44e")
|
||||||
|
const liquidityProviderPub = chooseEnv("LIQUIDITY_PROVIDER_PUB", dbEnv, "76ed45f00cea7bac59d8d0b7d204848f5319d7b96c140ffb6fcbaaab0a13d44e", addToDb)
|
||||||
|
const disableLiquidityProvider = chooseEnvBool("DISABLE_LIQUIDITY_PROVIDER", dbEnv, false, addToDb) || liquidityProviderPub === "null"
|
||||||
|
return { liquidityProviderPub, useOnlyLiquidityProvider: false, disableLiquidityProvider }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const LoadSecondLndSettingsFromEnv = (): LndNodeSettings => {
|
||||||
return {
|
return {
|
||||||
...settings,
|
|
||||||
storageSettings: GetTestStorageSettings(settings.storageSettings),
|
|
||||||
lndSettings: {
|
|
||||||
...settings.lndSettings,
|
|
||||||
otherNode: {
|
|
||||||
lndAddr: EnvMustBeNonEmptyString("LND_OTHER_ADDR"),
|
lndAddr: EnvMustBeNonEmptyString("LND_OTHER_ADDR"),
|
||||||
lndCertPath: EnvMustBeNonEmptyString("LND_OTHER_CERT_PATH"),
|
lndCertPath: EnvMustBeNonEmptyString("LND_OTHER_CERT_PATH"),
|
||||||
lndMacaroonPath: EnvMustBeNonEmptyString("LND_OTHER_MACAROON_PATH")
|
lndMacaroonPath: EnvMustBeNonEmptyString("LND_OTHER_MACAROON_PATH")
|
||||||
},
|
}
|
||||||
thirdNode: {
|
}
|
||||||
|
|
||||||
|
export const LoadThirdLndSettingsFromEnv = (): LndNodeSettings => {
|
||||||
|
|
||||||
|
return {
|
||||||
lndAddr: EnvMustBeNonEmptyString("LND_THIRD_ADDR"),
|
lndAddr: EnvMustBeNonEmptyString("LND_THIRD_ADDR"),
|
||||||
lndCertPath: EnvMustBeNonEmptyString("LND_THIRD_CERT_PATH"),
|
lndCertPath: EnvMustBeNonEmptyString("LND_THIRD_CERT_PATH"),
|
||||||
lndMacaroonPath: EnvMustBeNonEmptyString("LND_THIRD_MACAROON_PATH")
|
lndMacaroonPath: EnvMustBeNonEmptyString("LND_THIRD_MACAROON_PATH")
|
||||||
},
|
}
|
||||||
fourthNode: {
|
}
|
||||||
|
|
||||||
|
export const LoadFourthLndSettingsFromEnv = (): LndNodeSettings => {
|
||||||
|
|
||||||
|
return {
|
||||||
lndAddr: EnvMustBeNonEmptyString("LND_FOURTH_ADDR"),
|
lndAddr: EnvMustBeNonEmptyString("LND_FOURTH_ADDR"),
|
||||||
lndCertPath: EnvMustBeNonEmptyString("LND_FOURTH_CERT_PATH"),
|
lndCertPath: EnvMustBeNonEmptyString("LND_FOURTH_CERT_PATH"),
|
||||||
lndMacaroonPath: EnvMustBeNonEmptyString("LND_FOURTH_MACAROON_PATH")
|
lndMacaroonPath: EnvMustBeNonEmptyString("LND_FOURTH_MACAROON_PATH")
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
liquiditySettings: {
|
|
||||||
...settings.liquiditySettings,
|
export const LoadBitcoinCoreSettingsFromEnv = (): BitcoinCoreSettings => {
|
||||||
liquidityProviderPub: "",
|
return {
|
||||||
},
|
|
||||||
skipSanityCheck: true,
|
|
||||||
bitcoinCoreSettings: {
|
|
||||||
port: EnvMustBeInteger("BITCOIN_CORE_PORT"),
|
port: EnvMustBeInteger("BITCOIN_CORE_PORT"),
|
||||||
user: EnvMustBeNonEmptyString("BITCOIN_CORE_USER"),
|
user: EnvMustBeNonEmptyString("BITCOIN_CORE_USER"),
|
||||||
pass: EnvMustBeNonEmptyString("BITCOIN_CORE_PASS")
|
pass: EnvMustBeNonEmptyString("BITCOIN_CORE_PASS")
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const loadJwtSecret = (dataDir: string): string => {
|
|
||||||
const secret = process.env["JWT_SECRET"]
|
|
||||||
const log = getLogger({})
|
|
||||||
if (secret) {
|
|
||||||
return secret
|
|
||||||
}
|
|
||||||
log("JWT_SECRET not set in env, checking .jwt_secret file")
|
|
||||||
const secretPath = getDataPath(dataDir, ".jwt_secret")
|
|
||||||
try {
|
|
||||||
const fileContent = fs.readFileSync(secretPath, "utf-8")
|
|
||||||
return fileContent.trim()
|
|
||||||
} catch (e) {
|
|
||||||
log(".jwt_secret file not found, generating random secret")
|
|
||||||
const secret = crypto.randomBytes(32).toString('hex')
|
|
||||||
fs.writeFileSync(secretPath, secret)
|
|
||||||
return secret
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getDataPath = (dataDir: string, dataPath: string) => {
|
|
||||||
return dataDir !== "" ? `${dataDir}/${dataPath}` : dataPath
|
|
||||||
}
|
|
||||||
|
|
|
||||||
152
src/services/main/settingsManager.ts
Normal file
152
src/services/main/settingsManager.ts
Normal file
|
|
@ -0,0 +1,152 @@
|
||||||
|
import Storage, { StorageSettings } from "../storage/index.js"
|
||||||
|
import { EnvCacher } from "../helpers/envParser.js"
|
||||||
|
import { getLogger, PubLogger } from "../helpers/logger.js"
|
||||||
|
import {
|
||||||
|
LiquiditySettings, LndNodeSettings, LndSettings, LoadLiquiditySettingsFromEnv,
|
||||||
|
LoadLSPSettingsFromEnv, LSPSettings, ServiceFeeSettings, ServiceSettings, LoadServiceFeeSettingsFromEnv,
|
||||||
|
LoadNostrRelaySettingsFromEnv, LoadServiceSettingsFromEnv, LoadWatchdogSettingsFromEnv,
|
||||||
|
LoadLndNodeSettingsFromEnv, LoadLndSettingsFromEnv, NostrRelaySettings, WatchdogSettings
|
||||||
|
} from "./settings.js"
|
||||||
|
export default class SettingsManager {
|
||||||
|
storage: Storage
|
||||||
|
private settings: FullSettings | null = null
|
||||||
|
|
||||||
|
log: PubLogger
|
||||||
|
constructor(storage: Storage) {
|
||||||
|
this.storage = storage
|
||||||
|
this.log = getLogger({ component: "SettingsManager" })
|
||||||
|
}
|
||||||
|
|
||||||
|
loadEnvs(dbEnv: Record<string, string | undefined>, addToDb?: EnvCacher): FullSettings {
|
||||||
|
return {
|
||||||
|
lndNodeSettings: LoadLndNodeSettingsFromEnv(dbEnv, addToDb),
|
||||||
|
lndSettings: LoadLndSettingsFromEnv(dbEnv, addToDb),
|
||||||
|
liquiditySettings: LoadLiquiditySettingsFromEnv(dbEnv, addToDb),
|
||||||
|
lspSettings: LoadLSPSettingsFromEnv(dbEnv, addToDb),
|
||||||
|
nostrRelaySettings: LoadNostrRelaySettingsFromEnv(dbEnv, addToDb),
|
||||||
|
serviceFeeSettings: LoadServiceFeeSettingsFromEnv(dbEnv, addToDb),
|
||||||
|
serviceSettings: LoadServiceSettingsFromEnv(dbEnv, addToDb),
|
||||||
|
watchDogSettings: LoadWatchdogSettingsFromEnv(dbEnv, addToDb),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OverrideTestSettings(f: (s: FullSettings) => FullSettings) {
|
||||||
|
if (!this.settings) {
|
||||||
|
throw new Error("Settings not initialized")
|
||||||
|
}
|
||||||
|
this.settings = f(this.settings)
|
||||||
|
}
|
||||||
|
|
||||||
|
async InitSettings(): Promise<FullSettings> {
|
||||||
|
const dbSettings = await this.storage.settingsStorage.getAllDbEnvs()
|
||||||
|
const toAdd: Record<string, string> = {}
|
||||||
|
const addToDb = (key: string, value: string) => {
|
||||||
|
toAdd[key] = value
|
||||||
|
}
|
||||||
|
this.settings = this.loadEnvs(dbSettings, addToDb)
|
||||||
|
this.log("adding", toAdd.length, "settings to db")
|
||||||
|
for (const key in toAdd) {
|
||||||
|
await this.storage.settingsStorage.setDbEnvIFNeeded(key, toAdd[key])
|
||||||
|
}
|
||||||
|
return this.settings
|
||||||
|
}
|
||||||
|
|
||||||
|
getStorageSettings(): StorageSettings {
|
||||||
|
return this.storage.getStorageSettings()
|
||||||
|
}
|
||||||
|
|
||||||
|
getSettings(): FullSettings {
|
||||||
|
if (!this.settings) {
|
||||||
|
throw new Error("Settings not initialized")
|
||||||
|
}
|
||||||
|
return this.settings
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateDefaultAppName(name: string): Promise<boolean> {
|
||||||
|
if (!this.settings) {
|
||||||
|
throw new Error("Settings not initialized")
|
||||||
|
}
|
||||||
|
if (name === this.settings.serviceSettings.defaultAppName) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (!!process.env.DEFAULT_APP_NAME) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
await this.storage.settingsStorage.setDbEnvIFNeeded("DEFAULT_APP_NAME", name)
|
||||||
|
this.settings.serviceSettings.defaultAppName = name
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateRelayUrl(url: string): Promise<boolean> {
|
||||||
|
if (!this.settings) {
|
||||||
|
throw new Error("Settings not initialized")
|
||||||
|
}
|
||||||
|
if (url === this.settings.nostrRelaySettings.relays[0]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (!!process.env.RELAY_URL) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
await this.storage.settingsStorage.setDbEnvIFNeeded("NOSTR_RELAYS", url)
|
||||||
|
this.settings.nostrRelaySettings.relays = [url]
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateDisableLiquidityProvider(disable: boolean): Promise<boolean> {
|
||||||
|
if (!this.settings) {
|
||||||
|
throw new Error("Settings not initialized")
|
||||||
|
}
|
||||||
|
if (disable === this.settings.liquiditySettings.disableLiquidityProvider) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (!!process.env.DISABLE_LIQUIDITY_PROVIDER) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
await this.storage.settingsStorage.setDbEnvIFNeeded("DISABLE_LIQUIDITY_PROVIDER", disable ? "true" : "false")
|
||||||
|
this.settings.liquiditySettings.disableLiquidityProvider = disable
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
async updatePushBackupsToNostr(push: boolean): Promise<boolean> {
|
||||||
|
if (!this.settings) {
|
||||||
|
throw new Error("Settings not initialized")
|
||||||
|
}
|
||||||
|
if (push === this.settings.serviceSettings.pushBackupsToNostr) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (!!process.env.PUSH_BACKUPS_TO_NOSTR) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
await this.storage.settingsStorage.setDbEnvIFNeeded("PUSH_BACKUPS_TO_NOSTR", push ? "true" : "false")
|
||||||
|
this.settings.serviceSettings.pushBackupsToNostr = push
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateSkipSanityCheck(skip: boolean): Promise<boolean> {
|
||||||
|
if (!this.settings) {
|
||||||
|
throw new Error("Settings not initialized")
|
||||||
|
}
|
||||||
|
if (skip === this.settings.serviceSettings.skipSanityCheck) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (!!process.env.SKIP_SANITY_CHECK) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
await this.storage.settingsStorage.setDbEnvIFNeeded("SKIP_SANITY_CHECK", skip ? "true" : "false")
|
||||||
|
this.settings.serviceSettings.skipSanityCheck = skip
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type FullSettings = {
|
||||||
|
lndNodeSettings: LndNodeSettings
|
||||||
|
lndSettings: LndSettings
|
||||||
|
liquiditySettings: LiquiditySettings
|
||||||
|
watchDogSettings: WatchdogSettings,
|
||||||
|
nostrRelaySettings: NostrRelaySettings,
|
||||||
|
serviceFeeSettings: ServiceFeeSettings,
|
||||||
|
serviceSettings: ServiceSettings,
|
||||||
|
lspSettings: LSPSettings
|
||||||
|
}
|
||||||
|
|
@ -4,23 +4,23 @@ import { GrpcTransport } from "@protobuf-ts/grpc-transport";
|
||||||
import { credentials, Metadata } from '@grpc/grpc-js'
|
import { credentials, Metadata } from '@grpc/grpc-js'
|
||||||
import { getLogger } from '../helpers/logger.js';
|
import { getLogger } from '../helpers/logger.js';
|
||||||
import { WalletUnlockerClient } from '../../../proto/lnd/walletunlocker.client.js';
|
import { WalletUnlockerClient } from '../../../proto/lnd/walletunlocker.client.js';
|
||||||
import { MainSettings } from '../main/settings.js';
|
|
||||||
import { InitWalletReq } from '../lnd/initWalletReq.js';
|
import { InitWalletReq } from '../lnd/initWalletReq.js';
|
||||||
import Storage from '../storage/index.js'
|
import Storage from '../storage/index.js'
|
||||||
import { LightningClient } from '../../../proto/lnd/lightning.client.js';
|
import { LightningClient } from '../../../proto/lnd/lightning.client.js';
|
||||||
import * as Types from '../../../proto/autogenerated/ts/types.js'
|
import * as Types from '../../../proto/autogenerated/ts/types.js'
|
||||||
|
import SettingsManager from './settingsManager.js';
|
||||||
const DeadLineMetadata = (deadline = 10 * 1000) => ({ deadline: Date.now() + deadline })
|
const DeadLineMetadata = (deadline = 10 * 1000) => ({ deadline: Date.now() + deadline })
|
||||||
type EncryptedData = { iv: string, encrypted: string }
|
type EncryptedData = { iv: string, encrypted: string }
|
||||||
type Seed = { plaintextSeed: string[], encryptedSeed: EncryptedData }
|
type Seed = { plaintextSeed: string[], encryptedSeed: EncryptedData }
|
||||||
export class Unlocker {
|
export class Unlocker {
|
||||||
|
|
||||||
settings: MainSettings
|
settings: SettingsManager
|
||||||
storage: Storage
|
storage: Storage
|
||||||
abortController = new AbortController()
|
abortController = new AbortController()
|
||||||
subbedToBackups = false
|
subbedToBackups = false
|
||||||
nodePub: string | null = null
|
nodePub: string | null = null
|
||||||
log = getLogger({ component: "unlocker" })
|
log = getLogger({ component: "unlocker" })
|
||||||
constructor(settings: MainSettings, storage: Storage) {
|
constructor(settings: SettingsManager, storage: Storage) {
|
||||||
this.settings = settings
|
this.settings = settings
|
||||||
this.storage = storage
|
this.storage = storage
|
||||||
}
|
}
|
||||||
|
|
@ -30,8 +30,8 @@ export class Unlocker {
|
||||||
}
|
}
|
||||||
|
|
||||||
getCreds = () => {
|
getCreds = () => {
|
||||||
const macroonPath = this.settings.lndSettings.mainNode.lndMacaroonPath
|
const macroonPath = this.settings.getSettings().lndNodeSettings.lndMacaroonPath
|
||||||
const certPath = this.settings.lndSettings.mainNode.lndCertPath
|
const certPath = this.settings.getSettings().lndNodeSettings.lndCertPath
|
||||||
let macaroon = ""
|
let macaroon = ""
|
||||||
let lndCert: Buffer
|
let lndCert: Buffer
|
||||||
try {
|
try {
|
||||||
|
|
@ -96,8 +96,8 @@ export class Unlocker {
|
||||||
}
|
}
|
||||||
|
|
||||||
private waitForLndSync = async (timeoutSeconds: number): Promise<void> => {
|
private waitForLndSync = async (timeoutSeconds: number): Promise<void> => {
|
||||||
const lndLogPath = this.settings.lndSettings.lndLogDir;
|
const lndLogPath = this.settings.getSettings().lndSettings.lndLogDir;
|
||||||
if (this.settings.lndSettings.mockLnd) {
|
if (this.settings.getSettings().lndSettings.mockLnd) {
|
||||||
this.log("MOCK_LND set, skipping header sync wait.");
|
this.log("MOCK_LND set, skipping header sync wait.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -284,7 +284,7 @@ export class Unlocker {
|
||||||
}
|
}
|
||||||
|
|
||||||
GetWalletSecret = (create: boolean) => {
|
GetWalletSecret = (create: boolean) => {
|
||||||
const path = this.settings.walletSecretPath
|
const path = this.settings.getStorageSettings().walletSecretPath
|
||||||
let secret = ""
|
let secret = ""
|
||||||
try {
|
try {
|
||||||
secret = fs.readFileSync(path, 'utf-8')
|
secret = fs.readFileSync(path, 'utf-8')
|
||||||
|
|
@ -300,7 +300,7 @@ export class Unlocker {
|
||||||
}
|
}
|
||||||
|
|
||||||
GetWalletPassword = () => {
|
GetWalletPassword = () => {
|
||||||
const path = this.settings.walletPasswordPath
|
const path = this.settings.getStorageSettings().walletPasswordPath
|
||||||
let password = Buffer.alloc(0)
|
let password = Buffer.alloc(0)
|
||||||
try {
|
try {
|
||||||
password = fs.readFileSync(path)
|
password = fs.readFileSync(path)
|
||||||
|
|
@ -339,14 +339,14 @@ export class Unlocker {
|
||||||
}
|
}
|
||||||
|
|
||||||
GetUnlockerClient = (cert: Buffer) => {
|
GetUnlockerClient = (cert: Buffer) => {
|
||||||
const host = this.settings.lndSettings.mainNode.lndAddr
|
const host = this.settings.getSettings().lndNodeSettings.lndAddr
|
||||||
const channelCredentials = credentials.createSsl(cert)
|
const channelCredentials = credentials.createSsl(cert)
|
||||||
const transport = new GrpcTransport({ host, channelCredentials })
|
const transport = new GrpcTransport({ host, channelCredentials })
|
||||||
const client = new WalletUnlockerClient(transport)
|
const client = new WalletUnlockerClient(transport)
|
||||||
return client
|
return client
|
||||||
}
|
}
|
||||||
GetLightningClient = (cert: Buffer, macaroon: string) => {
|
GetLightningClient = (cert: Buffer, macaroon: string) => {
|
||||||
const host = this.settings.lndSettings.mainNode.lndAddr
|
const host = this.settings.getSettings().lndNodeSettings.lndAddr
|
||||||
const sslCreds = credentials.createSsl(cert)
|
const sslCreds = credentials.createSsl(cert)
|
||||||
const macaroonCreds = credentials.createFromMetadataGenerator(
|
const macaroonCreds = credentials.createFromMetadataGenerator(
|
||||||
function (args: any, callback: any) {
|
function (args: any, callback: any) {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import { EnvCanBeInteger } from "../helpers/envParser.js";
|
|
||||||
import FunctionQueue from "../helpers/functionQueue.js";
|
import FunctionQueue from "../helpers/functionQueue.js";
|
||||||
import { getLogger } from "../helpers/logger.js";
|
import { getLogger } from "../helpers/logger.js";
|
||||||
import { Utils } from "../helpers/utilsWrapper.js";
|
import { Utils } from "../helpers/utilsWrapper.js";
|
||||||
|
|
@ -8,14 +7,8 @@ import { ChannelBalance } from "../lnd/settings.js";
|
||||||
import Storage from '../storage/index.js'
|
import Storage from '../storage/index.js'
|
||||||
import { LiquidityManager } from "./liquidityManager.js";
|
import { LiquidityManager } from "./liquidityManager.js";
|
||||||
import { RugPullTracker } from "./rugPullTracker.js";
|
import { RugPullTracker } from "./rugPullTracker.js";
|
||||||
export type WatchdogSettings = {
|
import SettingsManager from "./settingsManager.js";
|
||||||
maxDiffSats: number
|
|
||||||
}
|
|
||||||
export const LoadWatchdogSettingsFromEnv = (test = false): WatchdogSettings => {
|
|
||||||
return {
|
|
||||||
maxDiffSats: EnvCanBeInteger("WATCHDOG_MAX_DIFF_SATS")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export class Watchdog {
|
export class Watchdog {
|
||||||
queue: FunctionQueue<void>
|
queue: FunctionQueue<void>
|
||||||
initialLndBalance: number;
|
initialLndBalance: number;
|
||||||
|
|
@ -27,7 +20,7 @@ export class Watchdog {
|
||||||
lnd: LND;
|
lnd: LND;
|
||||||
liquidProvider: LiquidityProvider;
|
liquidProvider: LiquidityProvider;
|
||||||
liquidityManager: LiquidityManager;
|
liquidityManager: LiquidityManager;
|
||||||
settings: WatchdogSettings;
|
settings: SettingsManager;
|
||||||
storage: Storage;
|
storage: Storage;
|
||||||
rugPullTracker: RugPullTracker
|
rugPullTracker: RugPullTracker
|
||||||
utils: Utils
|
utils: Utils
|
||||||
|
|
@ -36,7 +29,7 @@ export class Watchdog {
|
||||||
ready = false
|
ready = false
|
||||||
interval: NodeJS.Timer;
|
interval: NodeJS.Timer;
|
||||||
lndPubKey: string;
|
lndPubKey: string;
|
||||||
constructor(settings: WatchdogSettings, liquidityManager: LiquidityManager, lnd: LND, storage: Storage, utils: Utils, rugPullTracker: RugPullTracker) {
|
constructor(settings: SettingsManager, liquidityManager: LiquidityManager, lnd: LND, storage: Storage, utils: Utils, rugPullTracker: RugPullTracker) {
|
||||||
this.lnd = lnd;
|
this.lnd = lnd;
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
this.storage = storage;
|
this.storage = storage;
|
||||||
|
|
@ -114,7 +107,7 @@ export class Watchdog {
|
||||||
switch (result.type) {
|
switch (result.type) {
|
||||||
case 'mismatch':
|
case 'mismatch':
|
||||||
if (deltaLnd < 0) {
|
if (deltaLnd < 0) {
|
||||||
if (result.absoluteDiff > this.settings.maxDiffSats) {
|
if (result.absoluteDiff > this.settings.getSettings().watchDogSettings.maxDiffSats) {
|
||||||
await this.updateDisruption(true, result.absoluteDiff, lndWithDeltaUsers)
|
await this.updateDisruption(true, result.absoluteDiff, lndWithDeltaUsers)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
@ -126,7 +119,7 @@ export class Watchdog {
|
||||||
break
|
break
|
||||||
case 'negative':
|
case 'negative':
|
||||||
if (Math.abs(deltaLnd) > Math.abs(deltaUsers)) {
|
if (Math.abs(deltaLnd) > Math.abs(deltaUsers)) {
|
||||||
if (result.absoluteDiff > this.settings.maxDiffSats) {
|
if (result.absoluteDiff > this.settings.getSettings().watchDogSettings.maxDiffSats) {
|
||||||
await this.updateDisruption(true, result.absoluteDiff, lndWithDeltaUsers)
|
await this.updateDisruption(true, result.absoluteDiff, lndWithDeltaUsers)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
@ -142,7 +135,7 @@ export class Watchdog {
|
||||||
case 'positive':
|
case 'positive':
|
||||||
if (deltaLnd < deltaUsers) {
|
if (deltaLnd < deltaUsers) {
|
||||||
this.log("WARNING! LND balance increased less than users balance with a difference of", result.absoluteDiff, "sats")
|
this.log("WARNING! LND balance increased less than users balance with a difference of", result.absoluteDiff, "sats")
|
||||||
if (result.absoluteDiff > this.settings.maxDiffSats) {
|
if (result.absoluteDiff > this.settings.getSettings().watchDogSettings.maxDiffSats) {
|
||||||
await this.updateDisruption(true, result.absoluteDiff, lndWithDeltaUsers)
|
await this.updateDisruption(true, result.absoluteDiff, lndWithDeltaUsers)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
@ -160,12 +153,13 @@ export class Watchdog {
|
||||||
updateDisruption = async (isDisrupted: boolean, absoluteDiff: number, lndWithDeltaUsers: number) => {
|
updateDisruption = async (isDisrupted: boolean, absoluteDiff: number, lndWithDeltaUsers: number) => {
|
||||||
const tracker = await this.getTracker()
|
const tracker = await this.getTracker()
|
||||||
this.storage.liquidityStorage.UpdateTrackedProviderBalance('lnd', this.lndPubKey, lndWithDeltaUsers)
|
this.storage.liquidityStorage.UpdateTrackedProviderBalance('lnd', this.lndPubKey, lndWithDeltaUsers)
|
||||||
|
const maxDiffSats = this.settings.getSettings().watchDogSettings.maxDiffSats
|
||||||
if (isDisrupted) {
|
if (isDisrupted) {
|
||||||
if (tracker.latest_distruption_at_unix === 0) {
|
if (tracker.latest_distruption_at_unix === 0) {
|
||||||
await this.storage.liquidityStorage.UpdateTrackedProviderDisruption('lnd', this.lndPubKey, Math.floor(Date.now() / 1000))
|
await this.storage.liquidityStorage.UpdateTrackedProviderDisruption('lnd', this.lndPubKey, Math.floor(Date.now() / 1000))
|
||||||
this.log("detected lnd loss of", absoluteDiff, "sats,", absoluteDiff - this.settings.maxDiffSats, "above the max allowed")
|
this.log("detected lnd loss of", absoluteDiff, "sats,", absoluteDiff - maxDiffSats, "above the max allowed")
|
||||||
} else {
|
} else {
|
||||||
this.log("ongoing lnd loss of", absoluteDiff, "sats,", absoluteDiff - this.settings.maxDiffSats, "above the max allowed")
|
this.log("ongoing lnd loss of", absoluteDiff, "sats,", absoluteDiff - maxDiffSats, "above the max allowed")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (tracker.latest_distruption_at_unix !== 0) {
|
if (tracker.latest_distruption_at_unix !== 0) {
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ import { BalanceEvent } from '../storage/entity/BalanceEvent.js'
|
||||||
import { ChannelBalanceEvent } from '../storage/entity/ChannelsBalanceEvent.js'
|
import { ChannelBalanceEvent } from '../storage/entity/ChannelsBalanceEvent.js'
|
||||||
import LND from '../lnd/lnd.js'
|
import LND from '../lnd/lnd.js'
|
||||||
import HtlcTracker from './htlcTracker.js'
|
import HtlcTracker from './htlcTracker.js'
|
||||||
import { MainSettings } from '../main/settings.js'
|
|
||||||
import { getLogger } from '../helpers/logger.js'
|
import { getLogger } from '../helpers/logger.js'
|
||||||
import { encodeTLV, usageMetricsToTlv } from '../helpers/tlv.js'
|
import { encodeTLV, usageMetricsToTlv } from '../helpers/tlv.js'
|
||||||
import { ChannelCloseSummary_ClosureType } from '../../../proto/lnd/lightning.js'
|
import { ChannelCloseSummary_ClosureType } from '../../../proto/lnd/lightning.js'
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import { ERROR, getLogger } from '../helpers/logger.js'
|
||||||
import { nip19 } from 'nostr-tools'
|
import { nip19 } from 'nostr-tools'
|
||||||
import { encrypt as encryptV1, decrypt as decryptV1, getSharedSecret as getConversationKeyV1 } from './nip44v1.js'
|
import { encrypt as encryptV1, decrypt as decryptV1, getSharedSecret as getConversationKeyV1 } from './nip44v1.js'
|
||||||
import { ProcessMetrics, ProcessMetricsCollector } from '../storage/tlv/processMetricsCollector.js'
|
import { ProcessMetrics, ProcessMetricsCollector } from '../storage/tlv/processMetricsCollector.js'
|
||||||
import { EnvCanBeInteger, } from '../helpers/envParser.js'
|
import { Subscription } from 'nostr-tools/lib/types/abstract-relay.js';
|
||||||
const { nprofileEncode } = nip19
|
const { nprofileEncode } = nip19
|
||||||
const { v2 } = nip44
|
const { v2 } = nip44
|
||||||
const { encrypt: encryptV2, decrypt: decryptV2, utils } = v2
|
const { encrypt: encryptV2, decrypt: decryptV2, utils } = v2
|
||||||
|
|
@ -28,24 +28,6 @@ export type NostrSettings = {
|
||||||
maxEventContentLength: number
|
maxEventContentLength: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type NostrRelaySettings = {
|
|
||||||
relays: string[],
|
|
||||||
maxEventContentLength: number
|
|
||||||
}
|
|
||||||
|
|
||||||
const getEnvOrDefault = (name: string, defaultValue: string): string => {
|
|
||||||
return process.env[name] || defaultValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const LoadNosrtRelaySettingsFromEnv = (test = false): NostrRelaySettings => {
|
|
||||||
const relaysEnv = getEnvOrDefault("NOSTR_RELAYS", "wss://relay.lightning.pub");
|
|
||||||
const maxEventContentLength = EnvCanBeInteger("NOSTR_MAX_EVENT_CONTENT_LENGTH", 40000)
|
|
||||||
return {
|
|
||||||
relays: relaysEnv.split(' '),
|
|
||||||
maxEventContentLength
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export type NostrEvent = {
|
export type NostrEvent = {
|
||||||
id: string
|
id: string
|
||||||
pub: string
|
pub: string
|
||||||
|
|
@ -104,7 +86,7 @@ let subProcessHandler: Handler | undefined
|
||||||
process.on("message", (message: ChildProcessRequest) => {
|
process.on("message", (message: ChildProcessRequest) => {
|
||||||
switch (message.type) {
|
switch (message.type) {
|
||||||
case 'settings':
|
case 'settings':
|
||||||
initSubprocessHandler(message.settings)
|
handleNostrSettings(message.settings)
|
||||||
break
|
break
|
||||||
case 'send':
|
case 'send':
|
||||||
sendToNostr(message.initiator, message.data, message.relays)
|
sendToNostr(message.initiator, message.data, message.relays)
|
||||||
|
|
@ -117,18 +99,14 @@ process.on("message", (message: ChildProcessRequest) => {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const initSubprocessHandler = (settings: NostrSettings) => {
|
const handleNostrSettings = (settings: NostrSettings) => {
|
||||||
if (subProcessHandler) {
|
if (subProcessHandler) {
|
||||||
getLogger({ component: "nostrMiddleware" })(ERROR, "nostr settings ignored since handler already exists")
|
getLogger({ component: "nostrMiddleware" })("got new nostr setting, resetting nostr handler")
|
||||||
|
subProcessHandler.Stop()
|
||||||
|
initNostrHandler(settings)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
subProcessHandler = new Handler(settings, event => {
|
initNostrHandler(settings)
|
||||||
send({
|
|
||||||
type: 'event',
|
|
||||||
event: event
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
new ProcessMetricsCollector((metrics) => {
|
new ProcessMetricsCollector((metrics) => {
|
||||||
send({
|
send({
|
||||||
type: 'processMetrics',
|
type: 'processMetrics',
|
||||||
|
|
@ -136,6 +114,14 @@ const initSubprocessHandler = (settings: NostrSettings) => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
const initNostrHandler = (settings: NostrSettings) => {
|
||||||
|
subProcessHandler = new Handler(settings, event => {
|
||||||
|
send({
|
||||||
|
type: 'event',
|
||||||
|
event: event
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
const sendToNostr: NostrSend = (initiator, data, relays) => {
|
const sendToNostr: NostrSend = (initiator, data, relays) => {
|
||||||
if (!subProcessHandler) {
|
if (!subProcessHandler) {
|
||||||
getLogger({ component: "nostrMiddleware" })(ERROR, "nostr was not initialized")
|
getLogger({ component: "nostrMiddleware" })(ERROR, "nostr was not initialized")
|
||||||
|
|
@ -152,13 +138,16 @@ export default class Handler {
|
||||||
apps: Record<string, AppInfo> = {}
|
apps: Record<string, AppInfo> = {}
|
||||||
eventCallback: (event: NostrEvent) => void
|
eventCallback: (event: NostrEvent) => void
|
||||||
log = getLogger({ component: "nostrMiddleware" })
|
log = getLogger({ component: "nostrMiddleware" })
|
||||||
|
relay: Relay | null = null
|
||||||
|
sub: Subscription | null = null
|
||||||
|
stopped = false
|
||||||
constructor(settings: NostrSettings, eventCallback: (event: NostrEvent) => void) {
|
constructor(settings: NostrSettings, eventCallback: (event: NostrEvent) => void) {
|
||||||
this.settings = settings
|
this.settings = settings
|
||||||
this.log("connecting to relays:", settings.relays)
|
this.log("connecting to relays:", settings.relays)
|
||||||
this.settings.apps.forEach(app => {
|
this.settings.apps.forEach(app => {
|
||||||
this.log("appId:", app.appId, "pubkey:", app.publicKey, "nprofile:", nprofileEncode({ pubkey: app.publicKey, relays: settings.relays }))
|
this.log("appId:", app.appId, "pubkey:", app.publicKey, "nprofile:", nprofileEncode({ pubkey: app.publicKey, relays: settings.relays }))
|
||||||
})
|
})
|
||||||
this.eventCallback = eventCallback
|
this.eventCallback = (e) => { if (!this.stopped) eventCallback(e) }
|
||||||
this.settings.apps.forEach(app => {
|
this.settings.apps.forEach(app => {
|
||||||
this.apps[app.publicKey] = app
|
this.apps[app.publicKey] = app
|
||||||
})
|
})
|
||||||
|
|
@ -167,78 +156,60 @@ export default class Handler {
|
||||||
|
|
||||||
async ConnectLoop() {
|
async ConnectLoop() {
|
||||||
let failures = 0
|
let failures = 0
|
||||||
while (true) {
|
while (!this.stopped) {
|
||||||
await this.ConnectPromise()
|
await this.ConnectPromise()
|
||||||
const pow = Math.pow(2, failures)
|
const pow = Math.pow(2, failures)
|
||||||
const delay = Math.min(pow, 900)
|
const delay = Math.min(pow, 900)
|
||||||
this.log("relay connection failed, will try again in", delay, "seconds (failures:", failures, ")")
|
this.log("relay connection failed, will try again in", delay, "seconds (failures:", failures, ")")
|
||||||
await new Promise(resolve => setTimeout(resolve, delay*1000))
|
await new Promise(resolve => setTimeout(resolve, delay * 1000))
|
||||||
failures++
|
failures++
|
||||||
}
|
}
|
||||||
|
this.log("nostr handler stopped")
|
||||||
|
}
|
||||||
|
|
||||||
|
Stop() {
|
||||||
|
this.stopped = true
|
||||||
|
this.sub?.close()
|
||||||
|
this.relay?.close()
|
||||||
|
this.relay = null
|
||||||
|
this.sub = null
|
||||||
}
|
}
|
||||||
|
|
||||||
async ConnectPromise() {
|
async ConnectPromise() {
|
||||||
return new Promise<void>( async (res) => {
|
return new Promise<void>(async (res) => {
|
||||||
const relay = await this.GetRelay()
|
this.relay = await this.GetRelay()
|
||||||
if (!relay) {
|
if (!this.relay) {
|
||||||
res()
|
res()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const sub = this.Subscribe(relay)
|
this.sub = this.Subscribe(this.relay)
|
||||||
relay.onclose = (() => {
|
this.relay.onclose = (() => {
|
||||||
this.log("relay disconnected")
|
this.log("relay disconnected")
|
||||||
sub.close()
|
this.sub?.close()
|
||||||
relay.onclose = null
|
if (this.relay) {
|
||||||
relay.close()
|
this.relay.onclose = null
|
||||||
|
this.relay.close()
|
||||||
|
this.relay = null
|
||||||
|
}
|
||||||
|
this.sub = null
|
||||||
res()
|
res()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async GetRelay(): Promise<Relay|null> {
|
async GetRelay(): Promise<Relay | null> {
|
||||||
try {
|
try {
|
||||||
const relay = await Relay.connect(this.settings.relays[0])
|
const relay = await Relay.connect(this.settings.relays[0])
|
||||||
if (!relay.connected) {
|
if (!relay.connected) {
|
||||||
throw new Error("failed to connect to relay")
|
throw new Error("failed to connect to relay")
|
||||||
}
|
}
|
||||||
return relay
|
return relay
|
||||||
} catch (err:any) {
|
} catch (err: any) {
|
||||||
this.log("failed to connect to relay", err.message || err)
|
this.log("failed to connect to relay", err.message || err)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* async Connect() {
|
|
||||||
const log = getLogger({})
|
|
||||||
log("conneting to relay...", this.settings.relays[0])
|
|
||||||
let relay: Relay | null = null
|
|
||||||
//const relay = relayInit(this.settings.relays[0]) // TODO: create multiple conns for multiple relays
|
|
||||||
try {
|
|
||||||
relay = await Relay.connect(this.settings.relays[0])
|
|
||||||
if (!relay.connected) {
|
|
||||||
throw new Error("failed to connect to relay")
|
|
||||||
}
|
|
||||||
} catch (err:any) {
|
|
||||||
log("failed to connect to relay, will try again in 2 seconds", err.message || err)
|
|
||||||
setTimeout(() => {
|
|
||||||
this.Connect()
|
|
||||||
}, 2000)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log("connected, subbing...")
|
|
||||||
relay.onclose = (() => {
|
|
||||||
log("relay disconnected, will try to reconnect in 2 seconds")
|
|
||||||
relay.close()
|
|
||||||
setTimeout(() => {
|
|
||||||
this.Connect()
|
|
||||||
}, 2000)
|
|
||||||
})
|
|
||||||
|
|
||||||
this.Subscribe(relay)
|
|
||||||
|
|
||||||
} */
|
|
||||||
|
|
||||||
Subscribe(relay: Relay) {
|
Subscribe(relay: Relay) {
|
||||||
const appIds = Object.keys(this.apps)
|
const appIds = Object.keys(this.apps)
|
||||||
this.log("🔍 [NOSTR SUBSCRIPTION] Setting up subscription", {
|
this.log("🔍 [NOSTR SUBSCRIPTION] Setting up subscription", {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
import { ChildProcess, fork } from 'child_process'
|
import { ChildProcess, fork } from 'child_process'
|
||||||
import { EnvCanBeInteger, EnvMustBeNonEmptyString } from "../helpers/envParser.js"
|
|
||||||
import { NostrSettings, NostrEvent, ChildProcessRequest, ChildProcessResponse, SendData, SendInitiator } from "./handler.js"
|
import { NostrSettings, NostrEvent, ChildProcessRequest, ChildProcessResponse, SendData, SendInitiator } from "./handler.js"
|
||||||
import { Utils } from '../helpers/utilsWrapper.js'
|
import { Utils } from '../helpers/utilsWrapper.js'
|
||||||
import {getLogger, ERROR} from '../helpers/logger.js'
|
import { getLogger, ERROR } from '../helpers/logger.js'
|
||||||
type EventCallback = (event: NostrEvent) => void
|
type EventCallback = (event: NostrEvent) => void
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -10,7 +9,6 @@ type EventCallback = (event: NostrEvent) => void
|
||||||
|
|
||||||
|
|
||||||
export default class NostrSubprocess {
|
export default class NostrSubprocess {
|
||||||
settings: NostrSettings
|
|
||||||
childProcess: ChildProcess
|
childProcess: ChildProcess
|
||||||
utils: Utils
|
utils: Utils
|
||||||
awaitingPongs: (() => void)[] = []
|
awaitingPongs: (() => void)[] = []
|
||||||
|
|
@ -55,6 +53,10 @@ export default class NostrSubprocess {
|
||||||
this.childProcess.send(message)
|
this.childProcess.send(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reset(settings: NostrSettings) {
|
||||||
|
this.sendToChildProcess({ type: 'settings', settings })
|
||||||
|
}
|
||||||
|
|
||||||
Ping() {
|
Ping() {
|
||||||
this.sendToChildProcess({ type: 'ping' })
|
this.sendToChildProcess({ type: 'ping' })
|
||||||
return new Promise<void>((resolve) => {
|
return new Promise<void>((resolve) => {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
import * as Types from '../../../proto/autogenerated/ts/types.js'
|
import * as Types from '../../../proto/autogenerated/ts/types.js'
|
||||||
import { getLogger } from '../helpers/logger.js'
|
|
||||||
import main from '../main/index.js'
|
|
||||||
import Main from '../main/index.js'
|
import Main from '../main/index.js'
|
||||||
export default (mainHandler: Main): Types.ServerMethods => {
|
export default (mainHandler: Main): Types.ServerMethods => {
|
||||||
return {
|
return {
|
||||||
|
|
@ -350,7 +348,7 @@ export default (mainHandler: Main): Types.ServerMethods => {
|
||||||
if (err != null) throw new Error(err.message)
|
if (err != null) throw new Error(err.message)
|
||||||
return mainHandler.adminManager.GetInviteTokenState(ctx, req);
|
return mainHandler.adminManager.GetInviteTokenState(ctx, req);
|
||||||
},
|
},
|
||||||
/* AuthorizeDebit: async ({ ctx, req }) => {
|
/* AuthorizeDebit: async ({ ctx, req }) => {
|
||||||
return mainHandler.debitManager.AuthorizeDebit(ctx, req)
|
return mainHandler.debitManager.AuthorizeDebit(ctx, req)
|
||||||
}, */
|
}, */
|
||||||
GetDebitAuthorizations: async ({ ctx }) => {
|
GetDebitAuthorizations: async ({ ctx }) => {
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import { User } from "../entity/User.js"
|
||||||
import { UserReceivingAddress } from "../entity/UserReceivingAddress.js"
|
import { UserReceivingAddress } from "../entity/UserReceivingAddress.js"
|
||||||
import { UserReceivingInvoice } from "../entity/UserReceivingInvoice.js"
|
import { UserReceivingInvoice } from "../entity/UserReceivingInvoice.js"
|
||||||
import { UserInvoicePayment } from "../entity/UserInvoicePayment.js"
|
import { UserInvoicePayment } from "../entity/UserInvoicePayment.js"
|
||||||
import { EnvMustBeNonEmptyString } from "../../helpers/envParser.js"
|
|
||||||
import { UserTransactionPayment } from "../entity/UserTransactionPayment.js"
|
import { UserTransactionPayment } from "../entity/UserTransactionPayment.js"
|
||||||
import { UserBasicAuth } from "../entity/UserBasicAuth.js"
|
import { UserBasicAuth } from "../entity/UserBasicAuth.js"
|
||||||
import { UserEphemeralKey } from "../entity/UserEphemeralKey.js"
|
import { UserEphemeralKey } from "../entity/UserEphemeralKey.js"
|
||||||
|
|
@ -29,6 +28,7 @@ import { ChannelEvent } from "../entity/ChannelEvent.js"
|
||||||
import { AppUserDevice } from "../entity/AppUserDevice.js"
|
import { AppUserDevice } from "../entity/AppUserDevice.js"
|
||||||
import * as fs from 'fs'
|
import * as fs from 'fs'
|
||||||
import { UserAccess } from "../entity/UserAccess.js"
|
import { UserAccess } from "../entity/UserAccess.js"
|
||||||
|
import { AdminSettings } from "../entity/AdminSettings.js"
|
||||||
|
|
||||||
|
|
||||||
export type DbSettings = {
|
export type DbSettings = {
|
||||||
|
|
@ -73,7 +73,8 @@ export const MainDbEntities = {
|
||||||
'Product': Product,
|
'Product': Product,
|
||||||
'ManagementGrant': ManagementGrant,
|
'ManagementGrant': ManagementGrant,
|
||||||
'AppUserDevice': AppUserDevice,
|
'AppUserDevice': AppUserDevice,
|
||||||
'UserAccess': UserAccess
|
'UserAccess': UserAccess,
|
||||||
|
'AdminSettings': AdminSettings
|
||||||
}
|
}
|
||||||
export type MainDbNames = keyof typeof MainDbEntities
|
export type MainDbNames = keyof typeof MainDbEntities
|
||||||
export const MainDbEntitiesNames = Object.keys(MainDbEntities)
|
export const MainDbEntitiesNames = Object.keys(MainDbEntities)
|
||||||
|
|
|
||||||
19
src/services/storage/entity/AdminSettings.ts
Normal file
19
src/services/storage/entity/AdminSettings.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { Entity, PrimaryGeneratedColumn, Column, Index, Check, CreateDateColumn, UpdateDateColumn } from "typeorm"
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
export class AdminSettings {
|
||||||
|
@PrimaryGeneratedColumn()
|
||||||
|
serial_id: number
|
||||||
|
|
||||||
|
@Column({ unique: true })
|
||||||
|
env_name: string
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
env_value: string
|
||||||
|
|
||||||
|
@CreateDateColumn()
|
||||||
|
created_at: Date
|
||||||
|
|
||||||
|
@UpdateDateColumn()
|
||||||
|
updated_at: Date
|
||||||
|
}
|
||||||
|
|
@ -12,16 +12,58 @@ import DebitStorage from "./debitStorage.js"
|
||||||
import OfferStorage from "./offerStorage.js"
|
import OfferStorage from "./offerStorage.js"
|
||||||
import { ManagementStorage } from "./managementStorage.js";
|
import { ManagementStorage } from "./managementStorage.js";
|
||||||
import { StorageInterface, TX } from "./db/storageInterface.js";
|
import { StorageInterface, TX } from "./db/storageInterface.js";
|
||||||
import { PubLogger } from "../helpers/logger.js"
|
import { getLogger, PubLogger } from "../helpers/logger.js"
|
||||||
import { TlvStorageFactory } from './tlv/tlvFilesStorageFactory.js';
|
import { TlvStorageFactory } from './tlv/tlvFilesStorageFactory.js';
|
||||||
import { Utils } from '../helpers/utilsWrapper.js';
|
import { Utils } from '../helpers/utilsWrapper.js';
|
||||||
|
import SettingsStorage from "./settingsStorage.js";
|
||||||
|
import crypto from 'crypto';
|
||||||
export type StorageSettings = {
|
export type StorageSettings = {
|
||||||
dbSettings: DbSettings
|
dbSettings: DbSettings
|
||||||
eventLogPath: string
|
eventLogPath: string
|
||||||
dataDir: string
|
dataDir: string
|
||||||
|
allowResetMetricsStorages: boolean
|
||||||
|
walletPasswordPath: string
|
||||||
|
walletSecretPath: string
|
||||||
|
jwtSecret: string // Secret
|
||||||
|
}
|
||||||
|
const getDataPath = (dataDir: string, dataPath: string) => {
|
||||||
|
return dataDir !== "" ? `${dataDir}/${dataPath}` : dataPath
|
||||||
}
|
}
|
||||||
export const LoadStorageSettingsFromEnv = (): StorageSettings => {
|
export const LoadStorageSettingsFromEnv = (): StorageSettings => {
|
||||||
return { dbSettings: LoadDbSettingsFromEnv(), eventLogPath: "logs/eventLogV3.csv", dataDir: process.env.DATA_DIR || "" }
|
const dataDir = process.env.DATA_DIR || ""
|
||||||
|
return {
|
||||||
|
dbSettings: LoadDbSettingsFromEnv(), eventLogPath: "logs/eventLogV3.csv", dataDir,
|
||||||
|
allowResetMetricsStorages: process.env.ALLOW_RESET_METRICS_STORAGES === 'true' || false,
|
||||||
|
walletSecretPath: process.env.WALLET_SECRET_PATH || getDataPath(dataDir, ".wallet_secret"),
|
||||||
|
walletPasswordPath: process.env.WALLET_PASSWORD_PATH || getDataPath(dataDir, ".wallet_password"),
|
||||||
|
jwtSecret: loadJwtSecret(dataDir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const GetTestStorageSettings = (s: StorageSettings): StorageSettings => {
|
||||||
|
const eventLogPath = `logs/eventLogV3Test${Date.now()}.csv`
|
||||||
|
return {
|
||||||
|
...s,
|
||||||
|
dbSettings: { ...s.dbSettings, databaseFile: ":memory:", metricsDatabaseFile: ":memory:" },
|
||||||
|
eventLogPath, dataDir: "test-data"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const loadJwtSecret = (dataDir: string): string => {
|
||||||
|
const secret = process.env["JWT_SECRET"]
|
||||||
|
const log = getLogger({})
|
||||||
|
if (secret) {
|
||||||
|
return secret
|
||||||
|
}
|
||||||
|
log("JWT_SECRET not set in env, checking .jwt_secret file")
|
||||||
|
const secretPath = getDataPath(dataDir, ".jwt_secret")
|
||||||
|
try {
|
||||||
|
const fileContent = fs.readFileSync(secretPath, "utf-8")
|
||||||
|
return fileContent.trim()
|
||||||
|
} catch (e) {
|
||||||
|
log(".jwt_secret file not found, generating random secret")
|
||||||
|
const secret = crypto.randomBytes(32).toString('hex')
|
||||||
|
fs.writeFileSync(secretPath, secret)
|
||||||
|
return secret
|
||||||
|
}
|
||||||
}
|
}
|
||||||
export default class {
|
export default class {
|
||||||
//DB: DataSource | EntityManager
|
//DB: DataSource | EntityManager
|
||||||
|
|
@ -39,6 +81,7 @@ export default class {
|
||||||
offerStorage: OfferStorage
|
offerStorage: OfferStorage
|
||||||
managementStorage: ManagementStorage
|
managementStorage: ManagementStorage
|
||||||
eventsLog: EventsLogManager
|
eventsLog: EventsLogManager
|
||||||
|
settingsStorage: SettingsStorage
|
||||||
utils: Utils
|
utils: Utils
|
||||||
constructor(settings: StorageSettings, utils: Utils) {
|
constructor(settings: StorageSettings, utils: Utils) {
|
||||||
this.settings = settings
|
this.settings = settings
|
||||||
|
|
@ -51,6 +94,7 @@ export default class {
|
||||||
//const { source, executedMigrations } = await NewDB(this.settings.dbSettings, allMigrations)
|
//const { source, executedMigrations } = await NewDB(this.settings.dbSettings, allMigrations)
|
||||||
//this.DB = source
|
//this.DB = source
|
||||||
//this.txQueue = new TransactionsQueue("main", this.DB)
|
//this.txQueue = new TransactionsQueue("main", this.DB)
|
||||||
|
this.settingsStorage = new SettingsStorage(this.dbs)
|
||||||
this.userStorage = new UserStorage(this.dbs, this.eventsLog)
|
this.userStorage = new UserStorage(this.dbs, this.eventsLog)
|
||||||
this.productStorage = new ProductStorage(this.dbs)
|
this.productStorage = new ProductStorage(this.dbs)
|
||||||
this.applicationStorage = new ApplicationStorage(this.dbs, this.userStorage)
|
this.applicationStorage = new ApplicationStorage(this.dbs, this.userStorage)
|
||||||
|
|
@ -74,6 +118,10 @@ export default class {
|
||||||
} */
|
} */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getStorageSettings(): StorageSettings {
|
||||||
|
return this.settings
|
||||||
|
}
|
||||||
|
|
||||||
Stop() {
|
Stop() {
|
||||||
this.dbs.disconnect()
|
this.dbs.disconnect()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||||
|
|
||||||
|
export class AdminSettings1761683639419 implements MigrationInterface {
|
||||||
|
name = 'AdminSettings1761683639419'
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`CREATE TABLE "admin_settings" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "env_name" varchar NOT NULL, "env_value" varchar NOT NULL, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), CONSTRAINT "UQ_d8a6092ee66a2e65a9d278cf041" UNIQUE ("env_name"))`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`DROP TABLE "admin_settings"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -26,12 +26,14 @@ import { UserReceivingInvoiceIdx1753109184611 } from './1753109184611-user_recei
|
||||||
import { UserAccess1759426050669 } from './1759426050669-user_access.js'
|
import { UserAccess1759426050669 } from './1759426050669-user_access.js'
|
||||||
import { AddBlindToUserOffer1760000000000 } from './1760000000000-add_blind_to_user_offer.js'
|
import { AddBlindToUserOffer1760000000000 } from './1760000000000-add_blind_to_user_offer.js'
|
||||||
import { ApplicationAvatarUrl1761000001000 } from './1761000001000-application_avatar_url.js'
|
import { ApplicationAvatarUrl1761000001000 } from './1761000001000-application_avatar_url.js'
|
||||||
|
import { AdminSettings1761683639419 } from './1761683639419-admin_settings.js'
|
||||||
|
|
||||||
|
|
||||||
export const allMigrations = [Initial1703170309875, LspOrder1718387847693, LiquidityProvider1719335699480, LndNodeInfo1720187506189,
|
export const allMigrations = [Initial1703170309875, LspOrder1718387847693, LiquidityProvider1719335699480, LndNodeInfo1720187506189,
|
||||||
TrackedProvider1720814323679, CreateInviteTokenTable1721751414878, PaymentIndex1721760297610, DebitAccess1726496225078, DebitAccessFixes1726685229264,
|
TrackedProvider1720814323679, CreateInviteTokenTable1721751414878, PaymentIndex1721760297610, DebitAccess1726496225078, DebitAccessFixes1726685229264,
|
||||||
DebitToPub1727105758354, UserCbUrl1727112281043, UserOffer1733502626042, ManagementGrant1751307732346, ManagementGrantBanned1751989251513,
|
DebitToPub1727105758354, UserCbUrl1727112281043, UserOffer1733502626042, ManagementGrant1751307732346, ManagementGrantBanned1751989251513,
|
||||||
InvoiceCallbackUrls1752425992291, OldSomethingLeftover1753106599604, UserReceivingInvoiceIdx1753109184611, AppUserDevice1753285173175, UserAccess1759426050669, AddBlindToUserOffer1760000000000, ApplicationAvatarUrl1761000001000]
|
InvoiceCallbackUrls1752425992291, OldSomethingLeftover1753106599604, UserReceivingInvoiceIdx1753109184611, AppUserDevice1753285173175,
|
||||||
|
UserAccess1759426050669, AddBlindToUserOffer1760000000000, ApplicationAvatarUrl1761000001000, AdminSettings1761683639419]
|
||||||
|
|
||||||
export const allMetricsMigrations = [LndMetrics1703170330183, ChannelRouting1709316653538, HtlcCount1724266887195, BalanceEvents1724860966825, RootOps1732566440447, RootOpsTime1745428134124, ChannelEvents1750777346411]
|
export const allMetricsMigrations = [LndMetrics1703170330183, ChannelRouting1709316653538, HtlcCount1724266887195, BalanceEvents1724860966825, RootOps1732566440447, RootOpsTime1745428134124, ChannelEvents1750777346411]
|
||||||
/* export const TypeOrmMigrationRunner = async (log: PubLogger, storageManager: Storage, settings: DbSettings, arg: string | undefined): Promise<boolean> => {
|
/* export const TypeOrmMigrationRunner = async (log: PubLogger, storageManager: Storage, settings: DbSettings, arg: string | undefined): Promise<boolean> => {
|
||||||
|
|
|
||||||
35
src/services/storage/settingsStorage.ts
Normal file
35
src/services/storage/settingsStorage.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
import { StorageInterface } from "./db/storageInterface.js";
|
||||||
|
import { AdminSettings } from "./entity/AdminSettings.js";
|
||||||
|
export default class SettingsStorage {
|
||||||
|
dbs: StorageInterface
|
||||||
|
constructor(dbs: StorageInterface) {
|
||||||
|
this.dbs = dbs
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAllDbEnvs(): Promise<Record<string, string>> {
|
||||||
|
const settings = await this.dbs.Find<AdminSettings>('AdminSettings', {});
|
||||||
|
const envs: Record<string, string> = {};
|
||||||
|
for (const setting of settings) {
|
||||||
|
envs[setting.env_name] = setting.env_value;
|
||||||
|
}
|
||||||
|
return envs;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getDbEnv(envName: string): Promise<string | undefined> {
|
||||||
|
const setting = await this.dbs.FindOne<AdminSettings>('AdminSettings', { where: { env_name: envName } });
|
||||||
|
if (!setting) return undefined;
|
||||||
|
return setting.env_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
async setDbEnvIFNeeded(envName: string, envValue: string): Promise<void> {
|
||||||
|
await this.dbs.Tx(async tx => {
|
||||||
|
const setting = await this.dbs.FindOne<AdminSettings>('AdminSettings', { where: { env_name: envName } }, tx);
|
||||||
|
if (!setting) {
|
||||||
|
await this.dbs.CreateAndSave<AdminSettings>('AdminSettings', { env_name: envName, env_value: envValue }, tx);
|
||||||
|
} else if (setting.env_value !== envValue) {
|
||||||
|
setting.env_value = envValue;
|
||||||
|
await this.dbs.Update<AdminSettings>('AdminSettings', setting.serial_id, setting, tx);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,10 +1,7 @@
|
||||||
import fs from 'fs'
|
|
||||||
import path from 'path';
|
|
||||||
import { config as loadEnvFile } from 'dotenv'
|
|
||||||
import { getLogger } from "../helpers/logger.js"
|
import { getLogger } from "../helpers/logger.js"
|
||||||
import NewWizardServer from "../../../proto/wizard_service/autogenerated/ts/express_server.js"
|
import NewWizardServer from "../../../proto/wizard_service/autogenerated/ts/express_server.js"
|
||||||
import * as WizardTypes from "../../../proto/wizard_service/autogenerated/ts/types.js"
|
import * as WizardTypes from "../../../proto/wizard_service/autogenerated/ts/types.js"
|
||||||
import { MainSettings } from "../main/settings.js"
|
import SettingsManager from "../main/settingsManager.js"
|
||||||
import Storage from '../storage/index.js'
|
import Storage from '../storage/index.js'
|
||||||
import { Unlocker } from "../main/unlocker.js"
|
import { Unlocker } from "../main/unlocker.js"
|
||||||
import { AdminManager } from '../main/adminManager.js';
|
import { AdminManager } from '../main/adminManager.js';
|
||||||
|
|
@ -17,16 +14,15 @@ export type WizardSettings = {
|
||||||
const defaultProviderPub = ""
|
const defaultProviderPub = ""
|
||||||
export class Wizard {
|
export class Wizard {
|
||||||
log = getLogger({ component: "wizard" })
|
log = getLogger({ component: "wizard" })
|
||||||
settings: MainSettings
|
settings: SettingsManager
|
||||||
adminManager: AdminManager
|
adminManager: AdminManager
|
||||||
storage: Storage
|
storage: Storage
|
||||||
configQueue: { res: (reload: boolean) => void }[] = []
|
configQueue: { res: (reload: boolean) => void }[] = []
|
||||||
pendingConfig: WizardSettings | null = null
|
|
||||||
awaitingNprofile: { res: (nprofile: string) => void }[] = []
|
awaitingNprofile: { res: (nprofile: string) => void }[] = []
|
||||||
nprofile = ""
|
nprofile = ""
|
||||||
relays: string[] = []
|
relays: string[] = []
|
||||||
constructor(mainSettings: MainSettings, storage: Storage, adminManager: AdminManager) {
|
constructor(settings: SettingsManager, storage: Storage, adminManager: AdminManager) {
|
||||||
this.settings = mainSettings
|
this.settings = settings
|
||||||
this.adminManager = adminManager
|
this.adminManager = adminManager
|
||||||
this.storage = storage
|
this.storage = storage
|
||||||
this.log('Starting wizard...')
|
this.log('Starting wizard...')
|
||||||
|
|
@ -36,16 +32,16 @@ export class Wizard {
|
||||||
GetAdminConnectInfo: async () => { return this.GetAdminConnectInfo() },
|
GetAdminConnectInfo: async () => { return this.GetAdminConnectInfo() },
|
||||||
GetServiceState: async () => { return this.GetServiceState() }
|
GetServiceState: async () => { return this.GetServiceState() }
|
||||||
}, { GuestAuthGuard: async () => "", metricsCallback: () => { }, staticFiles: 'static' })
|
}, { GuestAuthGuard: async () => "", metricsCallback: () => { }, staticFiles: 'static' })
|
||||||
wizardServer.Listen(mainSettings.servicePort + 1)
|
wizardServer.Listen(settings.getSettings().serviceSettings.servicePort + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
GetServiceState = async (): Promise<WizardTypes.ServiceStateResponse> => {
|
GetServiceState = async (): Promise<WizardTypes.ServiceStateResponse> => {
|
||||||
try {
|
try {
|
||||||
const apps = await this.storage.applicationStorage.GetApplications()
|
const apps = await this.storage.applicationStorage.GetApplications()
|
||||||
const appNamesList = apps.map(app => app.name).join(', ')
|
const appNamesList = apps.map(app => app.name).join(', ')
|
||||||
const relays = this.settings.nostrRelaySettings ? this.settings.nostrRelaySettings.relays : [];
|
const relays = this.settings.getSettings().nostrRelaySettings.relays
|
||||||
const relayUrl = (relays && relays.length > 0) ? relays[0] : '';
|
const relayUrl = (relays && relays.length > 0) ? relays[0] : '';
|
||||||
const defaultApp = apps.find(a => a.name === this.settings.defaultAppName) || apps[0]
|
const defaultApp = apps.find(a => a.name === this.settings.getSettings().serviceSettings.defaultAppName) || apps[0]
|
||||||
// Determine LND state and watchdog
|
// Determine LND state and watchdog
|
||||||
let lndState: WizardTypes.LndState = WizardTypes.LndState.OFFLINE
|
let lndState: WizardTypes.LndState = WizardTypes.LndState.OFFLINE
|
||||||
let watchdogOk = false
|
let watchdogOk = false
|
||||||
|
|
@ -60,17 +56,17 @@ export class Wizard {
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
admin_npub: this.adminManager.GetAdminNpub(),
|
admin_npub: this.adminManager.GetAdminNpub(),
|
||||||
http_url: this.settings.serviceUrl,
|
http_url: this.settings.getSettings().serviceSettings.serviceUrl,
|
||||||
lnd_state: lndState,
|
lnd_state: lndState,
|
||||||
nprofile: this.nprofile,
|
nprofile: this.nprofile,
|
||||||
provider_name: defaultApp?.name || appNamesList,
|
provider_name: defaultApp?.name || appNamesList,
|
||||||
relay_connected: this.adminManager.GetNostrConnected(),
|
relay_connected: this.adminManager.GetNostrConnected(),
|
||||||
relays: this.relays,
|
relays: this.relays,
|
||||||
watchdog_ok: watchdogOk,
|
watchdog_ok: watchdogOk,
|
||||||
source_name: defaultApp?.name || this.settings.defaultAppName || appNamesList,
|
source_name: defaultApp?.name || this.settings.getSettings().serviceSettings.defaultAppName || appNamesList,
|
||||||
relay_url: relayUrl,
|
relay_url: relayUrl,
|
||||||
automate_liquidity: this.settings.liquiditySettings.liquidityProviderPub !== 'null',
|
automate_liquidity: this.settings.getSettings().liquiditySettings.liquidityProviderPub !== 'null',
|
||||||
push_backups_to_nostr: this.settings.pushBackupsToNostr,
|
push_backups_to_nostr: this.settings.getSettings().serviceSettings.pushBackupsToNostr,
|
||||||
avatar_url: defaultApp?.avatar_url || '',
|
avatar_url: defaultApp?.avatar_url || '',
|
||||||
app_id: defaultApp?.app_id || ''
|
app_id: defaultApp?.app_id || ''
|
||||||
}
|
}
|
||||||
|
|
@ -99,7 +95,7 @@ export class Wizard {
|
||||||
|
|
||||||
WizardState = async (): Promise<WizardTypes.StateResponse> => {
|
WizardState = async (): Promise<WizardTypes.StateResponse> => {
|
||||||
return {
|
return {
|
||||||
config_sent: this.pendingConfig !== null,
|
config_sent: false,
|
||||||
admin_linked: this.adminManager.GetAdminNpub() !== "",
|
admin_linked: this.adminManager.GetAdminNpub() !== "",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -148,7 +144,7 @@ export class Wizard {
|
||||||
}
|
}
|
||||||
|
|
||||||
Configure = async (): Promise<boolean> => {
|
Configure = async (): Promise<boolean> => {
|
||||||
if (this.IsInitialized() || this.pendingConfig !== null) {
|
if (this.IsInitialized()) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return new Promise((res) => {
|
return new Promise((res) => {
|
||||||
|
|
@ -165,78 +161,43 @@ export class Wizard {
|
||||||
const pendingConfig = { sourceName: req.source_name, relayUrl: req.relay_url, automateLiquidity: req.automate_liquidity, pushBackupsToNostr: req.push_backups_to_nostr }
|
const pendingConfig = { sourceName: req.source_name, relayUrl: req.relay_url, automateLiquidity: req.automate_liquidity, pushBackupsToNostr: req.push_backups_to_nostr }
|
||||||
|
|
||||||
// Persist app name/avatar to DB regardless (idempotent behavior)
|
// Persist app name/avatar to DB regardless (idempotent behavior)
|
||||||
try {
|
await this.settings.updateDisableLiquidityProvider(pendingConfig.automateLiquidity)
|
||||||
const appsList = await this.storage.applicationStorage.GetApplications()
|
await this.settings.updatePushBackupsToNostr(pendingConfig.pushBackupsToNostr)
|
||||||
const defaultNames = ['wallet', 'wallet-test', this.settings.defaultAppName]
|
const oldAppName = this.settings.getSettings().serviceSettings.defaultAppName
|
||||||
const existingDefaultApp = appsList.find(app => defaultNames.includes(app.name)) || appsList[0]
|
const nameUpdated = await this.settings.updateDefaultAppName(pendingConfig.sourceName)
|
||||||
if (existingDefaultApp) {
|
if (nameUpdated) {
|
||||||
await this.storage.applicationStorage.UpdateApplication(existingDefaultApp, { name: req.source_name, avatar_url: (req as any).avatar_url || existingDefaultApp.avatar_url })
|
await this.updateDefaultApp(oldAppName, req.avatar_url)
|
||||||
}
|
}
|
||||||
} catch (e) {
|
const relayUpdated = await this.settings.updateRelayUrl(pendingConfig.relayUrl)
|
||||||
this.log(`Error updating app info: ${(e as Error).message}`)
|
if (relayUpdated && this.IsInitialized()) {
|
||||||
|
await this.adminManager.ResetNostr()
|
||||||
}
|
}
|
||||||
|
|
||||||
// If already initialized, treat as idempotent update for env and exit
|
// If already initialized, treat as idempotent update for env and exit
|
||||||
if (this.IsInitialized()) {
|
if (this.IsInitialized()) {
|
||||||
this.updateEnvFile(pendingConfig)
|
this.log("reloaded wizard config")
|
||||||
|
if (nameUpdated) this.log("name updated")
|
||||||
|
if (relayUpdated) this.log("relay updated")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// First-time configuration flow
|
// First-time configuration flow
|
||||||
if (this.pendingConfig !== null) {
|
|
||||||
throw new Error("already initializing")
|
|
||||||
}
|
|
||||||
this.updateEnvFile(pendingConfig)
|
|
||||||
this.configQueue.forEach(q => q.res(true))
|
this.configQueue.forEach(q => q.res(true))
|
||||||
this.configQueue = []
|
this.configQueue = []
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
updateEnvFile = (pendingConfig: WizardSettings) => {
|
updateDefaultApp = async (currentName: string, avatarUrl?: string): Promise<void> => {
|
||||||
let envFileContent: string[] = []
|
const newName = this.settings.getSettings().serviceSettings.defaultAppName
|
||||||
try {
|
try {
|
||||||
envFileContent = fs.readFileSync('.env', 'utf-8').split('\n')
|
const appsList = await this.storage.applicationStorage.GetApplications()
|
||||||
} catch (err: any) {
|
const defaultNames = ['wallet', 'wallet-test', currentName]
|
||||||
if (err.code !== 'ENOENT') {
|
const existingDefaultApp = appsList.find(app => defaultNames.includes(app.name)) || appsList[0]
|
||||||
throw err
|
if (existingDefaultApp) {
|
||||||
|
await this.storage.applicationStorage.UpdateApplication(existingDefaultApp, { name: newName, avatar_url: avatarUrl || existingDefaultApp.avatar_url })
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
this.log(`Error updating app info: ${(e as Error).message}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const toMerge: string[] = []
|
|
||||||
const sourceNameIndex = envFileContent.findIndex(line => line.startsWith('DEFAULT_APP_NAME'))
|
|
||||||
if (sourceNameIndex === -1) {
|
|
||||||
toMerge.push(`DEFAULT_APP_NAME=${pendingConfig.sourceName}`)
|
|
||||||
} else {
|
|
||||||
envFileContent[sourceNameIndex] = `DEFAULT_APP_NAME=${pendingConfig.sourceName}`
|
|
||||||
}
|
|
||||||
const relayUrlIndex = envFileContent.findIndex(line => line.startsWith('RELAY_URL'))
|
|
||||||
if (relayUrlIndex === -1) {
|
|
||||||
toMerge.push(`RELAY_URL=${pendingConfig.relayUrl}`)
|
|
||||||
} else {
|
|
||||||
envFileContent[relayUrlIndex] = `RELAY_URL=${pendingConfig.relayUrl}`
|
|
||||||
}
|
|
||||||
|
|
||||||
const automateLiquidityIndex = envFileContent.findIndex(line => line.startsWith('LIQUIDITY_PROVIDER_PUB'))
|
|
||||||
if (pendingConfig.automateLiquidity) {
|
|
||||||
if (automateLiquidityIndex !== -1) {
|
|
||||||
envFileContent.splice(automateLiquidityIndex, 1)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (automateLiquidityIndex === -1) {
|
|
||||||
toMerge.push(`LIQUIDITY_PROVIDER_PUB=null`)
|
|
||||||
} else {
|
|
||||||
envFileContent[automateLiquidityIndex] = `LIQUIDITY_PROVIDER_PUB=null`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const pushBackupsToNostrIndex = envFileContent.findIndex(line => line.startsWith('PUSH_BACKUPS_TO_NOSTR'))
|
|
||||||
if (pushBackupsToNostrIndex === -1) {
|
|
||||||
toMerge.push(`PUSH_BACKUPS_TO_NOSTR=${pendingConfig.pushBackupsToNostr ? 'true' : 'false'}`)
|
|
||||||
} else {
|
|
||||||
envFileContent[pushBackupsToNostrIndex] = `PUSH_BACKUPS_TO_NOSTR=${pendingConfig.pushBackupsToNostr ? 'true' : 'false'}`
|
|
||||||
}
|
|
||||||
const merged = [...envFileContent, ...toMerge].join('\n')
|
|
||||||
fs.writeFileSync('.env', merged)
|
|
||||||
loadEnvFile()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import BitcoinCore from 'bitcoin-core';
|
import BitcoinCore from 'bitcoin-core';
|
||||||
import { TestSettings } from '../services/main/settings';
|
import { BitcoinCoreSettings } from '../services/main/settings';
|
||||||
export class BitcoinCoreWrapper {
|
export class BitcoinCoreWrapper {
|
||||||
core: BitcoinCore
|
core: BitcoinCore
|
||||||
addr: { address: string }
|
addr: { address: string }
|
||||||
constructor(settings: TestSettings) {
|
constructor(settings: BitcoinCoreSettings) {
|
||||||
this.core = new BitcoinCore({
|
this.core = new BitcoinCore({
|
||||||
//network: 'regtest',
|
//network: 'regtest',
|
||||||
host: '127.0.0.1',
|
host: '127.0.0.1',
|
||||||
port: `${settings.bitcoinCoreSettings.port}`,
|
port: `${settings.port}`,
|
||||||
username: settings.bitcoinCoreSettings.user,
|
username: settings.user,
|
||||||
password: settings.bitcoinCoreSettings.pass,
|
password: settings.pass,
|
||||||
// use a long timeout due to the time it takes to mine a lot of blocks
|
// use a long timeout due to the time it takes to mine a lot of blocks
|
||||||
timeout: 5 * 60 * 1000,
|
timeout: 5 * 60 * 1000,
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,31 @@
|
||||||
import { LoadTestSettingsFromEnv } from "../services/main/settings.js"
|
import {
|
||||||
|
LiquiditySettings, LoadBitcoinCoreSettingsFromEnv, LoadLndNodeSettingsFromEnv,
|
||||||
|
LoadLndSettingsFromEnv, LoadSecondLndSettingsFromEnv
|
||||||
|
} from "../services/main/settings.js"
|
||||||
|
import { GetTestStorageSettings } from "../services/storage/index.js"
|
||||||
import { BitcoinCoreWrapper } from "./bitcoinCore.js"
|
import { BitcoinCoreWrapper } from "./bitcoinCore.js"
|
||||||
import LND from '../services/lnd/lnd.js'
|
import LND from '../services/lnd/lnd.js'
|
||||||
import { LiquidityProvider } from "../services/main/liquidityProvider.js"
|
import { LiquidityProvider } from "../services/main/liquidityProvider.js"
|
||||||
import { Utils } from "../services/helpers/utilsWrapper.js"
|
import { Utils } from "../services/helpers/utilsWrapper.js"
|
||||||
|
import { LoadStorageSettingsFromEnv } from "../services/storage/index.js"
|
||||||
|
|
||||||
export type ChainTools = {
|
export type ChainTools = {
|
||||||
mine: (amount: number) => Promise<void>
|
mine: (amount: number) => Promise<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const setupNetwork = async (): Promise<ChainTools> => {
|
export const setupNetwork = async (): Promise<ChainTools> => {
|
||||||
const settings = LoadTestSettingsFromEnv()
|
const storageSettings = GetTestStorageSettings(LoadStorageSettingsFromEnv())
|
||||||
const core = new BitcoinCoreWrapper(settings)
|
const setupUtils = new Utils({ dataDir: storageSettings.dataDir, allowResetMetricsStorages: storageSettings.allowResetMetricsStorages })
|
||||||
|
//const settingsManager = new SettingsManager(storageSettings)
|
||||||
|
const core = new BitcoinCoreWrapper(LoadBitcoinCoreSettingsFromEnv())
|
||||||
await core.InitAddress()
|
await core.InitAddress()
|
||||||
await core.Mine(1)
|
await core.Mine(1)
|
||||||
const setupUtils = new Utils({ dataDir: settings.storageSettings.dataDir, allowResetMetricsStorages: settings.allowResetMetricsStorages })
|
const lndSettings = LoadLndSettingsFromEnv({})
|
||||||
const alice = new LND(settings.lndSettings, new LiquidityProvider("", setupUtils, async () => { }, async () => { }), setupUtils, async () => { }, async () => { }, () => { }, () => { }, () => { })
|
const lndNodeSettings = LoadLndNodeSettingsFromEnv({})
|
||||||
const bob = new LND({ ...settings.lndSettings, mainNode: settings.lndSettings.otherNode }, new LiquidityProvider("", setupUtils, async () => { }, async () => { }), setupUtils, async () => { }, async () => { }, () => { }, () => { }, () => { })
|
const secondLndNodeSettings = LoadSecondLndSettingsFromEnv()
|
||||||
|
const liquiditySettings: LiquiditySettings = { disableLiquidityProvider: true, liquidityProviderPub: "", useOnlyLiquidityProvider: false }
|
||||||
|
const alice = new LND(() => ({ lndSettings, lndNodeSettings }), new LiquidityProvider(() => liquiditySettings, setupUtils, async () => { }, async () => { }), setupUtils, async () => { }, async () => { }, () => { }, () => { }, () => { })
|
||||||
|
const bob = new LND(() => ({ lndSettings, lndNodeSettings: secondLndNodeSettings }), new LiquidityProvider(() => liquiditySettings, setupUtils, async () => { }, async () => { }), setupUtils, async () => { }, async () => { }, () => { }, () => { }, () => { })
|
||||||
await tryUntil<void>(async i => {
|
await tryUntil<void>(async i => {
|
||||||
const peers = await alice.ListPeers()
|
const peers = await alice.ListPeers()
|
||||||
if (peers.peers.length > 0) {
|
if (peers.peers.length > 0) {
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,22 @@
|
||||||
import { getLogger } from '../services/helpers/logger.js'
|
import { getLogger } from '../services/helpers/logger.js'
|
||||||
import { initMainHandler } from '../services/main/init.js'
|
import { initMainHandler, initSettings } from '../services/main/init.js'
|
||||||
import { LoadTestSettingsFromEnv } from '../services/main/settings.js'
|
|
||||||
import { SendData } from '../services/nostr/handler.js'
|
import { SendData } from '../services/nostr/handler.js'
|
||||||
import { TestBase, TestUserData } from './testBase.js'
|
import { TestBase, TestUserData } from './testBase.js'
|
||||||
import * as Types from '../../proto/autogenerated/ts/types.js'
|
import * as Types from '../../proto/autogenerated/ts/types.js'
|
||||||
|
import { GetTestStorageSettings, LoadStorageSettingsFromEnv } from '../services/storage/index.js'
|
||||||
|
import { LoadThirdLndSettingsFromEnv } from '../services/main/settings.js'
|
||||||
|
|
||||||
export const initBootstrappedInstance = async (T: TestBase) => {
|
export const initBootstrappedInstance = async (T: TestBase) => {
|
||||||
const settings = LoadTestSettingsFromEnv()
|
const storageSettings = GetTestStorageSettings(LoadStorageSettingsFromEnv())
|
||||||
settings.liquiditySettings.useOnlyLiquidityProvider = true
|
const settingsManager = await initSettings(getLogger({ component: "bootstrapped" }), storageSettings)
|
||||||
settings.liquiditySettings.liquidityProviderPub = T.app.publicKey
|
const thirdNodeSettings = LoadThirdLndSettingsFromEnv()
|
||||||
settings.lndSettings.mainNode = settings.lndSettings.thirdNode
|
settingsManager.OverrideTestSettings(s => {
|
||||||
const initialized = await initMainHandler(getLogger({ component: "bootstrapped" }), settings)
|
s.liquiditySettings.useOnlyLiquidityProvider = true
|
||||||
|
s.liquiditySettings.liquidityProviderPub = T.app.publicKey
|
||||||
|
s.lndNodeSettings = thirdNodeSettings
|
||||||
|
return s
|
||||||
|
})
|
||||||
|
const initialized = await initMainHandler(getLogger({ component: "bootstrapped" }), settingsManager)
|
||||||
if (!initialized) {
|
if (!initialized) {
|
||||||
throw new Error("failed to initialize bootstrapped main handler")
|
throw new Error("failed to initialize bootstrapped main handler")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
import 'dotenv/config' // TODO - test env
|
import 'dotenv/config' // TODO - test env
|
||||||
import chai from 'chai'
|
import chai from 'chai'
|
||||||
import { AppData, initMainHandler } from '../services/main/init.js'
|
import { AppData, initMainHandler, initSettings } from '../services/main/init.js'
|
||||||
import Main from '../services/main/index.js'
|
import Main from '../services/main/index.js'
|
||||||
import Storage from '../services/storage/index.js'
|
import Storage, { GetTestStorageSettings, LoadStorageSettingsFromEnv } from '../services/storage/index.js'
|
||||||
import { User } from '../services/storage/entity/User.js'
|
import { User } from '../services/storage/entity/User.js'
|
||||||
import { GetTestStorageSettings, LoadMainSettingsFromEnv, LoadTestSettingsFromEnv, MainSettings } from '../services/main/settings.js'
|
|
||||||
import chaiString from 'chai-string'
|
import chaiString from 'chai-string'
|
||||||
import { defaultInvoiceExpiry } from '../services/storage/paymentStorage.js'
|
import { defaultInvoiceExpiry } from '../services/storage/paymentStorage.js'
|
||||||
import SanityChecker from '../services/main/sanityChecker.js'
|
import SanityChecker from '../services/main/sanityChecker.js'
|
||||||
|
|
@ -15,6 +14,7 @@ import { Utils } from '../services/helpers/utilsWrapper.js'
|
||||||
import { AdminManager } from '../services/main/adminManager.js'
|
import { AdminManager } from '../services/main/adminManager.js'
|
||||||
import { TlvStorageFactory } from '../services/storage/tlv/tlvFilesStorageFactory.js'
|
import { TlvStorageFactory } from '../services/storage/tlv/tlvFilesStorageFactory.js'
|
||||||
import { ChainTools } from './networkSetup.js'
|
import { ChainTools } from './networkSetup.js'
|
||||||
|
import { LiquiditySettings, LoadLndSettingsFromEnv, LoadSecondLndSettingsFromEnv, LoadThirdLndSettingsFromEnv } from '../services/main/settings.js'
|
||||||
chai.use(chaiString)
|
chai.use(chaiString)
|
||||||
export const expect = chai.expect
|
export const expect = chai.expect
|
||||||
export type Describe = (message: string, failure?: boolean) => void
|
export type Describe = (message: string, failure?: boolean) => void
|
||||||
|
|
@ -45,7 +45,7 @@ export type StorageTestBase = {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const setupStorageTest = async (d: Describe): Promise<StorageTestBase> => {
|
export const setupStorageTest = async (d: Describe): Promise<StorageTestBase> => {
|
||||||
const settings = GetTestStorageSettings()
|
const settings = GetTestStorageSettings(LoadStorageSettingsFromEnv())
|
||||||
const utils = new Utils({ dataDir: settings.dataDir, allowResetMetricsStorages: true })
|
const utils = new Utils({ dataDir: settings.dataDir, allowResetMetricsStorages: true })
|
||||||
const storageManager = new Storage(settings, utils)
|
const storageManager = new Storage(settings, utils)
|
||||||
await storageManager.Connect(console.log)
|
await storageManager.Connect(console.log)
|
||||||
|
|
@ -61,8 +61,15 @@ export const teardownStorageTest = async (T: StorageTestBase) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SetupTest = async (d: Describe, chainTools: ChainTools): Promise<TestBase> => {
|
export const SetupTest = async (d: Describe, chainTools: ChainTools): Promise<TestBase> => {
|
||||||
const settings = LoadTestSettingsFromEnv()
|
const storageSettings = GetTestStorageSettings(LoadStorageSettingsFromEnv())
|
||||||
const initialized = await initMainHandler(getLogger({ component: "mainForTest" }), settings)
|
const settingsManager = await initSettings(getLogger({ component: "mainForTest" }), storageSettings)
|
||||||
|
settingsManager.OverrideTestSettings(s => {
|
||||||
|
s.liquiditySettings.disableLiquidityProvider = true
|
||||||
|
s.liquiditySettings.liquidityProviderPub = ""
|
||||||
|
s.liquiditySettings.useOnlyLiquidityProvider = false
|
||||||
|
return s
|
||||||
|
})
|
||||||
|
const initialized = await initMainHandler(getLogger({ component: "mainForTest" }), settingsManager)
|
||||||
if (!initialized) {
|
if (!initialized) {
|
||||||
throw new Error("failed to initialize main handler")
|
throw new Error("failed to initialize main handler")
|
||||||
}
|
}
|
||||||
|
|
@ -73,16 +80,19 @@ export const SetupTest = async (d: Describe, chainTools: ChainTools): Promise<Te
|
||||||
const user1 = { userId: u1.info.userId, appUserIdentifier: u1.identifier, appId: app.appId }
|
const user1 = { userId: u1.info.userId, appUserIdentifier: u1.identifier, appId: app.appId }
|
||||||
const user2 = { userId: u2.info.userId, appUserIdentifier: u2.identifier, appId: app.appId }
|
const user2 = { userId: u2.info.userId, appUserIdentifier: u2.identifier, appId: app.appId }
|
||||||
|
|
||||||
const extermnalUtils = new Utils({ dataDir: settings.storageSettings.dataDir, allowResetMetricsStorages: settings.allowResetMetricsStorages })
|
const extermnalUtils = new Utils({ dataDir: storageSettings.dataDir, allowResetMetricsStorages: storageSettings.allowResetMetricsStorages })
|
||||||
/* const externalAccessToMainLnd = new LND(settings.lndSettings, new LiquidityProvider("", extermnalUtils, async () => { }, async () => { }), extermnalUtils, async () => { }, async () => { }, () => { }, () => { })
|
/* const externalAccessToMainLnd = new LND(settings.lndSettings, new LiquidityProvider("", extermnalUtils, async () => { }, async () => { }), extermnalUtils, async () => { }, async () => { }, () => { }, () => { })
|
||||||
await externalAccessToMainLnd.Warmup() */
|
await externalAccessToMainLnd.Warmup() */
|
||||||
|
const liquiditySettings: LiquiditySettings = { disableLiquidityProvider: true, liquidityProviderPub: "", useOnlyLiquidityProvider: false }
|
||||||
const otherLndSetting = { ...settings.lndSettings, mainNode: settings.lndSettings.otherNode }
|
const lndSettings = LoadLndSettingsFromEnv({})
|
||||||
const externalAccessToOtherLnd = new LND(otherLndSetting, new LiquidityProvider("", extermnalUtils, async () => { }, async () => { }), extermnalUtils, async () => { }, async () => { }, () => { }, () => { }, () => { })
|
const secondLndNodeSettings = LoadSecondLndSettingsFromEnv()
|
||||||
|
const otherLndSetting = () => ({ lndSettings, lndNodeSettings: secondLndNodeSettings })
|
||||||
|
const externalAccessToOtherLnd = new LND(otherLndSetting, new LiquidityProvider(() => liquiditySettings, extermnalUtils, async () => { }, async () => { }), extermnalUtils, async () => { }, async () => { }, () => { }, () => { }, () => { })
|
||||||
await externalAccessToOtherLnd.Warmup()
|
await externalAccessToOtherLnd.Warmup()
|
||||||
|
|
||||||
const thirdLndSetting = { ...settings.lndSettings, mainNode: settings.lndSettings.thirdNode }
|
const thirdLndNodeSettings = LoadThirdLndSettingsFromEnv()
|
||||||
const externalAccessToThirdLnd = new LND(thirdLndSetting, new LiquidityProvider("", extermnalUtils, async () => { }, async () => { }), extermnalUtils, async () => { }, async () => { }, () => { }, () => { }, () => { })
|
const thirdLndSetting = () => ({ lndSettings, lndNodeSettings: thirdLndNodeSettings })
|
||||||
|
const externalAccessToThirdLnd = new LND(thirdLndSetting, new LiquidityProvider(() => liquiditySettings, extermnalUtils, async () => { }, async () => { }), extermnalUtils, async () => { }, async () => { }, () => { }, () => { }, () => { })
|
||||||
await externalAccessToThirdLnd.Warmup()
|
await externalAccessToThirdLnd.Warmup()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue