Skip to content
49 changes: 42 additions & 7 deletions app/src/main/java/to/bitkit/repositories/BlocktankRepo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,17 @@ import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
Expand All @@ -37,7 +41,10 @@ import kotlinx.coroutines.flow.update
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeoutOrNull
import org.lightningdevkit.ldknode.Bolt11Invoice
import org.lightningdevkit.ldknode.ChannelDetails
import org.lightningdevkit.ldknode.Event
import to.bitkit.async.ServiceQueue
import to.bitkit.data.CacheStore
import to.bitkit.di.BgDispatcher
Expand All @@ -47,6 +54,7 @@ import to.bitkit.ext.calculateRemoteBalance
import to.bitkit.ext.nowTimestamp
import to.bitkit.models.BlocktankBackupV1
import to.bitkit.models.EUR
import to.bitkit.models.msatCeilOf
import to.bitkit.services.CoreService
import to.bitkit.services.LightningService
import to.bitkit.utils.Logger
Expand Down Expand Up @@ -459,26 +467,52 @@ class BlocktankRepo @Inject constructor(
}
}

private suspend fun claimGiftCodeWithLiquidity(code: String, amount: ULong): GiftClaimResult {
private suspend fun claimGiftCodeWithLiquidity(code: String, amount: ULong): GiftClaimResult = coroutineScope {
val invoice = lightningRepo.createInvoice(
amountSats = null,
description = "blocktank-gift-code:$code",
expirySeconds = Defaults.bolt11InvoiceExpirySeconds,
).getOrThrow()

val expectedPaymentHash = Bolt11Invoice.fromStr(invoice).paymentHash()

Logger.debug("Created invoice for gift code, requesting payment from LSP", context = TAG)

val paymentReceivedDeferred = async(start = CoroutineStart.UNDISPATCHED) {
lightningRepo.nodeEvents
.filterIsInstance<Event.PaymentReceived>()
.first { it.paymentHash == expectedPaymentHash }
}

val giftResponse = ServiceQueue.CORE.background {
giftPay(invoice = invoice)
}

Logger.debug("Gift payment request completed: id=${giftResponse.id}", context = TAG)
Logger.debug(
"Gift payment request completed: id='${giftResponse.id}', awaiting LDK PaymentReceived",
context = TAG,
)

val paymentReceived = withTimeoutOrNull(GIFT_PAYMENT_RECEIVE_TIMEOUT) {
paymentReceivedDeferred.await()
}

if (paymentReceived == null) {
paymentReceivedDeferred.cancel()
throw ServiceError.GiftClaimPaymentNotReceived()
}

Logger.debug(
"Gift payment confirmed by LDK: hash='${paymentReceived.paymentHash}', " +
"amountMsat='${paymentReceived.amountMsat}'",
context = TAG,
)

val receivedSats = msatCeilOf(paymentReceived.amountMsat).toLong()

return GiftClaimResult.SuccessWithLiquidity(
paymentHashOrTxId = giftResponse.bolt11PaymentId ?: giftResponse.id,
sats = giftResponse.bolt11Payment?.paidSat?.toLong()
?: giftResponse.appliedGiftCode?.giftSat?.toLong()
?: amount.toLong(),
GiftClaimResult.SuccessWithLiquidity(
paymentHashOrTxId = paymentReceived.paymentHash,
sats = receivedSats.takeIf { it > 0 } ?: amount.toLong(),
invoice = invoice,
code = code,
)
Expand Down Expand Up @@ -518,6 +552,7 @@ class BlocktankRepo @Inject constructor(
private const val DEFAULT_SOURCE = "bitkit-android"
private const val PEER_CONNECTION_DELAY_MS = 2_000L
private val TIMEOUT_GIFT_CODE = 30.seconds
private val GIFT_PAYMENT_RECEIVE_TIMEOUT = 45.seconds
}
}

Expand Down
16 changes: 15 additions & 1 deletion app/src/main/java/to/bitkit/ui/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ import to.bitkit.viewmodels.WalletViewModel

@AndroidEntryPoint
class MainActivity : FragmentActivity() {
private companion object {
const val KEY_CONSUMED_DEEPLINK_URI = "consumed_deeplink_uri"
}

private val appViewModel by viewModels<AppViewModel>()
private val walletViewModel by viewModels<WalletViewModel>()
private val blocktankViewModel by viewModels<BlocktankViewModel>()
Expand All @@ -83,7 +87,12 @@ class MainActivity : FragmentActivity() {
desc = getString(R.string.notification__channel_node__body),
importance = NotificationManager.IMPORTANCE_LOW
)
appViewModel.handleDeeplinkIntent(intent)

val consumedUri = savedInstanceState?.getString(KEY_CONSUMED_DEEPLINK_URI)
val currentUri = intent?.data?.toString()
if (currentUri == null || currentUri != consumedUri) {
appViewModel.handleDeeplinkIntent(intent)
}

installSplashScreen()
enableAppEdgeToEdge()
Expand Down Expand Up @@ -201,6 +210,11 @@ class MainActivity : FragmentActivity() {
appViewModel.handleDeeplinkIntent(intent)
}

override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
intent?.data?.toString()?.let { outState.putString(KEY_CONSUMED_DEEPLINK_URI, it) }
}

override fun onDestroy() {
super.onDestroy()
if (!settingsViewModel.notificationsGranted.value) {
Expand Down
1 change: 1 addition & 0 deletions app/src/main/java/to/bitkit/utils/Errors.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ sealed class ServiceError(message: String) : AppError(message) {
class CurrencyRateUnavailable : ServiceError("Currency rate unavailable")
class BlocktankInfoUnavailable : ServiceError("Blocktank info not available")
class GeoBlocked : ServiceError("Geo blocked user")
class GiftClaimPaymentNotReceived : ServiceError("Gift claim payment not received")
}

class HttpError(message: String, val code: Int = 500, cause: Throwable? = null) : AppError(message, cause)
Expand Down
1 change: 1 addition & 0 deletions changelog.d/next/929.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix gift card flow showing false-positive confetti when the LSP payment fails, and re-opening unexpectedly after an app language change.
Loading