Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@

## Synonym Fork Additions

- Added `OnchainPayment::calculate_send_all_fee()` to preview the fee for a drain / send-all
transaction before broadcasting (fee-calculation counterpart of `send_all_to_address`)
- Added runtime APIs for dynamic address type management:
- `Node::add_address_type_to_monitor()` and `add_address_type_to_monitor_with_mnemonic()` to add an address type to the monitored set
- `Node::remove_address_type_from_monitor()` to unload an address type (persisted state retained for re-add)
Expand Down
4 changes: 2 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@

import PackageDescription

let tag = "v0.7.0-rc.29"
let checksum = "983475feca0a1c4677fbfb8cbc6acbb5691cb55798e2819c29faf7a71260c672"
let tag = "v0.7.0-rc.30"
let checksum = "61f686901875529cb54850846283ce709e17b2500a1728b51d629ad03298a641"
let url = "https://github.com/synonymdev/ldk-node/releases/download/\(tag)/LDKNodeFFI.xcframework.zip"

let package = Package(
Expand Down
2 changes: 1 addition & 1 deletion bindings/kotlin/ldk-node-android/gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ android.useAndroidX=true
android.enableJetifier=true
kotlin.code.style=official
group=com.synonym
version=0.7.0-rc.29
version=0.7.0-rc.30
Binary file not shown.
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -1515,6 +1515,8 @@ internal typealias UniffiVTableCallbackInterfaceVssHeaderProviderUniffiByValue =








Expand Down Expand Up @@ -2554,6 +2556,13 @@ internal interface UniffiLib : Library {
`urgent`: Byte,
uniffiCallStatus: UniffiRustCallStatus,
): Pointer?
fun uniffi_ldk_node_fn_method_onchainpayment_calculate_send_all_fee(
`ptr`: Pointer?,
`address`: RustBufferByValue,
`retainReserves`: Byte,
`feeRate`: RustBufferByValue,
uniffiCallStatus: UniffiRustCallStatus,
): Long
fun uniffi_ldk_node_fn_method_onchainpayment_calculate_total_fee(
`ptr`: Pointer?,
`address`: RustBufferByValue,
Expand Down Expand Up @@ -3310,6 +3319,8 @@ internal interface UniffiLib : Library {
): Short
fun uniffi_ldk_node_checksum_method_onchainpayment_calculate_cpfp_fee_rate(
): Short
fun uniffi_ldk_node_checksum_method_onchainpayment_calculate_send_all_fee(
): Short
fun uniffi_ldk_node_checksum_method_onchainpayment_calculate_total_fee(
): Short
fun uniffi_ldk_node_checksum_method_onchainpayment_list_spendable_outputs(
Expand Down Expand Up @@ -3897,6 +3908,9 @@ private fun uniffiCheckApiChecksums(lib: UniffiLib) {
if (lib.uniffi_ldk_node_checksum_method_onchainpayment_calculate_cpfp_fee_rate() != 32879.toShort()) {
throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
}
if (lib.uniffi_ldk_node_checksum_method_onchainpayment_calculate_send_all_fee() != 16052.toShort()) {
throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
}
if (lib.uniffi_ldk_node_checksum_method_onchainpayment_calculate_total_fee() != 57218.toShort()) {
throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
}
Expand Down Expand Up @@ -8050,6 +8064,21 @@ open class OnchainPayment: Disposable, OnchainPaymentInterface {
})
}

@Throws(NodeException::class)
override fun `calculateSendAllFee`(`address`: Address, `retainReserves`: kotlin.Boolean, `feeRate`: FeeRate?): kotlin.ULong {
return FfiConverterULong.lift(callWithPointer {
uniffiRustCallWithError(NodeExceptionErrorHandler) { uniffiRustCallStatus ->
UniffiLib.INSTANCE.uniffi_ldk_node_fn_method_onchainpayment_calculate_send_all_fee(
it,
FfiConverterTypeAddress.lower(`address`),
FfiConverterBoolean.lower(`retainReserves`),
FfiConverterOptionalTypeFeeRate.lower(`feeRate`),
uniffiRustCallStatus,
)
}
})
}

@Throws(NodeException::class)
override fun `calculateTotalFee`(`address`: Address, `amountSats`: kotlin.ULong, `feeRate`: FeeRate?, `utxosToSpend`: List<SpendableUtxo>?): kotlin.ULong {
return FfiConverterULong.lift(callWithPointer {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,9 @@ interface OnchainPaymentInterface {
@Throws(NodeException::class)
fun `calculateCpfpFeeRate`(`parentTxid`: Txid, `urgent`: kotlin.Boolean): FeeRate

@Throws(NodeException::class)
fun `calculateSendAllFee`(`address`: Address, `retainReserves`: kotlin.Boolean, `feeRate`: FeeRate?): kotlin.ULong

@Throws(NodeException::class)
fun `calculateTotalFee`(`address`: Address, `amountSats`: kotlin.ULong, `feeRate`: FeeRate?, `utxosToSpend`: List<SpendableUtxo>?): kotlin.ULong

Expand Down
2 changes: 1 addition & 1 deletion bindings/kotlin/ldk-node-jvm/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
org.gradle.jvmargs=-Xmx1536m
kotlin.code.style=official
group=com.synonym
version=0.7.0-rc.29
version=0.7.0-rc.30
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,9 @@ interface OnchainPaymentInterface {
@Throws(NodeException::class)
fun `calculateCpfpFeeRate`(`parentTxid`: Txid, `urgent`: kotlin.Boolean): FeeRate

@Throws(NodeException::class)
fun `calculateSendAllFee`(`address`: Address, `retainReserves`: kotlin.Boolean, `feeRate`: FeeRate?): kotlin.ULong

@Throws(NodeException::class)
fun `calculateTotalFee`(`address`: Address, `amountSats`: kotlin.ULong, `feeRate`: FeeRate?, `utxosToSpend`: List<SpendableUtxo>?): kotlin.ULong

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1513,6 +1513,8 @@ internal typealias UniffiVTableCallbackInterfaceVssHeaderProviderUniffiByValue =








Expand Down Expand Up @@ -2552,6 +2554,13 @@ internal interface UniffiLib : Library {
`urgent`: Byte,
uniffiCallStatus: UniffiRustCallStatus,
): Pointer?
fun uniffi_ldk_node_fn_method_onchainpayment_calculate_send_all_fee(
`ptr`: Pointer?,
`address`: RustBufferByValue,
`retainReserves`: Byte,
`feeRate`: RustBufferByValue,
uniffiCallStatus: UniffiRustCallStatus,
): Long
fun uniffi_ldk_node_fn_method_onchainpayment_calculate_total_fee(
`ptr`: Pointer?,
`address`: RustBufferByValue,
Expand Down Expand Up @@ -3308,6 +3317,8 @@ internal interface UniffiLib : Library {
): Short
fun uniffi_ldk_node_checksum_method_onchainpayment_calculate_cpfp_fee_rate(
): Short
fun uniffi_ldk_node_checksum_method_onchainpayment_calculate_send_all_fee(
): Short
fun uniffi_ldk_node_checksum_method_onchainpayment_calculate_total_fee(
): Short
fun uniffi_ldk_node_checksum_method_onchainpayment_list_spendable_outputs(
Expand Down Expand Up @@ -3895,6 +3906,9 @@ private fun uniffiCheckApiChecksums(lib: UniffiLib) {
if (lib.uniffi_ldk_node_checksum_method_onchainpayment_calculate_cpfp_fee_rate() != 32879.toShort()) {
throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
}
if (lib.uniffi_ldk_node_checksum_method_onchainpayment_calculate_send_all_fee() != 16052.toShort()) {
throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
}
if (lib.uniffi_ldk_node_checksum_method_onchainpayment_calculate_total_fee() != 57218.toShort()) {
throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
}
Expand Down Expand Up @@ -8039,6 +8053,21 @@ open class OnchainPayment: Disposable, OnchainPaymentInterface {
})
}

@Throws(NodeException::class)
override fun `calculateSendAllFee`(`address`: Address, `retainReserves`: kotlin.Boolean, `feeRate`: FeeRate?): kotlin.ULong {
return FfiConverterULong.lift(callWithPointer {
uniffiRustCallWithError(NodeExceptionErrorHandler) { uniffiRustCallStatus ->
UniffiLib.INSTANCE.uniffi_ldk_node_fn_method_onchainpayment_calculate_send_all_fee(
it,
FfiConverterTypeAddress.lower(`address`),
FfiConverterBoolean.lower(`retainReserves`),
FfiConverterOptionalTypeFeeRate.lower(`feeRate`),
uniffiRustCallStatus,
)
}
})
}

@Throws(NodeException::class)
override fun `calculateTotalFee`(`address`: Address, `amountSats`: kotlin.ULong, `feeRate`: FeeRate?, `utxosToSpend`: List<SpendableUtxo>?): kotlin.ULong {
return FfiConverterULong.lift(callWithPointer {
Expand Down
Binary file not shown.
Binary file not shown.
2 changes: 2 additions & 0 deletions bindings/ldk_node.udl
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,8 @@ interface OnchainPayment {
FeeRate calculate_cpfp_fee_rate([ByRef]Txid parent_txid, boolean urgent);
[Throws=NodeError]
u64 calculate_total_fee([ByRef]Address address, u64 amount_sats, FeeRate? fee_rate, sequence<SpendableUtxo>? utxos_to_spend);
[Throws=NodeError]
u64 calculate_send_all_fee([ByRef]Address address, boolean retain_reserves, FeeRate? fee_rate);
};

enum CoinSelectionAlgorithm {
Expand Down
2 changes: 1 addition & 1 deletion bindings/python/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "ldk_node"
version = "0.7.0-rc.29"
version = "0.7.0-rc.30"
authors = [
{ name="Elias Rohrer", email="dev@tnull.de" },
]
Expand Down
33 changes: 33 additions & 0 deletions bindings/python/src/ldk_node/ldk_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -797,6 +797,8 @@ def _uniffi_check_api_checksums(lib):
raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
if lib.uniffi_ldk_node_checksum_method_onchainpayment_calculate_cpfp_fee_rate() != 32879:
raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
if lib.uniffi_ldk_node_checksum_method_onchainpayment_calculate_send_all_fee() != 16052:
raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
if lib.uniffi_ldk_node_checksum_method_onchainpayment_calculate_total_fee() != 57218:
raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
if lib.uniffi_ldk_node_checksum_method_onchainpayment_list_spendable_outputs() != 19144:
Expand Down Expand Up @@ -2189,6 +2191,14 @@ class _UniffiVTableCallbackInterfaceVssHeaderProvider(ctypes.Structure):
ctypes.POINTER(_UniffiRustCallStatus),
)
_UniffiLib.uniffi_ldk_node_fn_method_onchainpayment_calculate_cpfp_fee_rate.restype = ctypes.c_void_p
_UniffiLib.uniffi_ldk_node_fn_method_onchainpayment_calculate_send_all_fee.argtypes = (
ctypes.c_void_p,
_UniffiRustBuffer,
ctypes.c_int8,
_UniffiRustBuffer,
ctypes.POINTER(_UniffiRustCallStatus),
)
_UniffiLib.uniffi_ldk_node_fn_method_onchainpayment_calculate_send_all_fee.restype = ctypes.c_uint64
_UniffiLib.uniffi_ldk_node_fn_method_onchainpayment_calculate_total_fee.argtypes = (
ctypes.c_void_p,
_UniffiRustBuffer,
Expand Down Expand Up @@ -3211,6 +3221,9 @@ class _UniffiVTableCallbackInterfaceVssHeaderProvider(ctypes.Structure):
_UniffiLib.uniffi_ldk_node_checksum_method_onchainpayment_calculate_cpfp_fee_rate.argtypes = (
)
_UniffiLib.uniffi_ldk_node_checksum_method_onchainpayment_calculate_cpfp_fee_rate.restype = ctypes.c_uint16
_UniffiLib.uniffi_ldk_node_checksum_method_onchainpayment_calculate_send_all_fee.argtypes = (
)
_UniffiLib.uniffi_ldk_node_checksum_method_onchainpayment_calculate_send_all_fee.restype = ctypes.c_uint16
_UniffiLib.uniffi_ldk_node_checksum_method_onchainpayment_calculate_total_fee.argtypes = (
)
_UniffiLib.uniffi_ldk_node_checksum_method_onchainpayment_calculate_total_fee.restype = ctypes.c_uint16
Expand Down Expand Up @@ -6563,6 +6576,8 @@ def bump_fee_by_rbf(self, txid: "Txid",fee_rate: "FeeRate"):
raise NotImplementedError
def calculate_cpfp_fee_rate(self, parent_txid: "Txid",urgent: "bool"):
raise NotImplementedError
def calculate_send_all_fee(self, address: "Address",retain_reserves: "bool",fee_rate: "typing.Optional[FeeRate]"):
raise NotImplementedError
def calculate_total_fee(self, address: "Address",amount_sats: "int",fee_rate: "typing.Optional[FeeRate]",utxos_to_spend: "typing.Optional[typing.List[SpendableUtxo]]"):
raise NotImplementedError
def list_spendable_outputs(self, ):
Expand Down Expand Up @@ -6652,6 +6667,24 @@ def calculate_cpfp_fee_rate(self, parent_txid: "Txid",urgent: "bool") -> "FeeRat



def calculate_send_all_fee(self, address: "Address",retain_reserves: "bool",fee_rate: "typing.Optional[FeeRate]") -> "int":
_UniffiConverterTypeAddress.check_lower(address)

_UniffiConverterBool.check_lower(retain_reserves)

_UniffiConverterOptionalTypeFeeRate.check_lower(fee_rate)

return _UniffiConverterUInt64.lift(
_uniffi_rust_call_with_error(_UniffiConverterTypeNodeError,_UniffiLib.uniffi_ldk_node_fn_method_onchainpayment_calculate_send_all_fee,self._uniffi_clone_pointer(),
_UniffiConverterTypeAddress.lower(address),
_UniffiConverterBool.lower(retain_reserves),
_UniffiConverterOptionalTypeFeeRate.lower(fee_rate))
)





def calculate_total_fee(self, address: "Address",amount_sats: "int",fee_rate: "typing.Optional[FeeRate]",utxos_to_spend: "typing.Optional[typing.List[SpendableUtxo]]") -> "int":
_UniffiConverterTypeAddress.check_lower(address)

Expand Down
14 changes: 14 additions & 0 deletions bindings/swift/Sources/LDKNode/LDKNode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3247,6 +3247,8 @@ public protocol OnchainPaymentProtocol: AnyObject {

func calculateCpfpFeeRate(parentTxid: Txid, urgent: Bool) throws -> FeeRate

func calculateSendAllFee(address: Address, retainReserves: Bool, feeRate: FeeRate?) throws -> UInt64

func calculateTotalFee(address: Address, amountSats: UInt64, feeRate: FeeRate?, utxosToSpend: [SpendableUtxo]?) throws -> UInt64

func listSpendableOutputs() throws -> [SpendableUtxo]
Expand Down Expand Up @@ -3336,6 +3338,15 @@ open class OnchainPayment:
})
}

open func calculateSendAllFee(address: Address, retainReserves: Bool, feeRate: FeeRate?) throws -> UInt64 {
return try FfiConverterUInt64.lift(rustCallWithError(FfiConverterTypeNodeError.lift) {
uniffi_ldk_node_fn_method_onchainpayment_calculate_send_all_fee(self.uniffiClonePointer(),
FfiConverterTypeAddress.lower(address),
FfiConverterBool.lower(retainReserves),
FfiConverterOptionTypeFeeRate.lower(feeRate), $0)
})
}

open func calculateTotalFee(address: Address, amountSats: UInt64, feeRate: FeeRate?, utxosToSpend: [SpendableUtxo]?) throws -> UInt64 {
return try FfiConverterUInt64.lift(rustCallWithError(FfiConverterTypeNodeError.lift) {
uniffi_ldk_node_fn_method_onchainpayment_calculate_total_fee(self.uniffiClonePointer(),
Expand Down Expand Up @@ -12618,6 +12629,9 @@ private var initializationResult: InitializationResult = {
if uniffi_ldk_node_checksum_method_onchainpayment_calculate_cpfp_fee_rate() != 32879 {
return InitializationResult.apiChecksumMismatch
}
if uniffi_ldk_node_checksum_method_onchainpayment_calculate_send_all_fee() != 16052 {
return InitializationResult.apiChecksumMismatch
}
if uniffi_ldk_node_checksum_method_onchainpayment_calculate_total_fee() != 57218 {
return InitializationResult.apiChecksumMismatch
}
Expand Down
71 changes: 66 additions & 5 deletions src/payment/onchain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,11 +171,12 @@ impl OnchainPayment {
/// The calculation respects any on-chain reserve requirements and validates that sufficient
/// funds are available, just like [`send_to_address`].
///
/// **Special handling for maximum amounts:** If the specified amount would result in
/// insufficient funds due to fees, but is within the spendable balance, this method will
/// automatically calculate the fee for sending all available funds while retaining the
/// anchor channel reserve. This allows users to calculate fees when trying to send
/// their maximum spendable balance.
/// **Note on maximum amounts:** For calculating the fee when sending the entire spendable
/// balance, prefer [`calculate_send_all_fee`] which is purpose-built for that use case.
/// This method includes a best-effort fallback for near-max amounts, but it may not
/// trigger in all cases depending on the underlying wallet error.
///
/// [`calculate_send_all_fee`]: Self::calculate_send_all_fee
///
/// # Arguments
///
Expand Down Expand Up @@ -247,6 +248,63 @@ impl OnchainPayment {
result
}

/// Calculates the total fee for sending all available on-chain funds without
/// actually broadcasting.
///
/// This is the fee-calculation counterpart of [`send_all_to_address`]. Use it to
/// show the user how much a drain / send-all transaction would cost before they
/// confirm.
///
/// When `retain_reserves` is `true`, the calculation accounts for the on-chain anchor
/// channel reserve (see [`BalanceDetails::total_anchor_channels_reserve_sats`]), sending only
/// the spendable portion. When `false`, the calculation covers draining the entire wallet
/// balance, which may be dangerous if you have open anchor channels whose counterparty you
/// don't trust to spend the anchor output after closure.
///
/// # Arguments
///
/// * `address` - The destination Bitcoin address
/// * `retain_reserves` - If `true`, retains the anchor channel reserve; if `false`, drains everything
/// * `fee_rate` - Optional fee rate to use (if `None`, will estimate based on current network conditions)
///
/// # Returns
///
/// The total fee in satoshis that would be paid for this transaction.
///
/// # Errors
///
/// * [`Error::NotRunning`] - If the node is not running
/// * [`Error::InvalidAddress`] - If the address is invalid
/// * [`Error::InsufficientFunds`] - If there are insufficient funds
/// * [`Error::WalletOperationFailed`] - If fee calculation fails
///
/// [`send_all_to_address`]: Self::send_all_to_address
/// [`BalanceDetails::total_anchor_channels_reserve_sats`]: crate::BalanceDetails::total_anchor_channels_reserve_sats
pub fn calculate_send_all_fee(
&self, address: &bitcoin::Address, retain_reserves: bool, fee_rate: Option<FeeRate>,
) -> Result<u64, Error> {
if !*self.is_running.read().unwrap() {
return Err(Error::NotRunning);
}

let send_amount = if retain_reserves {
let cur_anchor_reserve_sats =
crate::total_anchor_channels_reserve_sats(&self.channel_manager, &self.config);
OnchainSendAmount::AllRetainingReserve { cur_anchor_reserve_sats }
} else {
OnchainSendAmount::AllDrainingReserve
};

let fee_rate_opt = maybe_map_fee_rate_opt!(fee_rate);
self.wallet.calculate_transaction_fee(
address,
send_amount,
fee_rate_opt,
None,
&self.channel_manager,
)
}

/// Send an on-chain payment to the given address.
///
/// This will respect any on-chain reserve we need to keep, i.e., won't allow to cut into
Expand Down Expand Up @@ -284,6 +342,8 @@ impl OnchainPayment {
/// This is useful if you have closed all channels and want to migrate funds to another
/// on-chain wallet.
///
/// To preview the fee before broadcasting, use [`calculate_send_all_fee`].
///
/// Please note that if `retain_reserves` is set to `false` this will **not** retain any on-chain reserves, which might be potentially
/// dangerous if you have open Anchor channels for which you can't trust the counterparty to
/// spend the Anchor output after channel closure. If `retain_reserves` is set to `true`, this
Expand All @@ -293,6 +353,7 @@ impl OnchainPayment {
/// If `fee_rate` is set it will be used on the resulting transaction. Otherwise a reasonable
/// we'll retrieve an estimate from the configured chain source.
///
/// [`calculate_send_all_fee`]: Self::calculate_send_all_fee
/// [`BalanceDetails::spendable_onchain_balance_sats`]: crate::balance::BalanceDetails::spendable_onchain_balance_sats
pub fn send_all_to_address(
&self, address: &bitcoin::Address, retain_reserves: bool, fee_rate: Option<FeeRate>,
Expand Down
Loading