Skip to content

Commit fdf7516

Browse files
committed
Fix comments
1 parent b382507 commit fdf7516

File tree

5 files changed

+135
-94
lines changed

5 files changed

+135
-94
lines changed

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

Lines changed: 78 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,15 @@ 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 lightningRepo: LightningRepo,
5561
) {
5662
private val repoScope = CoroutineScope(bgDispatcher + SupervisorJob())
5763

@@ -399,10 +405,72 @@ class BlocktankRepo @Inject constructor(
399405
}
400406
}
401407

408+
suspend fun claimGiftCode(
409+
code: String,
410+
amount: ULong,
411+
waitTimeout: kotlin.time.Duration = kotlin.time.Duration.parse("30s"),
412+
): Result<GiftClaimResult> = withContext(bgDispatcher) {
413+
runCatching {
414+
lightningRepo.executeWhenNodeRunning(
415+
operationName = "claimGiftCode",
416+
waitTimeout = waitTimeout,
417+
) {
418+
Result.success(Unit)
419+
}.getOrThrow()
420+
421+
delay(PEER_CONNECTION_DELAY_MS)
422+
423+
val channels = lightningRepo.getChannelsAsync().getOrThrow()
424+
val maxInboundCapacity = channels.calculateRemoteBalance()
425+
426+
if (maxInboundCapacity >= amount) {
427+
claimGiftCodeWithLiquidity(code)
428+
} else {
429+
claimGiftCodeWithoutLiquidity(code, amount)
430+
}
431+
}.onFailure { e ->
432+
Logger.error("Failed to claim gift code", e, context = TAG)
433+
}
434+
}
435+
436+
private suspend fun claimGiftCodeWithLiquidity(code: String): GiftClaimResult {
437+
val invoice = lightningRepo.createInvoice(
438+
amountSats = null,
439+
description = "blocktank-gift-code:$code",
440+
expirySeconds = 3600u,
441+
).getOrThrow()
442+
443+
ServiceQueue.CORE.background {
444+
giftPay(invoice = invoice)
445+
}
446+
447+
return GiftClaimResult.SuccessWithLiquidity
448+
}
449+
450+
private suspend fun claimGiftCodeWithoutLiquidity(code: String, amount: ULong): GiftClaimResult {
451+
val nodeId = lightningService.nodeId ?: throw ServiceError.NodeNotStarted
452+
453+
val order = ServiceQueue.CORE.background {
454+
giftOrder(clientNodeId = nodeId, code = "blocktank-gift-code:$code")
455+
}
456+
457+
val orderId = checkNotNull(order.orderId) { "Order ID is nil" }
458+
459+
val openedOrder = openChannel(orderId).getOrThrow()
460+
461+
return GiftClaimResult.SuccessWithoutLiquidity(
462+
paymentHashOrTxId = openedOrder.channel?.fundingTx?.id ?: orderId,
463+
sats = amount.toLong(),
464+
invoice = openedOrder.payment?.bolt11Invoice?.request ?: "",
465+
code = code,
466+
)
467+
}
468+
402469
companion object {
403470
private const val TAG = "BlocktankRepo"
404471
private const val DEFAULT_CHANNEL_EXPIRY_WEEKS = 6u
405472
private const val DEFAULT_SOURCE = "bitkit-android"
473+
private const val PEER_CONNECTION_DELAY_MS = 2_000L
406474
}
407475
}
408476

@@ -413,3 +481,13 @@ data class BlocktankState(
413481
val info: IBtInfo? = null,
414482
val minCjitSats: Int? = null,
415483
)
484+
485+
sealed class GiftClaimResult {
486+
object SuccessWithLiquidity : GiftClaimResult()
487+
data class SuccessWithoutLiquidity(
488+
val paymentHashOrTxId: String,
489+
val sats: Long,
490+
val invoice: String,
491+
val code: String,
492+
) : GiftClaimResult()
493+
}

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: 48 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -3,35 +3,29 @@ package to.bitkit.ui.sheets
33
import androidx.lifecycle.ViewModel
44
import androidx.lifecycle.viewModelScope
55
import com.synonym.bitkitcore.Activity
6+
import com.synonym.bitkitcore.PaymentState
67
import com.synonym.bitkitcore.PaymentType
7-
import com.synonym.bitkitcore.giftOrder
8-
import com.synonym.bitkitcore.giftPay
98
import dagger.hilt.android.lifecycle.HiltViewModel
109
import kotlinx.coroutines.CoroutineDispatcher
11-
import kotlinx.coroutines.delay
1210
import kotlinx.coroutines.flow.MutableSharedFlow
1311
import kotlinx.coroutines.flow.asSharedFlow
1412
import kotlinx.coroutines.launch
1513
import kotlinx.coroutines.withContext
16-
import to.bitkit.async.ServiceQueue
1714
import to.bitkit.di.BgDispatcher
18-
import to.bitkit.ext.calculateRemoteBalance
15+
import to.bitkit.ext.nowTimestamp
1916
import to.bitkit.models.NewTransactionSheetDetails
2017
import to.bitkit.models.NewTransactionSheetDirection
2118
import to.bitkit.models.NewTransactionSheetType
2219
import to.bitkit.repositories.ActivityRepo
2320
import to.bitkit.repositories.BlocktankRepo
24-
import to.bitkit.repositories.LightningRepo
25-
import to.bitkit.services.LightningService
21+
import to.bitkit.repositories.GiftClaimResult
2622
import to.bitkit.utils.Logger
2723
import javax.inject.Inject
2824
import kotlin.time.Duration.Companion.milliseconds
2925

3026
@HiltViewModel
3127
class GiftViewModel @Inject constructor(
3228
@BgDispatcher private val bgDispatcher: CoroutineDispatcher,
33-
private val lightningRepo: LightningRepo,
34-
private val lightningService: LightningService,
3529
private val blocktankRepo: BlocktankRepo,
3630
private val activityRepo: ActivityRepo,
3731
) : ViewModel() {
@@ -65,98 +59,62 @@ class GiftViewModel @Inject constructor(
6559
}
6660
}
6761

68-
@Suppress("TooGenericExceptionCaught")
6962
private suspend fun claimGift() = withContext(bgDispatcher) {
7063
if (isClaiming) return@withContext
7164
isClaiming = true
7265

7366
try {
74-
lightningRepo.executeWhenNodeRunning(
75-
operationName = "waitForNodeRunning",
67+
blocktankRepo.claimGiftCode(
68+
code = code,
69+
amount = amount,
7670
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)
71+
).fold(
72+
onSuccess = { result ->
73+
when (result) {
74+
is GiftClaimResult.SuccessWithLiquidity -> {
75+
_navigationEvent.emit(GiftRoute.Success)
76+
}
77+
is GiftClaimResult.SuccessWithoutLiquidity -> {
78+
insertGiftActivity(result)
79+
_successEvent.emit(
80+
NewTransactionSheetDetails(
81+
type = NewTransactionSheetType.LIGHTNING,
82+
direction = NewTransactionSheetDirection.RECEIVED,
83+
paymentHashOrTxId = result.paymentHashOrTxId,
84+
sats = result.sats,
85+
)
86+
)
87+
_navigationEvent.emit(GiftRoute.Success)
88+
}
89+
}
90+
},
91+
onFailure = { error ->
92+
handleGiftClaimError(error)
93+
}
94+
)
9395
} finally {
9496
isClaiming = false
9597
}
9698
}
9799

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)
108-
}
109-
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-
}
100+
private suspend fun insertGiftActivity(result: GiftClaimResult.SuccessWithoutLiquidity) {
101+
val nowTimestamp = nowTimestamp().epochSecond.toULong()
102+
103+
val lightningActivity = com.synonym.bitkitcore.LightningActivity(
104+
id = result.paymentHashOrTxId,
105+
txType = PaymentType.RECEIVED,
106+
status = PaymentState.SUCCEEDED,
107+
value = result.sats.toULong(),
108+
fee = 0u,
109+
invoice = result.invoice,
110+
message = result.code,
111+
timestamp = nowTimestamp,
112+
preimage = null,
113+
createdAt = nowTimestamp,
114+
updatedAt = null,
115+
)
116+
117+
activityRepo.insertActivity(Activity.Lightning(lightningActivity)).getOrThrow()
160118
}
161119

162120
private suspend fun handleGiftClaimError(error: Throwable) {
@@ -186,6 +144,5 @@ class GiftViewModel @Inject constructor(
186144
companion object {
187145
private const val TAG = "GiftViewModel"
188146
private const val NODE_STARTUP_TIMEOUT_MS = 30_000L
189-
private const val PEER_CONNECTION_DELAY_MS = 2_000L
190147
}
191148
}

app/src/test/java/to/bitkit/repositories/BlocktankRepoTest.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ class BlocktankRepoTest : BaseUnitTest() {
2727
private val lightningService: LightningService = mock()
2828
private val currencyRepo: CurrencyRepo = mock()
2929
private val cacheStore: CacheStore = mock()
30+
private val lightningRepo: LightningRepo = mock()
3031

3132
private lateinit var sut: BlocktankRepo
3233

@@ -56,6 +57,7 @@ class BlocktankRepoTest : BaseUnitTest() {
5657
currencyRepo = currencyRepo,
5758
cacheStore = cacheStore,
5859
enablePolling = false,
60+
lightningRepo = lightningRepo,
5961
)
6062
}
6163

0 commit comments

Comments
 (0)