@@ -14,10 +14,14 @@ import dagger.hilt.components.SingletonComponent
1414import kotlinx.serialization.Serializable
1515import kotlinx.serialization.json.Json
1616import to.bitkit.async.ServiceQueue
17+ import to.bitkit.models.msatCeilOf
1718import to.bitkit.repositories.LightningRepo
19+ import to.bitkit.repositories.ProbeOutcome
1820import to.bitkit.utils.Logger
21+ import kotlin.time.Duration.Companion.seconds
1922
2023private const val TAG = " DevToolsProvider"
24+ private val DEV_JSON = Json { encodeDefaults = true }
2125
2226class DevToolsProvider : ContentProvider () {
2327
@@ -56,6 +60,7 @@ private sealed interface DevCommand {
5660 companion object {
5761 fun parse (method : String , arg : String? ): DevCommand ? = when (method) {
5862 CreateInvoice .METHOD -> CreateInvoice .parse(arg)
63+ ProbeInvoice .METHOD -> ProbeInvoice .parse(arg)
5964 else -> null
6065 }
6166 }
@@ -80,6 +85,44 @@ private sealed interface DevCommand {
8085 },
8186 )
8287 }
88+
89+ data class ProbeInvoice (val args : Args ) : DevCommand {
90+ companion object {
91+ const val METHOD = " probeInvoice"
92+ fun parse (arg : String? ) = ProbeInvoice (arg.deserialize<Args >())
93+ }
94+
95+ @Serializable
96+ data class Args (
97+ val targetName : String? = null ,
98+ val bolt11 : String ,
99+ val amountMsat : ULong? = null ,
100+ val amountSats : ULong? = null ,
101+ val timeoutSeconds : Long = 90 ,
102+ )
103+
104+ override suspend fun execute (deps : DevToolsProvider .Dependencies ): DevResult {
105+ val amountSats = args.amountSats ? : args.amountMsat?.let { msatCeilOf(it) }
106+ val timeout = args.timeoutSeconds.coerceAtLeast(1 ).seconds
107+
108+ Logger .info(
109+ " Sending probe for target '${args.targetName ? : " unknown" } ' amountSats='${amountSats ? : " invoice" } '" ,
110+ context = TAG ,
111+ )
112+
113+ return deps.lightningRepo().sendProbeForInvoice(args.bolt11, amountSats)
114+ .fold(
115+ onSuccess = {
116+ deps.lightningRepo().waitForProbeOutcome(it.paymentIds, timeout)
117+ .fold(
118+ onSuccess = { outcome -> outcome.toDevResult(it.paymentIds) },
119+ onFailure = { error -> DevResult .ProbeFailure .from(error, it.paymentIds) },
120+ )
121+ },
122+ onFailure = { DevResult .ProbeFailure .from(it) },
123+ )
124+ }
125+ }
83126}
84127
85128@Serializable
@@ -91,9 +134,49 @@ private sealed interface DevResult {
91134
92135 @Serializable data class Invoice (val bolt11 : String ) : DevResult
93136
137+ @Serializable
138+ data class ProbeSuccess (
139+ val success : Boolean = true ,
140+ val paymentId : String ,
141+ val paymentHash : String ,
142+ val paymentIds : List <String >,
143+ ) : DevResult
144+
145+ @Serializable
146+ data class ProbeFailure (
147+ val success : Boolean = false ,
148+ val message : String? = null ,
149+ val paymentId : String? = null ,
150+ val paymentHash : String? = null ,
151+ val shortChannelId : ULong? = null ,
152+ val paymentIds : List <String > = emptyList(),
153+ ) : DevResult {
154+ companion object {
155+ fun from (error : Throwable , paymentIds : Set <String > = emptySet()) = ProbeFailure (
156+ message = error.message,
157+ paymentIds = paymentIds.toList(),
158+ )
159+ }
160+ }
161+
94162 @Serializable data class Error (val message : String? = null ) : DevResult
95163
96- fun toBundle () = bundleOf(KEY_RESULT to Json .encodeToString(this ))
164+ fun toBundle () = bundleOf(KEY_RESULT to DEV_JSON .encodeToString(this ))
165+ }
166+
167+ private fun ProbeOutcome.toDevResult (paymentIds : Set <String >): DevResult = when (this ) {
168+ is ProbeOutcome .Success -> DevResult .ProbeSuccess (
169+ paymentId = paymentId,
170+ paymentHash = paymentHash,
171+ paymentIds = paymentIds.toList(),
172+ )
173+ is ProbeOutcome .Failure -> DevResult .ProbeFailure (
174+ message = " Probe failed" ,
175+ paymentId = paymentId,
176+ paymentHash = paymentHash,
177+ shortChannelId = shortChannelId,
178+ paymentIds = paymentIds.toList(),
179+ )
97180}
98181
99182private inline fun <reified T > String?.deserialize (): T =
0 commit comments