Skip to content

Commit 0583e2b

Browse files
committed
Fix comments
1 parent b382507 commit 0583e2b

File tree

4 files changed

+127
-103
lines changed

4 files changed

+127
-103
lines changed

app/src/main/java/to/bitkit/repositories/BlocktankRepo.kt

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import com.synonym.bitkitcore.IBtEstimateFeeResponse2
77
import com.synonym.bitkitcore.IBtInfo
88
import com.synonym.bitkitcore.IBtOrder
99
import com.synonym.bitkitcore.IcJitEntry
10+
import com.synonym.bitkitcore.giftOrder
11+
import com.synonym.bitkitcore.giftPay
1012
import kotlinx.coroutines.CoroutineDispatcher
1113
import kotlinx.coroutines.CoroutineScope
1214
import kotlinx.coroutines.SupervisorJob
@@ -27,9 +29,11 @@ import kotlinx.coroutines.isActive
2729
import kotlinx.coroutines.launch
2830
import kotlinx.coroutines.withContext
2931
import org.lightningdevkit.ldknode.ChannelDetails
32+
import to.bitkit.async.ServiceQueue
3033
import to.bitkit.data.CacheStore
3134
import to.bitkit.di.BgDispatcher
3235
import to.bitkit.env.Env
36+
import to.bitkit.ext.calculateRemoteBalance
3337
import to.bitkit.ext.nowTimestamp
3438
import to.bitkit.models.BlocktankBackupV1
3539
import to.bitkit.models.EUR_CURRENCY
@@ -45,13 +49,16 @@ import kotlin.math.ceil
4549
import kotlin.math.min
4650

4751
@Singleton
52+
@Suppress("LongParameterList")
4853
class BlocktankRepo @Inject constructor(
4954
@BgDispatcher private val bgDispatcher: CoroutineDispatcher,
5055
private val coreService: CoreService,
5156
private val lightningService: LightningService,
5257
private val currencyRepo: CurrencyRepo,
5358
private val cacheStore: CacheStore,
5459
@Named("enablePolling") private val enablePolling: Boolean,
60+
private val activityRepo: ActivityRepo,
61+
private val lightningRepo: LightningRepo,
5562
) {
5663
private val repoScope = CoroutineScope(bgDispatcher + SupervisorJob())
5764

@@ -399,6 +406,83 @@ class BlocktankRepo @Inject constructor(
399406
}
400407
}
401408

409+
suspend fun claimGiftCode(
410+
code: String,
411+
amount: ULong,
412+
waitTimeout: kotlin.time.Duration = kotlin.time.Duration.parse("30s"),
413+
): Result<GiftClaimResult> = withContext(bgDispatcher) {
414+
runCatching {
415+
lightningRepo.executeWhenNodeRunning(
416+
operationName = "claimGiftCode",
417+
waitTimeout = waitTimeout,
418+
) {
419+
Result.success(Unit)
420+
}.getOrThrow()
421+
422+
delay(2_000L)
423+
424+
val channels = lightningRepo.getChannelsAsync().getOrThrow()
425+
val maxInboundCapacity = channels.calculateRemoteBalance()
426+
427+
if (maxInboundCapacity >= amount) {
428+
claimGiftCodeWithLiquidity(code)
429+
} else {
430+
claimGiftCodeWithoutLiquidity(code, amount)
431+
}
432+
}.onFailure { e ->
433+
Logger.error("Failed to claim gift code", e, context = TAG)
434+
}
435+
}
436+
437+
private suspend fun claimGiftCodeWithLiquidity(code: String): GiftClaimResult {
438+
val invoice = lightningRepo.createInvoice(
439+
amountSats = null,
440+
description = "blocktank-gift-code:$code",
441+
expirySeconds = 3600u,
442+
).getOrThrow()
443+
444+
ServiceQueue.CORE.background {
445+
giftPay(invoice = invoice)
446+
}
447+
448+
return GiftClaimResult.SuccessWithLiquidity
449+
}
450+
451+
private suspend fun claimGiftCodeWithoutLiquidity(code: String, amount: ULong): GiftClaimResult {
452+
val nodeId = lightningService.nodeId ?: throw ServiceError.NodeNotStarted
453+
454+
val order = ServiceQueue.CORE.background {
455+
giftOrder(clientNodeId = nodeId, code = "blocktank-gift-code:$code")
456+
}
457+
458+
val orderId = order.orderId ?: throw IllegalStateException("Order ID is nil")
459+
460+
val openedOrder = openChannel(orderId).getOrThrow()
461+
462+
val nowTimestamp = nowTimestamp().epochSecond.toULong()
463+
464+
val lightningActivity = com.synonym.bitkitcore.LightningActivity(
465+
id = openedOrder.channel?.fundingTx?.id ?: orderId,
466+
txType = com.synonym.bitkitcore.PaymentType.RECEIVED,
467+
status = com.synonym.bitkitcore.PaymentState.SUCCEEDED,
468+
value = amount,
469+
fee = 0u,
470+
invoice = openedOrder.payment?.bolt11Invoice?.request ?: "",
471+
message = code,
472+
timestamp = nowTimestamp,
473+
preimage = null,
474+
createdAt = nowTimestamp,
475+
updatedAt = null,
476+
)
477+
478+
activityRepo.insertActivity(com.synonym.bitkitcore.Activity.Lightning(lightningActivity)).getOrThrow()
479+
480+
return GiftClaimResult.SuccessWithoutLiquidity(
481+
paymentHashOrTxId = openedOrder.channel?.fundingTx?.id ?: orderId,
482+
sats = amount.toLong(),
483+
)
484+
}
485+
402486
companion object {
403487
private const val TAG = "BlocktankRepo"
404488
private const val DEFAULT_CHANNEL_EXPIRY_WEEKS = 6u
@@ -413,3 +497,11 @@ data class BlocktankState(
413497
val info: IBtInfo? = null,
414498
val minCjitSats: Int? = null,
415499
)
500+
501+
sealed class GiftClaimResult {
502+
object SuccessWithLiquidity : GiftClaimResult()
503+
data class SuccessWithoutLiquidity(
504+
val paymentHashOrTxId: String,
505+
val sats: Long,
506+
) : GiftClaimResult()
507+
}

app/src/main/java/to/bitkit/repositories/LightningRepo.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -812,6 +812,10 @@ class LightningRepo @Inject constructor(
812812
Result.success(checkNotNull(lightningService.balances))
813813
}
814814

815+
suspend fun getChannelsAsync(): Result<List<ChannelDetails>> = executeWhenNodeRunning("getChannelsAsync") {
816+
Result.success(checkNotNull(lightningService.channels))
817+
}
818+
815819
fun getStatus(): NodeStatus? =
816820
if (_lightningState.value.nodeLifecycleState.isRunning()) lightningService.status else null
817821

app/src/main/java/to/bitkit/ui/sheets/GiftErrorSheet.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package to.bitkit.ui.sheets
33
import androidx.annotation.StringRes
44
import androidx.compose.foundation.Image
55
import androidx.compose.foundation.layout.Column
6-
import androidx.compose.foundation.layout.Spacer
76
import androidx.compose.foundation.layout.aspectRatio
87
import androidx.compose.foundation.layout.fillMaxWidth
98
import androidx.compose.foundation.layout.navigationBarsPadding
@@ -17,6 +16,7 @@ import androidx.compose.ui.res.stringResource
1716
import androidx.compose.ui.unit.dp
1817
import to.bitkit.R
1918
import to.bitkit.ui.components.BodyM
19+
import to.bitkit.ui.components.FillHeight
2020
import to.bitkit.ui.components.PrimaryButton
2121
import to.bitkit.ui.components.SheetSize
2222
import to.bitkit.ui.components.VerticalSpacer
@@ -63,7 +63,7 @@ private fun Content(
6363
color = Colors.White64,
6464
)
6565

66-
Spacer(modifier = Modifier.weight(1f))
66+
FillHeight()
6767

6868
Image(
6969
painter = painterResource(R.drawable.exclamation_mark),
@@ -74,7 +74,7 @@ private fun Content(
7474
.align(Alignment.CenterHorizontally)
7575
)
7676

77-
Spacer(modifier = Modifier.weight(1f))
77+
FillHeight()
7878

7979
PrimaryButton(
8080
text = stringResource(R.string.common__ok),

app/src/main/java/to/bitkit/ui/sheets/GiftViewModel.kt

Lines changed: 28 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -2,38 +2,26 @@ package to.bitkit.ui.sheets
22

33
import androidx.lifecycle.ViewModel
44
import androidx.lifecycle.viewModelScope
5-
import com.synonym.bitkitcore.Activity
6-
import com.synonym.bitkitcore.PaymentType
7-
import com.synonym.bitkitcore.giftOrder
8-
import com.synonym.bitkitcore.giftPay
95
import dagger.hilt.android.lifecycle.HiltViewModel
106
import kotlinx.coroutines.CoroutineDispatcher
11-
import kotlinx.coroutines.delay
127
import kotlinx.coroutines.flow.MutableSharedFlow
138
import kotlinx.coroutines.flow.asSharedFlow
149
import kotlinx.coroutines.launch
1510
import kotlinx.coroutines.withContext
16-
import to.bitkit.async.ServiceQueue
1711
import to.bitkit.di.BgDispatcher
18-
import to.bitkit.ext.calculateRemoteBalance
1912
import to.bitkit.models.NewTransactionSheetDetails
2013
import to.bitkit.models.NewTransactionSheetDirection
2114
import to.bitkit.models.NewTransactionSheetType
22-
import to.bitkit.repositories.ActivityRepo
2315
import to.bitkit.repositories.BlocktankRepo
24-
import to.bitkit.repositories.LightningRepo
25-
import to.bitkit.services.LightningService
16+
import to.bitkit.repositories.GiftClaimResult
2617
import to.bitkit.utils.Logger
2718
import javax.inject.Inject
2819
import kotlin.time.Duration.Companion.milliseconds
2920

3021
@HiltViewModel
3122
class GiftViewModel @Inject constructor(
3223
@BgDispatcher private val bgDispatcher: CoroutineDispatcher,
33-
private val lightningRepo: LightningRepo,
34-
private val lightningService: LightningService,
3524
private val blocktankRepo: BlocktankRepo,
36-
private val activityRepo: ActivityRepo,
3725
) : ViewModel() {
3826

3927
private val _navigationEvent = MutableSharedFlow<GiftRoute>(extraBufferCapacity = 1)
@@ -65,98 +53,39 @@ class GiftViewModel @Inject constructor(
6553
}
6654
}
6755

68-
@Suppress("TooGenericExceptionCaught")
6956
private suspend fun claimGift() = withContext(bgDispatcher) {
7057
if (isClaiming) return@withContext
7158
isClaiming = true
7259

73-
try {
74-
lightningRepo.executeWhenNodeRunning(
75-
operationName = "waitForNodeRunning",
76-
waitTimeout = NODE_STARTUP_TIMEOUT_MS.milliseconds,
77-
) {
78-
Result.success(Unit)
79-
}.getOrThrow()
80-
81-
delay(PEER_CONNECTION_DELAY_MS)
82-
83-
val channels = lightningRepo.lightningState.value.channels
84-
val maxInboundCapacity = channels.calculateRemoteBalance()
85-
86-
if (maxInboundCapacity >= amount) {
87-
claimWithLiquidity()
88-
} else {
89-
claimWithoutLiquidity()
90-
}
91-
} catch (e: Exception) {
92-
handleGiftClaimError(e)
93-
} finally {
94-
isClaiming = false
95-
}
96-
}
97-
98-
private suspend fun claimWithLiquidity() {
99-
runCatching {
100-
val invoice = lightningService.receive(
101-
sat = null,
102-
description = "blocktank-gift-code:$code",
103-
expirySecs = 3600u,
104-
)
105-
106-
ServiceQueue.CORE.background {
107-
giftPay(invoice = invoice)
60+
blocktankRepo.claimGiftCode(
61+
code = code,
62+
amount = amount,
63+
waitTimeout = NODE_STARTUP_TIMEOUT_MS.milliseconds,
64+
).fold(
65+
onSuccess = { result ->
66+
when (result) {
67+
is GiftClaimResult.SuccessWithLiquidity -> {
68+
_navigationEvent.emit(GiftRoute.Success)
69+
}
70+
is GiftClaimResult.SuccessWithoutLiquidity -> {
71+
_successEvent.emit(
72+
NewTransactionSheetDetails(
73+
type = NewTransactionSheetType.LIGHTNING,
74+
direction = NewTransactionSheetDirection.RECEIVED,
75+
paymentHashOrTxId = result.paymentHashOrTxId,
76+
sats = result.sats,
77+
)
78+
)
79+
_navigationEvent.emit(GiftRoute.Success)
80+
}
81+
}
82+
},
83+
onFailure = { error ->
84+
handleGiftClaimError(error)
10885
}
86+
)
10987

110-
_navigationEvent.emit(GiftRoute.Success)
111-
}.onFailure { e ->
112-
handleGiftClaimError(e)
113-
}
114-
}
115-
116-
private suspend fun claimWithoutLiquidity() {
117-
runCatching {
118-
check(lightningService.nodeId != null) { "Node not started" }
119-
val nodeId = lightningService.nodeId!!
120-
121-
val order = ServiceQueue.CORE.background {
122-
giftOrder(clientNodeId = nodeId, code = "blocktank-gift-code:$code")
123-
}
124-
125-
check(order.orderId != null) { "Order ID is nil" }
126-
val orderId = order.orderId!!
127-
128-
val openedOrder = blocktankRepo.openChannel(orderId).getOrThrow()
129-
130-
val nowTimestamp = (System.currentTimeMillis() / 1000).toULong()
131-
132-
val lightningActivity = com.synonym.bitkitcore.LightningActivity(
133-
id = openedOrder.channel?.fundingTx?.id ?: orderId,
134-
txType = PaymentType.RECEIVED,
135-
status = com.synonym.bitkitcore.PaymentState.SUCCEEDED,
136-
value = amount,
137-
fee = 0u,
138-
invoice = openedOrder.payment?.bolt11Invoice?.request ?: "",
139-
message = code,
140-
timestamp = nowTimestamp,
141-
preimage = null,
142-
createdAt = nowTimestamp,
143-
updatedAt = null,
144-
)
145-
146-
activityRepo.insertActivity(Activity.Lightning(lightningActivity)).getOrThrow()
147-
148-
_successEvent.emit(
149-
NewTransactionSheetDetails(
150-
type = NewTransactionSheetType.LIGHTNING,
151-
direction = NewTransactionSheetDirection.RECEIVED,
152-
paymentHashOrTxId = openedOrder.channel?.fundingTx?.id ?: orderId,
153-
sats = amount.toLong(),
154-
)
155-
)
156-
_navigationEvent.emit(GiftRoute.Success)
157-
}.onFailure { e ->
158-
handleGiftClaimError(e)
159-
}
88+
isClaiming = false
16089
}
16190

16291
private suspend fun handleGiftClaimError(error: Throwable) {
@@ -186,6 +115,5 @@ class GiftViewModel @Inject constructor(
186115
companion object {
187116
private const val TAG = "GiftViewModel"
188117
private const val NODE_STARTUP_TIMEOUT_MS = 30_000L
189-
private const val PEER_CONNECTION_DELAY_MS = 2_000L
190118
}
191119
}

0 commit comments

Comments
 (0)