diff --git a/src/subdomains/core/liquidity-management/adapters/balances/blockchain.adapter.ts b/src/subdomains/core/liquidity-management/adapters/balances/blockchain.adapter.ts index 943e6b9beb..7bbd8fe5c8 100644 --- a/src/subdomains/core/liquidity-management/adapters/balances/blockchain.adapter.ts +++ b/src/subdomains/core/liquidity-management/adapters/balances/blockchain.adapter.ts @@ -25,6 +25,7 @@ export class BlockchainAdapter implements LiquidityBalanceIntegration { private readonly logger = new DfxLogger(BlockchainAdapter); private readonly refreshInterval = 45; // seconds + private readonly balanceTimeout = 30000; // ms private readonly balanceCache = new Map(); private readonly updateCalls = new Map>(); @@ -71,7 +72,15 @@ export class BlockchainAdapter implements LiquidityBalanceIntegration { private async updateCacheFor(blockchain: Blockchain, assets: Asset[]): Promise { if (!this.updateCalls.has(blockchain)) { - this.updateCalls.set(blockchain, this.updateBalancesFor(blockchain, assets)); + const call = Util.timeout(this.updateBalancesFor(blockchain, assets), this.balanceTimeout) + .catch((e) => { + this.logger.error(`Timeout updating balances for ${blockchain}:`, e); + this.invalidateCacheFor(assets); + }) + .finally(() => { + this.updateCalls.delete(blockchain); + }); + this.updateCalls.set(blockchain, call); } return this.updateCalls.get(blockchain); @@ -122,8 +131,6 @@ export class BlockchainAdapter implements LiquidityBalanceIntegration { this.updateTimestamps.set(blockchain, updated); } catch (e) { this.logger.error(`Failed to update balances for ${blockchain}:`, e); - } finally { - this.updateCalls.delete(blockchain); } } diff --git a/src/subdomains/supporting/dashboard/dashboard-financial.service.ts b/src/subdomains/supporting/dashboard/dashboard-financial.service.ts index 3b5b6f622a..4099ddac96 100644 --- a/src/subdomains/supporting/dashboard/dashboard-financial.service.ts +++ b/src/subdomains/supporting/dashboard/dashboard-financial.service.ts @@ -138,12 +138,34 @@ export class DashboardFinancialService { const asset = assetMap.get(Number(idStr)); const blockchain = asset?.blockchain ?? 'Unknown'; const assetName = asset?.name ?? 'Unknown'; - const plusChf = (assetData.plusBalance?.total ?? 0) * assetData.priceChf; - if (!blockchainTotals[blockchain]) blockchainTotals[blockchain] = { plus: 0, assets: {} }; - blockchainTotals[blockchain].plus += plusChf; - blockchainTotals[blockchain].assets[assetName] = - (blockchainTotals[blockchain].assets[assetName] ?? 0) + Math.round(plusChf); + if ((blockchain as string) === 'Scrypt') { + const spotTotal = + (assetData.plusBalance?.liquidity?.total ?? 0) + (assetData.plusBalance?.custom?.total ?? 0); + const pendingTotal = assetData.plusBalance?.pending?.total ?? 0; + const spotChf = spotTotal * assetData.priceChf; + const pendingChf = pendingTotal * assetData.priceChf; + + if (spotChf > 0) { + if (!blockchainTotals['Scrypt Spot']) blockchainTotals['Scrypt Spot'] = { plus: 0, assets: {} }; + blockchainTotals['Scrypt Spot'].plus += spotChf; + blockchainTotals['Scrypt Spot'].assets[assetName] = + (blockchainTotals['Scrypt Spot'].assets[assetName] ?? 0) + Math.round(spotChf); + } + if (pendingChf > 0) { + if (!blockchainTotals['Scrypt Pending']) blockchainTotals['Scrypt Pending'] = { plus: 0, assets: {} }; + blockchainTotals['Scrypt Pending'].plus += pendingChf; + blockchainTotals['Scrypt Pending'].assets[assetName] = + (blockchainTotals['Scrypt Pending'].assets[assetName] ?? 0) + Math.round(pendingChf); + } + } else { + const plusChf = (assetData.plusBalance?.total ?? 0) * assetData.priceChf; + + if (!blockchainTotals[blockchain]) blockchainTotals[blockchain] = { plus: 0, assets: {} }; + blockchainTotals[blockchain].plus += plusChf; + blockchainTotals[blockchain].assets[assetName] = + (blockchainTotals[blockchain].assets[assetName] ?? 0) + Math.round(plusChf); + } } } diff --git a/src/subdomains/supporting/log/log-job.service.ts b/src/subdomains/supporting/log/log-job.service.ts index b5ec2bd1ca..6752f86cbb 100644 --- a/src/subdomains/supporting/log/log-job.service.ts +++ b/src/subdomains/supporting/log/log-job.service.ts @@ -228,8 +228,9 @@ export class LogJobService { return { blockchain: b, balances: [] }; } - const balances = await this.getCustomBalances(client, a, Config.financialLog.customAddresses).then((b) => - b.flat(), + const balances = await Util.timeout( + this.getCustomBalances(client, a, Config.financialLog.customAddresses).then((b) => b.flat()), + 30000, ); return { blockchain: b, balances }; } catch (e) { @@ -452,13 +453,13 @@ export class LogJobService { const manualLiqPosition = manualLiqPositions.find((p) => p.assetId === curr.id)?.value ?? 0; - // plus (use availableAmount to avoid double-counting with pending exchange orders) - const liquidity = (curr.balance?.availableAmount ?? 0) + (paymentDepositBalance ?? 0) + (manualLiqPosition ?? 0); + // plus + const liquidity = (curr.balance?.amount ?? 0) + (paymentDepositBalance ?? 0) + (manualLiqPosition ?? 0); const cryptoInput = [Blockchain.MONERO, Blockchain.LIGHTNING, Blockchain.ZANO].includes(curr.blockchain) ? 0 : pendingPayIns.reduce((sum, tx) => sum + (tx.asset.id === curr.id ? tx.amount : 0), 0); - const exchangeOrder = pendingExchangeOrders.reduce((sum, tx) => { + const rawExchangeOrder = pendingExchangeOrders.reduce((sum, tx) => { if (tx.pipeline.rule.targetAsset.id !== curr.id) return sum; // for transfer/deposit: only count when action.system matches the target asset's exchange @@ -468,6 +469,13 @@ export class LogJobService { return sum + tx.inputAmount; }, 0); + + // Deduct locked exchange funds from exchangeOrder to avoid double-counting: + // amount includes locked funds (e.g. Scrypt pending withdrawals) that may also + // appear as exchangeOrder. For exchanges without such overlap (e.g. XT trading + // orders), lockedAmount > exchangeOrder so this just clamps to 0. + const lockedAmount = (curr.balance?.amount ?? 0) - (curr.balance?.availableAmount ?? curr.balance?.amount ?? 0); + const exchangeOrder = Math.max(0, rawExchangeOrder - lockedAmount); const bridgeOrder = pendingBridgeOrders.reduce( (sum, tx) => sum + (tx.pipeline.rule.targetAsset.id === curr.id ? tx.inputAmount : 0), 0,