@@ -7,6 +7,8 @@ import com.synonym.bitkitcore.IBtEstimateFeeResponse2
77import com.synonym.bitkitcore.IBtInfo
88import com.synonym.bitkitcore.IBtOrder
99import com.synonym.bitkitcore.IcJitEntry
10+ import com.synonym.bitkitcore.giftOrder
11+ import com.synonym.bitkitcore.giftPay
1012import kotlinx.coroutines.CoroutineDispatcher
1113import kotlinx.coroutines.CoroutineScope
1214import kotlinx.coroutines.SupervisorJob
@@ -27,9 +29,11 @@ import kotlinx.coroutines.isActive
2729import kotlinx.coroutines.launch
2830import kotlinx.coroutines.withContext
2931import org.lightningdevkit.ldknode.ChannelDetails
32+ import to.bitkit.async.ServiceQueue
3033import to.bitkit.data.CacheStore
3134import to.bitkit.di.BgDispatcher
3235import to.bitkit.env.Env
36+ import to.bitkit.ext.calculateRemoteBalance
3337import to.bitkit.ext.nowTimestamp
3438import to.bitkit.models.BlocktankBackupV1
3539import to.bitkit.models.EUR_CURRENCY
@@ -43,15 +47,19 @@ import javax.inject.Named
4347import javax.inject.Singleton
4448import kotlin.math.ceil
4549import kotlin.math.min
50+ import kotlin.time.Duration
51+ import kotlin.time.Duration.Companion.seconds
4652
4753@Singleton
54+ @Suppress(" LongParameterList" )
4855class BlocktankRepo @Inject constructor(
4956 @BgDispatcher private val bgDispatcher : CoroutineDispatcher ,
5057 private val coreService : CoreService ,
5158 private val lightningService : LightningService ,
5259 private val currencyRepo : CurrencyRepo ,
5360 private val cacheStore : CacheStore ,
5461 @Named(" enablePolling" ) private val enablePolling : Boolean ,
62+ private val lightningRepo : LightningRepo ,
5563) {
5664 private val repoScope = CoroutineScope (bgDispatcher + SupervisorJob ())
5765
@@ -399,10 +407,74 @@ class BlocktankRepo @Inject constructor(
399407 }
400408 }
401409
410+ suspend fun claimGiftCode (
411+ code : String ,
412+ amount : ULong ,
413+ waitTimeout : Duration = TIMEOUT_GIFT_CODE ,
414+ ): Result <GiftClaimResult > = withContext(bgDispatcher) {
415+ runCatching {
416+ require(code.isNotBlank()) { " Gift code cannot be blank" }
417+ require(amount > 0u ) { " Gift amount must be positive" }
418+
419+ lightningRepo.executeWhenNodeRunning(
420+ operationName = " claimGiftCode" ,
421+ waitTimeout = waitTimeout,
422+ ) {
423+ delay(PEER_CONNECTION_DELAY_MS )
424+
425+ val channels = lightningRepo.getChannelsAsync().getOrThrow()
426+ val maxInboundCapacity = channels.calculateRemoteBalance()
427+
428+ if (maxInboundCapacity >= amount) {
429+ Result .success(claimGiftCodeWithLiquidity(code))
430+ } else {
431+ Result .success(claimGiftCodeWithoutLiquidity(code, amount))
432+ }
433+ }.getOrThrow()
434+ }.onFailure { e ->
435+ Logger .error(" Failed to claim gift code" , e, context = TAG )
436+ }
437+ }
438+
439+ private suspend fun claimGiftCodeWithLiquidity (code : String ): GiftClaimResult {
440+ val invoice = lightningRepo.createInvoice(
441+ amountSats = null ,
442+ description = " blocktank-gift-code:$code " ,
443+ expirySeconds = 3600u ,
444+ ).getOrThrow()
445+
446+ ServiceQueue .CORE .background {
447+ giftPay(invoice = invoice)
448+ }
449+
450+ return GiftClaimResult .SuccessWithLiquidity
451+ }
452+
453+ private suspend fun claimGiftCodeWithoutLiquidity (code : String , amount : ULong ): GiftClaimResult {
454+ val nodeId = lightningService.nodeId ? : throw ServiceError .NodeNotStarted
455+
456+ val order = ServiceQueue .CORE .background {
457+ giftOrder(clientNodeId = nodeId, code = " blocktank-gift-code:$code " )
458+ }
459+
460+ val orderId = checkNotNull(order.orderId) { " Order ID is null" }
461+
462+ val openedOrder = openChannel(orderId).getOrThrow()
463+
464+ return GiftClaimResult .SuccessWithoutLiquidity (
465+ paymentHashOrTxId = openedOrder.channel?.fundingTx?.id ? : orderId,
466+ sats = amount.toLong(),
467+ invoice = openedOrder.payment?.bolt11Invoice?.request ? : " " ,
468+ code = code,
469+ )
470+ }
471+
402472 companion object {
403473 private const val TAG = " BlocktankRepo"
404474 private const val DEFAULT_CHANNEL_EXPIRY_WEEKS = 6u
405475 private const val DEFAULT_SOURCE = " bitkit-android"
476+ private const val PEER_CONNECTION_DELAY_MS = 2_000L
477+ private val TIMEOUT_GIFT_CODE = 30 .seconds
406478 }
407479}
408480
@@ -413,3 +485,13 @@ data class BlocktankState(
413485 val info : IBtInfo ? = null ,
414486 val minCjitSats : Int? = null ,
415487)
488+
489+ sealed class GiftClaimResult {
490+ object SuccessWithLiquidity : GiftClaimResult()
491+ data class SuccessWithoutLiquidity (
492+ val paymentHashOrTxId : String ,
493+ val sats : Long ,
494+ val invoice : String ,
495+ val code : String ,
496+ ) : GiftClaimResult()
497+ }
0 commit comments