@@ -15,9 +15,12 @@ import kotlinx.serialization.Serializable
1515import kotlinx.serialization.json.Json
1616import to.bitkit.async.ServiceQueue
1717import to.bitkit.repositories.LightningRepo
18+ import to.bitkit.repositories.ProbeOutcome
1819import to.bitkit.utils.Logger
20+ import kotlin.time.Duration.Companion.seconds
1921
2022private const val TAG = " DevToolsProvider"
23+ private val DEV_JSON = Json { encodeDefaults = true }
2124
2225class DevToolsProvider : ContentProvider () {
2326
@@ -56,6 +59,7 @@ private sealed interface DevCommand {
5659 companion object {
5760 fun parse (method : String , arg : String? ): DevCommand ? = when (method) {
5861 CreateInvoice .METHOD -> CreateInvoice .parse(arg)
62+ ProbeInvoice .METHOD -> ProbeInvoice .parse(arg)
5963 else -> null
6064 }
6165 }
@@ -80,6 +84,44 @@ private sealed interface DevCommand {
8084 },
8185 )
8286 }
87+
88+ data class ProbeInvoice (val args : Args ) : DevCommand {
89+ companion object {
90+ const val METHOD = " probeInvoice"
91+ fun parse (arg : String? ) = ProbeInvoice (arg.deserialize<Args >())
92+ }
93+
94+ @Serializable
95+ data class Args (
96+ val targetName : String? = null ,
97+ val bolt11 : String ,
98+ val amountMsat : ULong? = null ,
99+ val amountSats : ULong? = null ,
100+ val timeoutSeconds : Long = 90 ,
101+ )
102+
103+ override suspend fun execute (deps : DevToolsProvider .Dependencies ): DevResult {
104+ val amountSats = args.amountSats ? : args.amountMsat?.div(1_000u )
105+ val timeout = args.timeoutSeconds.coerceAtLeast(1 ).seconds
106+
107+ Logger .info(
108+ " Sending probe for target '${args.targetName ? : " unknown" } ' amountSats='${amountSats ? : " invoice" } '" ,
109+ context = TAG ,
110+ )
111+
112+ return deps.lightningRepo().sendProbeForInvoice(args.bolt11, amountSats)
113+ .fold(
114+ onSuccess = {
115+ deps.lightningRepo().waitForProbeOutcome(it.paymentIds, timeout)
116+ .fold(
117+ onSuccess = { outcome -> outcome.toDevResult(it.paymentIds) },
118+ onFailure = { error -> DevResult .ProbeFailure .from(error, it.paymentIds) },
119+ )
120+ },
121+ onFailure = { DevResult .ProbeFailure .from(it) },
122+ )
123+ }
124+ }
83125}
84126
85127@Serializable
@@ -91,9 +133,49 @@ private sealed interface DevResult {
91133
92134 @Serializable data class Invoice (val bolt11 : String ) : DevResult
93135
136+ @Serializable
137+ data class ProbeSuccess (
138+ val success : Boolean = true ,
139+ val paymentId : String ,
140+ val paymentHash : String ,
141+ val paymentIds : List <String >,
142+ ) : DevResult
143+
144+ @Serializable
145+ data class ProbeFailure (
146+ val success : Boolean = false ,
147+ val message : String? = null ,
148+ val paymentId : String? = null ,
149+ val paymentHash : String? = null ,
150+ val shortChannelId : ULong? = null ,
151+ val paymentIds : List <String > = emptyList(),
152+ ) : DevResult {
153+ companion object {
154+ fun from (error : Throwable , paymentIds : Set <String > = emptySet()) = ProbeFailure (
155+ message = error.message,
156+ paymentIds = paymentIds.toList(),
157+ )
158+ }
159+ }
160+
94161 @Serializable data class Error (val message : String? = null ) : DevResult
95162
96- fun toBundle () = bundleOf(KEY_RESULT to Json .encodeToString(this ))
163+ fun toBundle () = bundleOf(KEY_RESULT to DEV_JSON .encodeToString(this ))
164+ }
165+
166+ private fun ProbeOutcome.toDevResult (paymentIds : Set <String >): DevResult = when (this ) {
167+ is ProbeOutcome .Success -> DevResult .ProbeSuccess (
168+ paymentId = paymentId,
169+ paymentHash = paymentHash,
170+ paymentIds = paymentIds.toList(),
171+ )
172+ is ProbeOutcome .Failure -> DevResult .ProbeFailure (
173+ message = " Probe failed" ,
174+ paymentId = paymentId,
175+ paymentHash = paymentHash,
176+ shortChannelId = shortChannelId,
177+ paymentIds = paymentIds.toList(),
178+ )
97179}
98180
99181private inline fun <reified T > String?.deserialize (): T =
0 commit comments