Skip to content

Commit f3481b5

Browse files
committed
Merge branch 'master' into feat/pubky-profile
# Conflicts: # gradle/libs.versions.toml
2 parents ca7fa32 + 9770509 commit f3481b5

36 files changed

Lines changed: 582 additions & 288 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88
## [Unreleased]
99

1010
### Fixed
11+
- Polish Primary, Secondary, and Tertiary buttons to match Figma design specs #887
12+
- Avoid msat truncation when paying invoices and LNURL callbacks #879
1113
- Fix ANR on RGS server settings screen caused by catastrophic regex backtracking #880
1214
- Fix crash when returning app to foreground on Receive screen #875
1315
- Show loading state on Spending tab when node is not running #875
@@ -23,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2325
- Mnemonic warning text transitions on reveal #857
2426

2527
### Changed
28+
- Updated design of the success screen in the manual channel setup flow #883
2629
- Unified send flow with payment method switcher, details toggle, Lightning support for BIP21 payments, and improved fee rate defaults #863
2730
- Settings redesigned with tabbed navigation (General/Security/Advanced) with swipe support #857
2831
- Icons added to all settings rows for faster scanning #857

app/src/main/java/to/bitkit/domain/commands/NotifyPaymentReceivedHandler.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ class NotifyPaymentReceivedHandler @Inject constructor(
9797
is NotifyPaymentReceived.Command.Onchain -> command.event.txid
9898
},
9999
sats = when (command) {
100-
is NotifyPaymentReceived.Command.Lightning -> (command.event.amountMsat / 1000u).toLong()
100+
is NotifyPaymentReceived.Command.Lightning -> ((command.event.amountMsat + 999u) / 1000u).toLong()
101101
is NotifyPaymentReceived.Command.Onchain -> command.event.details.amountSats
102102
},
103103
)

app/src/main/java/to/bitkit/ext/Lnurl.kt

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,36 @@ fun LnurlPayData.commentAllowed(): Boolean = commentAllowed?.let { it > 0u } ==
2424
fun LnurlPayData.maxSendableSat(): ULong = maxSendable / MSATS_PER_SAT
2525
fun LnurlPayData.minSendableSat(): ULong = msatsToSatsCeil(minSendable)
2626

27+
/**
28+
* True when the LNURL-pay endpoint specifies a single exact amount.
29+
*
30+
* This also covers the sub-sat edge case where `minSendable` and `maxSendable` differ
31+
* in their sub-sat fraction but map to the same (or inverted) sat range after rounding,
32+
* e.g. `minSendable = 500500, maxSendable = 500500` → `minSendableSat() = 501, maxSendableSat() = 500`.
33+
*/
34+
fun LnurlPayData.isFixedAmount(): Boolean =
35+
minSendable == maxSendable || (minSendable > 0u && minSendableSat() > maxSendableSat())
36+
37+
/**
38+
* Returns the amount in millisatoshis to send in the LNURL-pay callback.
39+
*
40+
* For fixed-amount requests (including sub-sat ranges) the original msat value
41+
* from the server is returned verbatim, avoiding precision loss from the
42+
* msat→sat→msat round-trip.
43+
*
44+
* For variable-amount requests the user-selected sat amount is converted to msats.
45+
*/
46+
fun LnurlPayData.callbackAmountMsats(userSats: ULong? = null): ULong =
47+
if (isFixedAmount()) minSendable else (userSats ?: minSendableSat()) * MSATS_PER_SAT
48+
2749
fun LnurlWithdrawData.minWithdrawableSat(): ULong = msatsToSatsCeil(minWithdrawable ?: 0u)
2850
fun LnurlWithdrawData.maxWithdrawableSat(): ULong = maxWithdrawable / MSATS_PER_SAT
51+
52+
/**
53+
* True when the LNURL-withdraw endpoint specifies a single exact amount,
54+
* including the sub-sat edge case where rounding causes `min > max` in whole sats.
55+
*/
56+
fun LnurlWithdrawData.isFixedAmount(): Boolean {
57+
val min = minWithdrawable ?: 0u
58+
return min == maxWithdrawable || (min > 0u && minWithdrawableSat() > maxWithdrawableSat())
59+
}

app/src/main/java/to/bitkit/ext/PaymentDetails.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ package to.bitkit.ext
22

33
import org.lightningdevkit.ldknode.PaymentDetails
44

5-
val PaymentDetails.amountSats: ULong? get() = amountMsat?.let { it / 1000u }
5+
val PaymentDetails.amountSats: ULong?
6+
get() = amountMsat?.let { (it + 999u) / 1000u }

app/src/main/java/to/bitkit/fcm/WakeNodeWorker.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ class WakeNodeWorker @AssistedInject constructor(
192192
showDetails: Boolean,
193193
hiddenBody: String,
194194
) {
195-
val sats = event.amountMsat / 1000u
195+
val sats = (event.amountMsat + 999u) / 1000u
196196
// Save for UI to pick up
197197
cacheStore.setBackgroundReceive(
198198
NewTransactionSheetDetails(

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

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -897,20 +897,29 @@ class LightningRepo @Inject constructor(
897897
runCatching { lightningService.receive(amountSats, description, expirySeconds) }
898898
}
899899

900+
suspend fun createInvoiceMsats(
901+
amountMsats: ULong,
902+
description: String,
903+
expirySeconds: UInt = 86_400u,
904+
): Result<String> = executeWhenNodeRunning("createInvoiceMsats") {
905+
updateGeoBlockState()
906+
runCatching { lightningService.receiveMsats(amountMsats, description, expirySeconds) }
907+
}
908+
900909
@Suppress("ForbiddenComment")
901910
suspend fun fetchLnurlInvoice(
902911
callbackUrl: String,
903-
amountSats: ULong,
912+
amountMsats: ULong,
904913
comment: String? = null,
905914
): Result<LightningInvoice> {
906915
return runCatching {
907916
// TODO use bitkit-core getLnurlInvoice if it works with callbackUrl
908-
val bolt11 = lnurlService.fetchLnurlInvoice(callbackUrl, amountSats, comment).getOrThrow().pr
917+
val bolt11 = lnurlService.fetchLnurlInvoice(callbackUrl, amountMsats, comment).getOrThrow().pr
909918
val decoded = (coreService.decode(bolt11) as Scanner.Lightning).invoice
910919
return@runCatching decoded
911920
}.onFailure {
912921
Logger.error(
913-
"fetchLnurlInvoice error, url: $callbackUrl, amount: $amountSats, comment: $comment",
922+
"Failed to fetch LNURL invoice, url: '$callbackUrl', amountMsats: '$amountMsats', comment: '$comment'",
914923
it,
915924
context = TAG,
916925
)

app/src/main/java/to/bitkit/services/LightningService.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -590,15 +590,19 @@ class LightningService @Inject constructor(
590590
}
591591

592592
suspend fun receive(sat: ULong? = null, description: String, expirySecs: UInt = 3600u): String {
593+
return receiveMsats(amountMsat = sat?.let { it * 1000u }, description = description, expirySecs = expirySecs)
594+
}
595+
596+
suspend fun receiveMsats(amountMsat: ULong? = null, description: String, expirySecs: UInt = 3600u): String {
593597
val node = this.node ?: throw ServiceError.NodeNotSetup()
594598

595599
val message = description
596600

597601
return ServiceQueue.LDK.background {
598-
val bolt11Invoice: Bolt11Invoice = if (sat != null) {
602+
val bolt11Invoice: Bolt11Invoice = if (amountMsat != null) {
599603
node.bolt11Payment()
600604
.receive(
601-
amountMsat = sat * 1000u,
605+
amountMsat = amountMsat,
602606
description = Bolt11InvoiceDescription.Direct(description = message),
603607
expirySecs = expirySecs,
604608
)

app/src/main/java/to/bitkit/services/LnurlService.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,14 @@ class LnurlService @Inject constructor(
4141

4242
suspend fun fetchLnurlInvoice(
4343
callbackUrl: String,
44-
amountSats: ULong,
44+
amountMsats: ULong,
4545
comment: String? = null,
4646
): Result<LnurlPayResponse> = runCatching {
4747
Logger.debug("Fetching LNURL pay invoice from: $callbackUrl", context = TAG)
4848

4949
val response = client.get(callbackUrl) {
5050
url {
51-
parameters["amount"] = "${amountSats * 1000u}" // convert to msat
51+
parameters["amount"] = "$amountMsats"
5252
comment?.takeIf { it.isNotBlank() }?.let {
5353
parameters["comment"] = it
5454
}

app/src/main/java/to/bitkit/ui/Locals.kt

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,3 @@ val settingsViewModel: SettingsViewModel?
5353

5454
val backupsViewModel: BackupsViewModel?
5555
@Composable get() = LocalBackupsViewModel.current
56-
57-
val drawerState: DrawerState?
58-
@Composable get() = LocalDrawerState.current

0 commit comments

Comments
 (0)