diff --git a/src/e2e.ts b/src/e2e.ts index bb7973af..bb2cb17d 100644 --- a/src/e2e.ts +++ b/src/e2e.ts @@ -21,7 +21,7 @@ const start = async () => { return } - const { mainHandler, liquidityProviderInfo, wizard, adminManager } = keepOn + const { mainHandler, localProviderClient, wizard, adminManager } = keepOn const serverMethods = GetServerMethods(mainHandler) const nostrSettings = settingsManager.getSettings().nostrRelaySettings log("initializing nostr middleware") @@ -33,8 +33,8 @@ const start = async () => { privateKey: app.privateKey, publicKey: app.publicKey, name: app.name, - provider: app.publicKey === liquidityProviderInfo.publicKey ? { - clientId: liquidityProviderInfo.clientId, + provider: app.publicKey === localProviderClient.publicKey ? { + clientId: `client_${localProviderClient.appId}`, pubkey: settingsManager.getSettings().liquiditySettings.liquidityProviderPub, relayUrl: settingsManager.getSettings().liquiditySettings.providerRelayUrl } : undefined @@ -49,7 +49,7 @@ const start = async () => { log("starting server") mainHandler.attachNostrSend(Send) mainHandler.StartBeacons() - const appNprofile = nprofileEncode({ pubkey: liquidityProviderInfo.publicKey, relays: nostrSettings.relays }) + const appNprofile = nprofileEncode({ pubkey: localProviderClient.publicKey, relays: nostrSettings.relays }) if (wizard) { wizard.AddConnectInfo(appNprofile, nostrSettings.relays) } diff --git a/src/index.ts b/src/index.ts index 4b70ae85..fbe6802c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -23,7 +23,7 @@ const start = async () => { return } - const { mainHandler, liquidityProviderInfo, wizard, adminManager } = keepOn + const { mainHandler, localProviderClient, wizard, adminManager } = keepOn const serverMethods = GetServerMethods(mainHandler) log("initializing nostr middleware") const relays = settingsManager.getSettings().nostrRelaySettings.relays @@ -34,8 +34,8 @@ const start = async () => { privateKey: app.privateKey, publicKey: app.publicKey, name: app.name, - provider: app.publicKey === liquidityProviderInfo.publicKey ? { - clientId: liquidityProviderInfo.clientId, + provider: app.publicKey === localProviderClient.publicKey ? { + clientId: `client_${localProviderClient.appId}`, pubkey: settingsManager.getSettings().liquiditySettings.liquidityProviderPub, relayUrl: settingsManager.getSettings().liquiditySettings.providerRelayUrl } : undefined @@ -53,7 +53,7 @@ const start = async () => { mainHandler.attachNostrProcessPing(Ping) mainHandler.attachNostrReset(Reset) mainHandler.StartBeacons() - const appNprofile = nprofileEncode({ pubkey: liquidityProviderInfo.publicKey, relays }) + const appNprofile = nprofileEncode({ pubkey: localProviderClient.publicKey, relays }) if (wizard) { wizard.AddConnectInfo(appNprofile, relays) } diff --git a/src/services/lnd/lnd.ts b/src/services/lnd/lnd.ts index 07cd8750..65855b2e 100644 --- a/src/services/lnd/lnd.ts +++ b/src/services/lnd/lnd.ts @@ -372,8 +372,8 @@ export default class { if (mustUseProvider) { console.log("using provider") const invoice = await this.liquidProvider.AddInvoice(value, memo, from, expiry) - const providerDst = this.liquidProvider.GetProviderDestination() - return { payRequest: invoice, providerDst } + const providerPubkey = this.liquidProvider.GetProviderPubkey() + return { payRequest: invoice, providerPubkey } } try { const res = await this.lightning.addInvoice(AddInvoiceReq(value, expiry, true, memo, blind), DeadLineMetadata()) @@ -435,8 +435,8 @@ export default class { const mustUseProvider = this.liquidProvider.getSettings().useOnlyLiquidityProvider || useProvider if (mustUseProvider) { const res = await this.liquidProvider.PayInvoice(invoice, decodedAmount, from, serviceFee) - const providerDst = this.liquidProvider.GetProviderDestination() - return { feeSat: res.service_fee, valueSat: res.amount_paid, paymentPreimage: res.preimage, providerDst } + const providerPubkey = this.liquidProvider.GetProviderPubkey() + return { feeSat: res.service_fee, valueSat: res.amount_paid, paymentPreimage: res.preimage, providerPubkey } } await this.Health() try { diff --git a/src/services/lnd/settings.ts b/src/services/lnd/settings.ts index 43a125a5..842cd57b 100644 --- a/src/services/lnd/settings.ts +++ b/src/services/lnd/settings.ts @@ -35,7 +35,7 @@ export type NodeInfo = { } export type Invoice = { payRequest: string - providerDst?: string + providerPubkey?: string } export type DecodedInvoice = { numSatoshis: number @@ -45,7 +45,7 @@ export type PaidInvoice = { feeSat: number valueSat: number paymentPreimage: string - providerDst?: string + providerPubkey?: string } diff --git a/src/services/main/index.ts b/src/services/main/index.ts index 6185e2e2..fa9be22e 100644 --- a/src/services/main/index.ts +++ b/src/services/main/index.ts @@ -468,15 +468,11 @@ export default class { } 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 local = apps.find(app => defaultNames.includes(app.name)) + if (!local) { + throw new Error("local app not initialized correctly") } + this.liquidityProvider.setNostrInfo({ localId: `client_${local.app_id}`, localPubkey: local.nostr_public_key || "" }) const relays = this.settings.getSettings().nostrRelaySettings.relays const appsInfo: AppInfo[] = apps.map(app => { return { @@ -484,8 +480,8 @@ export default class { privateKey: app.nostr_private_key || "", publicKey: app.nostr_public_key || "", name: app.name, - provider: app.nostr_public_key === liquidityProviderInfo.publicKey ? { - clientId: liquidityProviderInfo.clientId, + provider: app.nostr_public_key === local.nostr_public_key ? { + clientId: `client_${local.app_id}`, pubkey: this.settings.getSettings().liquiditySettings.liquidityProviderPub, relayUrl: this.settings.getSettings().liquiditySettings.providerRelayUrl } : undefined @@ -495,7 +491,7 @@ export default class { apps: appsInfo, relays, maxEventContentLength: this.settings.getSettings().nostrRelaySettings.maxEventContentLength, - /* clients: [liquidityProviderInfo], + /* clients: [local], providerDestinationPub: this.settings.getSettings().liquiditySettings.liquidityProviderPub */ } this.nostrReset(s) diff --git a/src/services/main/init.ts b/src/services/main/init.ts index 006b8e5d..776e989a 100644 --- a/src/services/main/init.ts +++ b/src/services/main/init.ts @@ -63,16 +63,11 @@ export const initMainHandler = async (log: PubLogger, settingsManager: SettingsM return { privateKey: app.nostr_private_key, publicKey: app.nostr_public_key, appId: app.app_id, name: app.name } } })) - const liquidityProviderApp = apps.find(app => defaultNames.includes(app.name)) - if (!liquidityProviderApp) { - throw new Error("wallet app not initialized correctly") + const localProviderClient = apps.find(app => defaultNames.includes(app.name)) + if (!localProviderClient) { + throw new Error("local app not initialized correctly") } - const liquidityProviderInfo = { - privateKey: liquidityProviderApp.privateKey, - publicKey: liquidityProviderApp.publicKey, - name: "liquidity_provider", clientId: `client_${liquidityProviderApp.appId}` - } - mainHandler.liquidityProvider.setNostrInfo({ clientId: liquidityProviderInfo.clientId, myPub: liquidityProviderInfo.publicKey }) + mainHandler.liquidityProvider.setNostrInfo({ localId: `client_${localProviderClient.appId}`, localPubkey: localProviderClient.publicKey }) const stop = await processArgs(mainHandler) if (stop) { return @@ -82,7 +77,7 @@ export const initMainHandler = async (log: PubLogger, settingsManager: SettingsM await mainHandler.appUserManager.CleanupInactiveUsers() await mainHandler.appUserManager.CleanupNeverActiveUsers() await mainHandler.paymentManager.watchDog.Start() - return { mainHandler, apps, liquidityProviderInfo, liquidityProviderApp, wizard, adminManager } + return { mainHandler, apps, localProviderClient, wizard, adminManager } } const processArgs = async (mainHandler: Main) => { diff --git a/src/services/main/liquidityProvider.ts b/src/services/main/liquidityProvider.ts index f308cea3..474c04ba 100644 --- a/src/services/main/liquidityProvider.ts +++ b/src/services/main/liquidityProvider.ts @@ -12,12 +12,12 @@ export class LiquidityProvider { getSettings: () => LiquiditySettings client: ReturnType clientCbs: Record> = {} - clientId: string = "" - myPub: string = "" + localId: string = "" + localPubkey: string = "" log = getLogger({ component: 'liquidityProvider' }) // nostrSend: NostrSend | null = null configured = false - pubDestination: string + providerPubkey: string ready: boolean invoicePaidCb: InvoicePaidCb connecting = false @@ -34,9 +34,9 @@ export class LiquidityProvider { constructor(getSettings: () => LiquiditySettings, utils: Utils, invoicePaidCb: InvoicePaidCb, incrementProviderBalance: (balance: number) => Promise) { this.utils = utils this.getSettings = getSettings - const pubDestination = getSettings().liquidityProviderPub + const providerPubkey = getSettings().liquidityProviderPub const disableLiquidityProvider = getSettings().disableLiquidityProvider - if (!pubDestination) { + if (!providerPubkey) { this.log("No pub provider to liquidity provider, will not be initialized") return } @@ -44,19 +44,29 @@ export class LiquidityProvider { this.log("Liquidity provider is disabled, will not be initialized") return } - this.log("connecting to liquidity provider:", pubDestination) - this.pubDestination = pubDestination + this.log("connecting to liquidity provider:", providerPubkey) + this.providerPubkey = providerPubkey this.invoicePaidCb = invoicePaidCb this.incrementProviderBalance = incrementProviderBalance this.client = newNostrClient({ - pubDestination: this.pubDestination, - retrieveNostrUserAuth: async () => this.myPub, - retrieveNostrAdminAuth: async () => this.myPub, - retrieveNostrMetricsAuth: async () => this.myPub, - retrieveNostrGuestWithPubAuth: async () => this.myPub + pubDestination: this.providerPubkey, + retrieveNostrUserAuth: async () => this.localPubkey, + retrieveNostrAdminAuth: async () => this.localPubkey, + retrieveNostrMetricsAuth: async () => this.localPubkey, + retrieveNostrGuestWithPubAuth: async () => this.localPubkey }, this.clientSend, this.clientSub) + this.utils.nostrSender.OnReady(() => { + this.setSetIfConfigured() + if (this.configured) { + clearInterval(this.configuredInterval) + this.Connect() + } + }) this.configuredInterval = setInterval(() => { + if (!this.configured && this.utils.nostrSender.IsReady()) { + this.setSetIfConfigured() + } if (this.configured) { clearInterval(this.configuredInterval) this.Connect() @@ -64,8 +74,8 @@ export class LiquidityProvider { }, 1000) } - GetProviderDestination() { - return this.pubDestination + GetProviderPubkey() { + return this.providerPubkey } IsReady = () => { @@ -74,7 +84,7 @@ export class LiquidityProvider { } AwaitProviderReady = async (): Promise<'inactive' | 'ready'> => { - if (!this.pubDestination || this.getSettings().disableLiquidityProvider) { + if (!this.providerPubkey || this.getSettings().disableLiquidityProvider) { return 'inactive' } if (this.IsReady()) { @@ -283,24 +293,27 @@ export class LiquidityProvider { return res } - setNostrInfo = ({ clientId, myPub }: { myPub: string, clientId: string }) => { - this.log("setting nostr info") - this.clientId = clientId - this.myPub = myPub + setNostrInfo = ({ localId, localPubkey }: { localPubkey: string, localId: string }) => { + this.localId = localId + this.localPubkey = localPubkey this.setSetIfConfigured() + // If nostrSender becomes ready after setNostrInfo, ensure we check again + if (!this.configured && this.utils.nostrSender.IsReady()) { + this.setSetIfConfigured() + } } setSetIfConfigured = () => { - if (this.utils.nostrSender.IsReady() && !!this.pubDestination && !!this.clientId && !!this.myPub) { - this.configured = true - this.log("configured to send to ") + if (this.utils.nostrSender.IsReady() && !!this.providerPubkey && !!this.localId && !!this.localPubkey) { + if (!this.configured) { + this.configured = true + } } } onBeaconEvent = async (beaconData: { content: string, pub: string }) => { - this.log("received beacon event from", beaconData.pub, "expected", this.pubDestination) - if (beaconData.pub !== this.pubDestination) { - this.log(ERROR, "got beacon from invalid pub", beaconData.pub, this.pubDestination) + if (beaconData.pub !== this.providerPubkey) { + this.log(ERROR, "got beacon from invalid pub", beaconData.pub, this.providerPubkey) return } const beacon = JSON.parse(beaconData.content) as Types.BeaconData @@ -313,7 +326,6 @@ export class LiquidityProvider { this.log(ERROR, "got beacon from invalid type", beacon.type) return } - this.log("valid beacon received, updating ready state") this.lastSeenBeacon = Date.now() if (beacon.fees) { this.feesCache = beacon.fees @@ -321,8 +333,8 @@ export class LiquidityProvider { } onEvent = async (res: { requestId: string }, fromPub: string) => { - if (fromPub !== this.pubDestination) { - this.log("got event from invalid pub", fromPub, this.pubDestination) + if (fromPub !== this.providerPubkey) { + this.log("got event from invalid pub", fromPub, this.providerPubkey) return false } if (this.clientCbs[res.requestId]) { @@ -339,9 +351,6 @@ export class LiquidityProvider { } clientSend = (to: string, message: NostrRequest): Promise => { - if (!this.configured || !this.utils.nostrSender.IsReady()) { - throw new Error("liquidity provider not initialized") - } if (!message.requestId) { message.requestId = makeId(16) } @@ -349,7 +358,7 @@ export class LiquidityProvider { if (this.clientCbs[reqId]) { throw new Error("request was already sent") } - this.utils.nostrSender.Send({ type: 'client', clientId: this.clientId }, { + this.utils.nostrSender.Send({ type: 'client', clientId: this.localId }, { type: 'content', pub: to, content: JSON.stringify(message) @@ -368,9 +377,6 @@ export class LiquidityProvider { } clientSub = (to: string, message: NostrRequest, cb: (res: any) => void): void => { - if (!this.configured || !this.utils.nostrSender.IsReady()) { - throw new Error("liquidity provider not initialized") - } if (!message.requestId) { message.requestId = message.rpcName } @@ -387,7 +393,7 @@ export class LiquidityProvider { this.log("sub for", reqId, "was already registered, overriding") return } - this.utils.nostrSender.Send({ type: 'client', clientId: this.clientId }, { + this.utils.nostrSender.Send({ type: 'client', clientId: this.localId }, { type: 'content', pub: to, content: JSON.stringify(message) diff --git a/src/services/main/paymentManager.ts b/src/services/main/paymentManager.ts index d2e9c5b3..b4a86766 100644 --- a/src/services/main/paymentManager.ts +++ b/src/services/main/paymentManager.ts @@ -259,7 +259,7 @@ export default class { } const use = await this.liquidityManager.beforeInvoiceCreation(req.amountSats) const res = await this.lnd.NewInvoice(req.amountSats, req.memo, options.expiry, { useProvider: use === 'provider', from: 'user' }, req.blind) - const userInvoice = await this.storage.paymentStorage.AddUserInvoice(user, res.payRequest, options, res.providerDst) + const userInvoice = await this.storage.paymentStorage.AddUserInvoice(user, res.payRequest, options, res.providerPubkey) const appId = options.linkedApplication ? options.linkedApplication.app_id : "" this.storage.eventsLog.LogEvent({ type: 'new_invoice', userId: user.user_id, appUserId: "", appId, balance: user.balance_sats, data: userInvoice.invoice, amount: req.amountSats }) return { @@ -373,7 +373,7 @@ export default class { const totalAmountToDecrement = payAmount + serviceFee const routingFeeLimit = this.getRoutingFeeLimit(payAmount) const use = await this.liquidityManager.beforeOutInvoicePayment(payAmount, serviceFee) - const provider = use === 'provider' ? this.lnd.liquidProvider.GetProviderDestination() : undefined + const provider = use === 'provider' ? this.lnd.liquidProvider.GetProviderPubkey() : undefined const pendingPayment = await this.storage.StartTransaction(async tx => { await this.storage.userStorage.DecrementUserBalance(userId, totalAmountToDecrement, invoice, tx) return await this.storage.paymentStorage.AddPendingExternalPayment(userId, invoice, { payAmount, serviceFee, networkFee: 0 }, linkedApplication, provider, tx, optionals) @@ -386,7 +386,7 @@ export default class { const payment = await this.lnd.PayInvoice(invoice, amountForLnd, { routingFeeLimit, serviceFee }, payAmount, { useProvider: use === 'provider', from: 'user' }, index => { this.storage.paymentStorage.SetExternalPaymentIndex(pendingPayment.serial_id, index) }) - await this.storage.paymentStorage.UpdateExternalPayment(pendingPayment.serial_id, payment.feeSat, serviceFee, true, payment.providerDst) + await this.storage.paymentStorage.UpdateExternalPayment(pendingPayment.serial_id, payment.feeSat, serviceFee, true, payment.providerPubkey) const feeDiff = serviceFee - payment.feeSat if (feeDiff < 0) { // should not happen to lnd beacuse of the fee limit, culd happen to provider if the fee used to calculate the provider fee are out of date this.log("WARNING: network fee was higher than expected,", feeDiff, "were lost by", use === 'provider' ? "provider" : "lnd") diff --git a/src/services/main/rugPullTracker.ts b/src/services/main/rugPullTracker.ts index 8feecb30..87d4f2df 100644 --- a/src/services/main/rugPullTracker.ts +++ b/src/services/main/rugPullTracker.ts @@ -20,7 +20,7 @@ export class RugPullTracker { } CheckProviderBalance = async (): Promise<{ balance: number, prevBalance?: number }> => { - const pubDst = this.liquidProvider.GetProviderDestination() + const pubDst = this.liquidProvider.GetProviderPubkey() if (!pubDst) { return { balance: 0 } } @@ -31,7 +31,7 @@ export class RugPullTracker { const pendingBalance = await this.liquidProvider.GetPendingBalance() const trackedBalance = balance + pendingBalance if (!providerTracker) { - this.log("starting to track provider", this.liquidProvider.GetProviderDestination()) + this.log("starting to track provider", this.liquidProvider.GetProviderPubkey()) await this.storage.liquidityStorage.CreateTrackedProvider('lnPub', pubDst, trackedBalance) return { balance: trackedBalance } } diff --git a/src/services/nostr/sender.ts b/src/services/nostr/sender.ts index c3d0c2f2..c061dc56 100644 --- a/src/services/nostr/sender.ts +++ b/src/services/nostr/sender.ts @@ -2,9 +2,19 @@ import { NostrSend, SendData, SendInitiator } from "./nostrPool.js" export class NostrSender { private _nostrSend: NostrSend = () => { throw new Error('nostr send not initialized yet') } private isReady: boolean = false + private onReadyCallbacks: (() => void)[] = [] AttachNostrSend(nostrSend: NostrSend) { this._nostrSend = nostrSend this.isReady = true + this.onReadyCallbacks.forEach(cb => cb()) + this.onReadyCallbacks = [] + } + OnReady(callback: () => void) { + if (this.isReady) { + callback() + } else { + this.onReadyCallbacks.push(callback) + } } Send(initiator: SendInitiator, data: SendData, relays?: string[] | undefined) { if (!this._nostrSend) { diff --git a/src/tests/setupBootstrapped.ts b/src/tests/setupBootstrapped.ts index f4e12220..81e2e8bd 100644 --- a/src/tests/setupBootstrapped.ts +++ b/src/tests/setupBootstrapped.ts @@ -20,13 +20,13 @@ export const initBootstrappedInstance = async (T: TestBase) => { if (!initialized) { throw new Error("failed to initialize bootstrapped main handler") } - const { mainHandler: bootstrapped, liquidityProviderInfo, liquidityProviderApp } = initialized + const { mainHandler: bootstrapped, localProviderClient } = initialized T.main.attachNostrSend(async (_, data, r) => { if (data.type === 'event') { throw new Error("unsupported event type") } - if (data.pub !== liquidityProviderInfo.publicKey) { - throw new Error("invalid pub " + data.pub + " expected " + liquidityProviderInfo.publicKey) + if (data.pub !== localProviderClient.publicKey) { + throw new Error("invalid pub " + data.pub + " expected " + localProviderClient.publicKey) } const j = JSON.parse(data.content) as { requestId: string } console.log("sending new operation to provider") @@ -42,7 +42,7 @@ export const initBootstrappedInstance = async (T: TestBase) => { } bootstrapped.liquidityProvider.onEvent(res, data.pub) }) - bootstrapped.liquidityProvider.setNostrInfo({ clientId: liquidityProviderInfo.clientId, myPub: liquidityProviderInfo.publicKey }) + bootstrapped.liquidityProvider.setNostrInfo({ localId: `client_${localProviderClient.appId}`, localPubkey: localProviderClient.publicKey }) await new Promise(res => { const interval = setInterval(async () => { const canHandle = bootstrapped.liquidityProvider.IsReady() @@ -54,10 +54,10 @@ export const initBootstrappedInstance = async (T: TestBase) => { } }, 500) }) - const bUser = await bootstrapped.applicationManager.AddAppUser(liquidityProviderApp.appId, { identifier: "user1_bootstrapped", balance: 0, fail_if_exists: true }) - const bootstrappedUser: TestUserData = { userId: bUser.info.userId, appUserIdentifier: bUser.identifier, appId: liquidityProviderApp.appId } + const bUser = await bootstrapped.applicationManager.AddAppUser(localProviderClient.appId, { identifier: "user1_bootstrapped", balance: 0, fail_if_exists: true }) + const bootstrappedUser: TestUserData = { userId: bUser.info.userId, appUserIdentifier: bUser.identifier, appId: localProviderClient.appId } return { - bootstrapped, liquidityProviderInfo, liquidityProviderApp, bootstrappedUser, stop: () => { + bootstrapped, localProviderClient, bootstrappedUser, stop: () => { bootstrapped.Stop() } }