diff --git a/package-lock.json b/package-lock.json index ed242466..f9ad7caf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,6 +33,7 @@ "globby": "^13.1.2", "grpc-tools": "^1.12.4", "jsonwebtoken": "^9.0.2", + "light-bolt11-decoder": "^3.2.0", "lodash": "^4.17.21", "nostr-tools": "^2.13.0", "pg": "^8.4.0", @@ -4333,6 +4334,27 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/light-bolt11-decoder": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/light-bolt11-decoder/-/light-bolt11-decoder-3.2.0.tgz", + "integrity": "sha512-3QEofgiBOP4Ehs9BI+RkZdXZNtSys0nsJ6fyGeSiAGCBsMwHGUDS/JQlY/sTnWs91A2Nh0S9XXfA8Sy9g6QpuQ==", + "license": "MIT", + "dependencies": { + "@scure/base": "1.1.1" + } + }, + "node_modules/light-bolt11-decoder/node_modules/@scure/base": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz", + "integrity": "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "license": "MIT" + }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", diff --git a/package.json b/package.json index 123cd17c..56a45e1e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "globby": "^13.1.2", "grpc-tools": "^1.12.4", "jsonwebtoken": "^9.0.2", + "light-bolt11-decoder": "^3.2.0", "lodash": "^4.17.21", "nostr-tools": "^2.13.0", "pg": "^8.4.0", diff --git a/src/services/lnd/lnd.ts b/src/services/lnd/lnd.ts index dae955bd..f6c72459 100644 --- a/src/services/lnd/lnd.ts +++ b/src/services/lnd/lnd.ts @@ -3,6 +3,7 @@ import crypto from 'crypto' import { credentials, Metadata } from '@grpc/grpc-js' import { GrpcTransport } from "@protobuf-ts/grpc-transport"; import fs from 'fs' +import { decode as decodeBolt11 } from 'light-bolt11-decoder' import * as Types from '../../../proto/autogenerated/ts/types.js' import { LightningClient } from '../../../proto/lnd/lightning.client.js' import { InvoicesClient } from '../../../proto/lnd/invoices.client.js' @@ -170,6 +171,9 @@ export default class { return res.response } async ListPendingChannels(): Promise { + if (this.liquidProvider.getSettings().useOnlyLiquidityProvider) { + return { pendingOpenChannels: [], pendingClosingChannels: [], pendingForceClosingChannels: [], waitingCloseChannels: [], totalLimboBalance: 0n } + } // console.log("Listing pending channels") const res = await this.lightning.pendingChannels({ includeRawTx: false }, DeadLineMetadata()) return res.response @@ -195,6 +199,10 @@ export default class { } async Health(): Promise { + // Skip health check when bypass is enabled + if (this.liquidProvider.getSettings().useOnlyLiquidityProvider) { + return + } // console.log("Checking health") if (!this.ready) { throw new Error("not ready") @@ -324,6 +332,10 @@ export default class { } async NewAddress(addressType: Types.AddressType, { useProvider, from }: TxActionOptions): Promise { + // Force use of provider when bypass is enabled (addresses not supported by provider, but we should fail gracefully) + if (this.liquidProvider.getSettings().useOnlyLiquidityProvider) { + throw new Error("Address generation not supported when USE_ONLY_LIQUIDITY_PROVIDER is enabled") + } // console.log("Creating new address") let lndAddressType: AddressType switch (addressType) { @@ -355,7 +367,9 @@ export default class { async NewInvoice(value: number, memo: string, expiry: number, { useProvider, from }: TxActionOptions, blind = false): Promise { // console.log("Creating new invoice") - if (useProvider) { + // Force use of provider when bypass is enabled + const mustUseProvider = this.liquidProvider.getSettings().useOnlyLiquidityProvider || useProvider + if (mustUseProvider) { console.log("using provider") const invoice = await this.liquidProvider.AddInvoice(value, memo, from, expiry) const providerDst = this.liquidProvider.GetProviderDestination() @@ -372,6 +386,23 @@ export default class { } async DecodeInvoice(paymentRequest: string): Promise { + if (this.liquidProvider.getSettings().useOnlyLiquidityProvider) { + // Use light-bolt11-decoder when LND is bypassed + const decoded = decodeBolt11(paymentRequest) + let numSatoshis = 0 + let paymentHash = '' + + for (const section of decoded.sections) { + if (section.name === 'amount') { + // Amount is in millisatoshis + numSatoshis = Math.floor(Number(section.value) / 1000) + } else if (section.name === 'payment_hash') { + paymentHash = section.value as string + } + } + + return { numSatoshis, paymentHash } + } // console.log("Decoding invoice") const res = await this.lightning.decodePayReq({ payReq: paymentRequest }, DeadLineMetadata()) return { numSatoshis: Number(res.response.numSatoshis), paymentHash: res.response.paymentHash } @@ -400,7 +431,9 @@ export default class { this.log("outgoing ops locked, rejecting payment request") throw new Error("lnd node is currently out of sync") } - if (useProvider) { + // Force use of provider when bypass is enabled + const mustUseProvider = this.liquidProvider.getSettings().useOnlyLiquidityProvider || useProvider + if (mustUseProvider) { const res = await this.liquidProvider.PayInvoice(invoice, decodedAmount, from) const providerDst = this.liquidProvider.GetProviderDestination() return { feeSat: res.network_fee + res.service_fee, valueSat: res.amount_paid, paymentPreimage: res.preimage, providerDst } @@ -455,6 +488,10 @@ export default class { } async PayAddress(address: string, amount: number, satPerVByte: number, label = "", { useProvider, from }: TxActionOptions): Promise { + // Address payments not supported when bypass is enabled + if (this.liquidProvider.getSettings().useOnlyLiquidityProvider) { + throw new Error("Address payments not supported when USE_ONLY_LIQUIDITY_PROVIDER is enabled") + } // console.log("Paying address") if (this.outgoingOpsLocked) { this.log("outgoing ops locked, rejecting payment request") @@ -551,6 +588,9 @@ export default class { } async GetForwardingHistory(indexOffset: number, startTime = 0, endTime = 0): Promise { + if (this.liquidProvider.getSettings().useOnlyLiquidityProvider) { + return { forwardingEvents: [], lastOffsetIndex: indexOffset } + } // console.log("Getting forwarding history") const { response } = await this.lightning.forwardingHistory({ indexOffset, numMaxEvents: 0, startTime: BigInt(startTime), endTime: BigInt(endTime), peerAliasLookup: false }, DeadLineMetadata()) return response @@ -586,6 +626,9 @@ export default class { } async GetLatestPaymentIndex(from = 0) { + if (this.liquidProvider.getSettings().useOnlyLiquidityProvider) { + return from + } // console.log("Getting latest payment index") let indexOffset = BigInt(from) while (true) { diff --git a/src/services/main/index.ts b/src/services/main/index.ts index dea44a6f..601ff291 100644 --- a/src/services/main/index.ts +++ b/src/services/main/index.ts @@ -212,6 +212,11 @@ export default class { addressPaidCb: AddressPaidCb = (txOutput, address, amount, used) => { return this.storage.StartTransaction(async tx => { + // On-chain payments not supported when bypass is enabled + if (this.liquidityProvider.getSettings().useOnlyLiquidityProvider) { + getLogger({})("addressPaidCb called but USE_ONLY_LIQUIDITY_PROVIDER is enabled, ignoring") + return + } const { blockHeight } = await this.lnd.GetInfo() const userAddress = await this.storage.paymentStorage.GetAddressOwner(address, tx) if (!userAddress) { diff --git a/src/services/main/liquidityManager.ts b/src/services/main/liquidityManager.ts index ac72975e..183988ef 100644 --- a/src/services/main/liquidityManager.ts +++ b/src/services/main/liquidityManager.ts @@ -71,6 +71,10 @@ export class LiquidityManager { } afterInInvoicePaid = async () => { + // Skip channel ordering if using only liquidity provider + if (this.settings.getSettings().liquiditySettings.useOnlyLiquidityProvider) { + return + } try { await this.orderChannelIfNeeded() } catch (e: any) { @@ -91,6 +95,10 @@ export class LiquidityManager { afterOutInvoicePaid = async () => { } shouldDrainProvider = async () => { + // Skip draining when bypass is enabled + if (this.settings.getSettings().liquiditySettings.useOnlyLiquidityProvider) { + return + } const maxW = await this.liquidityProvider.GetLatestMaxWithdrawable() const { remote } = await this.lnd.ChannelBalance() const drainable = Math.min(maxW, remote) @@ -148,6 +156,10 @@ export class LiquidityManager { shouldOpenChannel = async (): Promise<{ shouldOpen: false } | { shouldOpen: true, maxSpendable: number }> => { + // Skip channel operations if using only liquidity provider + if (this.settings.getSettings().liquiditySettings.useOnlyLiquidityProvider) { + return { shouldOpen: false } + } const threshold = this.settings.getSettings().lspSettings.channelThreshold if (threshold === 0) { return { shouldOpen: false } diff --git a/src/services/main/offerManager.ts b/src/services/main/offerManager.ts index ec21c6f4..11113746 100644 --- a/src/services/main/offerManager.ts +++ b/src/services/main/offerManager.ts @@ -262,10 +262,18 @@ export class OfferManager { async getNofferInvoice(offerReq: NofferData, appId: string, clinkRequester?: { pub: string, eventId: string }): Promise<{ success: true, invoice: string } | { success: false, code: number, max: number }> { try { - const { remote } = await this.lnd.ChannelBalance() - let maxSendable = remote - if (remote === 0 && (await this.liquidityManager.liquidityProvider.IsReady())) { - maxSendable = 10_000_000 + // When bypass is enabled, use provider balance instead of LND channel balance + let maxSendable = 0 + if (this.liquidityManager.settings.getSettings().liquiditySettings.useOnlyLiquidityProvider) { + if (await this.liquidityManager.liquidityProvider.IsReady()) { + maxSendable = 10_000_000 + } + } else { + const { remote } = await this.lnd.ChannelBalance() + maxSendable = remote + if (remote === 0 && (await this.liquidityManager.liquidityProvider.IsReady())) { + maxSendable = 10_000_000 + } } const split = offerReq.offer.split(':') if (split.length === 1) { diff --git a/src/services/main/paymentManager.ts b/src/services/main/paymentManager.ts index 4dcd0b6e..08bf8ee0 100644 --- a/src/services/main/paymentManager.ts +++ b/src/services/main/paymentManager.ts @@ -115,6 +115,11 @@ export default class { } checkPendingLndPayment = async (log: PubLogger, p: UserInvoicePayment) => { + // Skip LND payment checks when bypass is enabled + if (this.liquidityManager.settings.getSettings().liquiditySettings.useOnlyLiquidityProvider) { + log("USE_ONLY_LIQUIDITY_PROVIDER enabled, skipping LND payment check for", p.serial_id) + return + } const decoded = await this.lnd.DecodeInvoice(p.invoice) const payment = await this.lnd.GetPaymentFromHash(decoded.paymentHash) if (!payment || payment.paymentHash !== decoded.paymentHash) { diff --git a/src/services/main/watchdog.ts b/src/services/main/watchdog.ts index 1e6415cc..778e09ce 100644 --- a/src/services/main/watchdog.ts +++ b/src/services/main/watchdog.ts @@ -211,6 +211,10 @@ export class Watchdog { } PaymentRequested = async () => { + // Skip watchdog check when bypass is enabled + if (this.liquidProvider.getSettings().useOnlyLiquidityProvider) { + return + } if (!this.ready) { throw new Error("Watchdog not ready") }