From e76c672d5da9847a0611da7442e4f24979cdeeef Mon Sep 17 00:00:00 2001 From: Philipp Walter Date: Tue, 14 Jul 2026 11:41:55 +0200 Subject: [PATCH 1/4] feat: show probe route fee --- .gitignore | 1 + .../to/bitkit/repositories/LightningRepo.kt | 13 +++- .../ui/screens/settings/ProbingToolScreen.kt | 8 +-- .../bitkit/viewmodels/ProbingToolViewModel.kt | 10 ++- .../bitkit/repositories/LightningRepoTest.kt | 68 ++++++++++++++++--- .../viewmodels/ProbingToolViewModelTest.kt | 10 ++- changelog.d/next/1052.fixed.md | 1 + gradle/libs.versions.toml | 2 +- 8 files changed, 87 insertions(+), 26 deletions(-) create mode 100644 changelog.d/next/1052.fixed.md diff --git a/.gitignore b/.gitignore index 85acba5a5d..4ac0cea68c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ *.iml .gradle .idea +.vscode !.idea/codeStyles/ .kotlin .DS_Store diff --git a/app/src/main/java/to/bitkit/repositories/LightningRepo.kt b/app/src/main/java/to/bitkit/repositories/LightningRepo.kt index 1175d9fcad..1e8800355c 100644 --- a/app/src/main/java/to/bitkit/repositories/LightningRepo.kt +++ b/app/src/main/java/to/bitkit/repositories/LightningRepo.kt @@ -578,8 +578,13 @@ class LightningRepo @Inject constructor( private suspend fun recordProbeOutcome(event: Event) { val outcome = when (event) { - is Event.ProbeSuccessful -> ProbeOutcome.Success(event.paymentId, event.paymentHash) - is Event.ProbeFailed -> ProbeOutcome.Failure(event.paymentId, event.paymentHash, event.shortChannelId) + is Event.ProbeSuccessful -> ProbeOutcome.Success(event.paymentId, event.paymentHash, event.routeFeeMsat) + is Event.ProbeFailed -> ProbeOutcome.Failure( + event.paymentId, + event.paymentHash, + event.shortChannelId?.toString(), + event.routeFeeMsat, + ) else -> return } @@ -1802,11 +1807,13 @@ sealed interface ProbeOutcome { data class Success( override val paymentId: PaymentId, override val paymentHash: PaymentHash, + val routeFeeMsat: ULong?, ) : ProbeOutcome data class Failure( override val paymentId: PaymentId, override val paymentHash: PaymentHash, - val shortChannelId: ULong?, + val shortChannelId: String?, + val routeFeeMsat: ULong?, ) : ProbeOutcome } diff --git a/app/src/main/java/to/bitkit/ui/screens/settings/ProbingToolScreen.kt b/app/src/main/java/to/bitkit/ui/screens/settings/ProbingToolScreen.kt index 10b47f31dc..6cecd87013 100644 --- a/app/src/main/java/to/bitkit/ui/screens/settings/ProbingToolScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/settings/ProbingToolScreen.kt @@ -186,11 +186,11 @@ private fun ProbingToolContent( iconRes = R.drawable.ic_clock, value = "${result.durationMs} ms", ) - result.estimatedFeeSats?.let { fee -> + result.routeFeeMsat?.let { fee -> SettingsTextButtonRow( - title = "Estimated Fee", + title = "Route Fee", iconRes = R.drawable.ic_coins, - value = "$fee sats", + value = "$fee msat", ) } result.errorMessage?.let { error -> @@ -216,7 +216,7 @@ private fun Preview() { probeResult = ProbeResult( success = true, durationMs = 342, - estimatedFeeSats = 5uL, + routeFeeMsat = 5_000uL, ), ) ) diff --git a/app/src/main/java/to/bitkit/viewmodels/ProbingToolViewModel.kt b/app/src/main/java/to/bitkit/viewmodels/ProbingToolViewModel.kt index 4fb1bfc13b..3b7ab65bfb 100644 --- a/app/src/main/java/to/bitkit/viewmodels/ProbingToolViewModel.kt +++ b/app/src/main/java/to/bitkit/viewmodels/ProbingToolViewModel.kt @@ -161,7 +161,7 @@ class ProbingToolViewModel @Inject constructor( dispatch .onSuccess { probe -> lightningRepo.waitForProbeOutcome(probe.paymentIds) - .onSuccess { handleProbeOutcome(startTime, it, bolt11, amountSats) } + .onSuccess { handleProbeOutcome(startTime, it) } .onFailure { handleProbeFailure(startTime, it) } } .onFailure { handleProbeFailure(startTime, it) } @@ -257,8 +257,6 @@ class ProbingToolViewModel @Inject constructor( private suspend fun handleProbeOutcome( startTime: Long, outcome: ProbeOutcome, - invoice: String?, - amountSats: ULong?, ) { val durationMs = Clock.System.nowMs() - startTime when (outcome) { @@ -268,13 +266,12 @@ class ProbingToolViewModel @Inject constructor( context = TAG, ) - val estimatedFee = invoice?.let { getEstimatedFee(it, amountSats) } _uiState.update { it.copy( probeResult = ProbeResult( success = true, durationMs = durationMs, - estimatedFeeSats = estimatedFee, + routeFeeMsat = outcome.routeFeeMsat, ) ) } @@ -294,6 +291,7 @@ class ProbingToolViewModel @Inject constructor( probeResult = ProbeResult( success = false, durationMs = durationMs, + routeFeeMsat = outcome.routeFeeMsat, errorMessage = message, ) ) @@ -379,6 +377,6 @@ data class ProbingToolUiState( data class ProbeResult( val success: Boolean, val durationMs: Long, - val estimatedFeeSats: ULong? = null, + val routeFeeMsat: ULong? = null, val errorMessage: String? = null, ) diff --git a/app/src/test/java/to/bitkit/repositories/LightningRepoTest.kt b/app/src/test/java/to/bitkit/repositories/LightningRepoTest.kt index 6e4117f03d..5d721ce3fa 100644 --- a/app/src/test/java/to/bitkit/repositories/LightningRepoTest.kt +++ b/app/src/test/java/to/bitkit/repositories/LightningRepoTest.kt @@ -1348,24 +1348,26 @@ class LightningRepoTest : BaseUnitTest() { val onEvent = startNodeAndCaptureEvents() val result = async { sut.waitForProbeOutcome(setOf(probePaymentA)) } - onEvent(Event.ProbeSuccessful(paymentId = probePaymentA, paymentHash = probeHashA)) + onEvent(Event.ProbeSuccessful(paymentId = probePaymentA, paymentHash = probeHashA, routeFeeMsat = 123uL)) val outcome = result.await().getOrThrow() assertIs(outcome) assertEquals(probePaymentA, outcome.paymentId) assertEquals(probeHashA, outcome.paymentHash) + assertEquals(123uL, outcome.routeFeeMsat) } @Test fun `waitForProbeOutcome returns cached success when event arrives before wait`() = test { val onEvent = startNodeAndCaptureEvents() - onEvent(Event.ProbeSuccessful(paymentId = probePaymentA, paymentHash = probeHashA)) + onEvent(Event.ProbeSuccessful(paymentId = probePaymentA, paymentHash = probeHashA, routeFeeMsat = 123uL)) val outcome = sut.waitForProbeOutcome(setOf(probePaymentA)).getOrThrow() assertIs(outcome) assertEquals(probePaymentA, outcome.paymentId) assertEquals(probeHashA, outcome.paymentHash) + assertEquals(123uL, outcome.routeFeeMsat) } @Test @@ -1373,14 +1375,29 @@ class LightningRepoTest : BaseUnitTest() { val onEvent = startNodeAndCaptureEvents() val result = async { sut.waitForProbeOutcome(setOf(probePaymentA, probePaymentB)) } - onEvent(Event.ProbeFailed(paymentId = probePaymentA, paymentHash = probeHashA, shortChannelId = 1uL)) - onEvent(Event.ProbeFailed(paymentId = probePaymentB, paymentHash = probeHashB, shortChannelId = 2uL)) + onEvent( + Event.ProbeFailed( + paymentId = probePaymentA, + paymentHash = probeHashA, + shortChannelId = 1uL, + routeFeeMsat = 123uL, + ) + ) + onEvent( + Event.ProbeFailed( + paymentId = probePaymentB, + paymentHash = probeHashB, + shortChannelId = 990_718_250_873_192_449uL, + routeFeeMsat = 456uL, + ) + ) val outcome = result.await().getOrThrow() assertIs(outcome) assertEquals(probePaymentB, outcome.paymentId) assertEquals(probeHashB, outcome.paymentHash) - assertEquals(2uL, outcome.shortChannelId) + assertEquals("990718250873192449", outcome.shortChannelId) + assertEquals(456uL, outcome.routeFeeMsat) } @Test @@ -1388,27 +1405,56 @@ class LightningRepoTest : BaseUnitTest() { val onEvent = startNodeAndCaptureEvents() val result = async { sut.waitForProbeOutcome(setOf(probePaymentA, probePaymentB)) } - onEvent(Event.ProbeFailed(paymentId = probePaymentA, paymentHash = probeHashA, shortChannelId = 1uL)) - onEvent(Event.ProbeSuccessful(paymentId = probePaymentB, paymentHash = probeHashB)) + onEvent( + Event.ProbeFailed( + paymentId = probePaymentA, + paymentHash = probeHashA, + shortChannelId = 1uL, + routeFeeMsat = 123uL, + ) + ) + onEvent( + Event.ProbeSuccessful( + paymentId = probePaymentB, + paymentHash = probeHashB, + routeFeeMsat = 456uL, + ) + ) val outcome = result.await().getOrThrow() assertIs(outcome) assertEquals(probePaymentB, outcome.paymentId) assertEquals(probeHashB, outcome.paymentHash) + assertEquals(456uL, outcome.routeFeeMsat) } @Test fun `waitForProbeOutcome does not hang on partial cached failures`() = test { val onEvent = startNodeAndCaptureEvents() - onEvent(Event.ProbeFailed(paymentId = probePaymentA, paymentHash = probeHashA, shortChannelId = 1uL)) + onEvent( + Event.ProbeFailed( + paymentId = probePaymentA, + paymentHash = probeHashA, + shortChannelId = 1uL, + routeFeeMsat = 123uL, + ) + ) val result = async { sut.waitForProbeOutcome(setOf(probePaymentA, probePaymentB)) } - onEvent(Event.ProbeFailed(paymentId = probePaymentB, paymentHash = probeHashB, shortChannelId = 2uL)) + onEvent( + Event.ProbeFailed( + paymentId = probePaymentB, + paymentHash = probeHashB, + shortChannelId = 2uL, + routeFeeMsat = 456uL, + ) + ) val outcome = result.await().getOrThrow() assertIs(outcome) assertEquals(probePaymentB, outcome.paymentId) - assertEquals(2uL, outcome.shortChannelId) + assertEquals("2", outcome.shortChannelId) + assertEquals(456uL, outcome.routeFeeMsat) } @Test @@ -1425,7 +1471,7 @@ class LightningRepoTest : BaseUnitTest() { fun `stop clears probe cache`() = test { val onEvent = startNodeAndCaptureEvents() whenever(lightningService.stop()).thenReturn(Unit) - onEvent(Event.ProbeSuccessful(paymentId = probePaymentA, paymentHash = probeHashA)) + onEvent(Event.ProbeSuccessful(paymentId = probePaymentA, paymentHash = probeHashA, routeFeeMsat = null)) sut.stop() val result = sut.waitForProbeOutcome(setOf(probePaymentA), timeout = 1.seconds) diff --git a/app/src/test/java/to/bitkit/viewmodels/ProbingToolViewModelTest.kt b/app/src/test/java/to/bitkit/viewmodels/ProbingToolViewModelTest.kt index 958c9252bd..d651ddb0b5 100644 --- a/app/src/test/java/to/bitkit/viewmodels/ProbingToolViewModelTest.kt +++ b/app/src/test/java/to/bitkit/viewmodels/ProbingToolViewModelTest.kt @@ -47,7 +47,15 @@ class ProbingToolViewModelTest : BaseUnitTest() { whenever(lightningRepo.sendProbeForNode(nodeId, 42uL)) .thenReturn(Result.success(ProbeDispatch(paymentIds = setOf(paymentId)))) whenever(lightningRepo.waitForProbeOutcome(setOf(paymentId))) - .thenReturn(Result.success(ProbeOutcome.Success(paymentId = paymentId, paymentHash = paymentHash))) + .thenReturn( + Result.success( + ProbeOutcome.Success( + paymentId = paymentId, + paymentHash = paymentHash, + routeFeeMsat = null, + ) + ) + ) sut.updateInvoice(nodeUri) sut.updateAmountSats("42") diff --git a/changelog.d/next/1052.fixed.md b/changelog.d/next/1052.fixed.md new file mode 100644 index 0000000000..f397e0c62b --- /dev/null +++ b/changelog.d/next/1052.fixed.md @@ -0,0 +1 @@ +Improved Lightning payment route selection reliability. diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f5aad7bc9b..0ae4c8ff80 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -64,7 +64,7 @@ ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" } ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" } ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" } ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" } -ldk-node-android = { module = "com.synonym:ldk-node-android", version = "0.7.0-rc.51" } +ldk-node-android = { module = "com.synonym:ldk-node-android", version = "0.7.0-rc.52" } lifecycle-process = { group = "androidx.lifecycle", name = "lifecycle-process", version.ref = "lifecycle" } lifecycle-runtime-compose = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "lifecycle" } lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycle" } From 5b6d7b26913f1504ab084d0cbff7a5bf696b0de5 Mon Sep 17 00:00:00 2001 From: Philipp Walter Date: Tue, 14 Jul 2026 12:03:53 +0200 Subject: [PATCH 2/4] fix: tune scorer fee params --- .../java/to/bitkit/services/LightningService.kt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/app/src/main/java/to/bitkit/services/LightningService.kt b/app/src/main/java/to/bitkit/services/LightningService.kt index 3ca4de9eef..f7ccc30252 100644 --- a/app/src/main/java/to/bitkit/services/LightningService.kt +++ b/app/src/main/java/to/bitkit/services/LightningService.kt @@ -38,6 +38,7 @@ import org.lightningdevkit.ldknode.PaymentDetails import org.lightningdevkit.ldknode.PaymentId import org.lightningdevkit.ldknode.PeerDetails import org.lightningdevkit.ldknode.PublicKey +import org.lightningdevkit.ldknode.ScoringFeeParameters import org.lightningdevkit.ldknode.SpendableUtxo import org.lightningdevkit.ldknode.Txid import org.lightningdevkit.ldknode.defaultConfig @@ -89,6 +90,12 @@ class LightningService @Inject constructor( companion object { private const val TAG = "LightningService" private const val NODE_ID_PREVIEW_LEN = 20 + private const val SCORING_BASE_PENALTY_MSAT = 50_000uL + private const val SCORING_LIQUIDITY_PENALTY_MULTIPLIER_MSAT = 10_000uL + private const val SCORING_LIQUIDITY_PENALTY_AMOUNT_MULTIPLIER_MSAT = 10_000uL + private const val SCORING_HISTORICAL_LIQUIDITY_PENALTY_AMOUNT_MULTIPLIER_MSAT = 20_000uL + private const val SCORING_CONSIDERED_IMPOSSIBLE_PENALTY_MSAT = 1_000_000_000_000uL + private const val SCORING_PROBING_DIVERSITY_PENALTY_MSAT = 60_000uL } @Volatile @@ -168,6 +175,7 @@ class LightningService @Inject constructor( configureChainSource(customServerUrl) configureGossipSource(customRgsServerUrl) configureScorerSource() + setScoringFeeParams(scorerFeeParameters(config)) setAddressType(selectedType) setAddressTypesToMonitor(monitoredTypes) @@ -860,6 +868,15 @@ class LightningService @Inject constructor( } // endregion + private fun scorerFeeParameters(config: Config): ScoringFeeParameters = config.scoringFeeParams.copy( + basePenaltyMsat = SCORING_BASE_PENALTY_MSAT, + liquidityPenaltyMultiplierMsat = SCORING_LIQUIDITY_PENALTY_MULTIPLIER_MSAT, + liquidityPenaltyAmountMultiplierMsat = SCORING_LIQUIDITY_PENALTY_AMOUNT_MULTIPLIER_MSAT, + historicalLiquidityPenaltyAmountMultiplierMsat = SCORING_HISTORICAL_LIQUIDITY_PENALTY_AMOUNT_MULTIPLIER_MSAT, + consideredImpossiblePenaltyMsat = SCORING_CONSIDERED_IMPOSSIBLE_PENALTY_MSAT, + probingDiversityPenaltyMsat = SCORING_PROBING_DIVERSITY_PENALTY_MSAT, + ) + // region utxo selection suspend fun listSpendableOutputs(): Result> { val node = this.node ?: throw ServiceError.NodeNotSetup() From ad7cdc826420dc6cc9109258bbaf57c8dd333ffc Mon Sep 17 00:00:00 2001 From: Philipp Walter Date: Tue, 14 Jul 2026 12:14:11 +0200 Subject: [PATCH 3/4] fix: address probe compile errors --- .../java/to/bitkit/dev/DevToolsProvider.kt | 2 +- .../to/bitkit/services/LightningService.kt | 19 +++++++++++-------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/app/src/debug/java/to/bitkit/dev/DevToolsProvider.kt b/app/src/debug/java/to/bitkit/dev/DevToolsProvider.kt index ea61fd726e..732dc16648 100644 --- a/app/src/debug/java/to/bitkit/dev/DevToolsProvider.kt +++ b/app/src/debug/java/to/bitkit/dev/DevToolsProvider.kt @@ -215,7 +215,7 @@ private sealed interface DevResult { val message: String? = null, val paymentId: String? = null, val paymentHash: String? = null, - val shortChannelId: ULong? = null, + val shortChannelId: String? = null, val paymentIds: List = emptyList(), ) : DevResult { companion object { diff --git a/app/src/main/java/to/bitkit/services/LightningService.kt b/app/src/main/java/to/bitkit/services/LightningService.kt index f7ccc30252..607f0dffcf 100644 --- a/app/src/main/java/to/bitkit/services/LightningService.kt +++ b/app/src/main/java/to/bitkit/services/LightningService.kt @@ -868,14 +868,17 @@ class LightningService @Inject constructor( } // endregion - private fun scorerFeeParameters(config: Config): ScoringFeeParameters = config.scoringFeeParams.copy( - basePenaltyMsat = SCORING_BASE_PENALTY_MSAT, - liquidityPenaltyMultiplierMsat = SCORING_LIQUIDITY_PENALTY_MULTIPLIER_MSAT, - liquidityPenaltyAmountMultiplierMsat = SCORING_LIQUIDITY_PENALTY_AMOUNT_MULTIPLIER_MSAT, - historicalLiquidityPenaltyAmountMultiplierMsat = SCORING_HISTORICAL_LIQUIDITY_PENALTY_AMOUNT_MULTIPLIER_MSAT, - consideredImpossiblePenaltyMsat = SCORING_CONSIDERED_IMPOSSIBLE_PENALTY_MSAT, - probingDiversityPenaltyMsat = SCORING_PROBING_DIVERSITY_PENALTY_MSAT, - ) + private fun scorerFeeParameters(config: Config): ScoringFeeParameters { + val defaultParams = requireNotNull(config.scoringFeeParams) { "scoringFeeParams" } + return defaultParams.copy( + basePenaltyMsat = SCORING_BASE_PENALTY_MSAT, + liquidityPenaltyMultiplierMsat = SCORING_LIQUIDITY_PENALTY_MULTIPLIER_MSAT, + liquidityPenaltyAmountMultiplierMsat = SCORING_LIQUIDITY_PENALTY_AMOUNT_MULTIPLIER_MSAT, + historicalLiquidityPenaltyAmountMultiplierMsat = SCORING_HISTORICAL_LIQUIDITY_PENALTY_AMOUNT_MULTIPLIER_MSAT, + consideredImpossiblePenaltyMsat = SCORING_CONSIDERED_IMPOSSIBLE_PENALTY_MSAT, + probingDiversityPenaltyMsat = SCORING_PROBING_DIVERSITY_PENALTY_MSAT, + ) + } // region utxo selection suspend fun listSpendableOutputs(): Result> { From 8320bddf0223c79adf665d2664279cb6167e6a73 Mon Sep 17 00:00:00 2001 From: Philipp Walter Date: Tue, 14 Jul 2026 12:44:48 +0200 Subject: [PATCH 4/4] fix: use scorer defaults --- .../java/to/bitkit/dev/DevToolsProvider.kt | 3 +- .../to/bitkit/services/LightningService.kt | 35 ++++++++++++------- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/app/src/debug/java/to/bitkit/dev/DevToolsProvider.kt b/app/src/debug/java/to/bitkit/dev/DevToolsProvider.kt index 732dc16648..f149261835 100644 --- a/app/src/debug/java/to/bitkit/dev/DevToolsProvider.kt +++ b/app/src/debug/java/to/bitkit/dev/DevToolsProvider.kt @@ -149,7 +149,8 @@ private sealed interface DevCommand { val timeout = args.timeoutSeconds.coerceAtLeast(1).seconds Logger.info( - "Sending keysend probe for target '${args.targetName ?: "unknown"}' nodeId='${args.nodeId}' amountSats='$amountSats'", + "Sending keysend probe for target '${args.targetName ?: "unknown"}' " + + "nodeId='${args.nodeId}' amountSats='$amountSats'", context = TAG, ) diff --git a/app/src/main/java/to/bitkit/services/LightningService.kt b/app/src/main/java/to/bitkit/services/LightningService.kt index 607f0dffcf..c58b1151cf 100644 --- a/app/src/main/java/to/bitkit/services/LightningService.kt +++ b/app/src/main/java/to/bitkit/services/LightningService.kt @@ -96,6 +96,19 @@ class LightningService @Inject constructor( private const val SCORING_HISTORICAL_LIQUIDITY_PENALTY_AMOUNT_MULTIPLIER_MSAT = 20_000uL private const val SCORING_CONSIDERED_IMPOSSIBLE_PENALTY_MSAT = 1_000_000_000_000uL private const val SCORING_PROBING_DIVERSITY_PENALTY_MSAT = 60_000uL + + private val DEFAULT_SCORING_FEE_PARAMETERS = ScoringFeeParameters( + basePenaltyMsat = 1_024uL, + basePenaltyAmountMultiplierMsat = 131_072uL, + liquidityPenaltyMultiplierMsat = 0uL, + liquidityPenaltyAmountMultiplierMsat = 0uL, + historicalLiquidityPenaltyMultiplierMsat = 10_000uL, + historicalLiquidityPenaltyAmountMultiplierMsat = 1_250uL, + antiProbingPenaltyMsat = 250uL, + consideredImpossiblePenaltyMsat = 100_000_000_000uL, + linearSuccessProbability = false, + probingDiversityPenaltyMsat = 0uL, + ) } @Volatile @@ -175,7 +188,7 @@ class LightningService @Inject constructor( configureChainSource(customServerUrl) configureGossipSource(customRgsServerUrl) configureScorerSource() - setScoringFeeParams(scorerFeeParameters(config)) + setScoringFeeParams(scorerFeeParameters()) setAddressType(selectedType) setAddressTypesToMonitor(monitoredTypes) @@ -868,17 +881,15 @@ class LightningService @Inject constructor( } // endregion - private fun scorerFeeParameters(config: Config): ScoringFeeParameters { - val defaultParams = requireNotNull(config.scoringFeeParams) { "scoringFeeParams" } - return defaultParams.copy( - basePenaltyMsat = SCORING_BASE_PENALTY_MSAT, - liquidityPenaltyMultiplierMsat = SCORING_LIQUIDITY_PENALTY_MULTIPLIER_MSAT, - liquidityPenaltyAmountMultiplierMsat = SCORING_LIQUIDITY_PENALTY_AMOUNT_MULTIPLIER_MSAT, - historicalLiquidityPenaltyAmountMultiplierMsat = SCORING_HISTORICAL_LIQUIDITY_PENALTY_AMOUNT_MULTIPLIER_MSAT, - consideredImpossiblePenaltyMsat = SCORING_CONSIDERED_IMPOSSIBLE_PENALTY_MSAT, - probingDiversityPenaltyMsat = SCORING_PROBING_DIVERSITY_PENALTY_MSAT, - ) - } + private fun scorerFeeParameters(): ScoringFeeParameters = DEFAULT_SCORING_FEE_PARAMETERS.copy( + basePenaltyMsat = SCORING_BASE_PENALTY_MSAT, + liquidityPenaltyMultiplierMsat = SCORING_LIQUIDITY_PENALTY_MULTIPLIER_MSAT, + liquidityPenaltyAmountMultiplierMsat = SCORING_LIQUIDITY_PENALTY_AMOUNT_MULTIPLIER_MSAT, + historicalLiquidityPenaltyAmountMultiplierMsat = + SCORING_HISTORICAL_LIQUIDITY_PENALTY_AMOUNT_MULTIPLIER_MSAT, + consideredImpossiblePenaltyMsat = SCORING_CONSIDERED_IMPOSSIBLE_PENALTY_MSAT, + probingDiversityPenaltyMsat = SCORING_PROBING_DIVERSITY_PENALTY_MSAT, + ) // region utxo selection suspend fun listSpendableOutputs(): Result> {