Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/src/main/java/to/bitkit/env/Env.kt
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ internal object Env {
@Suppress("ConstPropertyName")
object Defaults {
/** Default Bolt11 invoice expiry in seconds. */
const val bolt11InvoiceExpirySeconds = 3_600u
const val bolt11ExpirySec = 86_400u

/** Recommended transaction base fee in sats */
const val recommendedBaseFee = 256u
Expand Down
3 changes: 2 additions & 1 deletion app/src/main/java/to/bitkit/repositories/BlocktankRepo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import javax.inject.Named
import javax.inject.Singleton
import kotlin.math.ceil
import kotlin.time.Duration
import kotlin.time.Duration.Companion.hours
import kotlin.time.Duration.Companion.seconds

@Singleton
Expand Down Expand Up @@ -463,7 +464,7 @@ class BlocktankRepo @Inject constructor(
val invoice = lightningRepo.createInvoice(
amountSats = null,
description = "blocktank-gift-code:$code",
expirySeconds = Defaults.bolt11InvoiceExpirySeconds,
expirySeconds = 1.hours.inWholeSeconds.toUInt(),
).getOrThrow()

Logger.debug("Created invoice for gift code, requesting payment from LSP", context = TAG)
Expand Down
44 changes: 40 additions & 4 deletions app/src/main/java/to/bitkit/repositories/LightningRepo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ import to.bitkit.data.SettingsStore
import to.bitkit.data.backup.VssBackupClientLdk
import to.bitkit.data.keychain.Keychain
import to.bitkit.di.BgDispatcher
import to.bitkit.env.Defaults
import to.bitkit.env.Env
import to.bitkit.ext.getSatsPerVByteFor
import to.bitkit.ext.nowTimestamp
Expand Down Expand Up @@ -926,7 +927,7 @@ class LightningRepo @Inject constructor(
suspend fun createInvoice(
amountSats: ULong? = null,
description: String,
expirySeconds: UInt = 86_400u,
expirySeconds: UInt = Defaults.bolt11ExpirySec,
): Result<String> = executeWhenNodeRunning("createInvoice") {
updateGeoBlockState()
runCatching { lightningService.receive(amountSats, description, expirySeconds) }
Expand All @@ -935,7 +936,7 @@ class LightningRepo @Inject constructor(
suspend fun createInvoiceMsats(
amountMsats: ULong,
description: String,
expirySeconds: UInt = 86_400u,
expirySeconds: UInt = Defaults.bolt11ExpirySec,
): Result<String> = executeWhenNodeRunning("createInvoiceMsats") {
updateGeoBlockState()
runCatching { lightningService.receiveMsats(amountMsats, description, expirySeconds) }
Expand Down Expand Up @@ -1019,15 +1020,50 @@ class LightningRepo @Inject constructor(
}

suspend fun waitForUsableChannels() = withContext(bgDispatcher) {
if (_lightningState.value.channels.any { it.isUsable }) return@withContext
var state = _lightningState.value
if (!state.nodeLifecycleState.canRun()) return@withContext
if (state.hasUsableChannels()) return@withContext

state = waitForChannelsToLoadIfNeeded(state) ?: return@withContext
if (!state.nodeLifecycleState.canRun()) return@withContext

if (state.channels.isEmpty()) {
if (state.nodeLifecycleState.isRunning()) {
syncState()
state = _lightningState.value
}

if (state.channels.isEmpty()) return@withContext // no channel exists, don't wait
if (state.hasUsableChannels()) return@withContext
}

Logger.info("Waiting for usable channels before sending payment", context = TAG)

withTimeoutOrNull(CHANNELS_USABLE_TIMEOUT_MS) {
_lightningState.first { state -> state.channels.any { it.isUsable } }
_lightningState.first { it.shouldStopWaitingForUsableChannels() }
} ?: Logger.warn("Timed out waiting for usable channels", context = TAG)
}

private suspend fun waitForChannelsToLoadIfNeeded(state: LightningState): LightningState? {
if (state.channels.isNotEmpty() || state.nodeLifecycleState.isRunning()) return state

Logger.info("Waiting for node to load channels before sending payment", context = TAG)
return withTimeoutOrNull(CHANNELS_USABLE_TIMEOUT_MS) {
_lightningState.first { it.shouldStopWaitingForLoadedChannels() }
} ?: run {
Logger.warn("Timed out waiting for node to load channels", context = TAG)
null
}
}

private fun LightningState.hasUsableChannels() = channels.any { it.isUsable }

private fun LightningState.shouldStopWaitingForLoadedChannels() =
!nodeLifecycleState.canRun() || nodeLifecycleState.isRunning() || channels.isNotEmpty()

private fun LightningState.shouldStopWaitingForUsableChannels() =
!nodeLifecycleState.canRun() || channels.isEmpty() || hasUsableChannels()

@Suppress("LongParameterList")
suspend fun sendOnChain(
address: Address,
Expand Down
9 changes: 7 additions & 2 deletions app/src/main/java/to/bitkit/services/CoreService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ import org.lightningdevkit.ldknode.TransactionDetails
import to.bitkit.async.ServiceQueue
import to.bitkit.data.CacheStore
import to.bitkit.data.SettingsStore
import to.bitkit.env.Defaults
import to.bitkit.env.Env
import to.bitkit.ext.amountSats
import to.bitkit.ext.channelId
Expand Down Expand Up @@ -1523,11 +1524,15 @@ class BlocktankService(
)
}

suspend fun regtestCloseChannel(fundingTxId: String, vout: UInt, forceCloseAfterS: ULong = 86_400uL): String {
suspend fun regtestCloseChannel(
fundingTxId: String,
vout: UInt,
forceCloseAfterS: UInt = Defaults.bolt11ExpirySec,
): String {
return com.synonym.bitkitcore.regtestCloseChannel(
fundingTxId = fundingTxId,
vout = vout,
forceCloseAfterS = forceCloseAfterS,
forceCloseAfterS = forceCloseAfterS.toULong(),
)
}
}
Expand Down
4 changes: 2 additions & 2 deletions app/src/main/java/to/bitkit/services/LightningService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -595,15 +595,15 @@ class LightningService @Inject constructor(
suspend fun receive(
sat: ULong? = null,
description: String,
expirySecs: UInt = Defaults.bolt11InvoiceExpirySeconds,
expirySecs: UInt = Defaults.bolt11ExpirySec,
): String {
return receiveMsats(amountMsat = sat?.let { it * 1000u }, description = description, expirySecs = expirySecs)
}

suspend fun receiveMsats(
amountMsat: ULong? = null,
description: String,
expirySecs: UInt = Defaults.bolt11InvoiceExpirySeconds,
expirySecs: UInt = Defaults.bolt11ExpirySec,
): String {
val node = this.node ?: throw ServiceError.NodeNotSetup()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ fun BlocktankRegtestScreen(
runCatching {
val voutNum = vout.toUIntOrNull() ?: error("Invalid Vout: $vout")
val closeAfter =
forceCloseAfter.toULongOrNull() ?: error("Invalid Force Close After: $forceCloseAfter")
forceCloseAfter.toUIntOrNull() ?: error("Invalid Force Close After: $forceCloseAfter")
val closingTxId = viewModel.regtestCloseChannel(
fundingTxId = fundingTxId,
vout = voutNum,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package to.bitkit.ui.settings

import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import to.bitkit.env.Defaults
import to.bitkit.services.CoreService
import javax.inject.Inject

Expand All @@ -28,7 +29,11 @@ class BlocktankRegtestViewModel @Inject constructor(
)
}

suspend fun regtestCloseChannel(fundingTxId: String, vout: UInt, forceCloseAfterS: ULong = 86_400uL): String {
suspend fun regtestCloseChannel(
fundingTxId: String,
vout: UInt,
forceCloseAfterS: UInt = Defaults.bolt11ExpirySec,
): String {
return coreService.blocktank.regtestCloseChannel(
fundingTxId = fundingTxId,
vout = vout,
Expand Down
Loading
Loading