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/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,7 @@ androidComponents {
buildConfigFields.put("E2E_HOMEGATE_URL", e2eHomegateUrlEnv.stringField())
buildConfigFields.put("E2E_HOMESERVER_PUBKY", e2eHomeserverPubkyEnv.stringField())
buildConfigFields.put("E2E_HOMEGATE_OVERRIDE", e2eHomegateOverrideEnv.stringField())
for (key in listOf("E2E_VSS_SERVER_URL", "E2E_LNURL_AUTH_SERVER_URL")) {
for (key in listOf("E2E_VSS_SERVER_URL", "E2E_LNURL_AUTH_SERVER_URL", "E2E_BLOCKTANK_API_URL")) {
val value = if (variant.buildType == "debug") localProp(key).orElse("") else provider { "" }
buildConfigFields.put(key, value.stringField())
}
Expand Down
7 changes: 7 additions & 0 deletions app/src/main/java/to/bitkit/data/WalletConnectionRecovery.kt
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,13 @@ class WalletConnectionRecovery @Inject constructor(
}
}

/** A queued UI intent remains bound to its original wallet, including same-seed replacement. */
internal fun currentGeneration(): Long? = synchronized(stateLock) { generation.takeUnless { wiping } }

internal fun isCurrentGeneration(expected: Long?): Boolean = synchronized(stateLock) {
expected != null && !wiping && generation == expected
}

private fun admittedGeneration(): Long = synchronized(stateLock) {
checkAdmission()
generation
Expand Down
17 changes: 17 additions & 0 deletions app/src/main/java/to/bitkit/env/BlocktankTestEndpoint.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package to.bitkit.env

import java.net.URI

internal object BlocktankTestEndpoint {
fun resolve(enabled: Boolean, value: String?): String? {
if (!enabled) return null
val endpoint = value.orEmpty().trim().takeIf { it.isNotEmpty() } ?: return null
val valid = runCatching {
val url = URI(endpoint)
url.scheme in listOf("http", "https") && url.host == "localhost" && url.port in 1..65535 &&
url.userInfo == null && url.query == null && url.fragment == null && !url.path.isNullOrEmpty()
}.getOrDefault(false)
require(valid) { "E2E Blocktank endpoint must be a localhost URL with an explicit port and path" }
return endpoint
}
}
11 changes: 7 additions & 4 deletions app/src/main/java/to/bitkit/env/Env.kt
Original file line number Diff line number Diff line change
Expand Up @@ -141,10 +141,13 @@ internal object Env {
Network.BITCOIN -> "https://api1.blocktank.to"
else -> "https://api.stag0.blocktank.to"
}
val blocktankApiUrl
get() = when (network) {
Network.BITCOIN -> "$blocktankBaseUrl/api"
else -> "$blocktankBaseUrl/blocktank/api/v2"
val blocktankApiUrl: String
get() {
val fixture = BlocktankTestEndpoint.resolve(isDebug && isE2eTest, BuildConfig.E2E_BLOCKTANK_API_URL)
return fixture ?: when (network) {
Network.BITCOIN -> "$blocktankBaseUrl/api"
else -> "$blocktankBaseUrl/blocktank/api/v2"
}
}
val blocktankNotificationApiUrl
get() = when (network) {
Expand Down
29 changes: 27 additions & 2 deletions app/src/main/java/to/bitkit/services/CoreService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.synonym.bitkitcore.Activity
import com.synonym.bitkitcore.ActivityFilter
import com.synonym.bitkitcore.ActivityTags
import com.synonym.bitkitcore.AddressType
import com.synonym.bitkitcore.BlocktankException
import com.synonym.bitkitcore.BtOrderState2
import com.synonym.bitkitcore.CJitStateEnum
import com.synonym.bitkitcore.ClosedChannelDetails
Expand Down Expand Up @@ -70,6 +71,8 @@ import io.ktor.client.request.get
import io.ktor.http.HttpStatusCode
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.flow.first
import org.lightningdevkit.ldknode.ChannelDetails
import org.lightningdevkit.ldknode.ConfirmationStatus
Expand Down Expand Up @@ -2170,18 +2173,40 @@ class BlocktankService(
}

suspend fun getFees(): Result<FeeRates> {
var fees = fees(refresh = true)
val fresh = runSuspendCatching { fees(refresh = true) }
val error = fresh.exceptionOrNull()
if (error != null && !isInfoBadGateway(error)) throw error
var fees = fresh.getOrNull()
if (fees == null) {
currentCoroutineContext().ensureActive()
Logger.warn("Failed to fetch fresh fee rate, using cached rate.")
fees = fees(refresh = false)
currentCoroutineContext().ensureActive()
}
if (fees == null) {
return Result.failure(AppError("Fees unavailable from bitkit-core"))
return Result.failure(error ?: AppError("Fees unavailable from bitkit-core"))
}

return Result.success(fees)
}

private fun isInfoBadGateway(error: Throwable): Boolean {
var cause = error
repeat(8) {
when (val current = cause) {
is BlocktankException.DataException -> {
val prefix = "Failed to fetch info from Blocktank: Blocktank error: " +
"Failed to get info: Client error: "
return current.errorDetails.startsWith(prefix + "Request failed with status 502 Bad Gateway:") ||
current.errorDetails.startsWith(prefix + "Request failed (502 Bad Gateway):")
}
is AppError -> cause = current.cause ?: return false
else -> return false
}
}
return false
}

suspend fun createCjit(
channelSizeSat: ULong,
invoiceSat: ULong,
Expand Down
5 changes: 4 additions & 1 deletion app/src/main/java/to/bitkit/ui/sheets/SendSheet.kt
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,10 @@ fun SendSheet(
LaunchedEffect(startDestination) {
// always reset state on new user-initiated send
if (startDestination == SendRoute.Recipient) {
appViewModel.resetSendState(hardwareWalletId = hardwareWalletId)
if (!appViewModel.resetSendState(hardwareWalletId = hardwareWalletId)) {
appViewModel.hideSheet()
return@LaunchedEffect
}
appViewModel.resetQuickPay()
routingCacheResetAttempted = false
}
Expand Down
Loading
Loading