Skip to content
Merged
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
6 changes: 6 additions & 0 deletions app/src/main/java/to/bitkit/env/Env.kt
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,15 @@ internal object Env {
val vssServerUrl
get() = when (network) {
Network.BITCOIN -> TODO("VSS not implemented for mainnet")
// Network.REGTEST -> "http://localhost:5050/vss"
else -> "https://bitkit.stag0.blocktank.to/vss_rs/"
}

val lnurlAuthSeverUrl = when (network) {
// Network.REGTEST -> "http://localhost:3000/auth"
else -> "" // TODO implement LNURL-auth Server for other networks
}

val vssStoreId
get() = when (network) {
Network.REGTEST -> "bitkit_regtest"
Expand Down
4 changes: 4 additions & 0 deletions app/src/main/java/to/bitkit/repositories/BlocktankRepo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,8 @@ class BlocktankRepo @Inject constructor(
receivingBalanceSats: ULong,
channelExpiryWeeks: UInt = DEFAULT_CHANNEL_EXPIRY_WEEKS,
): Result<IBtEstimateFeeResponse2> = withContext(bgDispatcher) {
Logger.info("Estimating order fee for spendingSats=$spendingBalanceSats, receivingSats=$receivingBalanceSats")

try {
val options = defaultCreateOrderOptions(clientBalanceSat = spendingBalanceSats)

Expand All @@ -239,6 +241,8 @@ class BlocktankRepo @Inject constructor(
options = options,
)

Logger.debug("Estimated order fee: '$estimate'")

Result.success(estimate)
} catch (e: Throwable) {
Logger.error("Failed to estimate order fee", e, context = TAG)
Expand Down
6 changes: 2 additions & 4 deletions app/src/main/java/to/bitkit/repositories/WalletRepo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -322,10 +322,8 @@ class WalletRepo @Inject constructor(
}

try {
val minFeeBuffer = 1000uL
val amountSats = (totalOnchainSats - minFeeBuffer).coerceAtLeast(0uL)
val fee = lightningRepo.calculateTotalFee(amountSats).getOrThrow()
val maxSendable = (totalOnchainSats - fee).coerceAtLeast(0uL)
val fee = lightningRepo.calculateTotalFee(totalOnchainSats).getOrThrow()
val maxSendable = (totalOnchainSats - fee).coerceAtLeast(0u)

return@withContext maxSendable
} catch (_: Throwable) {
Expand Down
19 changes: 14 additions & 5 deletions app/src/main/java/to/bitkit/services/LightningService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -113,11 +113,20 @@ class LightningService @Inject constructor(

ServiceQueue.LDK.background {
node = try {
builder.buildWithVssStoreAndFixedHeaders(
vssUrl = Env.vssServerUrl,
storeId = vssStoreId,
fixedHeaders = emptyMap(),
)
if (Env.lnurlAuthSeverUrl.isNotBlank()) {
builder.buildWithVssStore(
vssUrl = Env.vssServerUrl,
storeId = vssStoreId,
lnurlAuthServerUrl = Env.lnurlAuthSeverUrl,
fixedHeaders = emptyMap(),
)
} else {
builder.buildWithVssStoreAndFixedHeaders(
vssUrl = Env.vssServerUrl,
storeId = vssStoreId,
fixedHeaders = emptyMap(),
)
}
} catch (e: BuildException) {
throw LdkError(e)
}
Expand Down
14 changes: 8 additions & 6 deletions app/src/main/java/to/bitkit/ui/components/Spacers.kt
Original file line number Diff line number Diff line change
@@ -1,24 +1,20 @@
package to.bitkit.ui.components

import androidx.annotation.FloatRange
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.layout.width
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import to.bitkit.ui.theme.Colors

@Composable
fun VerticalSpacer(height: Dp) {
Expand All @@ -30,19 +26,25 @@ fun HorizontalSpacer(width: Dp) {
Spacer(modifier = Modifier.width(width))
}

@Suppress("ComposeMultipleContentEmitters")
@Composable
fun ColumnScope.FillHeight(
@FloatRange weight: Float = 1f,
fill: Boolean = true
fill: Boolean = true,
min: Dp = 0.dp,
) {
if (min > 0.dp) Spacer(modifier = Modifier.height(min))
Spacer(modifier = Modifier.weight(weight, fill = fill))
}

@Suppress("ComposeMultipleContentEmitters")
@Composable
fun RowScope.FillWidth(
@FloatRange weight: Float = 1f,
fill: Boolean = true
fill: Boolean = true,
min: Dp = 0.dp,
) {
if (min > 0.dp) Spacer(modifier = Modifier.width(min))
Spacer(modifier = Modifier.weight(weight, fill = fill))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,21 @@ import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import to.bitkit.R
import to.bitkit.ext.ellipsisMiddle
import to.bitkit.models.LnPeer
import to.bitkit.ui.Routes
import to.bitkit.ui.components.BodyM
import to.bitkit.ui.components.Caption13Up
import to.bitkit.ui.components.CaptionB
import to.bitkit.ui.components.Display
import to.bitkit.ui.components.FillHeight
import to.bitkit.ui.components.FillWidth
import to.bitkit.ui.components.PrimaryButton
import to.bitkit.ui.components.SecondaryButton
import to.bitkit.ui.components.VerticalSpacer
Expand Down Expand Up @@ -93,7 +95,7 @@ private fun Content(
VerticalSpacer(8.dp)

val peer = uiState.peer
if(peer != null) {
if (peer != null) {
BodyM(text = stringResource(R.string.other__lnurl_channel_message), color = Colors.White64)
VerticalSpacer(48.dp)

Expand All @@ -102,7 +104,7 @@ private fun Content(

InfoRow(
label = stringResource(R.string.other__lnurl_channel_node),
value = peer.nodeId.ellipsisMiddle(24),
value = peer.nodeId,
)
InfoRow(
label = stringResource(R.string.other__lnurl_channel_host),
Expand Down Expand Up @@ -151,14 +153,14 @@ private fun InfoRow(
value: String,
) {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 16.dp)
) {
CaptionB(text = label)
CaptionB(text = value)
FillWidth(min = 24.dp)
CaptionB(text = value, maxLines = 1, overflow = TextOverflow.MiddleEllipsis, textAlign = TextAlign.End)
}
HorizontalDivider()
}
Expand Down
26 changes: 13 additions & 13 deletions app/src/main/java/to/bitkit/viewmodels/AppViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ class AppViewModel @Inject constructor(
@ApplicationContext private val context: Context,
@BgDispatcher private val bgDispatcher: CoroutineDispatcher,
private val keychain: Keychain,
private val lightningService: LightningRepo,
private val lightningRepo: LightningRepo,
private val walletRepo: WalletRepo,
private val coreService: CoreService,
private val ldkNodeEventBus: LdkNodeEventBus,
Expand Down Expand Up @@ -200,7 +200,7 @@ class AppViewModel @Inject constructor(

is Event.ChannelReady -> {
// TODO: handle ONLY cjit as payment received. This makes it look like any channel confirmed is a received payment.
val channel = lightningService.getChannels()?.find { it.channelId == event.channelId }
val channel = lightningRepo.getChannels()?.find { it.channelId == event.channelId }
if (channel != null) {
showNewTransactionSheet(
NewTransactionSheetDetails(
Expand Down Expand Up @@ -403,12 +403,12 @@ class AppViewModel @Inject constructor(
val lnurl = _sendUiState.value.lnurl

val isValidLNAmount = when (lnurl) {
null -> lightningService.canSend(amount)
null -> lightningRepo.canSend(amount)
is LnurlParams.LnurlPay -> {
val minSat = lnurl.data.minSendableSat()
val maxSat = lnurl.data.maxSendableSat()

amount in minSat..maxSat && lightningService.canSend(amount)
amount in minSat..maxSat && lightningRepo.canSend(amount)
}

is LnurlParams.LnurlWithdraw -> {
Expand Down Expand Up @@ -531,7 +531,7 @@ class AppViewModel @Inject constructor(
val quickPayHandled = handleQuickPayIfApplicable(amountSats = invoice.amountSatoshis, invoice = invoice)
if (quickPayHandled) return

if (!lightningService.canSend(invoice.amountSatoshis)) {
if (!lightningRepo.canSend(invoice.amountSatoshis)) {
toast(
type = Toast.ToastType.ERROR,
title = "Insufficient Funds",
Expand Down Expand Up @@ -574,7 +574,7 @@ class AppViewModel @Inject constructor(
val minSendable = data.minSendableSat()
val maxSendable = data.maxSendableSat()

if (!lightningService.canSend(minSendable)) {
if (!lightningRepo.canSend(minSendable)) {
toast(
type = Toast.ToastType.WARNING,
title = context.getString(R.string.other__lnurl_pay_error),
Expand Down Expand Up @@ -662,7 +662,7 @@ class AppViewModel @Inject constructor(
fun requestLnurlAuth(callback: String, k1: String, domain: String) {
viewModelScope.launch {
// TODO pass callback and domain from bitkit-core when updated to accept decoded callback and return domain
lightningService.requestLnurlAuth(
lightningRepo.requestLnurlAuth(
callback = callback,
k1 = k1,
domain = domain,
Expand Down Expand Up @@ -803,7 +803,7 @@ class AppViewModel @Inject constructor(

if (_sendUiState.value.payMethod != SendMethod.ONCHAIN) return

val totalFee = lightningService.calculateTotalFee(
val totalFee = lightningRepo.calculateTotalFee(
amountSats = amountSats,
address = _sendUiState.value.address,
speed = _sendUiState.value.speed,
Expand Down Expand Up @@ -841,7 +841,7 @@ class AppViewModel @Inject constructor(
val isLnurlPay = lnurl is LnurlParams.LnurlPay

if (isLnurlPay) {
lightningService.fetchLnurlInvoice(
lightningRepo.fetchLnurlInvoice(
callbackUrl = lnurl.data.callback,
amountSats = amount,
comment = _sendUiState.value.comment.takeIf { it.isNotEmpty() },
Expand Down Expand Up @@ -942,7 +942,7 @@ class AppViewModel @Inject constructor(
)
}

val invoice = lightningService.createInvoice(
val invoice = lightningRepo.createInvoice(
amountSats = _sendUiState.value.amount,
description = lnurl.data.defaultDescription,
expirySeconds = 3600u,
Expand All @@ -954,7 +954,7 @@ class AppViewModel @Inject constructor(
return@launch
}

lightningService.requestLnurlWithdraw(
lightningRepo.requestLnurlWithdraw(
k1 = lnurl.data.k1,
callback = lnurl.data.callback,
paymentRequest = invoice
Expand Down Expand Up @@ -994,7 +994,7 @@ class AppViewModel @Inject constructor(

private suspend fun sendOnchain(address: String, amount: ULong): Result<Txid> {
val utxos = _sendUiState.value.selectedUtxos
return lightningService.sendOnChain(
return lightningRepo.sendOnChain(
address = address,
sats = amount,
utxosToSpend = utxos,
Expand All @@ -1005,7 +1005,7 @@ class AppViewModel @Inject constructor(
bolt11: String,
amount: ULong? = null,
): Result<PaymentId> {
return lightningService.payInvoice(bolt11 = bolt11, sats = amount).onSuccess { hash ->
return lightningRepo.payInvoice(bolt11 = bolt11, sats = amount).onSuccess { hash ->
// Wait until matching payment event is received
val result = ldkNodeEventBus.events.watchUntil { event ->
when (event) {
Expand Down
4 changes: 2 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[versions]
accompanistPermissions = "0.36.0"
activityCompose = "1.10.1"
agp = "8.11.1"
agp = "8.12.0"
appcompat = "1.7.0"
barcodeScanning = "17.3.0"
biometric = "1.4.0-alpha02"
Expand Down Expand Up @@ -86,7 +86,7 @@ ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "k
ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" }
ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
#ldk-node-android = { module = "org.lightningdevkit:ldk-node-android", version = "0.6.1" } # upstream
ldk-node-android = { module = "com.github.synonymdev:ldk-node", version = "v0.6.1-rc.4" } # fork
ldk-node-android = { module = "com.github.synonymdev:ldk-node", version = "v0.6.1-rc.5" } # fork
lifecycle-process = { group = "androidx.lifecycle", name = "lifecycle-process", version.ref = "lifecycle" }
lifecycle-runtime-compose = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "lifecycle" }
lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycle" }
Expand Down