diff --git a/backends/LdkNode.ts b/backends/LdkNode.ts
index bbdc840ab..30be81775 100644
--- a/backends/LdkNode.ts
+++ b/backends/LdkNode.ts
@@ -347,9 +347,6 @@ export default class LdkNode {
pendingCloseBalance = pendingCloseBalance.plus(sb.amountSatoshis);
}
- const totalPendingBalance =
- pendingOpenBalance.plus(pendingCloseBalance);
-
return {
balance: localBalance.toString(),
local_balance: {
@@ -360,10 +357,10 @@ export default class LdkNode {
sat: remoteBalance.toString(),
msat: remoteBalance.times(1000).toString()
},
- pending_open_balance: totalPendingBalance.toString(),
+ pending_open_balance: pendingOpenBalance.toString(),
pending_open_local_balance: {
- sat: totalPendingBalance.toString(),
- msat: totalPendingBalance.times(1000).toString()
+ sat: pendingOpenBalance.toString(),
+ msat: pendingOpenBalance.times(1000).toString()
}
};
};
@@ -686,7 +683,30 @@ export default class LdkNode {
}
}
+ // total_limbo_balance: sats in close-side limbo (cooperative closes
+ // awaiting confirmation + force-close timelocks + waiting closes).
+ // Matches lnrpc.PendingChannelsResponse.total_limbo_balance semantics
+ // so ChannelsStore can read it uniformly across backends.
+ let totalLimboBalance = new BigNumber(0);
+ for (const lb of balances.lightningBalances) {
+ if (lb.type === 'claimableOnChannelClose') continue;
+ if (activeChannelIds.has(lb.channelId)) continue;
+ totalLimboBalance = totalLimboBalance.plus(lb.amountSatoshis);
+ }
+ for (const sb of balances.pendingBalancesFromChannelClosures) {
+ if (sb.channelId && activeChannelIds.has(sb.channelId)) continue;
+ if (
+ sb.type === 'awaitingThresholdConfirmations' &&
+ sb.confirmationHeight != null &&
+ sb.confirmationHeight <= currentBlockHeight
+ ) {
+ continue;
+ }
+ totalLimboBalance = totalLimboBalance.plus(sb.amountSatoshis);
+ }
+
return {
+ total_limbo_balance: totalLimboBalance.toString(),
pending_open_channels: pendingOpen.map((channel) => ({
channel: this.formatChannel(channel)
})),
diff --git a/stores/BalanceStore.ts b/stores/BalanceStore.ts
index 9a119e1be..568ce5a7b 100644
--- a/stores/BalanceStore.ts
+++ b/stores/BalanceStore.ts
@@ -14,6 +14,11 @@ export default class BalanceStore {
@observable public loadingLightningBalance = false;
@observable public error = false;
@observable public pendingOpenBalance: number | string | any;
+ // Sats locked up in pending close, force close, and waiting close
+ // channels — i.e. funds in close-side limbo. Mirrors pendingOpenBalance
+ // semantics but for the closing side. Populated by ChannelsStore from
+ // PendingChannelsResponse.total_limbo_balance (or the LDK-node analogue).
+ @observable public pendingCloseBalance: number | string | any;
@observable public lightningBalance: number | string;
@observable public otherAccounts: any = {};
settingsStore: SettingsStore;
@@ -50,10 +55,16 @@ export default class BalanceStore {
private resetLightningBalance = () => {
this.pendingOpenBalance = 0;
+ this.pendingCloseBalance = 0;
this.lightningBalance = 0;
this.loadingLightningBalance = false;
};
+ @action
+ public setPendingCloseBalance = (value: number | string) => {
+ this.pendingCloseBalance = Number(value || 0);
+ };
+
@action
private balanceError = () => {
this.error = true;
diff --git a/stores/ChannelsStore.ts b/stores/ChannelsStore.ts
index 85e4e1d9a..d58d256f8 100644
--- a/stores/ChannelsStore.ts
+++ b/stores/ChannelsStore.ts
@@ -13,6 +13,7 @@ import Peer from '../models/Peer';
import OpenChannelRequest from '../models/OpenChannelRequest';
import CloseChannelRequest from '../models/CloseChannelRequest';
+import BalanceStore from './BalanceStore';
import SettingsStore from './SettingsStore';
import BackendUtils from '../utils/BackendUtils';
@@ -115,9 +116,11 @@ export default class ChannelsStore {
@observable public haveAnnouncedChannels = false;
settingsStore: SettingsStore;
+ balanceStore: BalanceStore;
- constructor(settingsStore: SettingsStore) {
+ constructor(settingsStore: SettingsStore, balanceStore: BalanceStore) {
this.settingsStore = settingsStore;
+ this.balanceStore = balanceStore;
reaction(
() => this.channelRequest,
@@ -598,6 +601,12 @@ export default class ChannelsStore {
.concat(pendingCloseChannels)
.concat(forceCloseChannels)
.concat(waitCloseChannels);
+ // Push close-side limbo (cooperative close + force-close
+ // timelocks + waiting-close confirmations) to BalanceStore
+ // so BalancePane can render it next to pendingOpenBalance.
+ this.balanceStore.setPendingCloseBalance(
+ data.total_limbo_balance || 0
+ );
})
);
}
diff --git a/stores/Stores.ts b/stores/Stores.ts
index 73807901c..9487dc17b 100644
--- a/stores/Stores.ts
+++ b/stores/Stores.ts
@@ -34,7 +34,8 @@ export const connectivityStore = new ConnectivityStore(settingsStore);
export const modalStore = new ModalStore();
export const offersStore = new OffersStore();
export const fiatStore = new FiatStore(settingsStore);
-export const channelsStore = new ChannelsStore(settingsStore);
+export const balanceStore = new BalanceStore(settingsStore);
+export const channelsStore = new ChannelsStore(settingsStore, balanceStore);
export const nodeInfoStore = new NodeInfoStore(channelsStore, settingsStore);
export const alertStore = new AlertStore(settingsStore, nodeInfoStore);
export const lspStore = new LSPStore(
@@ -52,7 +53,6 @@ export const invoicesStore = new InvoicesStore(
channelsStore,
nodeInfoStore
);
-export const balanceStore = new BalanceStore(settingsStore);
export const transactionsStore = new TransactionsStore(
settingsStore,
nodeInfoStore,
diff --git a/views/Wallet/BalancePane.tsx b/views/Wallet/BalancePane.tsx
index c3a8519a5..4075f6e4e 100644
--- a/views/Wallet/BalancePane.tsx
+++ b/views/Wallet/BalancePane.tsx
@@ -182,13 +182,19 @@ export default class BalancePane extends React.PureComponent<
totalBlockchainBalance,
unconfirmedBlockchainBalance,
lightningBalance,
- pendingOpenBalance
+ pendingOpenBalance,
+ pendingCloseBalance
} = BalanceStore;
const cashuBalance = CashuStore.totalBalanceSats;
const cashuOfflinePendingBalance = CashuStore.offlinePendingBalance;
const { implementation, settings, lndFolderMissing } = SettingsStore;
- const pendingUnconfirmedBalance = new BigNumber(pendingOpenBalance)
+ const lightningPendingTotal = new BigNumber(pendingOpenBalance || 0)
+ .plus(pendingCloseBalance || 0)
+ .toNumber();
+ const hasLightningPending = lightningPendingTotal > 0;
+
+ const pendingUnconfirmedBalance = new BigNumber(lightningPendingTotal)
.plus(unconfirmedBlockchainBalance)
.toNumber()
.toFixed(3);
@@ -206,40 +212,55 @@ export default class BalancePane extends React.PureComponent<
jumboText
toggleable
/>
- {!(pendingOpenBalance > 0) && (
+ {!hasLightningPending && (
)}
- {pendingOpenBalance > 0 ? (
- <>
-
-
- this.handlePendingPress('lightning')
- }
- />
-
-
-
-
- >
- ) : null}
+ {pendingOpenBalance > 0 && (
+
+
+ this.handlePendingPress('lightning')
+ }
+ />
+
+ )}
+ {pendingCloseBalance > 0 && (
+
+
+ this.handlePendingPress('lightning')
+ }
+ />
+
+ )}
+ {hasLightningPending && (
+
+
+
+ )}
);
const BalanceViewCombined = () => {
const hasOnchainPending = Boolean(
Number(unconfirmedBlockchainBalance ?? 0) ||
- Number(pendingOpenBalance ?? 0)
+ Number(pendingOpenBalance ?? 0) ||
+ Number(pendingCloseBalance ?? 0)
);
const hasAnyPending = hasOnchainPending;
const renderPendingBalance = (sats: string | number) => (