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 { AppUserDevice } from "./build/src/services/storage/entity/AppUserDevice.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 { 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 { 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 { 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({
|
||||
type: "better-sqlite3",
|
||||
|
|
@ -45,10 +49,10 @@ export default new DataSource({
|
|||
migrations: [Initial1703170309875, LspOrder1718387847693, LiquidityProvider1719335699480, LndNodeInfo1720187506189, CreateInviteTokenTable1721751414878,
|
||||
PaymentIndex1721760297610, DebitAccess1726496225078, DebitAccessFixes1726685229264, DebitToPub1727105758354, UserCbUrl1727112281043,
|
||||
UserOffer1733502626042, ManagementGrant1751307732346, InvoiceCallbackUrls1752425992291, OldSomethingLeftover1753106599604, UserReceivingInvoiceIdx1753109184611,
|
||||
AppUserDevice1753285173175],
|
||||
AppUserDevice1753285173175, UserAccess1759426050669, AddBlindToUserOffer1760000000000, ApplicationAvatarUrl1761000001000],
|
||||
entities: [User, UserReceivingInvoice, UserReceivingAddress, AddressReceivingTransaction, UserInvoicePayment, UserTransactionPayment,
|
||||
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,
|
||||
})
|
||||
//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
|
||||
# To disable this feature entirely overwrite the env with "null"
|
||||
#LIQUIDITY_PROVIDER_PUB=null
|
||||
#DISABLE_LIQUIDITY_PROVIDER=false
|
||||
|
||||
#DB
|
||||
#DATABASE_FILE=db.sqlite
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ const serverOptions = (mainHandler: Main): ServerOptions => {
|
|||
UserAuthGuard: async (authHeader) => { return mainHandler.appUserManager.DecodeUserToken(stripBearer(authHeader)) },
|
||||
GuestWithPubAuthGuard: async (_) => { throw new Error("Nostr only route") },
|
||||
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,
|
||||
logMethod: 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 nostrMiddleware from './nostrMiddleware.js'
|
||||
import { getLogger } from './services/helpers/logger.js';
|
||||
import { initMainHandler } from './services/main/init.js';
|
||||
import { LoadMainSettingsFromEnv } from './services/main/settings.js';
|
||||
import { initMainHandler, initSettings } from './services/main/init.js';
|
||||
import { nip19 } from 'nostr-tools'
|
||||
import { LoadStorageSettingsFromEnv } from './services/storage/index.js';
|
||||
//@ts-ignore
|
||||
const { nprofileEncode } = nip19
|
||||
|
||||
const start = async () => {
|
||||
const log = getLogger({})
|
||||
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) {
|
||||
log("manual process ended")
|
||||
return
|
||||
|
|
@ -21,7 +22,7 @@ const start = async () => {
|
|||
|
||||
const { apps, mainHandler, liquidityProviderInfo, wizard, adminManager } = keepOn
|
||||
const serverMethods = GetServerMethods(mainHandler)
|
||||
const nostrSettings = mainSettings.nostrRelaySettings
|
||||
const nostrSettings = settingsManager.getSettings().nostrRelaySettings
|
||||
log("initializing nostr middleware")
|
||||
const { Send } = nostrMiddleware(serverMethods, mainHandler,
|
||||
{ ...nostrSettings, apps, clients: [liquidityProviderInfo] },
|
||||
|
|
@ -36,6 +37,6 @@ const start = async () => {
|
|||
}
|
||||
adminManager.setAppNprofile(appNprofile)
|
||||
const Server = NewServer(serverMethods, serverOptions(mainHandler))
|
||||
Server.Listen(mainSettings.servicePort)
|
||||
Server.Listen(settingsManager.getSettings().serviceSettings.servicePort)
|
||||
}
|
||||
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 nostrMiddleware from './nostrMiddleware.js'
|
||||
import { getLogger } from './services/helpers/logger.js';
|
||||
import { initMainHandler } from './services/main/init.js';
|
||||
import { LoadMainSettingsFromEnv } from './services/main/settings.js';
|
||||
import { initMainHandler, initSettings } from './services/main/init.js';
|
||||
import { nip19 } from 'nostr-tools'
|
||||
import { LoadStorageSettingsFromEnv } from './services/storage/index.js';
|
||||
//@ts-ignore
|
||||
const { nprofileEncode } = nip19
|
||||
|
||||
|
||||
const start = async () => {
|
||||
const log = getLogger({})
|
||||
const mainSettings = LoadMainSettingsFromEnv()
|
||||
const keepOn = await initMainHandler(log, mainSettings)
|
||||
//const mainSettings = LoadMainSettingsFromEnv()
|
||||
const storageSettings = LoadStorageSettingsFromEnv()
|
||||
const settingsManager = await initSettings(log, storageSettings)
|
||||
const keepOn = await initMainHandler(log, settingsManager)
|
||||
if (!keepOn) {
|
||||
log("manual process ended")
|
||||
return
|
||||
|
|
@ -22,22 +25,25 @@ const start = async () => {
|
|||
const { apps, mainHandler, liquidityProviderInfo, wizard, adminManager } = keepOn
|
||||
const serverMethods = GetServerMethods(mainHandler)
|
||||
log("initializing nostr middleware")
|
||||
const { Send, Stop, Ping } = nostrMiddleware(serverMethods, mainHandler,
|
||||
{ ...mainSettings.nostrRelaySettings, apps, clients: [liquidityProviderInfo] },
|
||||
const relays = mainHandler.settings.getSettings().nostrRelaySettings.relays
|
||||
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)
|
||||
)
|
||||
exitHandler(() => { Stop(); mainHandler.Stop() })
|
||||
log("starting server")
|
||||
mainHandler.attachNostrSend(Send)
|
||||
mainHandler.attachNostrProcessPing(Ping)
|
||||
mainHandler.attachNostrReset(Reset)
|
||||
mainHandler.StartBeacons()
|
||||
const appNprofile = nprofileEncode({ pubkey: liquidityProviderInfo.publicKey, relays: mainSettings.nostrRelaySettings.relays })
|
||||
const appNprofile = nprofileEncode({ pubkey: liquidityProviderInfo.publicKey, relays })
|
||||
if (wizard) {
|
||||
wizard.AddConnectInfo(appNprofile, mainSettings.nostrRelaySettings.relays)
|
||||
wizard.AddConnectInfo(appNprofile, relays)
|
||||
}
|
||||
adminManager.setAppNprofile(appNprofile)
|
||||
const Server = NewServer(serverMethods, serverOptions(mainHandler))
|
||||
Server.Listen(mainSettings.servicePort)
|
||||
Server.Listen(mainHandler.settings.getSettings().serviceSettings.servicePort)
|
||||
}
|
||||
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 { ERROR, getLogger } from "./services/helpers/logger.js";
|
||||
import { NdebitData, NofferData, NmanageRequest } from "@shocknet/clink-sdk";
|
||||
|
||||
export default (serverMethods: Types.ServerMethods, mainHandler: Main, nostrSettings: NostrSettings, onClientEvent: (e: { requestId: string }, fromPub: string) => void): { Stop: () => void, Send: NostrSend, Ping: () => Promise<void> } => {
|
||||
type ExportedCalls = { Stop: () => void, Send: NostrSend, Ping: () => Promise<void>, Reset: (settings: NostrSettings) => 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 nostrTransport = NewNostrTransport(serverMethods, {
|
||||
NostrUserAuthGuard: async (appId, pub) => {
|
||||
|
|
@ -29,7 +30,7 @@ export default (serverMethods: Types.ServerMethods, mainHandler: Main, nostrSett
|
|||
log("operator access from", pub)
|
||||
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) => {
|
||||
if (!pub || !appId) {
|
||||
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
|
||||
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
|
||||
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 { TlvStorageFactory } from "../storage/tlv/tlvFilesStorageFactory.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 { PayInvoiceReq } from './payInvoiceReq.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 { HtlcEvent_EventType } from '../../../proto/lnd/router.js';
|
||||
import { LiquidityProvider, LiquidityRequest } from '../main/liquidityProvider.js';
|
||||
import { Utils } from '../helpers/utilsWrapper.js';
|
||||
import { TxPointSettings } from '../storage/tlv/stateBundler.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 deadLndRetrySeconds = 5
|
||||
type TxActionOptions = { useProvider: boolean, from: 'user' | 'system' }
|
||||
type NodeSettingsOverride = {
|
||||
lndAddr: string
|
||||
lndCertPath: string
|
||||
lndMacaroonPath: string
|
||||
}
|
||||
export default class {
|
||||
lightning: LightningClient
|
||||
invoices: InvoicesClient
|
||||
router: RouterClient
|
||||
chainNotifier: ChainNotifierClient
|
||||
walletKit: WalletKitClient
|
||||
settings: LndSettings
|
||||
getSettings: () => { lndSettings: LndSettings, lndNodeSettings: LndNodeSettings }
|
||||
ready = false
|
||||
latestKnownBlockHeigh = 0
|
||||
latestKnownSettleIndex = 0
|
||||
|
|
@ -43,15 +51,15 @@ export default class {
|
|||
outgoingOpsLocked = false
|
||||
liquidProvider: LiquidityProvider
|
||||
utils: Utils
|
||||
constructor(settings: LndSettings, liquidProvider: LiquidityProvider, utils: Utils, addressPaidCb: AddressPaidCb, invoicePaidCb: InvoicePaidCb, newBlockCb: NewBlockCb, htlcCb: HtlcCb, channelEventCb: ChannelEventCb) {
|
||||
this.settings = settings
|
||||
constructor(getSettings: () => { lndSettings: LndSettings, lndNodeSettings: LndNodeSettings }, liquidProvider: LiquidityProvider, utils: Utils, addressPaidCb: AddressPaidCb, invoicePaidCb: InvoicePaidCb, newBlockCb: NewBlockCb, htlcCb: HtlcCb, channelEventCb: ChannelEventCb) {
|
||||
this.getSettings = getSettings
|
||||
this.utils = utils
|
||||
this.addressPaidCb = addressPaidCb
|
||||
this.invoicePaidCb = invoicePaidCb
|
||||
this.newBlockCb = newBlockCb
|
||||
this.htlcCb = htlcCb
|
||||
this.channelEventCb = channelEventCb
|
||||
const { lndAddr, lndCertPath, lndMacaroonPath } = this.settings.mainNode
|
||||
const { lndAddr, lndCertPath, lndMacaroonPath } = this.getSettings().lndNodeSettings
|
||||
const lndCert = fs.readFileSync(lndCertPath);
|
||||
const macaroon = fs.readFileSync(lndMacaroonPath).toString('hex');
|
||||
const sslCreds = credentials.createSsl(lndCert);
|
||||
|
|
@ -311,11 +319,11 @@ export default class {
|
|||
}
|
||||
|
||||
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 {
|
||||
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 }> {
|
||||
|
|
|
|||
|
|
@ -3,24 +3,8 @@ import { LiquidityProvider } from "../main/liquidityProvider.js"
|
|||
import { getLogger, PubLogger } from '../helpers/logger.js'
|
||||
import LND from "./lnd.js"
|
||||
import { AddressType } from "../../../proto/autogenerated/ts/types.js"
|
||||
import { EnvCanBeInteger } from "../helpers/envParser.js"
|
||||
export type LSPSettings = {
|
||||
olympusServiceUrl: string
|
||||
voltageServiceUrl: string
|
||||
flashsatsServiceUrl: string
|
||||
channelThreshold: number
|
||||
maxRelativeFee: number
|
||||
}
|
||||
import SettingsManager from "../main/settingsManager.js"
|
||||
|
||||
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 = {
|
||||
"lsp_balance_sat": string,
|
||||
"client_balance_sat": string,
|
||||
|
|
@ -50,11 +34,11 @@ type OrderResponse = {
|
|||
}
|
||||
|
||||
class LSP {
|
||||
settings: LSPSettings
|
||||
settings: SettingsManager
|
||||
liquidityProvider: LiquidityProvider
|
||||
lnd: LND
|
||||
log: PubLogger
|
||||
constructor(serviceName: string, settings: LSPSettings, lnd: LND, liquidityProvider: LiquidityProvider) {
|
||||
constructor(serviceName: string, settings: SettingsManager, lnd: LND, liquidityProvider: LiquidityProvider) {
|
||||
this.settings = settings
|
||||
this.lnd = lnd
|
||||
this.liquidityProvider = liquidityProvider
|
||||
|
|
@ -71,12 +55,15 @@ class 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)
|
||||
}
|
||||
|
||||
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")
|
||||
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")
|
||||
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 chanExpiryBlocks = serviceInfo.options.max_channel_expiry_blocks
|
||||
const order = await this.createOrder({ nodeUri: myUri, lspBalance, clientBalance: "0", chanExpiryBlocks })
|
||||
|
|
@ -109,8 +96,8 @@ export class FlashsatsLSP extends LSP {
|
|||
return null
|
||||
}
|
||||
const relativeFee = +order.payment.fee_total_sat / channelSize
|
||||
if (relativeFee > this.settings.maxRelativeFee) {
|
||||
this.log("invoice relative fee of", relativeFee, "exceeds max relative fee of", this.settings.maxRelativeFee)
|
||||
if (relativeFee > maxRelativeFee) {
|
||||
this.log("invoice relative fee of", relativeFee, "exceeds max relative fee of", maxRelativeFee)
|
||||
return null
|
||||
}
|
||||
const res = await this.liquidityProvider.PayInvoice(order.payment.bolt11_invoice, decoded.numSatoshis, 'system')
|
||||
|
|
@ -120,7 +107,7 @@ export class FlashsatsLSP extends LSP {
|
|||
|
||||
}
|
||||
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 } }
|
||||
return json
|
||||
}
|
||||
|
|
@ -134,7 +121,7 @@ export class FlashsatsLSP extends LSP {
|
|||
confirms_within_blocks: 6,
|
||||
token: "flashsats"
|
||||
}
|
||||
const res = await fetch(`${this.settings.flashsatsServiceUrl}/channel`, {
|
||||
const res = await fetch(`${this.settings.getSettings().lspSettings.flashsatsServiceUrl}/channel`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(req),
|
||||
headers: { "Content-Type": "application/json" }
|
||||
|
|
@ -145,12 +132,15 @@ export class FlashsatsLSP 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)
|
||||
}
|
||||
|
||||
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")
|
||||
return null
|
||||
}
|
||||
|
|
@ -164,7 +154,7 @@ export class OlympusLSP extends LSP {
|
|||
const lndInfo = await this.lnd.GetInfo()
|
||||
const myPub = lndInfo.identityPubkey
|
||||
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 chanExpiryBlocks = serviceInfo.max_channel_expiry_blocks
|
||||
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
|
||||
}
|
||||
const relativeFee = +order.payment.bolt11.fee_total_sat / channelSize
|
||||
if (relativeFee > this.settings.maxRelativeFee) {
|
||||
this.log("invoice relative fee of", relativeFee, "exceeds max relative fee of", this.settings.maxRelativeFee)
|
||||
if (relativeFee > maxRelativeFee) {
|
||||
this.log("invoice relative fee of", relativeFee, "exceeds max relative fee of", maxRelativeFee)
|
||||
return null
|
||||
}
|
||||
const res = await this.liquidityProvider.PayInvoice(order.payment.bolt11.invoice, decoded.numSatoshis, 'system')
|
||||
|
|
@ -193,7 +183,7 @@ export class OlympusLSP extends LSP {
|
|||
}
|
||||
|
||||
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[] }
|
||||
return json
|
||||
}
|
||||
|
|
@ -209,7 +199,7 @@ export class OlympusLSP extends LSP {
|
|||
funding_confirms_within_blocks: 6,
|
||||
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",
|
||||
body: JSON.stringify(req),
|
||||
headers: { "Content-Type": "application/json" }
|
||||
|
|
@ -219,7 +209,7 @@ export class OlympusLSP extends LSP {
|
|||
}
|
||||
|
||||
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 {}
|
||||
return json
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,5 @@
|
|||
import { Channel, ChannelEventUpdate } from "../../../proto/lnd/lightning"
|
||||
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 = {
|
||||
hash: string
|
||||
|
|
@ -63,3 +47,6 @@ export type PaidInvoice = {
|
|||
paymentPreimage: string
|
||||
providerDst?: string
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,16 +1,11 @@
|
|||
import fs, { watchFile } from "fs";
|
||||
import crypto from 'crypto'
|
||||
import { ERROR, getLogger } from "../helpers/logger.js";
|
||||
import { MainSettings, getDataPath } from "./settings.js";
|
||||
import Storage from "../storage/index.js";
|
||||
import * as Types from '../../../proto/autogenerated/ts/types.js'
|
||||
import LND from "../lnd/lnd.js";
|
||||
import SettingsManager from "./settingsManager.js";
|
||||
export class AdminManager {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
storage: Storage
|
||||
log = getLogger({ component: "adminManager" })
|
||||
adminNpub = ""
|
||||
|
|
@ -23,9 +18,10 @@ export class AdminManager {
|
|||
appNprofile: string
|
||||
lnd: LND
|
||||
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.dataDir = mainSettings.storageSettings.dataDir
|
||||
this.dataDir = settings.getStorageSettings().dataDir
|
||||
this.adminNpubPath = getDataPath(this.dataDir, 'admin.npub')
|
||||
this.adminEnrollTokenPath = getDataPath(this.dataDir, 'admin.enroll')
|
||||
this.adminConnectPath = getDataPath(this.dataDir, 'admin.connect')
|
||||
|
|
@ -39,6 +35,14 @@ export class AdminManager {
|
|||
this.start()
|
||||
}
|
||||
|
||||
attachNostrReset(f: () => Promise<void>) {
|
||||
this.nostrReset = f
|
||||
}
|
||||
|
||||
async ResetNostr() {
|
||||
await this.nostrReset()
|
||||
}
|
||||
|
||||
setLND = (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 * as Types from '../../../proto/autogenerated/ts/types.js'
|
||||
|
||||
import { MainSettings } from './settings.js'
|
||||
import ApplicationManager from './applicationManager.js'
|
||||
import { OfferPriceType, ndebitEncode, nmanageEncode, nofferEncode } from '@shocknet/clink-sdk'
|
||||
import { getLogger } from '../helpers/logger.js'
|
||||
import SettingsManager from './settingsManager.js'
|
||||
export default class {
|
||||
|
||||
storage: Storage
|
||||
settings: MainSettings
|
||||
settings: SettingsManager
|
||||
applicationManager: ApplicationManager
|
||||
log = getLogger({ component: 'AppUserManager' })
|
||||
constructor(storage: Storage, settings: MainSettings, applicationManager: ApplicationManager) {
|
||||
constructor(storage: Storage, settings: SettingsManager, applicationManager: ApplicationManager) {
|
||||
this.storage = storage
|
||||
this.settings = settings
|
||||
this.applicationManager = applicationManager
|
||||
}
|
||||
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 } {
|
||||
|
|
@ -28,7 +28,7 @@ export default class {
|
|||
t = token.substring("Bearer ".length)
|
||||
}
|
||||
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) {
|
||||
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 {
|
||||
if (!this.settings.allowHttpUpgrade) {
|
||||
if (!this.settings.getSettings().serviceSettings.allowHttpUpgrade) {
|
||||
throw new Error("http upgrade not allowed")
|
||||
}
|
||||
return {
|
||||
url: this.settings.serviceUrl,
|
||||
url: this.settings.getSettings().serviceSettings.serviceUrl,
|
||||
token: this.SignUserToken(ctx.user_id, ctx.app_id, ctx.app_user_id)
|
||||
}
|
||||
}
|
||||
|
|
@ -68,20 +68,20 @@ export default class {
|
|||
if (!appUser) {
|
||||
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 {
|
||||
userId: ctx.user_id,
|
||||
balance: user.balance_sats,
|
||||
max_withdrawable: this.applicationManager.paymentManager.GetMaxPayableInvoice(user.balance_sats, true),
|
||||
user_identifier: appUser.identifier,
|
||||
network_max_fee_bps: this.settings.lndSettings.feeRateBps,
|
||||
network_max_fee_fixed: this.settings.lndSettings.feeFixedLimit,
|
||||
service_fee_bps: this.settings.outgoingAppUserInvoiceFeeBps,
|
||||
network_max_fee_bps: this.settings.getSettings().lndSettings.feeRateBps,
|
||||
network_max_fee_fixed: this.settings.getSettings().lndSettings.feeFixedLimit,
|
||||
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] }),
|
||||
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] }),
|
||||
callback_url: appUser.callback_url,
|
||||
bridge_url: this.settings.bridgeUrl
|
||||
bridge_url: this.settings.getSettings().serviceSettings.bridgeUrl
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import jwt from 'jsonwebtoken'
|
||||
import Storage from '../storage/index.js'
|
||||
import * as Types from '../../../proto/autogenerated/ts/types.js'
|
||||
import { MainSettings } from './settings.js'
|
||||
import PaymentManager from './paymentManager.js'
|
||||
import { InboundOptionals, defaultInvoiceExpiry } from '../storage/paymentStorage.js'
|
||||
import { ApplicationUser } from '../storage/entity/ApplicationUser.js'
|
||||
|
|
@ -10,6 +9,7 @@ import crypto from 'crypto'
|
|||
import { Application } from '../storage/entity/Application.js'
|
||||
import { ZapInfo } from '../storage/entity/UserReceivingInvoice.js'
|
||||
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
|
||||
|
||||
type NsecLinkingData = {
|
||||
|
|
@ -19,13 +19,13 @@ type NsecLinkingData = {
|
|||
export default class {
|
||||
|
||||
storage: Storage
|
||||
settings: MainSettings
|
||||
settings: SettingsManager
|
||||
paymentManager: PaymentManager
|
||||
nPubLinkingTokens = new Map<string, NsecLinkingData>();
|
||||
linkingTokenInterval: NodeJS.Timeout | null = null
|
||||
serviceBeaconInterval: NodeJS.Timeout | null = null
|
||||
log: PubLogger
|
||||
constructor(storage: Storage, settings: MainSettings, paymentManager: PaymentManager) {
|
||||
constructor(storage: Storage, settings: SettingsManager, paymentManager: PaymentManager) {
|
||||
this.log = getLogger({ component: "ApplicationManager" })
|
||||
this.storage = storage
|
||||
this.settings = settings
|
||||
|
|
@ -69,7 +69,7 @@ export default class {
|
|||
}
|
||||
}
|
||||
SignAppToken(appId: string): string {
|
||||
return jwt.sign({ appId }, this.settings.jwtSecret);
|
||||
return jwt.sign({ appId }, this.settings.getStorageSettings().jwtSecret);
|
||||
}
|
||||
DecodeAppToken(token?: string): string {
|
||||
if (!token) throw new Error("empty app token provided")
|
||||
|
|
@ -78,7 +78,7 @@ export default class {
|
|||
t = token.substring("Bearer ".length)
|
||||
}
|
||||
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) {
|
||||
throw new Error("the provided token is not an app token")
|
||||
}
|
||||
|
|
@ -150,7 +150,7 @@ export default class {
|
|||
u = user
|
||||
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] })
|
||||
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,
|
||||
max_withdrawable: this.paymentManager.GetMaxPayableInvoice(u.user.balance_sats, true),
|
||||
user_identifier: u.identifier,
|
||||
network_max_fee_bps: this.settings.lndSettings.feeRateBps,
|
||||
network_max_fee_fixed: this.settings.lndSettings.feeFixedLimit,
|
||||
service_fee_bps: this.settings.outgoingAppUserInvoiceFeeBps,
|
||||
network_max_fee_bps: this.settings.getSettings().lndSettings.feeRateBps,
|
||||
network_max_fee_fixed: this.settings.getSettings().lndSettings.feeFixedLimit,
|
||||
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] }),
|
||||
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] }),
|
||||
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)
|
||||
|
|
@ -212,20 +212,20 @@ export default class {
|
|||
const app = await this.storage.applicationStorage.GetApplication(appId)
|
||||
const user = await this.storage.applicationStorage.GetApplicationUser(app, req.user_identifier)
|
||||
const max = this.paymentManager.GetMaxPayableInvoice(user.user.balance_sats, true)
|
||||
const nostrSettings = this.settings.nostrRelaySettings
|
||||
const nostrSettings = this.settings.getSettings().nostrRelaySettings
|
||||
return {
|
||||
max_withdrawable: max, identifier: req.user_identifier, info: {
|
||||
userId: user.user.user_id, balance: user.user.balance_sats,
|
||||
max_withdrawable: this.paymentManager.GetMaxPayableInvoice(user.user.balance_sats, true),
|
||||
user_identifier: user.identifier,
|
||||
network_max_fee_bps: this.settings.lndSettings.feeRateBps,
|
||||
network_max_fee_fixed: this.settings.lndSettings.feeFixedLimit,
|
||||
service_fee_bps: this.settings.outgoingAppUserInvoiceFeeBps,
|
||||
network_max_fee_bps: this.settings.getSettings().lndSettings.feeRateBps,
|
||||
network_max_fee_fixed: this.settings.getSettings().lndSettings.feeFixedLimit,
|
||||
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] }),
|
||||
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] }),
|
||||
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 ApplicationManager from './applicationManager.js'
|
||||
import PaymentManager, { PendingTx } from './paymentManager.js'
|
||||
import { MainSettings } from './settings.js'
|
||||
import LND from "../lnd/lnd.js"
|
||||
import { AddressPaidCb, ChannelEventCb, HtlcCb, InvoicePaidCb, NewBlockCb } from "../lnd/settings.js"
|
||||
import { ERROR, getLogger, PubLogger } from "../helpers/logger.js"
|
||||
|
|
@ -31,7 +30,8 @@ import { ManagementManager } from "./managementManager.js"
|
|||
import { Agent } from "https"
|
||||
import { NotificationsManager } from "./notificationsManager.js"
|
||||
import { ApplicationUser } from '../storage/entity/ApplicationUser.js'
|
||||
|
||||
import SettingsManager from './settingsManager.js'
|
||||
import { NostrSettings } from '../nostr/handler.js'
|
||||
type UserOperationsSub = {
|
||||
id: string
|
||||
newIncomingInvoice: (operation: Types.UserOperation) => void
|
||||
|
|
@ -44,7 +44,7 @@ const appTag = "Lightning.Pub"
|
|||
export default class {
|
||||
storage: Storage
|
||||
lnd: LND
|
||||
settings: MainSettings
|
||||
settings: SettingsManager
|
||||
userOperationsSub: UserOperationsSub | null = null
|
||||
adminManager: AdminManager
|
||||
productManager: ProductManager
|
||||
|
|
@ -65,17 +65,22 @@ export default class {
|
|||
//webRTC: webRTC
|
||||
nostrSend: NostrSend = () => { getLogger({})("nostr send not initialized yet") }
|
||||
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.storage = storage
|
||||
this.adminManager = adminManager
|
||||
this.utils = utils
|
||||
this.unlocker = unlocker
|
||||
const updateProviderBalance = (b: number) => this.storage.liquidityStorage.IncrementTrackedProviderBalance('lnPub', settings.liquiditySettings.liquidityProviderPub, b)
|
||||
this.liquidityProvider = new LiquidityProvider(settings.liquiditySettings.liquidityProviderPub, this.utils, this.invoicePaidCb, updateProviderBalance)
|
||||
const updateProviderBalance = (b: number) => this.storage.liquidityStorage.IncrementTrackedProviderBalance('lnPub', settings.getSettings().liquiditySettings.liquidityProviderPub, b)
|
||||
this.liquidityProvider = new LiquidityProvider(() => this.settings.getSettings().liquiditySettings, this.utils, this.invoicePaidCb, updateProviderBalance)
|
||||
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)
|
||||
this.liquidityManager = new LiquidityManager(this.settings.liquiditySettings, this.storage, this.utils, this.liquidityProvider, this.lnd, this.rugPullTracker)
|
||||
const lndGetSettings = () => ({
|
||||
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.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.offerManager = new OfferManager(this.storage, this.settings, this.lnd, this.applicationManager, this.productManager, this.liquidityManager)
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
@ -99,7 +104,7 @@ export default class {
|
|||
|
||||
StartBeacons() {
|
||||
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
|
||||
}
|
||||
|
||||
attachNostrReset(f: (settings: NostrSettings) => void) {
|
||||
this.nostrReset = f
|
||||
this.adminManager.attachNostrReset(() => this.ResetNostr())
|
||||
}
|
||||
|
||||
async pingSubProcesses() {
|
||||
if (!this.nostrProcessPing) {
|
||||
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) {
|
||||
getLogger({ appName: app.name })("cannot update beacon, public key not set")
|
||||
return
|
||||
|
|
@ -421,6 +431,32 @@ export default class {
|
|||
log({ unsigned: event })
|
||||
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 { LiquidityProvider } from "./liquidityProvider.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 Main from "./index.js"
|
||||
import SanityChecker from "./sanityChecker.js"
|
||||
import { LoadMainSettingsFromEnv, MainSettings } from "./settings.js"
|
||||
import { Utils } from "../helpers/utilsWrapper.js"
|
||||
import { Wizard } from "../wizard/index.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 = {
|
||||
privateKey: string;
|
||||
publicKey: string;
|
||||
appId: string;
|
||||
name: string;
|
||||
}
|
||||
export const initMainHandler = async (log: PubLogger, mainSettings: MainSettings) => {
|
||||
const utils = new Utils({ dataDir: mainSettings.storageSettings.dataDir, allowResetMetricsStorages: mainSettings.allowResetMetricsStorages })
|
||||
const storageManager = new Storage(mainSettings.storageSettings, utils)
|
||||
|
||||
export const initSettings = async (log: PubLogger, storageSettings: StorageSettings): Promise<SettingsManager> => {
|
||||
const utils = new Utils({ dataDir: storageSettings.dataDir, allowResetMetricsStorages: storageSettings.allowResetMetricsStorages })
|
||||
const storageManager = new Storage(storageSettings, utils)
|
||||
await storageManager.Connect(log)
|
||||
/* const manualMigration = await TypeOrmMigrationRunner(log, storageManager, mainSettings.storageSettings.dbSettings, process.argv[2])
|
||||
if (manualMigration) {
|
||||
return
|
||||
} */
|
||||
const unlocker = new Unlocker(mainSettings, storageManager)
|
||||
await unlocker.Unlock()
|
||||
const adminManager = new AdminManager(mainSettings, storageManager)
|
||||
let reloadedSettings = mainSettings
|
||||
let wizard: Wizard | null = null
|
||||
if (mainSettings.wizard) {
|
||||
wizard = new Wizard(mainSettings, storageManager, adminManager)
|
||||
const reload = await wizard.Configure()
|
||||
if (reload) {
|
||||
reloadedSettings = LoadMainSettingsFromEnv()
|
||||
const settingsManager = new SettingsManager(storageManager)
|
||||
await settingsManager.InitSettings()
|
||||
return settingsManager
|
||||
}
|
||||
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()
|
||||
const adminManager = new AdminManager(settingsManager, storageManager)
|
||||
let wizard: Wizard | null = null
|
||||
if (settingsManager.getSettings().serviceSettings.wizard) {
|
||||
wizard = new Wizard(settingsManager, storageManager, adminManager)
|
||||
await wizard.Configure()
|
||||
}
|
||||
|
||||
const mainHandler = new Main(reloadedSettings, storageManager, adminManager, utils, unlocker)
|
||||
const mainHandler = new Main(settingsManager, storageManager, adminManager, utils, unlocker)
|
||||
adminManager.setLND(mainHandler.lnd)
|
||||
await mainHandler.lnd.Warmup()
|
||||
if (!reloadedSettings.skipSanityCheck) {
|
||||
if (!settingsManager.getSettings().serviceSettings.skipSanityCheck) {
|
||||
const sanityChecker = new SanityChecker(storageManager, mainHandler.lnd)
|
||||
await sanityChecker.VerifyEventsLog()
|
||||
}
|
||||
const defaultAppName = settingsManager.getSettings().serviceSettings.defaultAppName
|
||||
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))
|
||||
if (!existingWalletApp) {
|
||||
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)
|
||||
}
|
||||
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 { LiquidityProvider } from "./liquidityProvider.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 { defaultInvoiceExpiry } from "../storage/paymentStorage.js"
|
||||
import { RugPullTracker } from "./rugPullTracker.js"
|
||||
export type LiquiditySettings = {
|
||||
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 }
|
||||
}
|
||||
import SettingsManager from "./settingsManager.js"
|
||||
|
||||
export class LiquidityManager {
|
||||
settings: LiquiditySettings
|
||||
settings: SettingsManager
|
||||
storage: Storage
|
||||
liquidityProvider: LiquidityProvider
|
||||
rugPullTracker: RugPullTracker
|
||||
|
|
@ -32,16 +24,16 @@ export class LiquidityManager {
|
|||
utils: Utils
|
||||
latestDrain: ({ success: true, amt: number } | { success: false, amt: number, attempt: number, at: Date }) = { success: true, amt: 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.storage = storage
|
||||
this.liquidityProvider = liquidityProvider
|
||||
this.lnd = lnd
|
||||
this.rugPullTracker = rugPullTracker
|
||||
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.flashsatsLSP = new FlashsatsLSP(settings.lspSettings, lnd, liquidityProvider)
|
||||
this.flashsatsLSP = new FlashsatsLSP(settings, lnd, liquidityProvider)
|
||||
}
|
||||
|
||||
GetPaidFees = () => {
|
||||
|
|
@ -58,7 +50,8 @@ export class LiquidityManager {
|
|||
}
|
||||
|
||||
beforeInvoiceCreation = async (amount: number): Promise<'lnd' | 'provider'> => {
|
||||
if (this.settings.useOnlyLiquidityProvider) {
|
||||
|
||||
if (this.settings.getSettings().liquiditySettings.useOnlyLiquidityProvider) {
|
||||
return 'provider'
|
||||
}
|
||||
|
||||
|
|
@ -86,7 +79,7 @@ export class LiquidityManager {
|
|||
}
|
||||
|
||||
beforeOutInvoicePayment = async (amount: number): Promise<'lnd' | 'provider'> => {
|
||||
if (this.settings.useOnlyLiquidityProvider) {
|
||||
if (this.settings.getSettings().liquiditySettings.useOnlyLiquidityProvider) {
|
||||
return 'provider'
|
||||
}
|
||||
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 }> => {
|
||||
const threshold = this.settings.lspSettings.channelThreshold
|
||||
const threshold = this.settings.getSettings().lspSettings.channelThreshold
|
||||
if (threshold === 0) {
|
||||
return { shouldOpen: false }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,11 +6,13 @@ import { Utils } from '../helpers/utilsWrapper.js'
|
|||
import { NostrEvent, NostrSend } from '../nostr/handler.js'
|
||||
import { InvoicePaidCb } from '../lnd/settings.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 nostrCallback<T> = { startedAtMillis: number, type: 'single' | 'stream', f: (res: T) => void }
|
||||
export class LiquidityProvider {
|
||||
|
||||
getSettings: () => LiquiditySettings
|
||||
client: ReturnType<typeof newNostrClient>
|
||||
clientCbs: Record<string, nostrCallback<any>> = {}
|
||||
clientId: string = ""
|
||||
|
|
@ -28,12 +30,19 @@ export class LiquidityProvider {
|
|||
pendingPayments: Record<string, number> = {}
|
||||
incrementProviderBalance: (balance: number) => Promise<void>
|
||||
// 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.getSettings = getSettings
|
||||
const pubDestination = getSettings().liquidityProviderPub
|
||||
const disableLiquidityProvider = getSettings().disableLiquidityProvider
|
||||
if (!pubDestination) {
|
||||
this.log("No pub provider to liquidity provider, will not be initialized")
|
||||
return
|
||||
}
|
||||
if (disableLiquidityProvider) {
|
||||
this.log("Liquidity provider is disabled, will not be initialized")
|
||||
return
|
||||
}
|
||||
this.log("connecting to liquidity provider:", pubDestination)
|
||||
this.pubDestination = pubDestination
|
||||
this.invoicePaidCb = invoicePaidCb
|
||||
|
|
@ -59,14 +68,14 @@ export class LiquidityProvider {
|
|||
}
|
||||
|
||||
IsReady = () => {
|
||||
return this.ready
|
||||
return this.ready && !this.getSettings().disableLiquidityProvider
|
||||
}
|
||||
|
||||
AwaitProviderReady = async (): Promise<'inactive' | 'ready'> => {
|
||||
if (!this.pubDestination) {
|
||||
if (!this.pubDestination || this.getSettings().disableLiquidityProvider) {
|
||||
return 'inactive'
|
||||
}
|
||||
if (this.ready) {
|
||||
if (this.IsReady()) {
|
||||
return 'ready'
|
||||
}
|
||||
return new Promise<'ready'>(res => {
|
||||
|
|
@ -119,7 +128,7 @@ export class LiquidityProvider {
|
|||
}
|
||||
|
||||
GetLatestMaxWithdrawable = async () => {
|
||||
if (!this.ready) {
|
||||
if (!this.IsReady()) {
|
||||
return 0
|
||||
}
|
||||
const res = await this.GetUserState()
|
||||
|
|
@ -131,7 +140,7 @@ export class LiquidityProvider {
|
|||
}
|
||||
|
||||
GetLatestBalance = async () => {
|
||||
if (!this.ready) {
|
||||
if (!this.IsReady()) {
|
||||
return 0
|
||||
}
|
||||
const res = await this.GetUserState()
|
||||
|
|
@ -155,7 +164,7 @@ export class LiquidityProvider {
|
|||
}
|
||||
|
||||
CanProviderHandle = async (req: LiquidityRequest) => {
|
||||
if (!this.ready) {
|
||||
if (!this.IsReady()) {
|
||||
return false
|
||||
}
|
||||
const maxW = await this.GetLatestMaxWithdrawable()
|
||||
|
|
@ -167,8 +176,8 @@ export class LiquidityProvider {
|
|||
|
||||
AddInvoice = async (amount: number, memo: string, from: 'user' | 'system', expiry: number) => {
|
||||
try {
|
||||
if (!this.ready) {
|
||||
throw new Error("liquidity provider is not ready yet")
|
||||
if (!this.IsReady()) {
|
||||
throw new Error("liquidity provider is not ready yet or disabled")
|
||||
}
|
||||
const res = await this.client.NewInvoice({ amountSats: amount, memo, expiry })
|
||||
if (res.status === 'ERROR') {
|
||||
|
|
@ -186,8 +195,8 @@ export class LiquidityProvider {
|
|||
|
||||
PayInvoice = async (invoice: string, decodedAmount: number, from: 'user' | 'system') => {
|
||||
try {
|
||||
if (!this.ready) {
|
||||
throw new Error("liquidity provider is not ready yet")
|
||||
if (!this.IsReady()) {
|
||||
throw new Error("liquidity provider is not ready yet or disabled")
|
||||
}
|
||||
const userInfo = await this.GetUserState()
|
||||
if (userInfo.status === 'ERROR') {
|
||||
|
|
@ -211,8 +220,8 @@ export class LiquidityProvider {
|
|||
}
|
||||
|
||||
GetPaymentState = async (invoice: string) => {
|
||||
if (!this.ready) {
|
||||
throw new Error("liquidity provider is not ready yet")
|
||||
if (!this.IsReady()) {
|
||||
throw new Error("liquidity provider is not ready yet or disabled")
|
||||
}
|
||||
const res = await this.client.GetPaymentState({ invoice })
|
||||
if (res.status === 'ERROR') {
|
||||
|
|
@ -223,8 +232,8 @@ export class LiquidityProvider {
|
|||
}
|
||||
|
||||
GetOperations = async () => {
|
||||
if (!this.ready) {
|
||||
throw new Error("liquidity provider is not ready yet")
|
||||
if (!this.IsReady()) {
|
||||
throw new Error("liquidity provider is not ready yet or disabled")
|
||||
}
|
||||
const res = await this.client.GetUserOperations({
|
||||
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 { OfferManager } from "./offerManager.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 { UnsignedEvent } from "nostr-tools";
|
||||
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' }
|
||||
|
||||
export class ManagementManager {
|
||||
private nostrSend: NostrSend;
|
||||
private storage: Storage;
|
||||
private settings: MainSettings;
|
||||
private settings: SettingsManager;
|
||||
private awaitingRequests: Record<string, { request: NmanageRequest, event: NostrEvent }> = {}
|
||||
private logger: PubLogger
|
||||
constructor(storage: Storage, settings: MainSettings) {
|
||||
constructor(storage: Storage, settings: SettingsManager) {
|
||||
this.storage = storage;
|
||||
this.settings = settings;
|
||||
this.logger = getLogger({ component: 'ManagementManager' })
|
||||
|
|
@ -141,7 +141,7 @@ export class ManagementManager {
|
|||
const pointer: OfferPointer = {
|
||||
offer: offer.offer_id,
|
||||
pubkey: appPub,
|
||||
relay: this.settings.nostrRelaySettings.relays[0],
|
||||
relay: this.settings.getSettings().nostrRelaySettings.relays[0],
|
||||
priceType: offer.price_sats > 0 ? OfferPriceType.Fixed : OfferPriceType.Spontaneous,
|
||||
price: offer.price_sats,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
import { PushPair, ShockPush } from "../ShockPush/index.js"
|
||||
import { getLogger, PubLogger } from "../helpers/logger.js"
|
||||
import SettingsManager from "./settingsManager.js"
|
||||
|
||||
export class NotificationsManager {
|
||||
private shockPushBaseUrl: string
|
||||
private settings: SettingsManager
|
||||
private clients: Record<string, ShockPush> = {}
|
||||
private logger: PubLogger
|
||||
constructor(shockPushBaseUrl: string) {
|
||||
this.shockPushBaseUrl = shockPushBaseUrl
|
||||
constructor(settings: SettingsManager) {
|
||||
this.settings = settings
|
||||
this.logger = getLogger({ component: 'notificationsManager' })
|
||||
}
|
||||
|
||||
|
|
@ -15,13 +16,13 @@ export class NotificationsManager {
|
|||
if (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
|
||||
return newClient
|
||||
}
|
||||
|
||||
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")
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import { UnsignedEvent } from 'nostr-tools';
|
|||
import { UserOffer } from '../storage/entity/UserOffer.js';
|
||||
import { LiquidityManager } from "./liquidityManager.js"
|
||||
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 offerStr = offer.offer_id
|
||||
|
|
@ -34,14 +34,14 @@ export class OfferManager {
|
|||
|
||||
|
||||
_nostrSend: NostrSend | null = null
|
||||
settings: MainSettings
|
||||
settings: SettingsManager
|
||||
applicationManager: ApplicationManager
|
||||
productManager: ProductManager
|
||||
storage: Storage
|
||||
lnd: LND
|
||||
liquidityManager: LiquidityManager
|
||||
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.settings = settings
|
||||
this.lnd = lnd
|
||||
|
|
@ -112,7 +112,7 @@ export class OfferManager {
|
|||
if (!offer) {
|
||||
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] })
|
||||
}
|
||||
|
||||
|
|
@ -130,7 +130,7 @@ export class OfferManager {
|
|||
if (toAppend) {
|
||||
offers.push(toAppend)
|
||||
}
|
||||
const nostrSettings = this.settings.nostrRelaySettings
|
||||
const nostrSettings = this.settings.getSettings().nostrRelaySettings
|
||||
return {
|
||||
offers: offers.map(o => mapToOfferConfig(ctx.app_user_id, o, { pubkey: app.npub, relay: nostrSettings.relays[0] }))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import { bech32 } from 'bech32'
|
|||
import crypto from 'crypto'
|
||||
import Storage from '../storage/index.js'
|
||||
import * as Types from '../../../proto/autogenerated/ts/types.js'
|
||||
import { MainSettings } from './settings.js'
|
||||
import { InboundOptionals, defaultInvoiceExpiry } from '../storage/paymentStorage.js'
|
||||
import LND from '../lnd/lnd.js'
|
||||
import { Application } from '../storage/entity/Application.js'
|
||||
|
|
@ -17,6 +16,7 @@ import { Watchdog } from './watchdog.js'
|
|||
import { LiquidityManager } from './liquidityManager.js'
|
||||
import { Utils } from '../helpers/utilsWrapper.js'
|
||||
import { UserInvoicePayment } from '../storage/entity/UserInvoicePayment.js'
|
||||
import SettingsManager from './settingsManager.js'
|
||||
interface UserOperationInfo {
|
||||
serial_id: number
|
||||
paid_amount: number
|
||||
|
|
@ -43,7 +43,7 @@ const confInOne = 1000 * 1000
|
|||
const confInTwo = 100 * 1000 * 1000
|
||||
export default class {
|
||||
storage: Storage
|
||||
settings: MainSettings
|
||||
settings: SettingsManager
|
||||
lnd: LND
|
||||
addressPaidCb: AddressPaidCb
|
||||
invoicePaidCb: InvoicePaidCb
|
||||
|
|
@ -51,13 +51,13 @@ export default class {
|
|||
watchDog: Watchdog
|
||||
liquidityManager: LiquidityManager
|
||||
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.settings = settings
|
||||
this.lnd = lnd
|
||||
this.liquidityManager = liquidityManager
|
||||
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.invoicePaidCb = invoicePaidCb
|
||||
}
|
||||
|
|
@ -163,38 +163,38 @@ export default class {
|
|||
getServiceFee(action: Types.UserOperationType, amount: number, appUser: boolean): number {
|
||||
switch (action) {
|
||||
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:
|
||||
return Math.ceil(this.settings.outgoingTxFee * amount)
|
||||
return Math.ceil(this.settings.getSettings().serviceFeeSettings.outgoingTxFee * amount)
|
||||
case Types.UserOperationType.INCOMING_INVOICE:
|
||||
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:
|
||||
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:
|
||||
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:
|
||||
throw new Error("Unknown service action type")
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
await this.lnd.SetMockInvoiceAsPaid(req.invoice, req.amount)
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
getLogger({})("setting mock balance...")
|
||||
|
|
@ -235,9 +235,9 @@ export default class {
|
|||
GetMaxPayableInvoice(balance: number, appUser: boolean): number {
|
||||
let maxWithinServiceFee = 0
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
|
|
@ -293,7 +293,7 @@ export default class {
|
|||
}
|
||||
|
||||
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")
|
||||
}
|
||||
const existingPendingPayment = await this.storage.paymentStorage.GetPaymentOwner(invoice)
|
||||
|
|
@ -412,14 +412,14 @@ export default class {
|
|||
}
|
||||
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
|
@ -471,7 +471,7 @@ export default class {
|
|||
}
|
||||
|
||||
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> {
|
||||
|
|
@ -493,7 +493,7 @@ export default class {
|
|||
}
|
||||
const { baseUrl, metadata } = opts
|
||||
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()
|
||||
let maxSendable = remote * 1000
|
||||
if (remote === 0 && (await this.liquidityManager.liquidityProvider.IsReady())) {
|
||||
|
|
@ -504,7 +504,7 @@ export default class {
|
|||
callback: `${url}?k1=${payK1.key}`,
|
||||
maxSendable: maxSendable,
|
||||
minSendable: 10000,
|
||||
metadata: metadata ? metadata : defaultLnurlPayMetadata(this.settings.lnurlMetaText),
|
||||
metadata: metadata ? metadata : defaultLnurlPayMetadata(this.settings.getSettings().serviceSettings.lnurlMetaText),
|
||||
allowsNostr: !!linkedApplication.nostr_public_key,
|
||||
nostrPubkey: linkedApplication.nostr_public_key || ""
|
||||
}
|
||||
|
|
@ -525,10 +525,10 @@ export default class {
|
|||
}
|
||||
return {
|
||||
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,
|
||||
minSendable: 10000,
|
||||
metadata: defaultLnurlPayMetadata(this.settings.lnurlMetaText),
|
||||
metadata: defaultLnurlPayMetadata(this.settings.getSettings().serviceSettings.lnurlMetaText),
|
||||
allowsNostr: !!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, {
|
||||
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 })
|
||||
return {
|
||||
pr: invoice.invoice,
|
||||
|
|
@ -620,7 +620,9 @@ export default class {
|
|||
if (!linkedUser) {
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -2,17 +2,17 @@ import Storage from '../storage/index.js'
|
|||
import * as Types from '../../../proto/autogenerated/ts/types.js'
|
||||
|
||||
|
||||
import { MainSettings } from './settings.js'
|
||||
import PaymentManager from './paymentManager.js'
|
||||
import { defaultInvoiceExpiry } from '../storage/paymentStorage.js'
|
||||
import { nofferEncode, OfferPriceType } from '@shocknet/clink-sdk'
|
||||
import SettingsManager from './settingsManager.js'
|
||||
|
||||
export default class {
|
||||
storage: Storage
|
||||
settings: MainSettings
|
||||
settings: SettingsManager
|
||||
paymentManager: PaymentManager
|
||||
|
||||
constructor(storage: Storage, paymentManager: PaymentManager, settings: MainSettings) {
|
||||
constructor(storage: Storage, paymentManager: PaymentManager, settings: SettingsManager) {
|
||||
this.storage = storage
|
||||
this.settings = settings
|
||||
this.paymentManager = paymentManager
|
||||
|
|
|
|||
|
|
@ -1,23 +1,8 @@
|
|||
import { LoadStorageSettingsFromEnv, StorageSettings } from '../storage/index.js'
|
||||
import { LndSettings, NodeSettings } from '../lnd/settings.js'
|
||||
import { LoadWatchdogSettingsFromEnv, WatchdogSettings } from './watchdog.js'
|
||||
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'
|
||||
import { EnvCacher, EnvMustBeNonEmptyString, EnvMustBeInteger, chooseEnv, chooseEnvBool, chooseEnvInt } from '../helpers/envParser.js'
|
||||
import os from 'os'
|
||||
import path from 'path'
|
||||
|
||||
export type MainSettings = {
|
||||
storageSettings: StorageSettings,
|
||||
lndSettings: LndSettings,
|
||||
watchDogSettings: WatchdogSettings,
|
||||
liquiditySettings: LiquiditySettings,
|
||||
nostrRelaySettings: NostrRelaySettings,
|
||||
jwtSecret: string
|
||||
walletPasswordPath: string
|
||||
walletSecretPath: string
|
||||
export type ServiceFeeSettings = {
|
||||
incomingTxFee: number
|
||||
outgoingTxFee: number
|
||||
incomingAppInvoiceFee: number
|
||||
|
|
@ -27,19 +12,57 @@ export type MainSettings = {
|
|||
outgoingAppUserInvoiceFeeBps: number
|
||||
userToUserFee: 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
|
||||
recordPerformance: boolean
|
||||
skipSanityCheck: boolean
|
||||
disableExternalPayments: boolean
|
||||
wizard: boolean
|
||||
bridgeUrl: string,
|
||||
shockPushBaseUrl: string
|
||||
|
||||
serviceUrl: string
|
||||
disableExternalPayments: boolean
|
||||
defaultAppName: string
|
||||
pushBackupsToNostr: boolean
|
||||
lnurlMetaText: string,
|
||||
bridgeUrl: string,
|
||||
allowResetMetricsStorages: 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 = {
|
||||
|
|
@ -48,109 +71,136 @@ export type BitcoinCoreSettings = {
|
|||
pass: string
|
||||
}
|
||||
|
||||
export type TestSettings = MainSettings & { lndSettings: { otherNode: NodeSettings, thirdNode: NodeSettings, fourthNode: NodeSettings }, bitcoinCoreSettings: BitcoinCoreSettings }
|
||||
export const LoadMainSettingsFromEnv = (): MainSettings => {
|
||||
const storageSettings = LoadStorageSettingsFromEnv()
|
||||
const outgoingAppUserInvoiceFeeBps = EnvCanBeInteger("OUTGOING_INVOICE_FEE_USER_BPS", 0)
|
||||
const nostrRelaySettings = LoadNosrtRelaySettingsFromEnv()
|
||||
export type LndNodeSettings = {
|
||||
lndAddr: string // cold setting
|
||||
lndCertPath: string // cold setting
|
||||
lndMacaroonPath: string // cold setting
|
||||
}
|
||||
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 {
|
||||
watchDogSettings: LoadWatchdogSettingsFromEnv(),
|
||||
lndSettings: LoadLndSettingsFromEnv(),
|
||||
storageSettings: storageSettings,
|
||||
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 || ""
|
||||
lndAddr: chooseEnv('LND_ADDRESS', dbEnv, "127.0.0.1:10009", addToDb),
|
||||
lndCertPath: chooseEnv('LND_CERT_PATH', dbEnv, resolveHome("/.lnd/tls.cert"), addToDb),
|
||||
lndMacaroonPath: chooseEnv('LND_MACAROON_PATH', dbEnv, resolveHome("/.lnd/data/chain/bitcoin/mainnet/admin.macaroon"), addToDb),
|
||||
}
|
||||
}
|
||||
|
||||
export const GetTestStorageSettings = (s?: StorageSettings): StorageSettings => {
|
||||
const eventLogPath = `logs/eventLogV3Test${Date.now()}.csv`
|
||||
if (s) {
|
||||
return { dbSettings: { ...s.dbSettings, databaseFile: ":memory:", metricsDatabaseFile: ":memory:" }, eventLogPath, dataDir: "test-data" }
|
||||
}
|
||||
return { dbSettings: { databaseFile: ":memory:", metricsDatabaseFile: ":memory:", migrate: true }, eventLogPath, dataDir: "test-data" }
|
||||
}
|
||||
|
||||
export const LoadTestSettingsFromEnv = (): TestSettings => {
|
||||
|
||||
const settings = LoadMainSettingsFromEnv()
|
||||
export const LoadLndSettingsFromEnv = (dbEnv: Record<string, string | undefined>, addToDb?: EnvCacher): LndSettings => {
|
||||
const feeRateBps: number = chooseEnvInt('OUTBOUND_MAX_FEE_BPS', dbEnv, 60, addToDb)
|
||||
return {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
export type NostrRelaySettings = {
|
||||
relays: string[],
|
||||
maxEventContentLength: number
|
||||
}
|
||||
|
||||
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 {
|
||||
...settings,
|
||||
storageSettings: GetTestStorageSettings(settings.storageSettings),
|
||||
lndSettings: {
|
||||
...settings.lndSettings,
|
||||
otherNode: {
|
||||
lndAddr: EnvMustBeNonEmptyString("LND_OTHER_ADDR"),
|
||||
lndCertPath: EnvMustBeNonEmptyString("LND_OTHER_CERT_PATH"),
|
||||
lndMacaroonPath: EnvMustBeNonEmptyString("LND_OTHER_MACAROON_PATH")
|
||||
},
|
||||
thirdNode: {
|
||||
}
|
||||
}
|
||||
|
||||
export const LoadThirdLndSettingsFromEnv = (): LndNodeSettings => {
|
||||
|
||||
return {
|
||||
lndAddr: EnvMustBeNonEmptyString("LND_THIRD_ADDR"),
|
||||
lndCertPath: EnvMustBeNonEmptyString("LND_THIRD_CERT_PATH"),
|
||||
lndMacaroonPath: EnvMustBeNonEmptyString("LND_THIRD_MACAROON_PATH")
|
||||
},
|
||||
fourthNode: {
|
||||
}
|
||||
}
|
||||
|
||||
export const LoadFourthLndSettingsFromEnv = (): LndNodeSettings => {
|
||||
|
||||
return {
|
||||
lndAddr: EnvMustBeNonEmptyString("LND_FOURTH_ADDR"),
|
||||
lndCertPath: EnvMustBeNonEmptyString("LND_FOURTH_CERT_PATH"),
|
||||
lndMacaroonPath: EnvMustBeNonEmptyString("LND_FOURTH_MACAROON_PATH")
|
||||
},
|
||||
},
|
||||
liquiditySettings: {
|
||||
...settings.liquiditySettings,
|
||||
liquidityProviderPub: "",
|
||||
},
|
||||
skipSanityCheck: true,
|
||||
bitcoinCoreSettings: {
|
||||
}
|
||||
}
|
||||
|
||||
export const LoadBitcoinCoreSettingsFromEnv = (): BitcoinCoreSettings => {
|
||||
return {
|
||||
port: EnvMustBeInteger("BITCOIN_CORE_PORT"),
|
||||
user: EnvMustBeNonEmptyString("BITCOIN_CORE_USER"),
|
||||
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 { getLogger } from '../helpers/logger.js';
|
||||
import { WalletUnlockerClient } from '../../../proto/lnd/walletunlocker.client.js';
|
||||
import { MainSettings } from '../main/settings.js';
|
||||
import { InitWalletReq } from '../lnd/initWalletReq.js';
|
||||
import Storage from '../storage/index.js'
|
||||
import { LightningClient } from '../../../proto/lnd/lightning.client.js';
|
||||
import * as Types from '../../../proto/autogenerated/ts/types.js'
|
||||
import SettingsManager from './settingsManager.js';
|
||||
const DeadLineMetadata = (deadline = 10 * 1000) => ({ deadline: Date.now() + deadline })
|
||||
type EncryptedData = { iv: string, encrypted: string }
|
||||
type Seed = { plaintextSeed: string[], encryptedSeed: EncryptedData }
|
||||
export class Unlocker {
|
||||
|
||||
settings: MainSettings
|
||||
settings: SettingsManager
|
||||
storage: Storage
|
||||
abortController = new AbortController()
|
||||
subbedToBackups = false
|
||||
nodePub: string | null = null
|
||||
log = getLogger({ component: "unlocker" })
|
||||
constructor(settings: MainSettings, storage: Storage) {
|
||||
constructor(settings: SettingsManager, storage: Storage) {
|
||||
this.settings = settings
|
||||
this.storage = storage
|
||||
}
|
||||
|
|
@ -30,8 +30,8 @@ export class Unlocker {
|
|||
}
|
||||
|
||||
getCreds = () => {
|
||||
const macroonPath = this.settings.lndSettings.mainNode.lndMacaroonPath
|
||||
const certPath = this.settings.lndSettings.mainNode.lndCertPath
|
||||
const macroonPath = this.settings.getSettings().lndNodeSettings.lndMacaroonPath
|
||||
const certPath = this.settings.getSettings().lndNodeSettings.lndCertPath
|
||||
let macaroon = ""
|
||||
let lndCert: Buffer
|
||||
try {
|
||||
|
|
@ -96,8 +96,8 @@ export class Unlocker {
|
|||
}
|
||||
|
||||
private waitForLndSync = async (timeoutSeconds: number): Promise<void> => {
|
||||
const lndLogPath = this.settings.lndSettings.lndLogDir;
|
||||
if (this.settings.lndSettings.mockLnd) {
|
||||
const lndLogPath = this.settings.getSettings().lndSettings.lndLogDir;
|
||||
if (this.settings.getSettings().lndSettings.mockLnd) {
|
||||
this.log("MOCK_LND set, skipping header sync wait.");
|
||||
return;
|
||||
}
|
||||
|
|
@ -284,7 +284,7 @@ export class Unlocker {
|
|||
}
|
||||
|
||||
GetWalletSecret = (create: boolean) => {
|
||||
const path = this.settings.walletSecretPath
|
||||
const path = this.settings.getStorageSettings().walletSecretPath
|
||||
let secret = ""
|
||||
try {
|
||||
secret = fs.readFileSync(path, 'utf-8')
|
||||
|
|
@ -300,7 +300,7 @@ export class Unlocker {
|
|||
}
|
||||
|
||||
GetWalletPassword = () => {
|
||||
const path = this.settings.walletPasswordPath
|
||||
const path = this.settings.getStorageSettings().walletPasswordPath
|
||||
let password = Buffer.alloc(0)
|
||||
try {
|
||||
password = fs.readFileSync(path)
|
||||
|
|
@ -339,14 +339,14 @@ export class Unlocker {
|
|||
}
|
||||
|
||||
GetUnlockerClient = (cert: Buffer) => {
|
||||
const host = this.settings.lndSettings.mainNode.lndAddr
|
||||
const host = this.settings.getSettings().lndNodeSettings.lndAddr
|
||||
const channelCredentials = credentials.createSsl(cert)
|
||||
const transport = new GrpcTransport({ host, channelCredentials })
|
||||
const client = new WalletUnlockerClient(transport)
|
||||
return client
|
||||
}
|
||||
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 macaroonCreds = credentials.createFromMetadataGenerator(
|
||||
function (args: any, callback: any) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import { EnvCanBeInteger } from "../helpers/envParser.js";
|
||||
import FunctionQueue from "../helpers/functionQueue.js";
|
||||
import { getLogger } from "../helpers/logger.js";
|
||||
import { Utils } from "../helpers/utilsWrapper.js";
|
||||
|
|
@ -8,14 +7,8 @@ import { ChannelBalance } from "../lnd/settings.js";
|
|||
import Storage from '../storage/index.js'
|
||||
import { LiquidityManager } from "./liquidityManager.js";
|
||||
import { RugPullTracker } from "./rugPullTracker.js";
|
||||
export type WatchdogSettings = {
|
||||
maxDiffSats: number
|
||||
}
|
||||
export const LoadWatchdogSettingsFromEnv = (test = false): WatchdogSettings => {
|
||||
return {
|
||||
maxDiffSats: EnvCanBeInteger("WATCHDOG_MAX_DIFF_SATS")
|
||||
}
|
||||
}
|
||||
import SettingsManager from "./settingsManager.js";
|
||||
|
||||
export class Watchdog {
|
||||
queue: FunctionQueue<void>
|
||||
initialLndBalance: number;
|
||||
|
|
@ -27,7 +20,7 @@ export class Watchdog {
|
|||
lnd: LND;
|
||||
liquidProvider: LiquidityProvider;
|
||||
liquidityManager: LiquidityManager;
|
||||
settings: WatchdogSettings;
|
||||
settings: SettingsManager;
|
||||
storage: Storage;
|
||||
rugPullTracker: RugPullTracker
|
||||
utils: Utils
|
||||
|
|
@ -36,7 +29,7 @@ export class Watchdog {
|
|||
ready = false
|
||||
interval: NodeJS.Timer;
|
||||
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.settings = settings;
|
||||
this.storage = storage;
|
||||
|
|
@ -114,7 +107,7 @@ export class Watchdog {
|
|||
switch (result.type) {
|
||||
case 'mismatch':
|
||||
if (deltaLnd < 0) {
|
||||
if (result.absoluteDiff > this.settings.maxDiffSats) {
|
||||
if (result.absoluteDiff > this.settings.getSettings().watchDogSettings.maxDiffSats) {
|
||||
await this.updateDisruption(true, result.absoluteDiff, lndWithDeltaUsers)
|
||||
return true
|
||||
}
|
||||
|
|
@ -126,7 +119,7 @@ export class Watchdog {
|
|||
break
|
||||
case 'negative':
|
||||
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)
|
||||
return true
|
||||
}
|
||||
|
|
@ -142,7 +135,7 @@ export class Watchdog {
|
|||
case 'positive':
|
||||
if (deltaLnd < deltaUsers) {
|
||||
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)
|
||||
return true
|
||||
}
|
||||
|
|
@ -160,12 +153,13 @@ export class Watchdog {
|
|||
updateDisruption = async (isDisrupted: boolean, absoluteDiff: number, lndWithDeltaUsers: number) => {
|
||||
const tracker = await this.getTracker()
|
||||
this.storage.liquidityStorage.UpdateTrackedProviderBalance('lnd', this.lndPubKey, lndWithDeltaUsers)
|
||||
const maxDiffSats = this.settings.getSettings().watchDogSettings.maxDiffSats
|
||||
if (isDisrupted) {
|
||||
if (tracker.latest_distruption_at_unix === 0) {
|
||||
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 {
|
||||
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 {
|
||||
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 LND from '../lnd/lnd.js'
|
||||
import HtlcTracker from './htlcTracker.js'
|
||||
import { MainSettings } from '../main/settings.js'
|
||||
import { getLogger } from '../helpers/logger.js'
|
||||
import { encodeTLV, usageMetricsToTlv } from '../helpers/tlv.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 { encrypt as encryptV1, decrypt as decryptV1, getSharedSecret as getConversationKeyV1 } from './nip44v1.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 { v2 } = nip44
|
||||
const { encrypt: encryptV2, decrypt: decryptV2, utils } = v2
|
||||
|
|
@ -28,24 +28,6 @@ export type NostrSettings = {
|
|||
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 = {
|
||||
id: string
|
||||
pub: string
|
||||
|
|
@ -104,7 +86,7 @@ let subProcessHandler: Handler | undefined
|
|||
process.on("message", (message: ChildProcessRequest) => {
|
||||
switch (message.type) {
|
||||
case 'settings':
|
||||
initSubprocessHandler(message.settings)
|
||||
handleNostrSettings(message.settings)
|
||||
break
|
||||
case 'send':
|
||||
sendToNostr(message.initiator, message.data, message.relays)
|
||||
|
|
@ -117,18 +99,14 @@ process.on("message", (message: ChildProcessRequest) => {
|
|||
break
|
||||
}
|
||||
})
|
||||
const initSubprocessHandler = (settings: NostrSettings) => {
|
||||
const handleNostrSettings = (settings: NostrSettings) => {
|
||||
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
|
||||
}
|
||||
subProcessHandler = new Handler(settings, event => {
|
||||
send({
|
||||
type: 'event',
|
||||
event: event
|
||||
})
|
||||
})
|
||||
|
||||
initNostrHandler(settings)
|
||||
new ProcessMetricsCollector((metrics) => {
|
||||
send({
|
||||
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) => {
|
||||
if (!subProcessHandler) {
|
||||
getLogger({ component: "nostrMiddleware" })(ERROR, "nostr was not initialized")
|
||||
|
|
@ -152,13 +138,16 @@ export default class Handler {
|
|||
apps: Record<string, AppInfo> = {}
|
||||
eventCallback: (event: NostrEvent) => void
|
||||
log = getLogger({ component: "nostrMiddleware" })
|
||||
relay: Relay | null = null
|
||||
sub: Subscription | null = null
|
||||
stopped = false
|
||||
constructor(settings: NostrSettings, eventCallback: (event: NostrEvent) => void) {
|
||||
this.settings = settings
|
||||
this.log("connecting to relays:", settings.relays)
|
||||
this.settings.apps.forEach(app => {
|
||||
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.apps[app.publicKey] = app
|
||||
})
|
||||
|
|
@ -167,7 +156,7 @@ export default class Handler {
|
|||
|
||||
async ConnectLoop() {
|
||||
let failures = 0
|
||||
while (true) {
|
||||
while (!this.stopped) {
|
||||
await this.ConnectPromise()
|
||||
const pow = Math.pow(2, failures)
|
||||
const delay = Math.min(pow, 900)
|
||||
|
|
@ -175,21 +164,34 @@ export default class Handler {
|
|||
await new Promise(resolve => setTimeout(resolve, delay * 1000))
|
||||
failures++
|
||||
}
|
||||
this.log("nostr handler stopped")
|
||||
}
|
||||
|
||||
Stop() {
|
||||
this.stopped = true
|
||||
this.sub?.close()
|
||||
this.relay?.close()
|
||||
this.relay = null
|
||||
this.sub = null
|
||||
}
|
||||
|
||||
async ConnectPromise() {
|
||||
return new Promise<void>(async (res) => {
|
||||
const relay = await this.GetRelay()
|
||||
if (!relay) {
|
||||
this.relay = await this.GetRelay()
|
||||
if (!this.relay) {
|
||||
res()
|
||||
return
|
||||
}
|
||||
const sub = this.Subscribe(relay)
|
||||
relay.onclose = (() => {
|
||||
this.sub = this.Subscribe(this.relay)
|
||||
this.relay.onclose = (() => {
|
||||
this.log("relay disconnected")
|
||||
sub.close()
|
||||
relay.onclose = null
|
||||
relay.close()
|
||||
this.sub?.close()
|
||||
if (this.relay) {
|
||||
this.relay.onclose = null
|
||||
this.relay.close()
|
||||
this.relay = null
|
||||
}
|
||||
this.sub = null
|
||||
res()
|
||||
})
|
||||
})
|
||||
|
|
@ -208,37 +210,6 @@ export default class Handler {
|
|||
}
|
||||
}
|
||||
|
||||
/* 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) {
|
||||
const appIds = Object.keys(this.apps)
|
||||
this.log("🔍 [NOSTR SUBSCRIPTION] Setting up subscription", {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { ChildProcess, fork } from 'child_process'
|
||||
import { EnvCanBeInteger, EnvMustBeNonEmptyString } from "../helpers/envParser.js"
|
||||
import { NostrSettings, NostrEvent, ChildProcessRequest, ChildProcessResponse, SendData, SendInitiator } from "./handler.js"
|
||||
import { Utils } from '../helpers/utilsWrapper.js'
|
||||
import { getLogger, ERROR } from '../helpers/logger.js'
|
||||
|
|
@ -10,7 +9,6 @@ type EventCallback = (event: NostrEvent) => void
|
|||
|
||||
|
||||
export default class NostrSubprocess {
|
||||
settings: NostrSettings
|
||||
childProcess: ChildProcess
|
||||
utils: Utils
|
||||
awaitingPongs: (() => void)[] = []
|
||||
|
|
@ -55,6 +53,10 @@ export default class NostrSubprocess {
|
|||
this.childProcess.send(message)
|
||||
}
|
||||
|
||||
Reset(settings: NostrSettings) {
|
||||
this.sendToChildProcess({ type: 'settings', settings })
|
||||
}
|
||||
|
||||
Ping() {
|
||||
this.sendToChildProcess({ type: 'ping' })
|
||||
return new Promise<void>((resolve) => {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
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'
|
||||
export default (mainHandler: Main): Types.ServerMethods => {
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import { User } from "../entity/User.js"
|
|||
import { UserReceivingAddress } from "../entity/UserReceivingAddress.js"
|
||||
import { UserReceivingInvoice } from "../entity/UserReceivingInvoice.js"
|
||||
import { UserInvoicePayment } from "../entity/UserInvoicePayment.js"
|
||||
import { EnvMustBeNonEmptyString } from "../../helpers/envParser.js"
|
||||
import { UserTransactionPayment } from "../entity/UserTransactionPayment.js"
|
||||
import { UserBasicAuth } from "../entity/UserBasicAuth.js"
|
||||
import { UserEphemeralKey } from "../entity/UserEphemeralKey.js"
|
||||
|
|
@ -29,6 +28,7 @@ import { ChannelEvent } from "../entity/ChannelEvent.js"
|
|||
import { AppUserDevice } from "../entity/AppUserDevice.js"
|
||||
import * as fs from 'fs'
|
||||
import { UserAccess } from "../entity/UserAccess.js"
|
||||
import { AdminSettings } from "../entity/AdminSettings.js"
|
||||
|
||||
|
||||
export type DbSettings = {
|
||||
|
|
@ -73,7 +73,8 @@ export const MainDbEntities = {
|
|||
'Product': Product,
|
||||
'ManagementGrant': ManagementGrant,
|
||||
'AppUserDevice': AppUserDevice,
|
||||
'UserAccess': UserAccess
|
||||
'UserAccess': UserAccess,
|
||||
'AdminSettings': AdminSettings
|
||||
}
|
||||
export type MainDbNames = keyof typeof 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 { ManagementStorage } from "./managementStorage.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 { Utils } from '../helpers/utilsWrapper.js';
|
||||
import SettingsStorage from "./settingsStorage.js";
|
||||
import crypto from 'crypto';
|
||||
export type StorageSettings = {
|
||||
dbSettings: DbSettings
|
||||
eventLogPath: 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 => {
|
||||
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 {
|
||||
//DB: DataSource | EntityManager
|
||||
|
|
@ -39,6 +81,7 @@ export default class {
|
|||
offerStorage: OfferStorage
|
||||
managementStorage: ManagementStorage
|
||||
eventsLog: EventsLogManager
|
||||
settingsStorage: SettingsStorage
|
||||
utils: Utils
|
||||
constructor(settings: StorageSettings, utils: Utils) {
|
||||
this.settings = settings
|
||||
|
|
@ -51,6 +94,7 @@ export default class {
|
|||
//const { source, executedMigrations } = await NewDB(this.settings.dbSettings, allMigrations)
|
||||
//this.DB = source
|
||||
//this.txQueue = new TransactionsQueue("main", this.DB)
|
||||
this.settingsStorage = new SettingsStorage(this.dbs)
|
||||
this.userStorage = new UserStorage(this.dbs, this.eventsLog)
|
||||
this.productStorage = new ProductStorage(this.dbs)
|
||||
this.applicationStorage = new ApplicationStorage(this.dbs, this.userStorage)
|
||||
|
|
@ -74,6 +118,10 @@ export default class {
|
|||
} */
|
||||
}
|
||||
|
||||
getStorageSettings(): StorageSettings {
|
||||
return this.settings
|
||||
}
|
||||
|
||||
Stop() {
|
||||
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 { AddBlindToUserOffer1760000000000 } from './1760000000000-add_blind_to_user_offer.js'
|
||||
import { ApplicationAvatarUrl1761000001000 } from './1761000001000-application_avatar_url.js'
|
||||
import { AdminSettings1761683639419 } from './1761683639419-admin_settings.js'
|
||||
|
||||
|
||||
export const allMigrations = [Initial1703170309875, LspOrder1718387847693, LiquidityProvider1719335699480, LndNodeInfo1720187506189,
|
||||
TrackedProvider1720814323679, CreateInviteTokenTable1721751414878, PaymentIndex1721760297610, DebitAccess1726496225078, DebitAccessFixes1726685229264,
|
||||
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 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 NewWizardServer from "../../../proto/wizard_service/autogenerated/ts/express_server.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 { Unlocker } from "../main/unlocker.js"
|
||||
import { AdminManager } from '../main/adminManager.js';
|
||||
|
|
@ -17,16 +14,15 @@ export type WizardSettings = {
|
|||
const defaultProviderPub = ""
|
||||
export class Wizard {
|
||||
log = getLogger({ component: "wizard" })
|
||||
settings: MainSettings
|
||||
settings: SettingsManager
|
||||
adminManager: AdminManager
|
||||
storage: Storage
|
||||
configQueue: { res: (reload: boolean) => void }[] = []
|
||||
pendingConfig: WizardSettings | null = null
|
||||
awaitingNprofile: { res: (nprofile: string) => void }[] = []
|
||||
nprofile = ""
|
||||
relays: string[] = []
|
||||
constructor(mainSettings: MainSettings, storage: Storage, adminManager: AdminManager) {
|
||||
this.settings = mainSettings
|
||||
constructor(settings: SettingsManager, storage: Storage, adminManager: AdminManager) {
|
||||
this.settings = settings
|
||||
this.adminManager = adminManager
|
||||
this.storage = storage
|
||||
this.log('Starting wizard...')
|
||||
|
|
@ -36,16 +32,16 @@ export class Wizard {
|
|||
GetAdminConnectInfo: async () => { return this.GetAdminConnectInfo() },
|
||||
GetServiceState: async () => { return this.GetServiceState() }
|
||||
}, { GuestAuthGuard: async () => "", metricsCallback: () => { }, staticFiles: 'static' })
|
||||
wizardServer.Listen(mainSettings.servicePort + 1)
|
||||
wizardServer.Listen(settings.getSettings().serviceSettings.servicePort + 1)
|
||||
}
|
||||
|
||||
GetServiceState = async (): Promise<WizardTypes.ServiceStateResponse> => {
|
||||
try {
|
||||
const apps = await this.storage.applicationStorage.GetApplications()
|
||||
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 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
|
||||
let lndState: WizardTypes.LndState = WizardTypes.LndState.OFFLINE
|
||||
let watchdogOk = false
|
||||
|
|
@ -60,17 +56,17 @@ export class Wizard {
|
|||
}
|
||||
return {
|
||||
admin_npub: this.adminManager.GetAdminNpub(),
|
||||
http_url: this.settings.serviceUrl,
|
||||
http_url: this.settings.getSettings().serviceSettings.serviceUrl,
|
||||
lnd_state: lndState,
|
||||
nprofile: this.nprofile,
|
||||
provider_name: defaultApp?.name || appNamesList,
|
||||
relay_connected: this.adminManager.GetNostrConnected(),
|
||||
relays: this.relays,
|
||||
watchdog_ok: watchdogOk,
|
||||
source_name: defaultApp?.name || this.settings.defaultAppName || appNamesList,
|
||||
source_name: defaultApp?.name || this.settings.getSettings().serviceSettings.defaultAppName || appNamesList,
|
||||
relay_url: relayUrl,
|
||||
automate_liquidity: this.settings.liquiditySettings.liquidityProviderPub !== 'null',
|
||||
push_backups_to_nostr: this.settings.pushBackupsToNostr,
|
||||
automate_liquidity: this.settings.getSettings().liquiditySettings.liquidityProviderPub !== 'null',
|
||||
push_backups_to_nostr: this.settings.getSettings().serviceSettings.pushBackupsToNostr,
|
||||
avatar_url: defaultApp?.avatar_url || '',
|
||||
app_id: defaultApp?.app_id || ''
|
||||
}
|
||||
|
|
@ -99,7 +95,7 @@ export class Wizard {
|
|||
|
||||
WizardState = async (): Promise<WizardTypes.StateResponse> => {
|
||||
return {
|
||||
config_sent: this.pendingConfig !== null,
|
||||
config_sent: false,
|
||||
admin_linked: this.adminManager.GetAdminNpub() !== "",
|
||||
}
|
||||
}
|
||||
|
|
@ -148,7 +144,7 @@ export class Wizard {
|
|||
}
|
||||
|
||||
Configure = async (): Promise<boolean> => {
|
||||
if (this.IsInitialized() || this.pendingConfig !== null) {
|
||||
if (this.IsInitialized()) {
|
||||
return false
|
||||
}
|
||||
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 }
|
||||
|
||||
// Persist app name/avatar to DB regardless (idempotent behavior)
|
||||
try {
|
||||
const appsList = await this.storage.applicationStorage.GetApplications()
|
||||
const defaultNames = ['wallet', 'wallet-test', this.settings.defaultAppName]
|
||||
const existingDefaultApp = appsList.find(app => defaultNames.includes(app.name)) || appsList[0]
|
||||
if (existingDefaultApp) {
|
||||
await this.storage.applicationStorage.UpdateApplication(existingDefaultApp, { name: req.source_name, avatar_url: (req as any).avatar_url || existingDefaultApp.avatar_url })
|
||||
await this.settings.updateDisableLiquidityProvider(pendingConfig.automateLiquidity)
|
||||
await this.settings.updatePushBackupsToNostr(pendingConfig.pushBackupsToNostr)
|
||||
const oldAppName = this.settings.getSettings().serviceSettings.defaultAppName
|
||||
const nameUpdated = await this.settings.updateDefaultAppName(pendingConfig.sourceName)
|
||||
if (nameUpdated) {
|
||||
await this.updateDefaultApp(oldAppName, req.avatar_url)
|
||||
}
|
||||
} catch (e) {
|
||||
this.log(`Error updating app info: ${(e as Error).message}`)
|
||||
const relayUpdated = await this.settings.updateRelayUrl(pendingConfig.relayUrl)
|
||||
if (relayUpdated && this.IsInitialized()) {
|
||||
await this.adminManager.ResetNostr()
|
||||
}
|
||||
|
||||
// If already initialized, treat as idempotent update for env and exit
|
||||
if (this.IsInitialized()) {
|
||||
this.updateEnvFile(pendingConfig)
|
||||
this.log("reloaded wizard config")
|
||||
if (nameUpdated) this.log("name updated")
|
||||
if (relayUpdated) this.log("relay updated")
|
||||
return
|
||||
}
|
||||
|
||||
// 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 = []
|
||||
return
|
||||
}
|
||||
|
||||
updateEnvFile = (pendingConfig: WizardSettings) => {
|
||||
let envFileContent: string[] = []
|
||||
updateDefaultApp = async (currentName: string, avatarUrl?: string): Promise<void> => {
|
||||
const newName = this.settings.getSettings().serviceSettings.defaultAppName
|
||||
try {
|
||||
envFileContent = fs.readFileSync('.env', 'utf-8').split('\n')
|
||||
} catch (err: any) {
|
||||
if (err.code !== 'ENOENT') {
|
||||
throw err
|
||||
const appsList = await this.storage.applicationStorage.GetApplications()
|
||||
const defaultNames = ['wallet', 'wallet-test', currentName]
|
||||
const existingDefaultApp = appsList.find(app => defaultNames.includes(app.name)) || appsList[0]
|
||||
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
|
||||
import BitcoinCore from 'bitcoin-core';
|
||||
import { TestSettings } from '../services/main/settings';
|
||||
import { BitcoinCoreSettings } from '../services/main/settings';
|
||||
export class BitcoinCoreWrapper {
|
||||
core: BitcoinCore
|
||||
addr: { address: string }
|
||||
constructor(settings: TestSettings) {
|
||||
constructor(settings: BitcoinCoreSettings) {
|
||||
this.core = new BitcoinCore({
|
||||
//network: 'regtest',
|
||||
host: '127.0.0.1',
|
||||
port: `${settings.bitcoinCoreSettings.port}`,
|
||||
username: settings.bitcoinCoreSettings.user,
|
||||
password: settings.bitcoinCoreSettings.pass,
|
||||
port: `${settings.port}`,
|
||||
username: settings.user,
|
||||
password: settings.pass,
|
||||
// use a long timeout due to the time it takes to mine a lot of blocks
|
||||
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 LND from '../services/lnd/lnd.js'
|
||||
import { LiquidityProvider } from "../services/main/liquidityProvider.js"
|
||||
import { Utils } from "../services/helpers/utilsWrapper.js"
|
||||
import { LoadStorageSettingsFromEnv } from "../services/storage/index.js"
|
||||
|
||||
export type ChainTools = {
|
||||
mine: (amount: number) => Promise<void>
|
||||
}
|
||||
|
||||
export const setupNetwork = async (): Promise<ChainTools> => {
|
||||
const settings = LoadTestSettingsFromEnv()
|
||||
const core = new BitcoinCoreWrapper(settings)
|
||||
const storageSettings = GetTestStorageSettings(LoadStorageSettingsFromEnv())
|
||||
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.Mine(1)
|
||||
const setupUtils = new Utils({ dataDir: settings.storageSettings.dataDir, allowResetMetricsStorages: settings.allowResetMetricsStorages })
|
||||
const alice = new LND(settings.lndSettings, new LiquidityProvider("", setupUtils, async () => { }, async () => { }), setupUtils, async () => { }, async () => { }, () => { }, () => { }, () => { })
|
||||
const bob = new LND({ ...settings.lndSettings, mainNode: settings.lndSettings.otherNode }, new LiquidityProvider("", setupUtils, async () => { }, async () => { }), setupUtils, async () => { }, async () => { }, () => { }, () => { }, () => { })
|
||||
const lndSettings = LoadLndSettingsFromEnv({})
|
||||
const lndNodeSettings = LoadLndNodeSettingsFromEnv({})
|
||||
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 => {
|
||||
const peers = await alice.ListPeers()
|
||||
if (peers.peers.length > 0) {
|
||||
|
|
|
|||
|
|
@ -1,16 +1,22 @@
|
|||
import { getLogger } from '../services/helpers/logger.js'
|
||||
import { initMainHandler } from '../services/main/init.js'
|
||||
import { LoadTestSettingsFromEnv } from '../services/main/settings.js'
|
||||
import { initMainHandler, initSettings } from '../services/main/init.js'
|
||||
import { SendData } from '../services/nostr/handler.js'
|
||||
import { TestBase, TestUserData } from './testBase.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) => {
|
||||
const settings = LoadTestSettingsFromEnv()
|
||||
settings.liquiditySettings.useOnlyLiquidityProvider = true
|
||||
settings.liquiditySettings.liquidityProviderPub = T.app.publicKey
|
||||
settings.lndSettings.mainNode = settings.lndSettings.thirdNode
|
||||
const initialized = await initMainHandler(getLogger({ component: "bootstrapped" }), settings)
|
||||
const storageSettings = GetTestStorageSettings(LoadStorageSettingsFromEnv())
|
||||
const settingsManager = await initSettings(getLogger({ component: "bootstrapped" }), storageSettings)
|
||||
const thirdNodeSettings = LoadThirdLndSettingsFromEnv()
|
||||
settingsManager.OverrideTestSettings(s => {
|
||||
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) {
|
||||
throw new Error("failed to initialize bootstrapped main handler")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
import 'dotenv/config' // TODO - test env
|
||||
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 Storage from '../services/storage/index.js'
|
||||
import Storage, { GetTestStorageSettings, LoadStorageSettingsFromEnv } from '../services/storage/index.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 { defaultInvoiceExpiry } from '../services/storage/paymentStorage.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 { TlvStorageFactory } from '../services/storage/tlv/tlvFilesStorageFactory.js'
|
||||
import { ChainTools } from './networkSetup.js'
|
||||
import { LiquiditySettings, LoadLndSettingsFromEnv, LoadSecondLndSettingsFromEnv, LoadThirdLndSettingsFromEnv } from '../services/main/settings.js'
|
||||
chai.use(chaiString)
|
||||
export const expect = chai.expect
|
||||
export type Describe = (message: string, failure?: boolean) => void
|
||||
|
|
@ -45,7 +45,7 @@ export type 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 storageManager = new Storage(settings, utils)
|
||||
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> => {
|
||||
const settings = LoadTestSettingsFromEnv()
|
||||
const initialized = await initMainHandler(getLogger({ component: "mainForTest" }), settings)
|
||||
const storageSettings = GetTestStorageSettings(LoadStorageSettingsFromEnv())
|
||||
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) {
|
||||
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 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 () => { }, () => { }, () => { })
|
||||
await externalAccessToMainLnd.Warmup() */
|
||||
|
||||
const otherLndSetting = { ...settings.lndSettings, mainNode: settings.lndSettings.otherNode }
|
||||
const externalAccessToOtherLnd = new LND(otherLndSetting, new LiquidityProvider("", extermnalUtils, async () => { }, async () => { }), extermnalUtils, async () => { }, async () => { }, () => { }, () => { }, () => { })
|
||||
const liquiditySettings: LiquiditySettings = { disableLiquidityProvider: true, liquidityProviderPub: "", useOnlyLiquidityProvider: false }
|
||||
const lndSettings = LoadLndSettingsFromEnv({})
|
||||
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()
|
||||
|
||||
const thirdLndSetting = { ...settings.lndSettings, mainNode: settings.lndSettings.thirdNode }
|
||||
const externalAccessToThirdLnd = new LND(thirdLndSetting, new LiquidityProvider("", extermnalUtils, async () => { }, async () => { }), extermnalUtils, async () => { }, async () => { }, () => { }, () => { }, () => { })
|
||||
const thirdLndNodeSettings = LoadThirdLndSettingsFromEnv()
|
||||
const thirdLndSetting = () => ({ lndSettings, lndNodeSettings: thirdLndNodeSettings })
|
||||
const externalAccessToThirdLnd = new LND(thirdLndSetting, new LiquidityProvider(() => liquiditySettings, extermnalUtils, async () => { }, async () => { }), extermnalUtils, async () => { }, async () => { }, () => { }, () => { }, () => { })
|
||||
await externalAccessToThirdLnd.Warmup()
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue