Skip to content

Commit e1b591c

Browse files
committed
feat: allow including received unconfirmed balance in spendable balance
1 parent 289aa1e commit e1b591c

File tree

5 files changed

+47
-12
lines changed

5 files changed

+47
-12
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,12 @@
4545
- `Bolt11Payment::estimate_routing_fees_using_amount()`: Estimates fees for amount-less invoices.
4646
- Enhanced `OnchainPayment::send_to_address()` to accept optional `utxos_to_spend` parameter
4747
for manual UTXO selection.
48+
- Added `Config::include_untrusted_pending_in_spendable` option to control whether unconfirmed
49+
funds from external sources are included in `spendable_onchain_balance_sats`. When set to `true`,
50+
the spendable balance will include `untrusted_pending` UTXOs (unconfirmed transactions received
51+
from external wallets). Default is `false` for safety, as spending unconfirmed external funds
52+
carries risk of double-spending. This affects all balance reporting including `list_balances()`
53+
and `BalanceChanged` events.
4854

4955
## Upstream v0.7.0 Release Notes
5056
This seventh minor release introduces numerous new features, bug fixes, and API improvements. In particular, it adds support for channel Splicing, Async Payments, as well as sourcing chain data from a Bitcoin Core REST backend.

bindings/ldk_node.udl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ dictionary Config {
1313
u64 probing_liquidity_limit_multiplier;
1414
AnchorChannelsConfig? anchor_channels_config;
1515
RouteParametersConfig? route_parameters;
16+
boolean include_untrusted_pending_in_spendable;
1617
};
1718

1819
dictionary AnchorChannelsConfig {

bindings/swift/Sources/LDKNode/LDKNode.swift

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5080,10 +5080,11 @@ public struct Config {
50805080
public var probingLiquidityLimitMultiplier: UInt64
50815081
public var anchorChannelsConfig: AnchorChannelsConfig?
50825082
public var routeParameters: RouteParametersConfig?
5083+
public var includeUntrustedPendingInSpendable: Bool
50835084

50845085
// Default memberwise initializers are never public by default, so we
50855086
// declare one manually.
5086-
public init(storageDirPath: String, network: Network, listeningAddresses: [SocketAddress]?, announcementAddresses: [SocketAddress]?, nodeAlias: NodeAlias?, trustedPeers0conf: [PublicKey], probingLiquidityLimitMultiplier: UInt64, anchorChannelsConfig: AnchorChannelsConfig?, routeParameters: RouteParametersConfig?) {
5087+
public init(storageDirPath: String, network: Network, listeningAddresses: [SocketAddress]?, announcementAddresses: [SocketAddress]?, nodeAlias: NodeAlias?, trustedPeers0conf: [PublicKey], probingLiquidityLimitMultiplier: UInt64, anchorChannelsConfig: AnchorChannelsConfig?, routeParameters: RouteParametersConfig?, includeUntrustedPendingInSpendable: Bool) {
50875088
self.storageDirPath = storageDirPath
50885089
self.network = network
50895090
self.listeningAddresses = listeningAddresses
@@ -5093,6 +5094,7 @@ public struct Config {
50935094
self.probingLiquidityLimitMultiplier = probingLiquidityLimitMultiplier
50945095
self.anchorChannelsConfig = anchorChannelsConfig
50955096
self.routeParameters = routeParameters
5097+
self.includeUntrustedPendingInSpendable = includeUntrustedPendingInSpendable
50965098
}
50975099
}
50985100

@@ -5127,6 +5129,9 @@ extension Config: Equatable, Hashable {
51275129
if lhs.routeParameters != rhs.routeParameters {
51285130
return false
51295131
}
5132+
if lhs.includeUntrustedPendingInSpendable != rhs.includeUntrustedPendingInSpendable {
5133+
return false
5134+
}
51305135
return true
51315136
}
51325137

@@ -5140,6 +5145,7 @@ extension Config: Equatable, Hashable {
51405145
hasher.combine(probingLiquidityLimitMultiplier)
51415146
hasher.combine(anchorChannelsConfig)
51425147
hasher.combine(routeParameters)
5148+
hasher.combine(includeUntrustedPendingInSpendable)
51435149
}
51445150
}
51455151

@@ -5151,16 +5157,17 @@ public struct FfiConverterTypeConfig: FfiConverterRustBuffer {
51515157
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Config {
51525158
return
51535159
try Config(
5154-
storageDirPath: FfiConverterString.read(from: &buf),
5155-
network: FfiConverterTypeNetwork.read(from: &buf),
5156-
listeningAddresses: FfiConverterOptionSequenceTypeSocketAddress.read(from: &buf),
5157-
announcementAddresses: FfiConverterOptionSequenceTypeSocketAddress.read(from: &buf),
5158-
nodeAlias: FfiConverterOptionTypeNodeAlias.read(from: &buf),
5159-
trustedPeers0conf: FfiConverterSequenceTypePublicKey.read(from: &buf),
5160-
probingLiquidityLimitMultiplier: FfiConverterUInt64.read(from: &buf),
5161-
anchorChannelsConfig: FfiConverterOptionTypeAnchorChannelsConfig.read(from: &buf),
5162-
routeParameters: FfiConverterOptionTypeRouteParametersConfig.read(from: &buf)
5163-
)
5160+
storageDirPath: FfiConverterString.read(from: &buf),
5161+
network: FfiConverterTypeNetwork.read(from: &buf),
5162+
listeningAddresses: FfiConverterOptionSequenceTypeSocketAddress.read(from: &buf),
5163+
announcementAddresses: FfiConverterOptionSequenceTypeSocketAddress.read(from: &buf),
5164+
nodeAlias: FfiConverterOptionTypeNodeAlias.read(from: &buf),
5165+
trustedPeers0conf: FfiConverterSequenceTypePublicKey.read(from: &buf),
5166+
probingLiquidityLimitMultiplier: FfiConverterUInt64.read(from: &buf),
5167+
anchorChannelsConfig: FfiConverterOptionTypeAnchorChannelsConfig.read(from: &buf),
5168+
routeParameters: FfiConverterOptionTypeRouteParametersConfig.read(from: &buf),
5169+
includeUntrustedPendingInSpendable: FfiConverterBool.read(from: &buf)
5170+
)
51645171
}
51655172

51665173
public static func write(_ value: Config, into buf: inout [UInt8]) {
@@ -5173,6 +5180,7 @@ public struct FfiConverterTypeConfig: FfiConverterRustBuffer {
51735180
FfiConverterUInt64.write(value.probingLiquidityLimitMultiplier, into: &buf)
51745181
FfiConverterOptionTypeAnchorChannelsConfig.write(value.anchorChannelsConfig, into: &buf)
51755182
FfiConverterOptionTypeRouteParametersConfig.write(value.routeParameters, into: &buf)
5183+
FfiConverterBool.write(value.includeUntrustedPendingInSpendable, into: &buf)
51765184
}
51775185
}
51785186

src/config.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,19 @@ pub struct Config {
184184
/// **Note:** If unset, default parameters will be used, and you will be able to override the
185185
/// parameters on a per-payment basis in the corresponding method calls.
186186
pub route_parameters: Option<RouteParametersConfig>,
187+
/// Whether to include unconfirmed funds from external sources in spendable balance.
188+
///
189+
/// If `true`, [`BalanceDetails::spendable_onchain_balance_sats`] will include
190+
/// unconfirmed UTXOs received from external wallets (`untrusted_pending` in BDK terms).
191+
///
192+
/// Default is `false`, meaning only confirmed funds and unconfirmed change from your own
193+
/// transactions are considered spendable.
194+
///
195+
/// **Warning:** Spending unconfirmed external funds is risky as the sender could
196+
/// double-spend, causing your transaction to fail.
197+
///
198+
/// [`BalanceDetails::spendable_onchain_balance_sats`]: crate::BalanceDetails::spendable_onchain_balance_sats
199+
pub include_untrusted_pending_in_spendable: bool,
187200
}
188201

189202
impl Default for Config {
@@ -198,6 +211,7 @@ impl Default for Config {
198211
anchor_channels_config: Some(AnchorChannelsConfig::default()),
199212
route_parameters: None,
200213
node_alias: None,
214+
include_untrusted_pending_in_spendable: false,
201215
}
202216
}
203217
}

src/wallet/mod.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -876,9 +876,15 @@ impl Wallet {
876876
fn get_balances_inner(
877877
&self, balance: Balance, total_anchor_channels_reserve_sats: u64,
878878
) -> Result<(u64, u64), Error> {
879+
let spendable_base = if self.config.include_untrusted_pending_in_spendable {
880+
balance.trusted_spendable().to_sat() + balance.untrusted_pending.to_sat()
881+
} else {
882+
balance.trusted_spendable().to_sat()
883+
};
884+
879885
let (total, spendable) = (
880886
balance.total().to_sat(),
881-
balance.trusted_spendable().to_sat().saturating_sub(total_anchor_channels_reserve_sats),
887+
spendable_base.saturating_sub(total_anchor_channels_reserve_sats),
882888
);
883889

884890
Ok((total, spendable))

0 commit comments

Comments
 (0)