From d4d8599f90a0c3260f7890b9c092a0769af809e6 Mon Sep 17 00:00:00 2001 From: CypherPoet Date: Fri, 5 Jun 2026 14:25:05 -0500 Subject: [PATCH 1/5] fix: cap send amount number pad at available balance The send number pad now rejects keystrokes that would push the amount above the available sendable balance, reusing the existing over-max block (haptic + error flash) via a dynamic maxAmountOverride. Continue-button validation is unchanged as a backstop. Closes #346 --- Bitkit/ViewModels/AmountInputViewModel.swift | 14 ++++++-- .../Views/Wallets/Send/SendAmountView.swift | 10 ++++++ BitkitTests/NumberPadTests.swift | 34 +++++++++++++++++++ changelog.d/next/346.fixed.md | 1 + 4 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 changelog.d/next/346.fixed.md diff --git a/Bitkit/ViewModels/AmountInputViewModel.swift b/Bitkit/ViewModels/AmountInputViewModel.swift index c8b80eb05..2ce743030 100644 --- a/Bitkit/ViewModels/AmountInputViewModel.swift +++ b/Bitkit/ViewModels/AmountInputViewModel.swift @@ -7,6 +7,10 @@ class AmountInputViewModel: ObservableObject { @Published var displayText: String = "" @Published var errorKey: String? + /// Optional per-screen cap (e.g. the max sendable balance in the send flow). + /// When set, input is additionally blocked above this value, on top of `maxAmount`. + var maxAmountOverride: UInt64? + // MARK: - Constants private let maxAmount: UInt64 = 999_999_999 @@ -15,6 +19,12 @@ class AmountInputViewModel: ObservableObject { private let classicBitcoinDecimals = 8 private let fiatDecimals = 2 + /// The active upper bound for input: the global `maxAmount`, further restricted by `maxAmountOverride` when set. + private var effectiveMaxAmount: UInt64 { + guard let maxAmountOverride else { return maxAmount } + return Swift.min(maxAmount, maxAmountOverride) + } + // MARK: - Private Properties private var rawInputText: String = "" @@ -43,7 +53,7 @@ class AmountInputViewModel: ObservableObject { if currency.primaryDisplay == .bitcoin && currency.displayUnit == .modern { let newAmount = convertToSats(newText, currency: currency) - if newAmount <= maxAmount { + if newAmount <= effectiveMaxAmount { rawInputText = newText displayText = formatDisplayTextFromAmount(newAmount, currency: currency) amountSats = newAmount @@ -59,7 +69,7 @@ class AmountInputViewModel: ObservableObject { // For decimal input, check limits before updating state if !newText.isEmpty { let newAmount = convertToSats(newText, currency: currency) - if newAmount <= maxAmount { + if newAmount <= effectiveMaxAmount { // Update both raw input and display text rawInputText = newText // Format with grouping separators but not decimal formatting diff --git a/Bitkit/Views/Wallets/Send/SendAmountView.swift b/Bitkit/Views/Wallets/Send/SendAmountView.swift index d81fe176e..a71afba76 100644 --- a/Bitkit/Views/Wallets/Send/SendAmountView.swift +++ b/Bitkit/Views/Wallets/Send/SendAmountView.swift @@ -163,6 +163,8 @@ struct SendAmountView: View { await calculateRoutingFee() } } + + updateInputCap() } .onChange(of: app.selectedWalletToPayFrom) { _, newValue in // Recalculate max sendable amount when switching wallet types @@ -186,6 +188,9 @@ struct SendAmountView: View { } } } + .onChange(of: availableAmount) { _, _ in + updateInputCap() + } } private func onContinue() async { @@ -252,6 +257,11 @@ struct SendAmountView: View { } } + private func updateInputCap() { + // Don't cap when nothing is sendable, so the pad stays usable (Continue stays disabled instead). + amountViewModel.maxAmountOverride = availableAmount > 0 ? availableAmount : nil + } + private func calculateMaxSendableAmount() async { // Make sure we have everything we need to calculate the max sendable amount guard app.selectedWalletToPayFrom == .onchain else { return } diff --git a/BitkitTests/NumberPadTests.swift b/BitkitTests/NumberPadTests.swift index e5d8d2d16..b15224b50 100644 --- a/BitkitTests/NumberPadTests.swift +++ b/BitkitTests/NumberPadTests.swift @@ -53,6 +53,40 @@ final class NumberPadTests: XCTestCase { XCTAssertNotNil(viewModel.errorKey) } + func testMaxAmountOverrideBlocksInputAboveBalance() { + let viewModel = AmountInputViewModel() + let currency = mockCurrency(primaryDisplay: .bitcoin, displayUnit: .modern) + viewModel.maxAmountOverride = 50000 + + // Up to the cap is allowed + for digit in "50000" { + viewModel.handleNumberPadInput(String(digit), currency: currency) + } + XCTAssertEqual(viewModel.amountSats, 50000) + + // Next keystroke would make 500_000 > 50_000 and is blocked + viewModel.handleNumberPadInput("0", currency: currency) + XCTAssertEqual(viewModel.amountSats, 50000) // Should not change + XCTAssertNotNil(viewModel.errorKey) + } + + func testClearingMaxAmountOverrideRestoresGlobalCap() { + let viewModel = AmountInputViewModel() + let currency = mockCurrency(primaryDisplay: .bitcoin, displayUnit: .modern) + + // With a low override, input is blocked above it + viewModel.maxAmountOverride = 100 + viewModel.handleNumberPadInput("9", currency: currency) + viewModel.handleNumberPadInput("9", currency: currency) + viewModel.handleNumberPadInput("9", currency: currency) // 999 > 100 -> blocked + XCTAssertEqual(viewModel.amountSats, 99) + + // Clearing the override lets input grow again, up to the global cap + viewModel.maxAmountOverride = nil + viewModel.handleNumberPadInput("9", currency: currency) + XCTAssertEqual(viewModel.amountSats, 999) + } + // MARK: - Classic Bitcoin Tests func testClassicBitcoinDecimalInput() { diff --git a/changelog.d/next/346.fixed.md b/changelog.d/next/346.fixed.md new file mode 100644 index 000000000..2b713053d --- /dev/null +++ b/changelog.d/next/346.fixed.md @@ -0,0 +1 @@ +The send amount number pad now caps entry at your available balance, so you can no longer enter more than you can send. From 4868096f03ae20f8097ac8e90228b0a91c48e408 Mon Sep 17 00:00:00 2001 From: CypherPoet Date: Sat, 6 Jun 2026 07:09:19 -0500 Subject: [PATCH 2/5] fix: allow deleting an amount that is above the cap The cap rejected every keystroke whose result still exceeded it, including deletions. When an amount lands above the cap (a prefilled invoice over the available balance, or a cap that dropped after input), the user could not backspace to reduce it, since each intermediate value was still over the cap. Deletions now always apply; the cap only blocks growing the amount. --- Bitkit/ViewModels/AmountInputViewModel.swift | 11 ++++++-- BitkitTests/NumberPadTests.swift | 29 ++++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/Bitkit/ViewModels/AmountInputViewModel.swift b/Bitkit/ViewModels/AmountInputViewModel.swift index 2ce743030..3b268248d 100644 --- a/Bitkit/ViewModels/AmountInputViewModel.swift +++ b/Bitkit/ViewModels/AmountInputViewModel.swift @@ -48,12 +48,19 @@ class AmountInputViewModel: ObservableObject { maxDecimals: maxDecimals ) + // Deletions must always apply, even when the amount is above the cap (e.g. a + // prefilled invoice amount over the available balance, or a cap that dropped + // after input). The cap only blocks growing the amount; without this, each + // delete still leaves the amount over the cap and gets rejected, trapping the + // user with an invalid amount they can't reduce. + let isDeletion = key == "delete" + // For decimal input (classic Bitcoin and fiat), preserve the text as-is // For integer input (modern Bitcoin), format the final amount if currency.primaryDisplay == .bitcoin && currency.displayUnit == .modern { let newAmount = convertToSats(newText, currency: currency) - if newAmount <= effectiveMaxAmount { + if isDeletion || newAmount <= effectiveMaxAmount { rawInputText = newText displayText = formatDisplayTextFromAmount(newAmount, currency: currency) amountSats = newAmount @@ -69,7 +76,7 @@ class AmountInputViewModel: ObservableObject { // For decimal input, check limits before updating state if !newText.isEmpty { let newAmount = convertToSats(newText, currency: currency) - if newAmount <= effectiveMaxAmount { + if isDeletion || newAmount <= effectiveMaxAmount { // Update both raw input and display text rawInputText = newText // Format with grouping separators but not decimal formatting diff --git a/BitkitTests/NumberPadTests.swift b/BitkitTests/NumberPadTests.swift index b15224b50..3122e43f0 100644 --- a/BitkitTests/NumberPadTests.swift +++ b/BitkitTests/NumberPadTests.swift @@ -240,6 +240,35 @@ final class NumberPadTests: XCTestCase { XCTAssertEqual(viewModel.amountSats, 100_000) } + func testDeleteAllowedWhenAmountAboveCap() { + let viewModel = AmountInputViewModel() + let currency = mockCurrency(primaryDisplay: .bitcoin, displayUnit: .modern) + + // A prefilled amount lands above a low cap (e.g. an invoice that exceeds the + // available balance, set via updateFromSats which does not enforce the cap). + viewModel.maxAmountOverride = 1000 + viewModel.updateFromSats(123_456, currency: currency) + XCTAssertEqual(viewModel.amountSats, 123_456) + + // Adding a digit is still blocked: it would grow the amount further above the cap. + viewModel.handleNumberPadInput("7", currency: currency) + XCTAssertEqual(viewModel.amountSats, 123_456) // unchanged + XCTAssertNotNil(viewModel.errorKey) + + // Deleting is allowed even though the result is still above the cap, so the user + // can reduce an over-cap amount instead of being stuck. + viewModel.handleNumberPadInput("delete", currency: currency) + XCTAssertEqual(viewModel.displayText, "12 345") + XCTAssertEqual(viewModel.amountSats, 12345) + XCTAssertNil(viewModel.errorKey) + + // Keep deleting down below the cap. + viewModel.handleNumberPadInput("delete", currency: currency) // 1 234 + viewModel.handleNumberPadInput("delete", currency: currency) // 123 + XCTAssertEqual(viewModel.amountSats, 123) + XCTAssertNil(viewModel.errorKey) + } + // MARK: - Leading Zero Tests func testLeadingZeroBehavior() { From 480d7ac2f3a86905f161d12e5773d65b5ed1e8aa Mon Sep 17 00:00:00 2001 From: CypherPoet Date: Fri, 5 Jun 2026 14:40:21 -0500 Subject: [PATCH 3/5] fix: cap number pad input on remaining amount screens Applies the maxAmountOverride cap (from the send-amount fix) to the LNURL pay/withdraw, spending, advanced-spending, and manual channel-funding screens, so the number pad refuses entry above each screen's contextual maximum. FundManualAmountView also gains the previously-missing upper-bound button validation, which let users proceed above the fundable balance. Receive and CJIT amount screens are intentionally left uncapped (a receive amount has no balance ceiling). --- .../Views/Transfer/FundManualAmountView.swift | 18 +++++++++++++++++- .../Views/Transfer/SpendingAdvancedView.swift | 7 +++++++ Bitkit/Views/Transfer/SpendingAmount.swift | 5 +++++ .../LnurlWithdraw/LnurlWithdrawAmount.swift | 7 +++++++ Bitkit/Views/Wallets/Send/LnurlPayAmount.swift | 8 ++++++++ changelog.d/next/amount-screens-cap.fixed.md | 1 + 6 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 changelog.d/next/amount-screens-cap.fixed.md diff --git a/Bitkit/Views/Transfer/FundManualAmountView.swift b/Bitkit/Views/Transfer/FundManualAmountView.swift index 880f6debc..563162450 100644 --- a/Bitkit/Views/Transfer/FundManualAmountView.swift +++ b/Bitkit/Views/Transfer/FundManualAmountView.swift @@ -15,6 +15,14 @@ struct FundManualAmountView: View { amountViewModel.amountSats } + private var fundableBalanceSats: UInt64 { + UInt64(max(0, wallet.channelFundableBalanceSats)) + } + + private var isValidAmount: Bool { + amountSats > 0 && amountSats <= fundableBalanceSats + } + var body: some View { VStack(spacing: 0) { NavigationBar(title: t("lightning__external__nav_title")) @@ -58,7 +66,7 @@ struct FundManualAmountView: View { amountViewModel.handleNumberPadInput(key, currency: currency) } - CustomButton(title: t("common__continue"), isDisabled: amountSats == 0) { + CustomButton(title: t("common__continue"), isDisabled: !isValidAmount) { navigation.navigate(.fundManualConfirm(lnPeer: lnPeer, amountSats: amountSats)) } .accessibilityIdentifier("ExternalAmountContinue") @@ -70,6 +78,14 @@ struct FundManualAmountView: View { .task { await connectToPeerIfNeeded() } + .onAppear { + updateInputCap() + } + .onChange(of: wallet.channelFundableBalanceSats) { updateInputCap() } + } + + private func updateInputCap() { + amountViewModel.maxAmountOverride = fundableBalanceSats > 0 ? fundableBalanceSats : nil } private var numberPadButtons: some View { diff --git a/Bitkit/Views/Transfer/SpendingAdvancedView.swift b/Bitkit/Views/Transfer/SpendingAdvancedView.swift index 825135f64..daa0066bf 100644 --- a/Bitkit/Views/Transfer/SpendingAdvancedView.swift +++ b/Bitkit/Views/Transfer/SpendingAdvancedView.swift @@ -108,6 +108,7 @@ struct SpendingAdvancedView: View { ) updateFeeEstimate() + updateInputCap() } .onChange(of: lspBalance) { if isValid { @@ -116,6 +117,12 @@ struct SpendingAdvancedView: View { feeEstimate = nil } } + .onChange(of: transfer.transferValues.maxLspBalance) { updateInputCap() } + } + + private func updateInputCap() { + let maxLspBalance = transfer.transferValues.maxLspBalance + amountViewModel.maxAmountOverride = maxLspBalance > 0 ? maxLspBalance : nil } private var actionButtons: some View { diff --git a/Bitkit/Views/Transfer/SpendingAmount.swift b/Bitkit/Views/Transfer/SpendingAmount.swift index 60c125bf9..c7a24bb76 100644 --- a/Bitkit/Views/Transfer/SpendingAmount.swift +++ b/Bitkit/Views/Transfer/SpendingAmount.swift @@ -87,6 +87,11 @@ struct SpendingAmount: View { await calculateMaxTransferAmount() } } + .onChange(of: maxTransferAmount) { updateInputCap() } + } + + private func updateInputCap() { + amountViewModel.maxAmountOverride = (maxTransferAmount ?? 0) > 0 ? maxTransferAmount : nil } private var actionButtons: some View { diff --git a/Bitkit/Views/Wallets/LnurlWithdraw/LnurlWithdrawAmount.swift b/Bitkit/Views/Wallets/LnurlWithdraw/LnurlWithdrawAmount.swift index 450c5b095..54cc03f42 100644 --- a/Bitkit/Views/Wallets/LnurlWithdraw/LnurlWithdrawAmount.swift +++ b/Bitkit/Views/Wallets/LnurlWithdraw/LnurlWithdrawAmount.swift @@ -77,7 +77,14 @@ struct LnurlWithdrawAmount: View { if amountViewModel.amountSats == 0 { amountViewModel.updateFromSats(UInt64(minAmount), currency: currency) } + updateInputCap() } + .onChange(of: maxAmount) { updateInputCap() } + } + + private func updateInputCap() { + let cap = max(minAmount, maxAmount) + amountViewModel.maxAmountOverride = cap > 0 ? UInt64(cap) : nil } private func handleContinue() { diff --git a/Bitkit/Views/Wallets/Send/LnurlPayAmount.swift b/Bitkit/Views/Wallets/Send/LnurlPayAmount.swift index dc1b46832..fe9ee8eb0 100644 --- a/Bitkit/Views/Wallets/Send/LnurlPayAmount.swift +++ b/Bitkit/Views/Wallets/Send/LnurlPayAmount.swift @@ -81,6 +81,14 @@ struct LnurlPayAmount: View { .navigationBarHidden(true) .padding(.horizontal, 16) .sheetBackground() + .onAppear { + updateInputCap() + } + .onChange(of: maxAmount) { updateInputCap() } + } + + private func updateInputCap() { + amountViewModel.maxAmountOverride = maxAmount > 0 ? maxAmount : nil } private func onContinue() { diff --git a/changelog.d/next/amount-screens-cap.fixed.md b/changelog.d/next/amount-screens-cap.fixed.md new file mode 100644 index 000000000..637e332ea --- /dev/null +++ b/changelog.d/next/amount-screens-cap.fixed.md @@ -0,0 +1 @@ +Number-pad entry on the spending, LNURL, and channel-funding amount screens now caps at the available maximum, and manual channel funding can no longer exceed your available balance. From bd7a772303cecc7f3d64e947ed696bef16926cbe Mon Sep 17 00:00:00 2001 From: CypherPoet Date: Fri, 5 Jun 2026 16:52:16 -0500 Subject: [PATCH 4/5] chore: rename changelog fragment --- changelog.d/next/{amount-screens-cap.fixed.md => 585.fixed.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog.d/next/{amount-screens-cap.fixed.md => 585.fixed.md} (100%) diff --git a/changelog.d/next/amount-screens-cap.fixed.md b/changelog.d/next/585.fixed.md similarity index 100% rename from changelog.d/next/amount-screens-cap.fixed.md rename to changelog.d/next/585.fixed.md From 9bcbe3ad1cd1f9ca155e65b9d822ffa47358ac5d Mon Sep 17 00:00:00 2001 From: CypherPoet Date: Fri, 5 Jun 2026 14:44:09 -0500 Subject: [PATCH 5/5] refactor: migrate AmountInputViewModel to @Observable Replaces the legacy ObservableObject/@StateObject/@ObservedObject pattern with @Observable + @State, aligning with the project's stated SwiftUI direction. No behavior change: there are no two-way bindings to the view model, so no @Bindable is needed. Touches the view model, NumberPadTextField, and the 8 amount-entry call sites. --- Bitkit/Components/NumberPadTextField.swift | 2 +- Bitkit/ViewModels/AmountInputViewModel.swift | 9 +++++---- Bitkit/Views/Transfer/FundManualAmountView.swift | 2 +- Bitkit/Views/Transfer/SpendingAdvancedView.swift | 2 +- Bitkit/Views/Transfer/SpendingAmount.swift | 2 +- .../Wallets/LnurlWithdraw/LnurlWithdrawAmount.swift | 2 +- Bitkit/Views/Wallets/Receive/ReceiveCjitAmount.swift | 2 +- Bitkit/Views/Wallets/Receive/ReceiveEdit.swift | 2 +- Bitkit/Views/Wallets/Send/LnurlPayAmount.swift | 2 +- Bitkit/Views/Wallets/Send/SendAmountView.swift | 2 +- 10 files changed, 14 insertions(+), 13 deletions(-) diff --git a/Bitkit/Components/NumberPadTextField.swift b/Bitkit/Components/NumberPadTextField.swift index 057b562d6..bf99b31c9 100644 --- a/Bitkit/Components/NumberPadTextField.swift +++ b/Bitkit/Components/NumberPadTextField.swift @@ -3,7 +3,7 @@ import SwiftUI /// NumberPadTextField - Amount view to be used with number pad struct NumberPadTextField: View { @EnvironmentObject var currency: CurrencyViewModel - @ObservedObject var viewModel: AmountInputViewModel + var viewModel: AmountInputViewModel var showConversion: Bool = true var showEditButton: Bool = false diff --git a/Bitkit/ViewModels/AmountInputViewModel.swift b/Bitkit/ViewModels/AmountInputViewModel.swift index 3b268248d..760afb898 100644 --- a/Bitkit/ViewModels/AmountInputViewModel.swift +++ b/Bitkit/ViewModels/AmountInputViewModel.swift @@ -1,11 +1,12 @@ import Foundation import SwiftUI +@Observable @MainActor -class AmountInputViewModel: ObservableObject { - @Published var amountSats: UInt64 = 0 - @Published var displayText: String = "" - @Published var errorKey: String? +final class AmountInputViewModel { + var amountSats: UInt64 = 0 + var displayText: String = "" + var errorKey: String? /// Optional per-screen cap (e.g. the max sendable balance in the send flow). /// When set, input is additionally blocked above this value, on top of `maxAmount`. diff --git a/Bitkit/Views/Transfer/FundManualAmountView.swift b/Bitkit/Views/Transfer/FundManualAmountView.swift index 563162450..351bd4121 100644 --- a/Bitkit/Views/Transfer/FundManualAmountView.swift +++ b/Bitkit/Views/Transfer/FundManualAmountView.swift @@ -8,7 +8,7 @@ struct FundManualAmountView: View { let lnPeer: LnPeer - @StateObject private var amountViewModel = AmountInputViewModel() + @State private var amountViewModel = AmountInputViewModel() @State private var didAttemptPeerConnection = false var amountSats: UInt64 { diff --git a/Bitkit/Views/Transfer/SpendingAdvancedView.swift b/Bitkit/Views/Transfer/SpendingAdvancedView.swift index daa0066bf..603a8f0b5 100644 --- a/Bitkit/Views/Transfer/SpendingAdvancedView.swift +++ b/Bitkit/Views/Transfer/SpendingAdvancedView.swift @@ -10,7 +10,7 @@ struct SpendingAdvancedView: View { @EnvironmentObject var transfer: TransferViewModel @Environment(\.dismiss) var dismiss - @StateObject private var amountViewModel = AmountInputViewModel() + @State private var amountViewModel = AmountInputViewModel() @State private var feeEstimate: UInt64? @State private var isLoading = false @State private var feeEstimateTask: Task? diff --git a/Bitkit/Views/Transfer/SpendingAmount.swift b/Bitkit/Views/Transfer/SpendingAmount.swift index c7a24bb76..c8a6fc870 100644 --- a/Bitkit/Views/Transfer/SpendingAmount.swift +++ b/Bitkit/Views/Transfer/SpendingAmount.swift @@ -10,7 +10,7 @@ struct SpendingAmount: View { @EnvironmentObject var transfer: TransferViewModel @EnvironmentObject var wallet: WalletViewModel - @StateObject private var amountViewModel = AmountInputViewModel() + @State private var amountViewModel = AmountInputViewModel() @State private var isLoading = false @State private var availableAmount: UInt64? @State private var maxTransferAmount: UInt64? diff --git a/Bitkit/Views/Wallets/LnurlWithdraw/LnurlWithdrawAmount.swift b/Bitkit/Views/Wallets/LnurlWithdraw/LnurlWithdrawAmount.swift index 54cc03f42..09da2e81f 100644 --- a/Bitkit/Views/Wallets/LnurlWithdraw/LnurlWithdrawAmount.swift +++ b/Bitkit/Views/Wallets/LnurlWithdraw/LnurlWithdrawAmount.swift @@ -6,7 +6,7 @@ struct LnurlWithdrawAmount: View { @EnvironmentObject var wallet: WalletViewModel let onContinue: () -> Void - @StateObject private var amountViewModel = AmountInputViewModel() + @State private var amountViewModel = AmountInputViewModel() var minAmount: Int { Int(max(1, app.lnurlWithdrawData!.minWithdrawableSat)) diff --git a/Bitkit/Views/Wallets/Receive/ReceiveCjitAmount.swift b/Bitkit/Views/Wallets/Receive/ReceiveCjitAmount.swift index f8350d99e..56265f522 100644 --- a/Bitkit/Views/Wallets/Receive/ReceiveCjitAmount.swift +++ b/Bitkit/Views/Wallets/Receive/ReceiveCjitAmount.swift @@ -9,7 +9,7 @@ struct ReceiveCjitAmount: View { @Binding var navigationPath: [ReceiveRoute] - @StateObject private var amountViewModel = AmountInputViewModel() + @State private var amountViewModel = AmountInputViewModel() var minimumAmount: UInt64 { blocktank.minCjitSats ?? 0 diff --git a/Bitkit/Views/Wallets/Receive/ReceiveEdit.swift b/Bitkit/Views/Wallets/Receive/ReceiveEdit.swift index f1cd554d4..f61389aba 100644 --- a/Bitkit/Views/Wallets/Receive/ReceiveEdit.swift +++ b/Bitkit/Views/Wallets/Receive/ReceiveEdit.swift @@ -12,7 +12,7 @@ struct ReceiveEdit: View { @Binding var navigationPath: [ReceiveRoute] - @StateObject private var amountViewModel = AmountInputViewModel() + @State private var amountViewModel = AmountInputViewModel() @State private var note = "" @State private var isAmountInputFocused: Bool = false @FocusState private var isNoteEditorFocused: Bool diff --git a/Bitkit/Views/Wallets/Send/LnurlPayAmount.swift b/Bitkit/Views/Wallets/Send/LnurlPayAmount.swift index fe9ee8eb0..49a5db19e 100644 --- a/Bitkit/Views/Wallets/Send/LnurlPayAmount.swift +++ b/Bitkit/Views/Wallets/Send/LnurlPayAmount.swift @@ -7,7 +7,7 @@ struct LnurlPayAmount: View { @Binding var navigationPath: [SendRoute] - @StateObject private var amountViewModel = AmountInputViewModel() + @State private var amountViewModel = AmountInputViewModel() var maxAmount: UInt64 { // TODO: subtract fee diff --git a/Bitkit/Views/Wallets/Send/SendAmountView.swift b/Bitkit/Views/Wallets/Send/SendAmountView.swift index a71afba76..da03ca857 100644 --- a/Bitkit/Views/Wallets/Send/SendAmountView.swift +++ b/Bitkit/Views/Wallets/Send/SendAmountView.swift @@ -8,7 +8,7 @@ struct SendAmountView: View { @Binding var navigationPath: [SendRoute] - @StateObject private var amountViewModel = AmountInputViewModel() + @State private var amountViewModel = AmountInputViewModel() @State private var maxSendableAmount: UInt64? @State private var routingFee: UInt64 = 0