Skip to content

Commit 6ea6b32

Browse files
committed
Fix legacy type issues
1 parent 8cfde7b commit 6ea6b32

File tree

5 files changed

+94
-9
lines changed

5 files changed

+94
-9
lines changed

Bitkit/Services/LightningService.swift

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -749,6 +749,49 @@ extension LightningService {
749749
}
750750
}
751751

752+
/// Get the total balance that can be used for channel funding (excludes Legacy/P2PKH UTXOs)
753+
/// LDK channel funding requires witness-compatible UTXOs (NativeSegwit, NestedSegwit, Taproot)
754+
/// - Returns: Total spendable sats from witness-compatible address types
755+
func getChannelFundableBalance() async throws -> UInt64 {
756+
guard let node else {
757+
throw AppError(serviceError: .nodeNotSetup)
758+
}
759+
760+
// Get monitored address types from UserDefaults
761+
let storedTypes = UserDefaults.standard.string(forKey: "addressTypesToMonitor") ?? "nativeSegwit"
762+
let typeStrings = storedTypes.split(separator: ",").map { String($0).trimmingCharacters(in: .whitespaces) }
763+
let monitoredTypes: [LDKNode.AddressType] = typeStrings.compactMap { str in
764+
switch str {
765+
case "legacy": return .legacy
766+
case "nestedSegwit": return .nestedSegwit
767+
case "nativeSegwit": return .nativeSegwit
768+
case "taproot": return .taproot
769+
default: return nil
770+
}
771+
}
772+
773+
var totalFundable: UInt64 = 0
774+
775+
for addressType in monitoredTypes {
776+
// Skip Legacy (P2PKH) as it cannot be used for channel funding
777+
if addressType == .legacy {
778+
continue
779+
}
780+
781+
do {
782+
let balance = try await ServiceQueue.background(.ldk) {
783+
try node.getBalanceForAddressType(addressType: addressType)
784+
}
785+
totalFundable += balance.spendableSats
786+
} catch {
787+
// If we can't get balance for this type, log and continue
788+
Logger.warn("Failed to get balance for \(addressType) when calculating channel fundable balance: \(error)")
789+
}
790+
}
791+
792+
return totalFundable
793+
}
794+
752795
/// Returns LSP (Blocktank) peer node IDs
753796
func getLspPeerNodeIds() -> [String] {
754797
return Env.trustedLnPeers.map(\.nodeId)

Bitkit/ViewModels/SettingsViewModel.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,16 @@ class SettingsViewModel: NSObject, ObservableObject {
333333
let balance = await getBalanceForAddressType(addressType)
334334
if balance > 0 { return false }
335335

336+
// If primary is Legacy, ensure at least one SegWit-compatible wallet remains enabled
337+
// (Legacy UTXOs cannot be used for Lightning channel funding)
338+
if selectedAddressType == .legacy {
339+
let segwitTypes: [AddressScriptType] = [.nestedSegwit, .nativeSegwit, .taproot]
340+
let remainingSegwit = current.filter { $0 != addressType && segwitTypes.contains($0) }
341+
if remainingSegwit.isEmpty {
342+
return false
343+
}
344+
}
345+
336346
current.removeAll { $0 == addressType }
337347
addressTypesToMonitor = current
338348
}
@@ -364,6 +374,22 @@ class SettingsViewModel: NSObject, ObservableObject {
364374
addressTypesToMonitor = Self.allAddressTypes
365375
}
366376

377+
/// Check if disabling an address type would leave no SegWit wallets when Legacy is primary
378+
/// - Parameter addressType: The address type to check
379+
/// - Returns: True if this is the last SegWit wallet and Legacy is primary
380+
func isLastRequiredSegwitWallet(_ addressType: AddressScriptType) -> Bool {
381+
// Only applies when Legacy is the primary wallet
382+
guard selectedAddressType == .legacy else { return false }
383+
384+
// Only applies to SegWit-compatible types
385+
let segwitTypes: [AddressScriptType] = [.nestedSegwit, .nativeSegwit, .taproot]
386+
guard segwitTypes.contains(addressType) else { return false }
387+
388+
// Check if disabling this would leave no SegWit wallets
389+
let remainingSegwit = addressTypesToMonitor.filter { $0 != addressType && segwitTypes.contains($0) }
390+
return remainingSegwit.isEmpty
391+
}
392+
367393
var selectedAddressType: AddressScriptType {
368394
get {
369395
// Parse the stored string value

Bitkit/ViewModels/WalletViewModel.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ class WalletViewModel: ObservableObject {
1111
@AppStorage("totalLightningSats") var totalLightningSats: Int = 0 // Combined LN
1212
@AppStorage("spendableOnchainBalanceSats") var spendableOnchainBalanceSats: Int = 0 // The spendable balance of our on-chain wallet
1313
@AppStorage("maxSendLightningSats") var maxSendLightningSats: Int = 0 // Maximum amount that can be sent via lightning (outbound capacity)
14+
@AppStorage("channelFundableBalanceSats") var channelFundableBalanceSats: Int = 0 // Balance usable for channel funding (excludes Legacy UTXOs)
1415

1516
// Receive flow
1617
@AppStorage("onchainAddress") var onchainAddress = ""
@@ -587,6 +588,11 @@ class WalletViewModel: ObservableObject {
587588
totalBalanceSats = Int(state.totalBalanceSats)
588589
maxSendLightningSats = Int(state.maxSendLightningSats)
589590

591+
// Update channel fundable balance (excludes Legacy UTXOs which can't be used for channels)
592+
if let fundableBalance = try? await lightningService.getChannelFundableBalance() {
593+
channelFundableBalanceSats = Int(fundableBalance)
594+
}
595+
590596
// Get force close timelock from active transfers
591597
let activeTransfers = try? transferService.getActiveTransfers()
592598
let forceCloseTransfer = activeTransfers?.first {

Bitkit/Views/Settings/Advanced/AddressTypePreferenceView.swift

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -265,11 +265,20 @@ struct AddressTypePreferenceView: View {
265265
description: "Address monitoring settings applied."
266266
)
267267
} else if !enabled {
268-
app.toast(
269-
type: .error,
270-
title: "Cannot Disable",
271-
description: "\(addressType.localizedTitle) addresses have balance."
272-
)
268+
// Determine reason for failure
269+
if settingsViewModel.isLastRequiredSegwitWallet(addressType) {
270+
app.toast(
271+
type: .error,
272+
title: "Cannot Disable",
273+
description: "At least one SegWit wallet is required for Lightning when using Legacy as primary."
274+
)
275+
} else {
276+
app.toast(
277+
type: .error,
278+
title: "Cannot Disable",
279+
description: "\(addressType.localizedTitle) addresses have balance."
280+
)
281+
}
273282
}
274283
}
275284
}

Bitkit/Views/Transfer/FundManualAmountView.swift

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,10 @@ struct FundManualAmountView: View {
3232
Spacer()
3333

3434
HStack(alignment: .bottom) {
35-
AvailableAmount(label: t("wallet__send_available"), amount: wallet.totalOnchainSats)
35+
// Show channel fundable balance (excludes Legacy UTXOs which can't be used for channel funding)
36+
AvailableAmount(label: t("wallet__send_available"), amount: wallet.channelFundableBalanceSats)
3637
.onTapGesture {
37-
amountViewModel.updateFromSats(UInt64(wallet.totalOnchainSats), currency: currency)
38+
amountViewModel.updateFromSats(UInt64(wallet.channelFundableBalanceSats), currency: currency)
3839
}
3940

4041
Spacer()
@@ -80,11 +81,11 @@ struct FundManualAmountView: View {
8081
}
8182

8283
NumberPadActionButton(text: t("lightning__spending_amount__quarter")) {
83-
amountViewModel.updateFromSats(UInt64(wallet.totalOnchainSats) / 4, currency: currency)
84+
amountViewModel.updateFromSats(UInt64(wallet.channelFundableBalanceSats) / 4, currency: currency)
8485
}
8586

8687
NumberPadActionButton(text: t("common__max")) {
87-
amountViewModel.updateFromSats(UInt64(wallet.totalOnchainSats), currency: currency)
88+
amountViewModel.updateFromSats(UInt64(wallet.channelFundableBalanceSats), currency: currency)
8889
}
8990
}
9091
}

0 commit comments

Comments
 (0)