From dff50783bbe3ce6d67e72cd6c0f6ddaa02ba1b65 Mon Sep 17 00:00:00 2001 From: shocknet-justin Date: Tue, 23 Dec 2025 10:11:31 -0500 Subject: [PATCH 1/8] check chain --- src/services/main/init.ts | 3 +- src/services/main/paymentManager.ts | 171 +++++++++++++++++++++++++++- 2 files changed, 170 insertions(+), 4 deletions(-) diff --git a/src/services/main/init.ts b/src/services/main/init.ts index 776e989a..dd3051e8 100644 --- a/src/services/main/init.ts +++ b/src/services/main/init.ts @@ -72,7 +72,8 @@ export const initMainHandler = async (log: PubLogger, settingsManager: SettingsM if (stop) { return } - await mainHandler.paymentManager.checkPendingPayments() + await mainHandler.paymentManager.checkPaymentStatus() + await mainHandler.paymentManager.checkMissedChainTxs() await mainHandler.paymentManager.CleanupOldUnpaidInvoices() await mainHandler.appUserManager.CleanupInactiveUsers() await mainHandler.appUserManager.CleanupNeverActiveUsers() diff --git a/src/services/main/paymentManager.ts b/src/services/main/paymentManager.ts index e5cb9bdf..1c67afe8 100644 --- a/src/services/main/paymentManager.ts +++ b/src/services/main/paymentManager.ts @@ -75,11 +75,11 @@ export default class { this.swaps.Stop() } - checkPendingPayments = async () => { - const log = getLogger({ component: 'pendingPaymentsOnStart' }) + checkPaymentStatus = async () => { + const log = getLogger({ component: 'checkPaymentStatus' }) const pendingPayments = await this.storage.paymentStorage.GetPendingPayments() for (const p of pendingPayments) { - log("checking state of payment: ", p.invoice) + log("checking status of payment: ", p.invoice) if (p.internal) { log("found pending internal payment", p.serial_id) } else if (p.liquidityProvider) { @@ -170,6 +170,171 @@ export default class { } } + checkMissedChainTxs = async () => { + const log = getLogger({ component: 'checkMissedChainTxs' }) + + // Skip chain tx check when bypass is enabled + if (this.liquidityManager.settings.getSettings().liquiditySettings.useOnlyLiquidityProvider) { + log("USE_ONLY_LIQUIDITY_PROVIDER enabled, skipping chain tx check") + return + } + + log("checking for missed confirmed chain transactions...") + + // Get transactions from LND (only includes transactions relevant to the wallet) + // Use startHeight 0 to check all transactions + const startHeight = 0 + + try { + const { transactions } = await this.lnd.GetTransactions(startHeight) + log(`retrieved ${transactions.length} transactions from LND`) + + let recoveredCount = 0 + + for (const tx of transactions) { + // Only process confirmed transactions (at least 1 confirmation) + // Pending transactions (0 confirmations) are handled by SubscribeAddressPaid() + // and can be dropped from mempool, so we don't process them here + if (tx.numConfirmations === 0) { + continue + } + + // Check each output detail for addresses we care about + if (!tx.outputDetails || tx.outputDetails.length === 0) { + continue + } + + for (const output of tx.outputDetails) { + // Only process outputs that are our addresses and have an address + if (!output.address || !output.isOurAddress) { + continue + } + + // Check if this address belongs to a user (lazy lookup - only for addresses in transactions) + const userAddress = await this.storage.paymentStorage.GetAddressOwner(output.address) + if (!userAddress) { + // Not a user address, skip + continue + } + + // Check if we already have this transaction in the database + const existingTx = await this.storage.paymentStorage.GetAddressReceivingTransactionOwner( + output.address, + tx.txHash + ) + + if (existingTx) { + // Transaction already recorded, skip + continue + } + + // This is a missed transaction - process it + const amount = Number(output.amount) + const outputIndex = Number(output.outputIndex) + log(`processing missed chain tx: address=${output.address}, txHash=${tx.txHash}, amount=${amount}, outputIndex=${outputIndex}`) + + try { + await this.storage.StartTransaction(async dbTx => { + if (!userAddress.linkedApplication) { + log(ERROR, "found address with no linked application during recovery:", output.address) + return + } + + const isManagedUser = userAddress.user.user_id !== userAddress.linkedApplication.owner.user_id + const fee = this.getReceiveServiceFee(Types.UserOperationType.INCOMING_TX, amount, isManagedUser) + const blockHeight = tx.blockHeight || 0 + + // Add the transaction record + const addedTx = await this.storage.paymentStorage.AddAddressReceivingTransaction( + userAddress, + tx.txHash, + outputIndex, + amount, + fee, + false, // not internal + blockHeight, + dbTx + ) + + // Update balance + const addressData = `${output.address}:${tx.txHash}` + this.storage.eventsLog.LogEvent({ + type: 'address_paid', + userId: userAddress.user.user_id, + appId: userAddress.linkedApplication.app_id, + appUserId: "", + balance: userAddress.user.balance_sats, + data: addressData, + amount + }) + + await this.storage.userStorage.IncrementUserBalance( + userAddress.user.user_id, + amount - fee, + addressData, + dbTx + ) + + if (fee > 0) { + await this.storage.userStorage.IncrementUserBalance( + userAddress.linkedApplication.owner.user_id, + fee, + 'fees', + dbTx + ) + } + + // Send operation to Nostr + const operationId = `${Types.UserOperationType.INCOMING_TX}-${addedTx.serial_id}` + const op = { + amount, + paidAtUnix: tx.timeStamp ? Number(tx.timeStamp) : Math.floor(Date.now() / 1000), + inbound: true, + type: Types.UserOperationType.INCOMING_TX, + identifier: userAddress.address, + operationId, + network_fee: 0, + service_fee: fee, + confirmed: tx.numConfirmations > 0, + tx_hash: tx.txHash, + internal: false + } + + // Note: sendOperationToNostr is not available here, but the operation will be synced + // when the user next syncs their operations + + this.utils.stateBundler.AddTxPoint( + 'addressWasPaid', + amount, + { used: 'lnd', from: 'system', timeDiscount: true }, + userAddress.linkedApplication.app_id + ) + + recoveredCount++ + log(`successfully processed missed chain tx: address=${output.address}, txHash=${tx.txHash}, amount=${amount}`) + }, "process missed chain tx") + } catch (err: any) { + log(ERROR, `failed to process missed chain tx for address=${output.address}, txHash=${tx.txHash}:`, err.message || err) + this.utils.stateBundler.AddTxPointFailed( + 'addressWasPaid', + amount, + { used: 'lnd', from: 'system' }, + userAddress.linkedApplication?.app_id + ) + } + } + } + + if (recoveredCount > 0) { + log(`processed ${recoveredCount} missed chain tx(s)`) + } else { + log("no missed chain transactions found") + } + } catch (err: any) { + log(ERROR, "failed to check for missed chain transactions:", err.message || err) + } + } + getReceiveServiceFee = (action: Types.UserOperationType, amount: number, managedUser: boolean): number => { switch (action) { case Types.UserOperationType.INCOMING_TX: From e65bf8c451e7b00cc400586c63b3ea4806b8e4d4 Mon Sep 17 00:00:00 2001 From: shocknet-justin Date: Tue, 23 Dec 2025 10:18:55 -0500 Subject: [PATCH 2/8] root credit --- src/services/main/paymentManager.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/services/main/paymentManager.ts b/src/services/main/paymentManager.ts index 1c67afe8..f97179c9 100644 --- a/src/services/main/paymentManager.ts +++ b/src/services/main/paymentManager.ts @@ -213,7 +213,10 @@ export default class { // Check if this address belongs to a user (lazy lookup - only for addresses in transactions) const userAddress = await this.storage.paymentStorage.GetAddressOwner(output.address) if (!userAddress) { - // Not a user address, skip + // Root address - track for metrics (matches addressPaidCb behavior) + const amount = Number(output.amount) + const outputIndex = Number(output.outputIndex) + await this.storage.metricsStorage.AddRootOperation("chain", `${output.address}:${tx.txHash}:${outputIndex}`, amount) continue } From 8820e85bfd4afe814deadf19b446db94c7287ad7 Mon Sep 17 00:00:00 2001 From: shocknet-justin Date: Tue, 23 Dec 2025 10:24:07 -0500 Subject: [PATCH 3/8] root ops --- src/services/main/paymentManager.ts | 30 ++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/src/services/main/paymentManager.ts b/src/services/main/paymentManager.ts index f97179c9..5473b244 100644 --- a/src/services/main/paymentManager.ts +++ b/src/services/main/paymentManager.ts @@ -12,6 +12,7 @@ import { Payment_PaymentStatus } from '../../../proto/lnd/lightning.js' import { Event, verifiedSymbol, verifyEvent } from 'nostr-tools' import { AddressReceivingTransaction } from '../storage/entity/AddressReceivingTransaction.js' import { UserTransactionPayment } from '../storage/entity/UserTransactionPayment.js' +import { UserReceivingAddress } from '../storage/entity/UserReceivingAddress.js' import { Watchdog } from './watchdog.js' import { LiquidityManager } from './liquidityManager.js' import { Utils } from '../helpers/utilsWrapper.js' @@ -204,19 +205,38 @@ export default class { continue } + // First pass: check if transaction has any user address outputs + // If it does, root outputs are likely change and shouldn't be tracked as new operations + let hasUserOutputs = false + const outputsToProcess: Array<{ output: typeof tx.outputDetails[0], userAddress: UserReceivingAddress | null }> = [] + for (const output of tx.outputDetails) { // Only process outputs that are our addresses and have an address if (!output.address || !output.isOurAddress) { continue } - // Check if this address belongs to a user (lazy lookup - only for addresses in transactions) const userAddress = await this.storage.paymentStorage.GetAddressOwner(output.address) + if (userAddress) { + hasUserOutputs = true + } + outputsToProcess.push({ output, userAddress }) + } + + for (const { output, userAddress } of outputsToProcess) { if (!userAddress) { - // Root address - track for metrics (matches addressPaidCb behavior) - const amount = Number(output.amount) - const outputIndex = Number(output.outputIndex) - await this.storage.metricsStorage.AddRootOperation("chain", `${output.address}:${tx.txHash}:${outputIndex}`, amount) + // Root address - only track if transaction doesn't have user outputs + // (if it has user outputs, root outputs are change from user txs, not new funds) + if (!hasUserOutputs) { + // Check if already recorded to prevent duplicates + const amount = Number(output.amount) + const outputIndex = Number(output.outputIndex) + const rootOpId = `${output.address}:${tx.txHash}:${outputIndex}` + const existingRootOp = await this.storage.dbs.FindOne('RootOperation', { where: { operation_identifier: rootOpId, operation_type: "chain" } }) + if (!existingRootOp) { + await this.storage.metricsStorage.AddRootOperation("chain", rootOpId, amount) + } + } continue } From 203069227fca1ed284b53db8c385aede6da8e155 Mon Sep 17 00:00:00 2001 From: shocknet-justin Date: Tue, 23 Dec 2025 10:27:04 -0500 Subject: [PATCH 4/8] small funcs --- src/services/main/paymentManager.ts | 315 ++++++++++++++-------------- 1 file changed, 152 insertions(+), 163 deletions(-) diff --git a/src/services/main/paymentManager.ts b/src/services/main/paymentManager.ts index 5473b244..b759e29b 100644 --- a/src/services/main/paymentManager.ts +++ b/src/services/main/paymentManager.ts @@ -174,7 +174,6 @@ export default class { checkMissedChainTxs = async () => { const log = getLogger({ component: 'checkMissedChainTxs' }) - // Skip chain tx check when bypass is enabled if (this.liquidityManager.settings.getSettings().liquiditySettings.useOnlyLiquidityProvider) { log("USE_ONLY_LIQUIDITY_PROVIDER enabled, skipping chain tx check") return @@ -182,171 +181,11 @@ export default class { log("checking for missed confirmed chain transactions...") - // Get transactions from LND (only includes transactions relevant to the wallet) - // Use startHeight 0 to check all transactions - const startHeight = 0 - try { - const { transactions } = await this.lnd.GetTransactions(startHeight) + const { transactions } = await this.lnd.GetTransactions(0) log(`retrieved ${transactions.length} transactions from LND`) - let recoveredCount = 0 - - for (const tx of transactions) { - // Only process confirmed transactions (at least 1 confirmation) - // Pending transactions (0 confirmations) are handled by SubscribeAddressPaid() - // and can be dropped from mempool, so we don't process them here - if (tx.numConfirmations === 0) { - continue - } - - // Check each output detail for addresses we care about - if (!tx.outputDetails || tx.outputDetails.length === 0) { - continue - } - - // First pass: check if transaction has any user address outputs - // If it does, root outputs are likely change and shouldn't be tracked as new operations - let hasUserOutputs = false - const outputsToProcess: Array<{ output: typeof tx.outputDetails[0], userAddress: UserReceivingAddress | null }> = [] - - for (const output of tx.outputDetails) { - // Only process outputs that are our addresses and have an address - if (!output.address || !output.isOurAddress) { - continue - } - - const userAddress = await this.storage.paymentStorage.GetAddressOwner(output.address) - if (userAddress) { - hasUserOutputs = true - } - outputsToProcess.push({ output, userAddress }) - } - - for (const { output, userAddress } of outputsToProcess) { - if (!userAddress) { - // Root address - only track if transaction doesn't have user outputs - // (if it has user outputs, root outputs are change from user txs, not new funds) - if (!hasUserOutputs) { - // Check if already recorded to prevent duplicates - const amount = Number(output.amount) - const outputIndex = Number(output.outputIndex) - const rootOpId = `${output.address}:${tx.txHash}:${outputIndex}` - const existingRootOp = await this.storage.dbs.FindOne('RootOperation', { where: { operation_identifier: rootOpId, operation_type: "chain" } }) - if (!existingRootOp) { - await this.storage.metricsStorage.AddRootOperation("chain", rootOpId, amount) - } - } - continue - } - - // Check if we already have this transaction in the database - const existingTx = await this.storage.paymentStorage.GetAddressReceivingTransactionOwner( - output.address, - tx.txHash - ) - - if (existingTx) { - // Transaction already recorded, skip - continue - } - - // This is a missed transaction - process it - const amount = Number(output.amount) - const outputIndex = Number(output.outputIndex) - log(`processing missed chain tx: address=${output.address}, txHash=${tx.txHash}, amount=${amount}, outputIndex=${outputIndex}`) - - try { - await this.storage.StartTransaction(async dbTx => { - if (!userAddress.linkedApplication) { - log(ERROR, "found address with no linked application during recovery:", output.address) - return - } - - const isManagedUser = userAddress.user.user_id !== userAddress.linkedApplication.owner.user_id - const fee = this.getReceiveServiceFee(Types.UserOperationType.INCOMING_TX, amount, isManagedUser) - const blockHeight = tx.blockHeight || 0 - - // Add the transaction record - const addedTx = await this.storage.paymentStorage.AddAddressReceivingTransaction( - userAddress, - tx.txHash, - outputIndex, - amount, - fee, - false, // not internal - blockHeight, - dbTx - ) - - // Update balance - const addressData = `${output.address}:${tx.txHash}` - this.storage.eventsLog.LogEvent({ - type: 'address_paid', - userId: userAddress.user.user_id, - appId: userAddress.linkedApplication.app_id, - appUserId: "", - balance: userAddress.user.balance_sats, - data: addressData, - amount - }) - - await this.storage.userStorage.IncrementUserBalance( - userAddress.user.user_id, - amount - fee, - addressData, - dbTx - ) - - if (fee > 0) { - await this.storage.userStorage.IncrementUserBalance( - userAddress.linkedApplication.owner.user_id, - fee, - 'fees', - dbTx - ) - } - - // Send operation to Nostr - const operationId = `${Types.UserOperationType.INCOMING_TX}-${addedTx.serial_id}` - const op = { - amount, - paidAtUnix: tx.timeStamp ? Number(tx.timeStamp) : Math.floor(Date.now() / 1000), - inbound: true, - type: Types.UserOperationType.INCOMING_TX, - identifier: userAddress.address, - operationId, - network_fee: 0, - service_fee: fee, - confirmed: tx.numConfirmations > 0, - tx_hash: tx.txHash, - internal: false - } - - // Note: sendOperationToNostr is not available here, but the operation will be synced - // when the user next syncs their operations - - this.utils.stateBundler.AddTxPoint( - 'addressWasPaid', - amount, - { used: 'lnd', from: 'system', timeDiscount: true }, - userAddress.linkedApplication.app_id - ) - - recoveredCount++ - log(`successfully processed missed chain tx: address=${output.address}, txHash=${tx.txHash}, amount=${amount}`) - }, "process missed chain tx") - } catch (err: any) { - log(ERROR, `failed to process missed chain tx for address=${output.address}, txHash=${tx.txHash}:`, err.message || err) - this.utils.stateBundler.AddTxPointFailed( - 'addressWasPaid', - amount, - { used: 'lnd', from: 'system' }, - userAddress.linkedApplication?.app_id - ) - } - } - } + const recoveredCount = await this.processMissedChainTransactions(transactions, log) if (recoveredCount > 0) { log(`processed ${recoveredCount} missed chain tx(s)`) @@ -358,6 +197,156 @@ export default class { } } + private async processMissedChainTransactions(transactions: any[], log: PubLogger): Promise { + let recoveredCount = 0 + + for (const tx of transactions) { + if (tx.numConfirmations === 0 || !tx.outputDetails || tx.outputDetails.length === 0) { + continue + } + + const outputsWithAddresses = await this.collectOutputsWithAddresses(tx.outputDetails) + const hasUserOutputs = outputsWithAddresses.some(o => o.userAddress !== null) + + for (const { output, userAddress } of outputsWithAddresses) { + if (!userAddress) { + await this.processRootAddressOutput(output, tx, hasUserOutputs, log) + continue + } + + const processed = await this.processUserAddressOutput(output, tx, userAddress, log) + if (processed) { + recoveredCount++ + } + } + } + + return recoveredCount + } + + private async collectOutputsWithAddresses(outputDetails: any[]): Promise> { + const outputs: Array<{ output: any, userAddress: UserReceivingAddress | null }> = [] + + for (const output of outputDetails) { + if (!output.address || !output.isOurAddress) { + continue + } + + const userAddress = await this.storage.paymentStorage.GetAddressOwner(output.address) + outputs.push({ output, userAddress }) + } + + return outputs + } + + private async processRootAddressOutput(output: any, tx: any, hasUserOutputs: boolean, log: PubLogger): Promise { + // Root outputs in transactions with user outputs are change, not new funds + if (hasUserOutputs) { + return + } + + const amount = Number(output.amount) + const outputIndex = Number(output.outputIndex) + const rootOpId = `${output.address}:${tx.txHash}:${outputIndex}` + + const existingRootOp = await this.storage.dbs.FindOne('RootOperation', { + where: { operation_identifier: rootOpId, operation_type: "chain" } + }) + + if (!existingRootOp) { + await this.storage.metricsStorage.AddRootOperation("chain", rootOpId, amount) + } + } + + private async processUserAddressOutput(output: any, tx: any, userAddress: UserReceivingAddress, log: PubLogger): Promise { + const existingTx = await this.storage.paymentStorage.GetAddressReceivingTransactionOwner( + output.address, + tx.txHash + ) + + if (existingTx) { + return false + } + + const amount = Number(output.amount) + const outputIndex = Number(output.outputIndex) + log(`processing missed chain tx: address=${output.address}, txHash=${tx.txHash}, amount=${amount}, outputIndex=${outputIndex}`) + + try { + await this.recordMissedUserTransaction(output, tx, userAddress, amount, outputIndex, log) + return true + } catch (err: any) { + log(ERROR, `failed to process missed chain tx for address=${output.address}, txHash=${tx.txHash}:`, err.message || err) + this.utils.stateBundler.AddTxPointFailed( + 'addressWasPaid', + amount, + { used: 'lnd', from: 'system' }, + userAddress.linkedApplication?.app_id + ) + return false + } + } + + private async recordMissedUserTransaction(output: any, tx: any, userAddress: UserReceivingAddress, amount: number, outputIndex: number, log: PubLogger): Promise { + await this.storage.StartTransaction(async dbTx => { + if (!userAddress.linkedApplication) { + log(ERROR, "found address with no linked application during recovery:", output.address) + return + } + + const isManagedUser = userAddress.user.user_id !== userAddress.linkedApplication.owner.user_id + const fee = this.getReceiveServiceFee(Types.UserOperationType.INCOMING_TX, amount, isManagedUser) + const blockHeight = tx.blockHeight || 0 + + const addedTx = await this.storage.paymentStorage.AddAddressReceivingTransaction( + userAddress, + tx.txHash, + outputIndex, + amount, + fee, + false, + blockHeight, + dbTx + ) + + const addressData = `${output.address}:${tx.txHash}` + this.storage.eventsLog.LogEvent({ + type: 'address_paid', + userId: userAddress.user.user_id, + appId: userAddress.linkedApplication.app_id, + appUserId: "", + balance: userAddress.user.balance_sats, + data: addressData, + amount + }) + + await this.storage.userStorage.IncrementUserBalance( + userAddress.user.user_id, + amount - fee, + addressData, + dbTx + ) + + if (fee > 0) { + await this.storage.userStorage.IncrementUserBalance( + userAddress.linkedApplication.owner.user_id, + fee, + 'fees', + dbTx + ) + } + + this.utils.stateBundler.AddTxPoint( + 'addressWasPaid', + amount, + { used: 'lnd', from: 'system', timeDiscount: true }, + userAddress.linkedApplication.app_id + ) + + log(`successfully processed missed chain tx: address=${output.address}, txHash=${tx.txHash}, amount=${amount}`) + }, "process missed chain tx") + } + getReceiveServiceFee = (action: Types.UserOperationType, amount: number, managedUser: boolean): number => { switch (action) { case Types.UserOperationType.INCOMING_TX: From 6eca15020e6305d4f7697a30fd74666d27d1894a Mon Sep 17 00:00:00 2001 From: shocknet-justin Date: Tue, 23 Dec 2025 10:35:12 -0500 Subject: [PATCH 5/8] check height --- src/services/main/paymentManager.ts | 14 +++++++++++--- src/services/storage/entity/TrackedProvider.ts | 3 +++ src/services/storage/liquidityStorage.ts | 9 +++++++++ .../1766000000000-tracked_provider_height.ts | 14 ++++++++++++++ .../1766504040000-tracked_provider_height.ts | 14 ++++++++++++++ src/services/storage/migrations/runner.ts | 3 ++- 6 files changed, 53 insertions(+), 4 deletions(-) create mode 100644 src/services/storage/migrations/1766000000000-tracked_provider_height.ts create mode 100644 src/services/storage/migrations/1766504040000-tracked_provider_height.ts diff --git a/src/services/main/paymentManager.ts b/src/services/main/paymentManager.ts index b759e29b..e5cc3fb7 100644 --- a/src/services/main/paymentManager.ts +++ b/src/services/main/paymentManager.ts @@ -179,14 +179,22 @@ export default class { return } - log("checking for missed confirmed chain transactions...") - try { - const { transactions } = await this.lnd.GetTransactions(0) + const lndInfo = await this.lnd.GetInfo() + const lndPubkey = lndInfo.identityPubkey + + const startHeight = await this.storage.liquidityStorage.GetLatestCheckedHeight('lnd', lndPubkey) + log(`checking for missed confirmed chain transactions from height ${startHeight}...`) + + const { transactions } = await this.lnd.GetTransactions(startHeight) log(`retrieved ${transactions.length} transactions from LND`) const recoveredCount = await this.processMissedChainTransactions(transactions, log) + // Update latest checked height to current block height + const currentHeight = lndInfo.blockHeight + await this.storage.liquidityStorage.UpdateLatestCheckedHeight('lnd', lndPubkey, currentHeight) + if (recoveredCount > 0) { log(`processed ${recoveredCount} missed chain tx(s)`) } else { diff --git a/src/services/storage/entity/TrackedProvider.ts b/src/services/storage/entity/TrackedProvider.ts index 5c07a64c..e9381205 100644 --- a/src/services/storage/entity/TrackedProvider.ts +++ b/src/services/storage/entity/TrackedProvider.ts @@ -18,6 +18,9 @@ export class TrackedProvider { @Column({ default: 0 }) latest_distruption_at_unix: number + @Column({ default: 0 }) + latest_checked_height: number + @CreateDateColumn() created_at: Date diff --git a/src/services/storage/liquidityStorage.ts b/src/services/storage/liquidityStorage.ts index 74364e33..1b349d40 100644 --- a/src/services/storage/liquidityStorage.ts +++ b/src/services/storage/liquidityStorage.ts @@ -64,4 +64,13 @@ export class LiquidityStorage { async UpdateTrackedProviderDisruption(providerType: 'lnd' | 'lnPub', pub: string, latestDisruptionAtUnix: number) { return this.dbs.Update('TrackedProvider', { provider_pubkey: pub, provider_type: providerType }, { latest_distruption_at_unix: latestDisruptionAtUnix }) } + + async GetLatestCheckedHeight(providerType: 'lnd' | 'lnPub', pub: string): Promise { + const provider = await this.GetTrackedProvider(providerType, pub) + return provider?.latest_checked_height || 0 + } + + async UpdateLatestCheckedHeight(providerType: 'lnd' | 'lnPub', pub: string, height: number) { + return this.dbs.Update('TrackedProvider', { provider_pubkey: pub, provider_type: providerType }, { latest_checked_height: height }) + } } \ No newline at end of file diff --git a/src/services/storage/migrations/1766000000000-tracked_provider_height.ts b/src/services/storage/migrations/1766000000000-tracked_provider_height.ts new file mode 100644 index 00000000..5e0b349d --- /dev/null +++ b/src/services/storage/migrations/1766000000000-tracked_provider_height.ts @@ -0,0 +1,14 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class TrackedProviderHeight1766504040000 implements MigrationInterface { + name = 'TrackedProviderHeight1766504040000' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "tracked_provider" ADD "latest_checked_height" integer NOT NULL DEFAULT (0)`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "tracked_provider" DROP COLUMN "latest_checked_height"`); + } +} + diff --git a/src/services/storage/migrations/1766504040000-tracked_provider_height.ts b/src/services/storage/migrations/1766504040000-tracked_provider_height.ts new file mode 100644 index 00000000..5e0b349d --- /dev/null +++ b/src/services/storage/migrations/1766504040000-tracked_provider_height.ts @@ -0,0 +1,14 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class TrackedProviderHeight1766504040000 implements MigrationInterface { + name = 'TrackedProviderHeight1766504040000' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "tracked_provider" ADD "latest_checked_height" integer NOT NULL DEFAULT (0)`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "tracked_provider" DROP COLUMN "latest_checked_height"`); + } +} + diff --git a/src/services/storage/migrations/runner.ts b/src/services/storage/migrations/runner.ts index 58ada956..a112f240 100644 --- a/src/services/storage/migrations/runner.ts +++ b/src/services/storage/migrations/runner.ts @@ -30,13 +30,14 @@ import { AdminSettings1761683639419 } from './1761683639419-admin_settings.js' import { TxSwap1762890527098 } from './1762890527098-tx_swap.js' import { TxSwapAddress1764779178945 } from './1764779178945-tx_swap_address.js' import { ClinkRequester1765497600000 } from './1765497600000-clink_requester.js' +import { TrackedProviderHeight1766504040000 } from './1766504040000-tracked_provider_height.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, AdminSettings1761683639419, TxSwap1762890527098, TxSwapAddress1764779178945, ClinkRequester1765497600000] + UserAccess1759426050669, AddBlindToUserOffer1760000000000, ApplicationAvatarUrl1761000001000, AdminSettings1761683639419, TxSwap1762890527098, TxSwapAddress1764779178945, ClinkRequester1765497600000, TrackedProviderHeight1766504040000] export const allMetricsMigrations = [LndMetrics1703170330183, ChannelRouting1709316653538, HtlcCount1724266887195, BalanceEvents1724860966825, RootOps1732566440447, RootOpsTime1745428134124, ChannelEvents1750777346411] /* export const TypeOrmMigrationRunner = async (log: PubLogger, storageManager: Storage, settings: DbSettings, arg: string | undefined): Promise => { From f2503af80a0adb774a8cf31af170f631d98c92af Mon Sep 17 00:00:00 2001 From: boufni95 Date: Thu, 8 Jan 2026 17:02:22 +0000 Subject: [PATCH 6/8] change outputs --- src/services/helpers/logger.ts | 10 +- src/services/lnd/lnd.ts | 17 +++ src/services/main/index.ts | 6 +- src/services/main/paymentManager.ts | 178 +++++++------------------ src/services/metrics/index.ts | 4 + src/services/storage/metricsStorage.ts | 4 + 6 files changed, 83 insertions(+), 136 deletions(-) diff --git a/src/services/helpers/logger.ts b/src/services/helpers/logger.ts index 80c73500..f58f6c97 100644 --- a/src/services/helpers/logger.ts +++ b/src/services/helpers/logger.ts @@ -23,8 +23,8 @@ const sanitizeFileName = (fileName: string): string => { const openWriter = (fileName: string): Writer => { const now = new Date() const date = `${now.getFullYear()}-${z(now.getMonth() + 1)}-${z(now.getDate())}` - const sanitizedFileName = sanitizeFileName(fileName) - const logPath = `${logsDir}/${sanitizedFileName}_${date}.log` + // const sanitizedFileName = sanitizeFileName(fileName) + const logPath = `${logsDir}/${fileName}_${date}.log` // Ensure parent directory exists const dirPath = logPath.substring(0, logPath.lastIndexOf('/')) if (!fs.existsSync(dirPath)) { @@ -48,13 +48,13 @@ if (!fs.existsSync(`${logsDir}/components`)) { export const getLogger = (params: LoggerParams): PubLogger => { const writers: Writer[] = [] if (params.appName) { - writers.push(openWriter(`apps/${params.appName}`)) + writers.push(openWriter(`apps/${sanitizeFileName(params.appName)}`)) } if (params.userId) { - writers.push(openWriter(`users/${params.userId}`)) + writers.push(openWriter(`users/${sanitizeFileName(params.userId)}`)) } if (params.component) { - writers.push(openWriter(`components/${params.component}`)) + writers.push(openWriter(`components/${sanitizeFileName(params.component)}`)) } if (writers.length === 0) { writers.push(rootWriter) diff --git a/src/services/lnd/lnd.ts b/src/services/lnd/lnd.ts index 65855b2e..33e2ed1e 100644 --- a/src/services/lnd/lnd.ts +++ b/src/services/lnd/lnd.ts @@ -23,6 +23,7 @@ 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'; +import { ListAddressesResponse } from '../../../proto/lnd/walletkit.js'; const DeadLineMetadata = (deadline = 10 * 1000) => ({ deadline: Date.now() + deadline }) const deadLndRetrySeconds = 20 @@ -32,6 +33,7 @@ type NodeSettingsOverride = { lndCertPath: string lndMacaroonPath: string } +export type LndAddress = { address: string, change: boolean } export default class { lightning: LightningClient invoices: InvoicesClient @@ -331,6 +333,21 @@ export default class { }) } + async IsChangeAddress(address: string): Promise { + const addresses = await this.ListAddresses() + const addr = addresses.find(a => a.address === address) + if (!addr) { + throw new Error(`address ${address} not found in list of addresses`) + } + return addr.change + } + + async ListAddresses(): Promise { + const res = await this.walletKit.listAddresses({ accountName: "", showCustomAccounts: false }, DeadLineMetadata()) + const addresses = res.response.accountWithAddresses.map(a => a.addresses.map(a => ({ address: a.address, change: a.isInternal }))).flat() + return addresses + } + 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) { diff --git a/src/services/main/index.ts b/src/services/main/index.ts index fa9be22e..ef07529e 100644 --- a/src/services/main/index.ts +++ b/src/services/main/index.ts @@ -80,7 +80,7 @@ export default class { 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) + this.paymentManager = new PaymentManager(this.storage, this.metricsManager, this.lnd, this.settings, this.liquidityManager, this.utils, 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) @@ -213,6 +213,10 @@ export default class { const { blockHeight } = await this.lnd.GetInfo() const userAddress = await this.storage.paymentStorage.GetAddressOwner(address, tx) if (!userAddress) { + const isChange = await this.lnd.IsChangeAddress(address) + if (isChange) { + return + } await this.metricsManager.AddRootAddressPaid(address, txOutput, amount) return } diff --git a/src/services/main/paymentManager.ts b/src/services/main/paymentManager.ts index e5cc3fb7..222c3a74 100644 --- a/src/services/main/paymentManager.ts +++ b/src/services/main/paymentManager.ts @@ -19,6 +19,9 @@ import { Utils } from '../helpers/utilsWrapper.js' import { UserInvoicePayment } from '../storage/entity/UserInvoicePayment.js' import SettingsManager from './settingsManager.js' import { Swaps, TransactionSwapData } from '../lnd/swaps.js' +import { Transaction, OutputDetail } from '../../../proto/lnd/lightning.js' +import { LndAddress } from '../lnd/lnd.js' +import Metrics from '../metrics/index.js' interface UserOperationInfo { serial_id: number paid_amount: number @@ -57,8 +60,10 @@ export default class { utils: Utils swaps: Swaps invoiceLock: InvoiceLock - constructor(storage: Storage, lnd: LND, settings: SettingsManager, liquidityManager: LiquidityManager, utils: Utils, addressPaidCb: AddressPaidCb, invoicePaidCb: InvoicePaidCb) { + metrics: Metrics + constructor(storage: Storage, metrics: Metrics, lnd: LND, settings: SettingsManager, liquidityManager: LiquidityManager, utils: Utils, addressPaidCb: AddressPaidCb, invoicePaidCb: InvoicePaidCb) { this.storage = storage + this.metrics = metrics this.settings = settings this.lnd = lnd this.liquidityManager = liquidityManager @@ -173,26 +178,18 @@ export default class { checkMissedChainTxs = async () => { const log = getLogger({ component: 'checkMissedChainTxs' }) - + if (this.liquidityManager.settings.getSettings().liquiditySettings.useOnlyLiquidityProvider) { log("USE_ONLY_LIQUIDITY_PROVIDER enabled, skipping chain tx check") return } try { - const lndInfo = await this.lnd.GetInfo() - const lndPubkey = lndInfo.identityPubkey - - const startHeight = await this.storage.liquidityStorage.GetLatestCheckedHeight('lnd', lndPubkey) - log(`checking for missed confirmed chain transactions from height ${startHeight}...`) + const { txs, currentHeight, lndPubkey } = await this.getLatestTransactions(log) - const { transactions } = await this.lnd.GetTransactions(startHeight) - log(`retrieved ${transactions.length} transactions from LND`) - - const recoveredCount = await this.processMissedChainTransactions(transactions, log) + const recoveredCount = await this.processMissedChainTransactions(txs, log) // Update latest checked height to current block height - const currentHeight = lndInfo.blockHeight await this.storage.liquidityStorage.UpdateLatestCheckedHeight('lnd', lndPubkey, currentHeight) if (recoveredCount > 0) { @@ -205,24 +202,36 @@ export default class { } } - private async processMissedChainTransactions(transactions: any[], log: PubLogger): Promise { - let recoveredCount = 0 + private async getLatestTransactions(log: PubLogger): Promise<{ txs: Transaction[], currentHeight: number, lndPubkey: string }> { + const lndInfo = await this.lnd.GetInfo() + const lndPubkey = lndInfo.identityPubkey + const startHeight = await this.storage.liquidityStorage.GetLatestCheckedHeight('lnd', lndPubkey) + log(`checking for missed confirmed chain transactions from height ${startHeight}...`) + + const { transactions } = await this.lnd.GetTransactions(startHeight) + log(`retrieved ${transactions.length} transactions from LND`) + + return { txs: transactions, currentHeight: lndInfo.blockHeight, lndPubkey } + } + + private async processMissedChainTransactions(transactions: Transaction[], log: PubLogger): Promise { + let recoveredCount = 0 + const addresses = await this.lnd.ListAddresses() for (const tx of transactions) { - if (tx.numConfirmations === 0 || !tx.outputDetails || tx.outputDetails.length === 0) { + if (!tx.outputDetails || tx.outputDetails.length === 0) { continue } - const outputsWithAddresses = await this.collectOutputsWithAddresses(tx.outputDetails) - const hasUserOutputs = outputsWithAddresses.some(o => o.userAddress !== null) + const outputsWithAddresses = await this.collectOutputsWithAddresses(tx) for (const { output, userAddress } of outputsWithAddresses) { if (!userAddress) { - await this.processRootAddressOutput(output, tx, hasUserOutputs, log) + await this.processRootAddressOutput(output, tx, addresses, log) continue } - const processed = await this.processUserAddressOutput(output, tx, userAddress, log) + const processed = await this.processUserAddressOutput(output, tx, log) if (processed) { recoveredCount++ } @@ -232,41 +241,37 @@ export default class { return recoveredCount } - private async collectOutputsWithAddresses(outputDetails: any[]): Promise> { - const outputs: Array<{ output: any, userAddress: UserReceivingAddress | null }> = [] - - for (const output of outputDetails) { + private async collectOutputsWithAddresses(tx: Transaction) { + const outputs: { output: OutputDetail, userAddress: UserReceivingAddress | null, tx: Transaction }[] = [] + for (const output of tx.outputDetails) { if (!output.address || !output.isOurAddress) { continue } - const userAddress = await this.storage.paymentStorage.GetAddressOwner(output.address) - outputs.push({ output, userAddress }) + outputs.push({ output, userAddress, tx }) } - return outputs } - private async processRootAddressOutput(output: any, tx: any, hasUserOutputs: boolean, log: PubLogger): Promise { - // Root outputs in transactions with user outputs are change, not new funds - if (hasUserOutputs) { - return + private async processRootAddressOutput(output: OutputDetail, tx: Transaction, addresses: LndAddress[], log: PubLogger): Promise { + const addr = addresses.find(a => a.address === output.address) + if (!addr) { + throw new Error(`address ${output.address} not found in list of addresses`) + } + if (addr.change) { + log(`ignoring change address ${output.address}`) + return false } - - const amount = Number(output.amount) const outputIndex = Number(output.outputIndex) - const rootOpId = `${output.address}:${tx.txHash}:${outputIndex}` - - const existingRootOp = await this.storage.dbs.FindOne('RootOperation', { - where: { operation_identifier: rootOpId, operation_type: "chain" } - }) - - if (!existingRootOp) { - await this.storage.metricsStorage.AddRootOperation("chain", rootOpId, amount) + const existingRootOp = await this.metrics.GetRootAddressTransaction(output.address, tx.txHash, outputIndex) + if (existingRootOp) { + return false } + this.addressPaidCb({ hash: tx.txHash, index: outputIndex }, output.address, Number(output.amount), 'lnd') + return true } - private async processUserAddressOutput(output: any, tx: any, userAddress: UserReceivingAddress, log: PubLogger): Promise { + private async processUserAddressOutput(output: OutputDetail, tx: Transaction, log: PubLogger) { const existingTx = await this.storage.paymentStorage.GetAddressReceivingTransactionOwner( output.address, tx.txHash @@ -279,80 +284,8 @@ export default class { const amount = Number(output.amount) const outputIndex = Number(output.outputIndex) log(`processing missed chain tx: address=${output.address}, txHash=${tx.txHash}, amount=${amount}, outputIndex=${outputIndex}`) - - try { - await this.recordMissedUserTransaction(output, tx, userAddress, amount, outputIndex, log) - return true - } catch (err: any) { - log(ERROR, `failed to process missed chain tx for address=${output.address}, txHash=${tx.txHash}:`, err.message || err) - this.utils.stateBundler.AddTxPointFailed( - 'addressWasPaid', - amount, - { used: 'lnd', from: 'system' }, - userAddress.linkedApplication?.app_id - ) - return false - } - } - - private async recordMissedUserTransaction(output: any, tx: any, userAddress: UserReceivingAddress, amount: number, outputIndex: number, log: PubLogger): Promise { - await this.storage.StartTransaction(async dbTx => { - if (!userAddress.linkedApplication) { - log(ERROR, "found address with no linked application during recovery:", output.address) - return - } - - const isManagedUser = userAddress.user.user_id !== userAddress.linkedApplication.owner.user_id - const fee = this.getReceiveServiceFee(Types.UserOperationType.INCOMING_TX, amount, isManagedUser) - const blockHeight = tx.blockHeight || 0 - - const addedTx = await this.storage.paymentStorage.AddAddressReceivingTransaction( - userAddress, - tx.txHash, - outputIndex, - amount, - fee, - false, - blockHeight, - dbTx - ) - - const addressData = `${output.address}:${tx.txHash}` - this.storage.eventsLog.LogEvent({ - type: 'address_paid', - userId: userAddress.user.user_id, - appId: userAddress.linkedApplication.app_id, - appUserId: "", - balance: userAddress.user.balance_sats, - data: addressData, - amount - }) - - await this.storage.userStorage.IncrementUserBalance( - userAddress.user.user_id, - amount - fee, - addressData, - dbTx - ) - - if (fee > 0) { - await this.storage.userStorage.IncrementUserBalance( - userAddress.linkedApplication.owner.user_id, - fee, - 'fees', - dbTx - ) - } - - this.utils.stateBundler.AddTxPoint( - 'addressWasPaid', - amount, - { used: 'lnd', from: 'system', timeDiscount: true }, - userAddress.linkedApplication.app_id - ) - - log(`successfully processed missed chain tx: address=${output.address}, txHash=${tx.txHash}, amount=${amount}`) - }, "process missed chain tx") + this.addressPaidCb({ hash: tx.txHash, index: outputIndex }, output.address, amount, 'lnd') + return true } getReceiveServiceFee = (action: Types.UserOperationType, amount: number, managedUser: boolean): number => { @@ -825,21 +758,6 @@ export default class { async GetLnurlWithdrawInfo(balanceCheckK1: string): Promise { throw new Error("LNURL withdraw currenlty not supported for non application users") - /*const key = await this.storage.paymentStorage.UseUserEphemeralKey(balanceCheckK1, 'balanceCheck') - const maxWithdrawable = this.GetMaxPayableInvoice(key.user.balance_sats) - const callbackK1 = await this.storage.paymentStorage.AddUserEphemeralKey(key.user.user_id, 'withdraw') - const newBalanceCheckK1 = await this.storage.paymentStorage.AddUserEphemeralKey(key.user.user_id, 'balanceCheck') - const payInfoK1 = await this.storage.paymentStorage.AddUserEphemeralKey(key.user.user_id, 'pay') - return { - tag: "withdrawRequest", - callback: `${this.settings.serviceUrl}/api/guest/lnurl_withdraw/handle`, - defaultDescription: "lnurl withdraw from lightning.pub", - k1: callbackK1.key, - maxWithdrawable: maxWithdrawable * 1000, - minWithdrawable: 10000, - balanceCheck: this.balanceCheckUrl(newBalanceCheckK1.key), - payLink: `${this.settings.serviceUrl}/api/guest/lnurl_pay/info?k1=${payInfoK1.key}`, - }*/ } async HandleLnurlWithdraw(k1: string, invoice: string): Promise { diff --git a/src/services/metrics/index.ts b/src/services/metrics/index.ts index 1e1b30c7..3dbafedb 100644 --- a/src/services/metrics/index.ts +++ b/src/services/metrics/index.ts @@ -414,6 +414,10 @@ export default class Handler { await this.storage.metricsStorage.AddRootOperation("chain", `${address}:${txOutput.hash}:${txOutput.index}`, amount) } + async GetRootAddressTransaction(address: string, txHash: string, index: number) { + return this.storage.metricsStorage.GetRootOperation("chain", `${address}:${txHash}:${index}`) + } + async AddRootInvoicePaid(paymentRequest: string, amount: number) { await this.storage.metricsStorage.AddRootOperation("invoice", paymentRequest, amount) } diff --git a/src/services/storage/metricsStorage.ts b/src/services/storage/metricsStorage.ts index 2a93249f..44da304c 100644 --- a/src/services/storage/metricsStorage.ts +++ b/src/services/storage/metricsStorage.ts @@ -149,6 +149,10 @@ export default class { return this.dbs.CreateAndSave('RootOperation', { operation_type: opType, operation_amount: amount, operation_identifier: id, at_unix: Math.floor(Date.now() / 1000) }, txId) } + async GetRootOperation(opType: string, id: string, txId?: string) { + return this.dbs.FindOne('RootOperation', { where: { operation_type: opType, operation_identifier: id } }, txId) + } + async GetRootOperations({ from, to }: { from?: number, to?: number }, txId?: string) { const q = getTimeQuery({ from, to }) return this.dbs.Find('RootOperation', q, txId) From ece291f0befe9f2044a4f7887ae8c785ea5fe57a Mon Sep 17 00:00:00 2001 From: boufni95 Date: Thu, 8 Jan 2026 17:34:32 +0000 Subject: [PATCH 7/8] fixes --- src/services/helpers/logger.ts | 1 - src/services/main/paymentManager.ts | 2 ++ .../1766000000000-tracked_provider_height.ts | 14 -------------- 3 files changed, 2 insertions(+), 15 deletions(-) delete mode 100644 src/services/storage/migrations/1766000000000-tracked_provider_height.ts diff --git a/src/services/helpers/logger.ts b/src/services/helpers/logger.ts index f58f6c97..4d6601c9 100644 --- a/src/services/helpers/logger.ts +++ b/src/services/helpers/logger.ts @@ -23,7 +23,6 @@ const sanitizeFileName = (fileName: string): string => { const openWriter = (fileName: string): Writer => { const now = new Date() const date = `${now.getFullYear()}-${z(now.getMonth() + 1)}-${z(now.getDate())}` - // const sanitizedFileName = sanitizeFileName(fileName) const logPath = `${logsDir}/${fileName}_${date}.log` // Ensure parent directory exists const dirPath = logPath.substring(0, logPath.lastIndexOf('/')) diff --git a/src/services/main/paymentManager.ts b/src/services/main/paymentManager.ts index 222c3a74..3d103b9f 100644 --- a/src/services/main/paymentManager.ts +++ b/src/services/main/paymentManager.ts @@ -268,6 +268,7 @@ export default class { return false } this.addressPaidCb({ hash: tx.txHash, index: outputIndex }, output.address, Number(output.amount), 'lnd') + .catch(err => log(ERROR, "failed to process root address output:", err.message || err)) return true } @@ -285,6 +286,7 @@ export default class { const outputIndex = Number(output.outputIndex) log(`processing missed chain tx: address=${output.address}, txHash=${tx.txHash}, amount=${amount}, outputIndex=${outputIndex}`) this.addressPaidCb({ hash: tx.txHash, index: outputIndex }, output.address, amount, 'lnd') + .catch(err => log(ERROR, "failed to process user address output:", err.message || err)) return true } diff --git a/src/services/storage/migrations/1766000000000-tracked_provider_height.ts b/src/services/storage/migrations/1766000000000-tracked_provider_height.ts deleted file mode 100644 index 5e0b349d..00000000 --- a/src/services/storage/migrations/1766000000000-tracked_provider_height.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { MigrationInterface, QueryRunner } from "typeorm"; - -export class TrackedProviderHeight1766504040000 implements MigrationInterface { - name = 'TrackedProviderHeight1766504040000' - - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "tracked_provider" ADD "latest_checked_height" integer NOT NULL DEFAULT (0)`); - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "tracked_provider" DROP COLUMN "latest_checked_height"`); - } -} - From be9e7bc035a296790a0e43f02277f4c83b568622 Mon Sep 17 00:00:00 2001 From: boufni95 Date: Thu, 8 Jan 2026 17:52:07 +0000 Subject: [PATCH 8/8] change addr cache --- src/services/lnd/lnd.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/services/lnd/lnd.ts b/src/services/lnd/lnd.ts index 33e2ed1e..daf7b411 100644 --- a/src/services/lnd/lnd.ts +++ b/src/services/lnd/lnd.ts @@ -55,6 +55,7 @@ export default class { liquidProvider: LiquidityProvider utils: Utils unlockLnd: () => Promise + addressesCache: Record = {} constructor(getSettings: () => { lndSettings: LndSettings, lndNodeSettings: LndNodeSettings }, liquidProvider: LiquidityProvider, unlockLnd: () => Promise, utils: Utils, addressPaidCb: AddressPaidCb, invoicePaidCb: InvoicePaidCb, newBlockCb: NewBlockCb, htlcCb: HtlcCb, channelEventCb: ChannelEventCb) { this.getSettings = getSettings this.utils = utils @@ -334,6 +335,10 @@ export default class { } async IsChangeAddress(address: string): Promise { + const cached = this.addressesCache[address] + if (cached) { + return cached.isChange + } const addresses = await this.ListAddresses() const addr = addresses.find(a => a.address === address) if (!addr) { @@ -345,6 +350,7 @@ export default class { async ListAddresses(): Promise { const res = await this.walletKit.listAddresses({ accountName: "", showCustomAccounts: false }, DeadLineMetadata()) const addresses = res.response.accountWithAddresses.map(a => a.addresses.map(a => ({ address: a.address, change: a.isInternal }))).flat() + addresses.forEach(a => this.addressesCache[a.address] = { isChange: a.change }) return addresses }