Skip to content

Commit 73dbd4f

Browse files
authored
Merge pull request #276 from synonymdev/fix/lnurl-min-send-ceil
Fix/lnurl minSendable edge case when msats are not divisable by 1000
2 parents 364e0a1 + b2510e3 commit 73dbd4f

File tree

4 files changed

+47
-7
lines changed

4 files changed

+47
-7
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import Foundation
2+
3+
enum LightningAmountConversion {
4+
/// Lightning amounts are commonly expressed in millisatoshis (msat).
5+
///
6+
/// The UI and amount input operate in whole sats. When converting a minimum bound to sats we must round up:
7+
/// `100500 msat` means the minimum payable amount is `101 sat` (not `100 sat`).
8+
static func satsCeil(fromMsats msats: UInt64) -> UInt64 {
9+
let quotient = msats / Env.msatsPerSat
10+
let remainder = msats % Env.msatsPerSat
11+
return remainder == 0 ? quotient : quotient + 1
12+
}
13+
14+
/// Converts msats → sats by rounding down (safe for maximum bounds).
15+
static func satsFloor(fromMsats msats: UInt64) -> UInt64 {
16+
msats / Env.msatsPerSat
17+
}
18+
}

Bitkit/ViewModels/AppViewModel.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -253,8 +253,8 @@ extension AppViewModel {
253253
}
254254

255255
var normalizedData = data
256-
normalizedData.minSendable = max(1, normalizedData.minSendable / Env.msatsPerSat)
257-
normalizedData.maxSendable = max(normalizedData.minSendable, normalizedData.maxSendable / Env.msatsPerSat)
256+
normalizedData.minSendable = max(1, LightningAmountConversion.satsCeil(fromMsats: normalizedData.minSendable))
257+
normalizedData.maxSendable = max(normalizedData.minSendable, LightningAmountConversion.satsFloor(fromMsats: normalizedData.maxSendable))
258258

259259
// Check if user has enough lightning balance to pay the minimum amount
260260
let lightningBalance = lightningService.balances?.totalLightningBalanceSats ?? 0
@@ -292,8 +292,8 @@ extension AppViewModel {
292292
}
293293

294294
var normalizedData = data
295-
let minSats = max(1, minMsats / Env.msatsPerSat)
296-
let maxSats = max(minSats, maxMsats / Env.msatsPerSat)
295+
let minSats = max(1, LightningAmountConversion.satsCeil(fromMsats: minMsats))
296+
let maxSats = max(minSats, LightningAmountConversion.satsFloor(fromMsats: maxMsats))
297297
normalizedData.minWithdrawable = minSats
298298
normalizedData.maxWithdrawable = maxSats
299299

Bitkit/Views/Wallets/Sheets/BoostSheet.swift

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,8 @@ struct BoostSheet: View {
389389
app.toast(
390390
type: .success,
391391
title: t("wallet__boost_success_title"),
392-
description: t("wallet__boost_success_msg")
392+
description: t("wallet__boost_success_msg"),
393+
accessibilityIdentifier: "BoostSuccessToast"
393394
)
394395

395396
Logger.info("Boost transaction completed successfully, hiding sheet", context: "BoostSheet.performBoost")
@@ -408,8 +409,9 @@ struct BoostSheet: View {
408409

409410
app.toast(
410411
type: .error,
411-
title: t("wallet__boost_error"),
412-
description: error.localizedDescription
412+
title: t("wallet__boost_error_title"),
413+
description: t("wallet__boost_error_msg"),
414+
accessibilityIdentifier: "BoostFailureToast"
413415
)
414416

415417
throw error
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
@testable import Bitkit
2+
import XCTest
3+
4+
final class LightningAmountConversionTests: XCTestCase {
5+
func testSatsCeilRoundsUpWhenNotDivisibleBy1000() {
6+
XCTAssertEqual(LightningAmountConversion.satsCeil(fromMsats: 100_500), 101)
7+
XCTAssertEqual(LightningAmountConversion.satsCeil(fromMsats: 1500), 2)
8+
}
9+
10+
func testSatsCeilKeepsExactSatAmounts() {
11+
XCTAssertEqual(LightningAmountConversion.satsCeil(fromMsats: 100_000), 100)
12+
XCTAssertEqual(LightningAmountConversion.satsCeil(fromMsats: 0), 0)
13+
}
14+
15+
func testSatsFloorRoundsDown() {
16+
XCTAssertEqual(LightningAmountConversion.satsFloor(fromMsats: 100_999), 100)
17+
XCTAssertEqual(LightningAmountConversion.satsFloor(fromMsats: 100_000), 100)
18+
XCTAssertEqual(LightningAmountConversion.satsFloor(fromMsats: 0), 0)
19+
}
20+
}

0 commit comments

Comments
 (0)