Skip to content

Commit dbe2ada

Browse files
committed
feat: add get_address_balance
1 parent 634ce3e commit dbe2ada

File tree

13 files changed

+259
-4
lines changed

13 files changed

+259
-4
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@
2121
and other transaction types.
2222
- Added `Node::get_transaction_details()` method to retrieve transaction details for any
2323
transaction ID that exists in the wallet, returning `None` if the transaction is not found.
24+
- Added `Node::get_address_balance()` method to retrieve the current balance (in satoshis) for
25+
any Bitcoin address. This queries the chain source (Esplora or Electrum) to get the balance.
26+
Throws `InvalidAddress` if the address string cannot be parsed or doesn't match the node's
27+
network. Returns 0 if the balance cannot be queried (e.g., chain source unavailable). Note: This
28+
method is not available for BitcoindRpc chain source.
2429
- Added `SyncType` enum to distinguish between onchain wallet sync, Lightning
2530
wallet sync, and fee rate cache updates.
2631
- Balance tracking is now persisted in `NodeMetrics` to detect changes across restarts.

MOBILE_DEVELOPER_GUIDE.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,34 @@ if let details = node.getTransactionDetails(txid: txid) {
133133

134134
This method returns `nil` if the transaction is not found in the wallet.
135135

136+
#### Retrieving Address Balance
137+
138+
You can retrieve the current balance for any Bitcoin address using `Node::get_address_balance()`:
139+
140+
```swift
141+
// Get balance for a Bitcoin address
142+
do {
143+
let balance = try node.getAddressBalance(addressStr: "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh")
144+
print("Address balance: \(balance) sats")
145+
} catch {
146+
// Invalid address or network mismatch
147+
print("Error: \(error)")
148+
}
149+
```
150+
151+
```kotlin
152+
// Get balance for a Bitcoin address
153+
try {
154+
val balance = node.getAddressBalance("bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh")
155+
println("Address balance: $balance sats")
156+
} catch (e: Exception) {
157+
// Invalid address or network mismatch
158+
println("Error: ${e.message}")
159+
}
160+
```
161+
162+
**Note**: This method queries the chain source directly and returns the balance in satoshis. It throws an error if the address string cannot be parsed or doesn't match the node's network. It returns `0` if the balance cannot be queried (e.g., chain source unavailable). This method is not available when using BitcoindRpc as the chain source.
163+
136164
---
137165

138166
## iOS/Swift Implementation
Binary file not shown.
Binary file not shown.
Binary file not shown.

bindings/kotlin/ldk-node-android/lib/src/main/kotlin/org/lightningdevkit/ldknode/ldk_node.android.kt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1347,6 +1347,8 @@ internal typealias UniffiVTableCallbackInterfaceVssHeaderProviderUniffiByValue =
13471347

13481348

13491349

1350+
1351+
13501352

13511353

13521354

@@ -1920,6 +1922,11 @@ internal interface UniffiLib : Library {
19201922
`reason`: RustBufferByValue,
19211923
uniffiCallStatus: UniffiRustCallStatus,
19221924
): Unit
1925+
fun uniffi_ldk_node_fn_method_node_get_address_balance(
1926+
`ptr`: Pointer?,
1927+
`addressStr`: RustBufferByValue,
1928+
uniffiCallStatus: UniffiRustCallStatus,
1929+
): Long
19231930
fun uniffi_ldk_node_fn_method_node_get_transaction_details(
19241931
`ptr`: Pointer?,
19251932
`txid`: RustBufferByValue,
@@ -2554,6 +2561,8 @@ internal interface UniffiLib : Library {
25542561
): Short
25552562
fun uniffi_ldk_node_checksum_method_node_force_close_channel(
25562563
): Short
2564+
fun uniffi_ldk_node_checksum_method_node_get_address_balance(
2565+
): Short
25572566
fun uniffi_ldk_node_checksum_method_node_get_transaction_details(
25582567
): Short
25592568
fun uniffi_ldk_node_checksum_method_node_list_balances(
@@ -2908,6 +2917,9 @@ private fun uniffiCheckApiChecksums(lib: UniffiLib) {
29082917
if (lib.uniffi_ldk_node_checksum_method_node_force_close_channel() != 48831.toShort()) {
29092918
throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
29102919
}
2920+
if (lib.uniffi_ldk_node_checksum_method_node_get_address_balance() != 45284.toShort()) {
2921+
throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
2922+
}
29112923
if (lib.uniffi_ldk_node_checksum_method_node_get_transaction_details() != 65000.toShort()) {
29122924
throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
29132925
}
@@ -5613,6 +5625,19 @@ open class Node: Disposable, NodeInterface {
56135625
}
56145626
}
56155627

5628+
@Throws(NodeException::class)
5629+
override fun `getAddressBalance`(`addressStr`: kotlin.String): kotlin.ULong {
5630+
return FfiConverterULong.lift(callWithPointer {
5631+
uniffiRustCallWithError(NodeExceptionErrorHandler) { uniffiRustCallStatus ->
5632+
UniffiLib.INSTANCE.uniffi_ldk_node_fn_method_node_get_address_balance(
5633+
it,
5634+
FfiConverterString.lower(`addressStr`),
5635+
uniffiRustCallStatus,
5636+
)
5637+
}
5638+
})
5639+
}
5640+
56165641
override fun `getTransactionDetails`(`txid`: Txid): TransactionDetails? {
56175642
return FfiConverterOptionalTypeTransactionDetails.lift(callWithPointer {
56185643
uniffiRustCall { uniffiRustCallStatus ->

bindings/kotlin/ldk-node-android/lib/src/main/kotlin/org/lightningdevkit/ldknode/ldk_node.common.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,9 @@ interface NodeInterface {
364364
@Throws(NodeException::class)
365365
fun `forceCloseChannel`(`userChannelId`: UserChannelId, `counterpartyNodeId`: PublicKey, `reason`: kotlin.String?)
366366

367+
@Throws(NodeException::class)
368+
fun `getAddressBalance`(`addressStr`: kotlin.String): kotlin.ULong
369+
367370
fun `getTransactionDetails`(`txid`: Txid): TransactionDetails?
368371

369372
fun `listBalances`(): BalanceDetails

bindings/ldk_node.udl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,8 @@ interface Node {
127127
SpontaneousPayment spontaneous_payment();
128128
OnchainPayment onchain_payment();
129129
TransactionDetails? get_transaction_details([ByRef]Txid txid);
130+
[Throws=NodeError]
131+
u64 get_address_balance([ByRef]string address_str);
130132
UnifiedQrPayment unified_qr_payment();
131133
LSPS1Liquidity lsps1_liquidity();
132134
[Throws=NodeError]

bindings/swift/Sources/LDKNode/LDKNode.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1937,6 +1937,8 @@ public protocol NodeProtocol: AnyObject {
19371937

19381938
func forceCloseChannel(userChannelId: UserChannelId, counterpartyNodeId: PublicKey, reason: String?) throws
19391939

1940+
func getAddressBalance(addressStr: String) throws -> UInt64
1941+
19401942
func getTransactionDetails(txid: Txid) -> TransactionDetails?
19411943

19421944
func listBalances() -> BalanceDetails
@@ -2096,6 +2098,13 @@ open class Node:
20962098
}
20972099
}
20982100

2101+
open func getAddressBalance(addressStr: String) throws -> UInt64 {
2102+
return try FfiConverterUInt64.lift(rustCallWithError(FfiConverterTypeNodeError.lift) {
2103+
uniffi_ldk_node_fn_method_node_get_address_balance(self.uniffiClonePointer(),
2104+
FfiConverterString.lower(addressStr), $0)
2105+
})
2106+
}
2107+
20992108
open func getTransactionDetails(txid: Txid) -> TransactionDetails? {
21002109
return try! FfiConverterOptionTypeTransactionDetails.lift(try! rustCall {
21012110
uniffi_ldk_node_fn_method_node_get_transaction_details(self.uniffiClonePointer(),
@@ -9598,6 +9607,9 @@ private var initializationResult: InitializationResult {
95989607
if uniffi_ldk_node_checksum_method_node_force_close_channel() != 48831 {
95999608
return InitializationResult.apiChecksumMismatch
96009609
}
9610+
if uniffi_ldk_node_checksum_method_node_get_address_balance() != 45284 {
9611+
return InitializationResult.apiChecksumMismatch
9612+
}
96019613
if uniffi_ldk_node_checksum_method_node_get_transaction_details() != 65000 {
96029614
return InitializationResult.apiChecksumMismatch
96039615
}

src/chain/electrum.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,31 @@ impl ElectrumRuntimeClient {
8282
Ok(Self { electrum_client, bdk_electrum_client, tx_sync, runtime, config, logger })
8383
}
8484

85+
pub(crate) async fn get_address_balance(&self, address: &bitcoin::Address) -> Option<u64> {
86+
use electrum_client::ElectrumApi;
87+
88+
let script = address.script_pubkey();
89+
let electrum_client = Arc::clone(&self.electrum_client);
90+
let script_clone = script.clone();
91+
let balance_result = self
92+
.runtime
93+
.spawn_blocking(move || {
94+
electrum_client
95+
.script_get_balance(&script_clone)
96+
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("{}", e)))
97+
})
98+
.await;
99+
100+
match balance_result {
101+
Ok(Ok(balance)) => {
102+
let confirmed = balance.confirmed.max(0) as u64;
103+
let unconfirmed = balance.unconfirmed.max(0) as u64;
104+
Some(confirmed + unconfirmed)
105+
},
106+
_ => None,
107+
}
108+
}
109+
85110
pub(crate) async fn sync_confirmables(
86111
&self, confirmables: Vec<Arc<dyn Confirm + Sync + Send>>,
87112
) -> Result<(), Error> {

0 commit comments

Comments
 (0)