Merge pull request #697 from shocknet/liquidity-ops

add liquidty ops
This commit is contained in:
Justin (shocknet) 2024-06-20 12:14:57 -04:00 committed by GitHub
commit 4df7a328ee
6 changed files with 52 additions and 11 deletions

View file

@ -69,6 +69,10 @@ export class LiquidityProvider {
}) })
} }
GetLatestMaxWithdrawable = () => {
return this.latestMaxWithdrawable || 0
}
CheckUserState = async () => { CheckUserState = async () => {
const res = await this.client.GetUserInfo() const res = await this.client.GetUserInfo()
if (res.status === 'ERROR') { if (res.status === 'ERROR') {

View file

@ -269,11 +269,11 @@ export default class {
return res.response return res.response
} }
async NewInvoice(value: number, memo: string, expiry: number): Promise<Invoice> { async NewInvoice(value: number, memo: string, expiry: number, useProvider = false): Promise<Invoice> {
this.log("generating new invoice for", value, "sats") this.log("generating new invoice for", value, "sats")
await this.Health() await this.Health()
const shouldUseLiquidityProvider = await this.ShouldUseLiquidityProvider({ action: 'receive', amount: value }) const shouldUseLiquidityProvider = await this.ShouldUseLiquidityProvider({ action: 'receive', amount: value })
if (shouldUseLiquidityProvider) { if (shouldUseLiquidityProvider || useProvider) {
const invoice = await this.liquidProvider.AddInvoice(value, memo) const invoice = await this.liquidProvider.AddInvoice(value, memo)
return { payRequest: invoice } return { payRequest: invoice }
} }
@ -300,7 +300,7 @@ export default class {
const r = res.response const r = res.response
return { local: r.localBalance ? Number(r.localBalance.sat) : 0, remote: r.remoteBalance ? Number(r.remoteBalance.sat) : 0 } 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<PaidInvoice> { async PayInvoice(invoice: string, amount: number, feeLimit: number, useProvider = false): Promise<PaidInvoice> {
if (this.outgoingOpsLocked) { if (this.outgoingOpsLocked) {
this.log("outgoing ops locked, rejecting payment request") this.log("outgoing ops locked, rejecting payment request")
throw new Error("lnd node is currently out of sync") throw new Error("lnd node is currently out of sync")
@ -308,7 +308,7 @@ export default class {
await this.Health() await this.Health()
this.log("paying invoice", invoice, "for", amount, "sats") this.log("paying invoice", invoice, "for", amount, "sats")
const shouldUseLiquidityProvider = await this.ShouldUseLiquidityProvider({ action: 'spend', amount }) const shouldUseLiquidityProvider = await this.ShouldUseLiquidityProvider({ action: 'spend', amount })
if (shouldUseLiquidityProvider) { if (shouldUseLiquidityProvider || useProvider) {
const res = await this.liquidProvider.PayInvoice(invoice) const res = await this.liquidProvider.PayInvoice(invoice)
return { feeSat: res.network_fee + res.service_fee, valueSat: res.amount_paid, paymentPreimage: res.preimage } return { feeSat: res.network_fee + res.service_fee, valueSat: res.amount_paid, paymentPreimage: res.preimage }
} }

View file

@ -49,7 +49,7 @@ export default class {
this.liquidityManager = new LiquidityManager(this.settings.liquiditySettings, this.storage, this.liquidProvider, this.lnd) this.liquidityManager = new LiquidityManager(this.settings.liquiditySettings, this.storage, this.liquidProvider, this.lnd)
this.metricsManager = new MetricsManager(this.storage, this.lnd) this.metricsManager = new MetricsManager(this.storage, this.lnd)
this.paymentManager = new PaymentManager(this.storage, this.lnd, this.settings, this.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.productManager = new ProductManager(this.storage, this.paymentManager, this.settings)
this.applicationManager = new ApplicationManager(this.storage, this.settings, this.paymentManager) this.applicationManager = new ApplicationManager(this.storage, this.settings, this.paymentManager)
this.appUserManager = new AppUserManager(this.storage, this.settings, this.applicationManager) this.appUserManager = new AppUserManager(this.storage, this.settings, this.applicationManager)

View file

@ -3,6 +3,7 @@ import { LiquidityProvider } from "../lnd/liquidityProvider.js"
import LND from "../lnd/lnd.js" import LND from "../lnd/lnd.js"
import { FlashsatsLSP, LoadLSPSettingsFromEnv, LSPSettings, OlympusLSP, VoltageLSP } from "../lnd/lsp.js" import { FlashsatsLSP, LoadLSPSettingsFromEnv, LSPSettings, OlympusLSP, VoltageLSP } from "../lnd/lsp.js"
import Storage from '../storage/index.js' import Storage from '../storage/index.js'
import { defaultInvoiceExpiry } from "../storage/paymentStorage.js"
export type LiquiditySettings = { export type LiquiditySettings = {
lspSettings: LSPSettings lspSettings: LSPSettings
liquidityProviderPub: string liquidityProviderPub: string
@ -32,7 +33,26 @@ export class LiquidityManager {
this.voltageLSP = new VoltageLSP(settings.lspSettings, lnd, liquidityProvider) this.voltageLSP = new VoltageLSP(settings.lspSettings, lnd, liquidityProvider)
this.flashsatsLSP = new FlashsatsLSP(settings.lspSettings, lnd, liquidityProvider) this.flashsatsLSP = new FlashsatsLSP(settings.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 () => { afterInInvoicePaid = async () => {
const existingOrder = await this.storage.liquidityStorage.GetLatestLspOrder() const existingOrder = await this.storage.liquidityStorage.GetLatestLspOrder()
if (existingOrder) { if (existingOrder) {
@ -67,6 +87,14 @@ export class LiquidityManager {
this.log("no channel requested") 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 () => { } afterOutInvoicePaid = async () => { }
} }

View file

@ -16,6 +16,7 @@ import { AddressReceivingTransaction } from '../storage/entity/AddressReceivingT
import { UserTransactionPayment } from '../storage/entity/UserTransactionPayment.js' import { UserTransactionPayment } from '../storage/entity/UserTransactionPayment.js'
import { Watchdog } from './watchdog.js' import { Watchdog } from './watchdog.js'
import { LiquidityProvider } from '../lnd/liquidityProvider.js' import { LiquidityProvider } from '../lnd/liquidityProvider.js'
import { LiquidityManager } from './liquidityManager.js'
interface UserOperationInfo { interface UserOperationInfo {
serial_id: number serial_id: number
paid_amount: number paid_amount: number
@ -47,11 +48,13 @@ export default class {
invoicePaidCb: InvoicePaidCb invoicePaidCb: InvoicePaidCb
log = getLogger({ component: "PaymentManager" }) log = getLogger({ component: "PaymentManager" })
watchDog: Watchdog 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.storage = storage
this.settings = settings this.settings = settings
this.lnd = lnd this.lnd = lnd
this.watchDog = new Watchdog(settings.watchDogSettings, lnd, storage) this.watchDog = new Watchdog(settings.watchDogSettings, lnd, storage)
this.liquidityManager = liquidityManager
this.addressPaidCb = addressPaidCb this.addressPaidCb = addressPaidCb
this.invoicePaidCb = invoicePaidCb this.invoicePaidCb = invoicePaidCb
} }
@ -121,7 +124,8 @@ export default class {
if (user.locked) { if (user.locked) {
throw new Error("user is banned, cannot generate invoice") 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 userInvoice = await this.storage.paymentStorage.AddUserInvoice(user, res.payRequest, options)
const appId = options.linkedApplication ? options.linkedApplication.app_id : "" 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 }) 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) const routingFeeLimit = this.lnd.GetFeeLimitAmount(payAmount)
await this.storage.userStorage.DecrementUserBalance(userId, totalAmountToDecrement + routingFeeLimit, invoice) await this.storage.userStorage.DecrementUserBalance(userId, totalAmountToDecrement + routingFeeLimit, invoice)
const pendingPayment = await this.storage.paymentStorage.AddPendingExternalPayment(userId, invoice, payAmount, linkedApplication) const pendingPayment = await this.storage.paymentStorage.AddPendingExternalPayment(userId, invoice, payAmount, linkedApplication)
const use = await this.liquidityManager.beforeOutInvoicePayment(payAmount)
try { 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) { if (routingFeeLimit - payment.feeSat > 0) {
this.log("refund routing fee", routingFeeLimit, payment.feeSat, "sats") this.log("refund routing fee", routingFeeLimit, payment.feeSat, "sats")

View file

@ -1,6 +1,7 @@
import { EnvCanBeInteger } from "../helpers/envParser.js"; import { EnvCanBeInteger } from "../helpers/envParser.js";
import FunctionQueue from "../helpers/functionQueue.js"; import FunctionQueue from "../helpers/functionQueue.js";
import { getLogger } from "../helpers/logger.js"; import { getLogger } from "../helpers/logger.js";
import { LiquidityProvider } from "../lnd/liquidityProvider.js";
import LND from "../lnd/lnd.js"; import LND from "../lnd/lnd.js";
import { ChannelBalance } from "../lnd/settings.js"; import { ChannelBalance } from "../lnd/settings.js";
import Storage from '../storage/index.js' import Storage from '../storage/index.js'
@ -20,6 +21,7 @@ export class Watchdog {
latestIndexOffset: number; latestIndexOffset: number;
accumulatedHtlcFees: number; accumulatedHtlcFees: number;
lnd: LND; lnd: LND;
liquidProvider: LiquidityProvider;
settings: WatchdogSettings; settings: WatchdogSettings;
storage: Storage; storage: Storage;
latestCheckStart = 0 latestCheckStart = 0
@ -30,6 +32,7 @@ export class Watchdog {
this.lnd = lnd; this.lnd = lnd;
this.settings = settings; this.settings = settings;
this.storage = storage; this.storage = storage;
this.liquidProvider = lnd.liquidProvider
this.queue = new FunctionQueue("watchdog_queue", () => this.StartCheck()) 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 }) getLogger({ component: "debugLndBalancev3" })({ w: walletBalance, c: channelsBalance, u: usersTotal, f: this.accumulatedHtlcFees })
const totalLightningBalanceMsats = (channelsBalance.localBalance?.msat || 0n) + (channelsBalance.unsettledLocalBalance?.msat || 0n) const totalLightningBalanceMsats = (channelsBalance.localBalance?.msat || 0n) + (channelsBalance.unsettledLocalBalance?.msat || 0n)
const totalLightningBalance = Math.ceil(Number(totalLightningBalanceMsats) / 1000) 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) => { checkBalanceUpdate = (deltaLnd: number, deltaUsers: number) => {