@@ -20,13 +20,17 @@ import kotlinx.collections.immutable.persistentListOf
2020import kotlinx.collections.immutable.toImmutableList
2121import kotlinx.coroutines.CoroutineDispatcher
2222import kotlinx.coroutines.CoroutineScope
23+ import kotlinx.coroutines.CoroutineStart
2324import kotlinx.coroutines.SupervisorJob
25+ import kotlinx.coroutines.async
26+ import kotlinx.coroutines.coroutineScope
2427import kotlinx.coroutines.currentCoroutineContext
2528import kotlinx.coroutines.delay
2629import kotlinx.coroutines.flow.MutableStateFlow
2730import kotlinx.coroutines.flow.StateFlow
2831import kotlinx.coroutines.flow.asStateFlow
2932import kotlinx.coroutines.flow.distinctUntilChanged
33+ import kotlinx.coroutines.flow.filterIsInstance
3034import kotlinx.coroutines.flow.first
3135import kotlinx.coroutines.flow.flow
3236import kotlinx.coroutines.flow.flowOn
@@ -37,7 +41,10 @@ import kotlinx.coroutines.flow.update
3741import kotlinx.coroutines.isActive
3842import kotlinx.coroutines.launch
3943import kotlinx.coroutines.withContext
44+ import kotlinx.coroutines.withTimeoutOrNull
45+ import org.lightningdevkit.ldknode.Bolt11Invoice
4046import org.lightningdevkit.ldknode.ChannelDetails
47+ import org.lightningdevkit.ldknode.Event
4148import to.bitkit.async.ServiceQueue
4249import to.bitkit.data.CacheStore
4350import to.bitkit.di.BgDispatcher
@@ -46,6 +53,7 @@ import to.bitkit.ext.calculateRemoteBalance
4653import to.bitkit.ext.nowTimestamp
4754import to.bitkit.models.BlocktankBackupV1
4855import to.bitkit.models.EUR
56+ import to.bitkit.models.msatCeilOf
4957import to.bitkit.services.CoreService
5058import to.bitkit.services.LightningService
5159import to.bitkit.utils.Logger
@@ -459,26 +467,52 @@ class BlocktankRepo @Inject constructor(
459467 }
460468 }
461469
462- private suspend fun claimGiftCodeWithLiquidity (code : String , amount : ULong ): GiftClaimResult {
470+ private suspend fun claimGiftCodeWithLiquidity (code : String , amount : ULong ): GiftClaimResult = coroutineScope {
463471 val invoice = lightningRepo.createInvoice(
464472 amountSats = null ,
465473 description = " blocktank-gift-code:$code " ,
466474 expirySeconds = 1 .hours.inWholeSeconds.toUInt(),
467475 ).getOrThrow()
468476
477+ val expectedPaymentHash = Bolt11Invoice .fromStr(invoice).paymentHash()
478+
469479 Logger .debug(" Created invoice for gift code, requesting payment from LSP" , context = TAG )
470480
481+ val paymentReceivedDeferred = async(start = CoroutineStart .UNDISPATCHED ) {
482+ lightningRepo.nodeEvents
483+ .filterIsInstance<Event .PaymentReceived >()
484+ .first { it.paymentHash == expectedPaymentHash }
485+ }
486+
471487 val giftResponse = ServiceQueue .CORE .background {
472488 giftPay(invoice = invoice)
473489 }
474490
475- Logger .debug(" Gift payment request completed: id=${giftResponse.id} " , context = TAG )
491+ Logger .debug(
492+ " Gift payment request completed: id='${giftResponse.id} ', awaiting LDK PaymentReceived" ,
493+ context = TAG ,
494+ )
495+
496+ val paymentReceived = withTimeoutOrNull(GIFT_PAYMENT_RECEIVE_TIMEOUT ) {
497+ paymentReceivedDeferred.await()
498+ }
499+
500+ if (paymentReceived == null ) {
501+ paymentReceivedDeferred.cancel()
502+ throw ServiceError .GiftClaimPaymentNotReceived ()
503+ }
504+
505+ Logger .debug(
506+ " Gift payment confirmed by LDK: hash='${paymentReceived.paymentHash} ', " +
507+ " amountMsat='${paymentReceived.amountMsat} '" ,
508+ context = TAG ,
509+ )
510+
511+ val receivedSats = msatCeilOf(paymentReceived.amountMsat).toLong()
476512
477- return GiftClaimResult .SuccessWithLiquidity (
478- paymentHashOrTxId = giftResponse.bolt11PaymentId ? : giftResponse.id,
479- sats = giftResponse.bolt11Payment?.paidSat?.toLong()
480- ? : giftResponse.appliedGiftCode?.giftSat?.toLong()
481- ? : amount.toLong(),
513+ GiftClaimResult .SuccessWithLiquidity (
514+ paymentHashOrTxId = paymentReceived.paymentHash,
515+ sats = receivedSats.takeIf { it > 0 } ? : amount.toLong(),
482516 invoice = invoice,
483517 code = code,
484518 )
@@ -518,6 +552,7 @@ class BlocktankRepo @Inject constructor(
518552 private const val DEFAULT_SOURCE = " bitkit-android"
519553 private const val PEER_CONNECTION_DELAY_MS = 2_000L
520554 private val TIMEOUT_GIFT_CODE = 30 .seconds
555+ private val GIFT_PAYMENT_RECEIVE_TIMEOUT = 45 .seconds
521556 }
522557}
523558
0 commit comments