diff --git a/src/services/lnd/liquidityProvider.ts b/src/services/lnd/liquidityProvider.ts index 92a9fc1f..c780ff2a 100644 --- a/src/services/lnd/liquidityProvider.ts +++ b/src/services/lnd/liquidityProvider.ts @@ -69,6 +69,10 @@ export class LiquidityProvider { }) } + GetLatestMaxWithdrawable = () => { + return this.latestMaxWithdrawable || 0 + } + CheckUserState = async () => { const res = await this.client.GetUserInfo() if (res.status === 'ERROR') { diff --git a/src/services/lnd/lnd.ts b/src/services/lnd/lnd.ts index 2930ae3a..67063a14 100644 --- a/src/services/lnd/lnd.ts +++ b/src/services/lnd/lnd.ts @@ -269,11 +269,11 @@ export default class { return res.response } - async NewInvoice(value: number, memo: string, expiry: number): Promise { + async NewInvoice(value: number, memo: string, expiry: number, useProvider = false): Promise { this.log("generating new invoice for", value, "sats") await this.Health() const shouldUseLiquidityProvider = await this.ShouldUseLiquidityProvider({ action: 'receive', amount: value }) - if (shouldUseLiquidityProvider) { + if (shouldUseLiquidityProvider || useProvider) { const invoice = await this.liquidProvider.AddInvoice(value, memo) return { payRequest: invoice } } @@ -300,7 +300,7 @@ export default class { const r = res.response return { local: r.localBalance ? Number(r.localBalance.sat) : 0, remote: r.remoteBalance ? Number(r.remoteBalance.sat) : 0 } } - async PayInvoice(invoice: string, amount: number, feeLimit: number): Promise { + async PayInvoice(invoice: string, amount: number, feeLimit: number, useProvider = false): Promise { if (this.outgoingOpsLocked) { this.log("outgoing ops locked, rejecting payment request") throw new Error("lnd node is currently out of sync") @@ -308,7 +308,7 @@ export default class { await this.Health() this.log("paying invoice", invoice, "for", amount, "sats") const shouldUseLiquidityProvider = await this.ShouldUseLiquidityProvider({ action: 'spend', amount }) - if (shouldUseLiquidityProvider) { + if (shouldUseLiquidityProvider || useProvider) { const res = await this.liquidProvider.PayInvoice(invoice) return { feeSat: res.network_fee + res.service_fee, valueSat: res.amount_paid, paymentPreimage: res.preimage } } diff --git a/src/services/main/index.ts b/src/services/main/index.ts index da2af924..7807509e 100644 --- a/src/services/main/index.ts +++ b/src/services/main/index.ts @@ -49,7 +49,7 @@ export default class { this.liquidityManager = new LiquidityManager(this.settings.liquiditySettings, this.storage, this.liquidProvider, this.lnd) this.metricsManager = new MetricsManager(this.storage, this.lnd) - this.paymentManager = new PaymentManager(this.storage, this.lnd, this.settings, this.addressPaidCb, this.invoicePaidCb) + this.paymentManager = new PaymentManager(this.storage, this.lnd, this.settings, this.liquidityManager, this.addressPaidCb, this.invoicePaidCb) this.productManager = new ProductManager(this.storage, this.paymentManager, this.settings) this.applicationManager = new ApplicationManager(this.storage, this.settings, this.paymentManager) this.appUserManager = new AppUserManager(this.storage, this.settings, this.applicationManager) diff --git a/src/services/main/liquidityManager.ts b/src/services/main/liquidityManager.ts index 80c75321..cf2d2b37 100644 --- a/src/services/main/liquidityManager.ts +++ b/src/services/main/liquidityManager.ts @@ -3,6 +3,7 @@ import { LiquidityProvider } from "../lnd/liquidityProvider.js" import LND from "../lnd/lnd.js" import { FlashsatsLSP, LoadLSPSettingsFromEnv, LSPSettings, OlympusLSP, VoltageLSP } from "../lnd/lsp.js" import Storage from '../storage/index.js' +import { defaultInvoiceExpiry } from "../storage/paymentStorage.js" export type LiquiditySettings = { lspSettings: LSPSettings liquidityProviderPub: string @@ -32,7 +33,26 @@ export class LiquidityManager { this.voltageLSP = new VoltageLSP(settings.lspSettings, lnd, liquidityProvider) this.flashsatsLSP = new FlashsatsLSP(settings.lspSettings, lnd, liquidityProvider) } - beforeInvoiceCreation = async () => { } + onNewBlock = async () => { + const balance = await this.liquidityProvider.GetLatestMaxWithdrawable() + const { remote } = await this.lnd.ChannelBalance() + if (remote > balance) { + this.log("draining provider balance to channel") + const invoice = await this.lnd.NewInvoice(balance, "liqudity provider drain", defaultInvoiceExpiry) + const res = await this.liquidityProvider.PayInvoice(invoice.payRequest) + this.log("drained provider balance to channel", res.amount_paid) + } + } + + beforeInvoiceCreation = async (amount: number): Promise<'lnd' | 'provider'> => { + const { remote } = await this.lnd.ChannelBalance() + if (remote > amount) { + this.log("channel has enough balance for invoice") + return 'lnd' + } + this.log("channel does not have enough balance for invoice,suggesting provider") + return 'provider' + } afterInInvoicePaid = async () => { const existingOrder = await this.storage.liquidityStorage.GetLatestLspOrder() if (existingOrder) { @@ -67,6 +87,14 @@ export class LiquidityManager { this.log("no channel requested") } - beforeOutInvoicePayment = async () => { } + beforeOutInvoicePayment = async (amount: number): Promise<'lnd' | 'provider'> => { + const balance = await this.liquidityProvider.GetLatestMaxWithdrawable() + if (balance > amount) { + this.log("provider has enough balance for payment") + return 'provider' + } + this.log("provider does not have enough balance for payment, suggesting lnd") + return 'lnd' + } afterOutInvoicePaid = async () => { } } \ No newline at end of file diff --git a/src/services/main/paymentManager.ts b/src/services/main/paymentManager.ts index 647f4fd8..77144209 100644 --- a/src/services/main/paymentManager.ts +++ b/src/services/main/paymentManager.ts @@ -16,6 +16,7 @@ import { AddressReceivingTransaction } from '../storage/entity/AddressReceivingT import { UserTransactionPayment } from '../storage/entity/UserTransactionPayment.js' import { Watchdog } from './watchdog.js' import { LiquidityProvider } from '../lnd/liquidityProvider.js' +import { LiquidityManager } from './liquidityManager.js' interface UserOperationInfo { serial_id: number paid_amount: number @@ -47,11 +48,13 @@ export default class { invoicePaidCb: InvoicePaidCb log = getLogger({ component: "PaymentManager" }) watchDog: Watchdog - constructor(storage: Storage, lnd: LND, settings: MainSettings, addressPaidCb: AddressPaidCb, invoicePaidCb: InvoicePaidCb) { + liquidityManager: LiquidityManager + constructor(storage: Storage, lnd: LND, settings: MainSettings, liquidityManager: LiquidityManager, addressPaidCb: AddressPaidCb, invoicePaidCb: InvoicePaidCb) { this.storage = storage this.settings = settings this.lnd = lnd this.watchDog = new Watchdog(settings.watchDogSettings, lnd, storage) + this.liquidityManager = liquidityManager this.addressPaidCb = addressPaidCb this.invoicePaidCb = invoicePaidCb } @@ -121,7 +124,8 @@ export default class { if (user.locked) { throw new Error("user is banned, cannot generate invoice") } - const res = await this.lnd.NewInvoice(req.amountSats, req.memo, options.expiry) + const use = await this.liquidityManager.beforeInvoiceCreation(req.amountSats) + const res = await this.lnd.NewInvoice(req.amountSats, req.memo, options.expiry, use === 'provider') const userInvoice = await this.storage.paymentStorage.AddUserInvoice(user, res.payRequest, options) 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 }) @@ -201,8 +205,9 @@ export default class { const routingFeeLimit = this.lnd.GetFeeLimitAmount(payAmount) await this.storage.userStorage.DecrementUserBalance(userId, totalAmountToDecrement + routingFeeLimit, invoice) const pendingPayment = await this.storage.paymentStorage.AddPendingExternalPayment(userId, invoice, payAmount, linkedApplication) + const use = await this.liquidityManager.beforeOutInvoicePayment(payAmount) try { - const payment = await this.lnd.PayInvoice(invoice, amountForLnd, routingFeeLimit) + const payment = await this.lnd.PayInvoice(invoice, amountForLnd, routingFeeLimit, use === 'provider') if (routingFeeLimit - payment.feeSat > 0) { this.log("refund routing fee", routingFeeLimit, payment.feeSat, "sats") diff --git a/src/services/main/watchdog.ts b/src/services/main/watchdog.ts index 257e2614..77fd1449 100644 --- a/src/services/main/watchdog.ts +++ b/src/services/main/watchdog.ts @@ -1,6 +1,7 @@ import { EnvCanBeInteger } from "../helpers/envParser.js"; import FunctionQueue from "../helpers/functionQueue.js"; import { getLogger } from "../helpers/logger.js"; +import { LiquidityProvider } from "../lnd/liquidityProvider.js"; import LND from "../lnd/lnd.js"; import { ChannelBalance } from "../lnd/settings.js"; import Storage from '../storage/index.js' @@ -20,6 +21,7 @@ export class Watchdog { latestIndexOffset: number; accumulatedHtlcFees: number; lnd: LND; + liquidProvider: LiquidityProvider; settings: WatchdogSettings; storage: Storage; latestCheckStart = 0 @@ -30,6 +32,7 @@ export class Watchdog { this.lnd = lnd; this.settings = settings; this.storage = storage; + this.liquidProvider = lnd.liquidProvider this.queue = new FunctionQueue("watchdog_queue", () => this.StartCheck()) } @@ -76,7 +79,8 @@ export class Watchdog { getLogger({ component: "debugLndBalancev3" })({ w: walletBalance, c: channelsBalance, u: usersTotal, f: this.accumulatedHtlcFees }) const totalLightningBalanceMsats = (channelsBalance.localBalance?.msat || 0n) + (channelsBalance.unsettledLocalBalance?.msat || 0n) const totalLightningBalance = Math.ceil(Number(totalLightningBalanceMsats) / 1000) - return Number(walletBalance.confirmedBalance) + totalLightningBalance + const providerBalance = this.liquidProvider.GetLatestMaxWithdrawable() + return Number(walletBalance.confirmedBalance) + totalLightningBalance + providerBalance } checkBalanceUpdate = (deltaLnd: number, deltaUsers: number) => {